Merge "Use explicit intent for installing credentials." into gingerbread
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index e93e684..e66f52a 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2775,7 +2775,7 @@
             synchronized (this) {
                 if (mDiskWritesInFlight > 0) {
                     // If we know we caused it, it's not unexpected.
-                    Log.d(TAG, "disk write in flight, not unexpected.");
+                    if (DEBUG) Log.d(TAG, "disk write in flight, not unexpected.");
                     return false;
                 }
             }
diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
index 8e655e2..df1d960 100644
--- a/core/java/android/bluetooth/BluetoothDeviceProfileState.java
+++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
@@ -67,10 +67,11 @@
     private static final int DISCONNECT_HFP_INCOMING = 6;
     public static final int DISCONNECT_A2DP_OUTGOING = 7;
     public static final int DISCONNECT_A2DP_INCOMING = 8;
+    public static final int DISCONNECT_PBAP_OUTGOING = 9;
 
-    public static final int UNPAIR = 9;
-    public static final int AUTO_CONNECT_PROFILES = 10;
-    public static final int TRANSITION_TO_STABLE = 11;
+    public static final int UNPAIR = 100;
+    public static final int AUTO_CONNECT_PROFILES = 101;
+    public static final int TRANSITION_TO_STABLE = 102;
 
     private static final int AUTO_CONNECT_DELAY = 6000; // 6 secs
 
@@ -84,7 +85,9 @@
     private BluetoothService mService;
     private BluetoothA2dpService mA2dpService;
     private BluetoothHeadset  mHeadsetService;
+    private BluetoothPbap     mPbapService;
     private boolean mHeadsetServiceConnected;
+    private boolean mPbapServiceConnected;
 
     private BluetoothDevice mDevice;
     private int mHeadsetState;
@@ -176,6 +179,7 @@
         mContext.registerReceiver(mBroadcastReceiver, filter);
 
         HeadsetServiceListener l = new HeadsetServiceListener();
+        PbapServiceListener p = new PbapServiceListener();
     }
 
     private class HeadsetServiceListener implements BluetoothHeadset.ServiceListener {
@@ -194,6 +198,22 @@
         }
     }
 
+    private class PbapServiceListener implements BluetoothPbap.ServiceListener {
+        public PbapServiceListener() {
+            mPbapService = new BluetoothPbap(mContext, this);
+        }
+        public void onServiceConnected() {
+            synchronized(BluetoothDeviceProfileState.this) {
+                mPbapServiceConnected = true;
+            }
+        }
+        public void onServiceDisconnected() {
+            synchronized(BluetoothDeviceProfileState.this) {
+                mPbapServiceConnected = false;
+            }
+        }
+    }
+
     private class BondedDevice extends HierarchicalState {
         @Override
         protected void enter() {
@@ -224,6 +244,9 @@
                 case DISCONNECT_A2DP_INCOMING:
                     transitionTo(mIncomingA2dp);
                     break;
+                case DISCONNECT_PBAP_OUTGOING:
+                    processCommand(DISCONNECT_PBAP_OUTGOING);
+                    break;
                 case UNPAIR:
                     if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) {
                         sendMessage(DISCONNECT_HFP_OUTGOING);
@@ -342,6 +365,7 @@
                        deferMessage(deferMsg);
                     }
                     break;
+                case DISCONNECT_PBAP_OUTGOING:
                 case UNPAIR:
                 case AUTO_CONNECT_PROFILES:
                     deferMessage(message);
@@ -409,6 +433,7 @@
                     // If this causes incoming HFP to fail, it is more of a headset problem
                     // since both connections are incoming ones.
                     break;
+                case DISCONNECT_PBAP_OUTGOING:
                 case UNPAIR:
                 case AUTO_CONNECT_PROFILES:
                     deferMessage(message);
@@ -496,6 +521,7 @@
                 case DISCONNECT_A2DP_INCOMING:
                     // Ignore, will be handled by Bluez
                     break;
+                case DISCONNECT_PBAP_OUTGOING:
                 case UNPAIR:
                 case AUTO_CONNECT_PROFILES:
                     deferMessage(message);
@@ -561,6 +587,7 @@
                 case DISCONNECT_A2DP_INCOMING:
                     // Ignore, will be handled by Bluez
                     break;
+                case DISCONNECT_PBAP_OUTGOING:
                 case UNPAIR:
                 case AUTO_CONNECT_PROFILES:
                     deferMessage(message);
@@ -588,7 +615,7 @@
         }
     }
 
-    synchronized void deferHeadsetMessage(int command) {
+    synchronized void deferProfileServiceMessage(int command) {
         Message msg = new Message();
         msg.what = command;
         deferMessage(msg);
@@ -604,7 +631,7 @@
                 break;
             case CONNECT_HFP_INCOMING:
                 if (!mHeadsetServiceConnected) {
-                    deferHeadsetMessage(command);
+                    deferProfileServiceMessage(command);
                 } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) {
                     return mHeadsetService.acceptIncomingConnect(mDevice);
                 } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) {
@@ -621,8 +648,13 @@
                 return true;
             case DISCONNECT_HFP_OUTGOING:
                 if (!mHeadsetServiceConnected) {
-                    deferHeadsetMessage(command);
+                    deferProfileServiceMessage(command);
                 } else {
+                    // Disconnect PBAP
+                    // TODO(): Add PBAP to the state machine.
+                    Message m = new Message();
+                    m.what = DISCONNECT_PBAP_OUTGOING;
+                    deferMessage(m);
                     if (mHeadsetService.getPriority(mDevice) ==
                         BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
                         mHeadsetService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
@@ -645,6 +677,13 @@
                     return mA2dpService.disconnectSinkInternal(mDevice);
                 }
                 break;
+            case DISCONNECT_PBAP_OUTGOING:
+                if (!mPbapServiceConnected) {
+                    deferProfileServiceMessage(command);
+                } else {
+                    return mPbapService.disconnect();
+                }
+                break;
             case UNPAIR:
                 return mService.removeBondInternal(mDevice.getAddress());
             default:
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 7b930d5..a27ba84 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -1354,8 +1354,23 @@
         /**
          * Sets the dimensions for preview pictures.
          *
+         * The sides of width and height are based on camera orientation. That
+         * is, the preview size is the size before it is rotated by display
+         * orientation. So applications need to consider the display orientation
+         * while setting preview size. For example, suppose the camera supports
+         * both 480x320 and 320x480 preview sizes. The application wants a 3:2
+         * preview ratio. If the display orientation is set to 0 or 180, preview
+         * size should be set to 480x320. If the display orientation is set to
+         * 90 or 270, preview size should be set to 320x480. The display
+         * orientation should also be considered while setting picture size and
+         * thumbnail size.
+         *
          * @param width  the width of the pictures, in pixels
          * @param height the height of the pictures, in pixels
+         * @see #setDisplayOrientation(int)
+         * @see #getCameraInfo(int, CameraInfo)
+         * @see #setPictureSize(int, int)
+         * @see #setJpegThumbnailSize(int, int)
          */
         public void setPreviewSize(int width, int height) {
             String v = Integer.toString(width) + "x" + Integer.toString(height);
@@ -1389,8 +1404,12 @@
          * applications set both width and height to 0, EXIF will not contain
          * thumbnail.
          *
+         * Applications need to consider the display orientation. See {@link
+         * #setPreviewSize(int,int)} for reference.
+         *
          * @param width  the width of the thumbnail, in pixels
          * @param height the height of the thumbnail, in pixels
+         * @see #setPreviewSize(int,int)
          */
         public void setJpegThumbnailSize(int width, int height) {
             set(KEY_JPEG_THUMBNAIL_WIDTH, width);
@@ -1606,8 +1625,13 @@
         /**
          * Sets the dimensions for pictures.
          *
+         * Applications need to consider the display orientation. See {@link
+         * #setPreviewSize(int,int)} for reference.
+         *
          * @param width  the width for pictures, in pixels
          * @param height the height for pictures, in pixels
+         * @see #setPreviewSize(int,int)
+         *
          */
         public void setPictureSize(int width, int height) {
             String v = Integer.toString(width) + "x" + Integer.toString(height);
diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java
index 23fdb0b..fa23458 100644
--- a/core/java/android/os/DropBoxManager.java
+++ b/core/java/android/os/DropBoxManager.java
@@ -152,7 +152,7 @@
         /** @return time when the entry was originally created. */
         public long getTimeMillis() { return mTimeMillis; }
 
-        /** @return flags describing the content returned by @{link #getInputStream()}. */
+        /** @return flags describing the content returned by {@link #getInputStream()}. */
         public int getFlags() { return mFlags & ~IS_GZIPPED; }  // getInputStream() decompresses.
 
         /**
@@ -288,8 +288,8 @@
     }
 
     /**
-     * Gets the next entry from the drop box *after* the specified time.
-     * Requires android.permission.READ_LOGS.  You must always call
+     * Gets the next entry from the drop box <em>after</em> the specified time.
+     * Requires <code>android.permission.READ_LOGS</code>.  You must always call
      * {@link Entry#close()} on the return value!
      *
      * @param tag of entry to look for, null for all tags
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 6237c0e..0090177 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -34,16 +34,19 @@
     Message mMessages;
     private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
     private IdleHandler[] mPendingIdleHandlers;
-    private boolean mQuiting = false;
+    private boolean mQuiting;
     boolean mQuitAllowed = true;
 
+    // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
+    private boolean mBlocked;
+
     @SuppressWarnings("unused")
     private int mPtr; // used by native code
     
     private native void nativeInit();
     private native void nativeDestroy();
-    private native boolean nativePollOnce(int timeoutMillis);
-    private native void nativeWake();
+    private native void nativePollOnce(int ptr, int timeoutMillis);
+    private native void nativeWake(int ptr);
 
     /**
      * Callback interface for discovering when a thread is going to block
@@ -113,7 +116,7 @@
             if (nextPollTimeoutMillis != 0) {
                 Binder.flushPendingCommands();
             }
-            nativePollOnce(nextPollTimeoutMillis);
+            nativePollOnce(mPtr, nextPollTimeoutMillis);
 
             synchronized (this) {
                 // Try to retrieve the next message.  Return if found.
@@ -122,7 +125,9 @@
                 if (msg != null) {
                     final long when = msg.when;
                     if (now >= when) {
+                        mBlocked = false;
                         mMessages = msg.next;
+                        msg.next = null;
                         if (Config.LOGV) Log.v("MessageQueue", "Returning message: " + msg);
                         return msg;
                     } else {
@@ -138,6 +143,7 @@
                 }
                 if (pendingIdleHandlerCount == 0) {
                     // No idle handlers to run.  Loop and wait some more.
+                    mBlocked = true;
                     continue;
                 }
 
@@ -184,6 +190,7 @@
         if (msg.target == null && !mQuitAllowed) {
             throw new RuntimeException("Main thread not allowed to quit");
         }
+        final boolean needWake;
         synchronized (this) {
             if (mQuiting) {
                 RuntimeException e = new RuntimeException(
@@ -200,6 +207,7 @@
             if (p == null || when == 0 || when < p.when) {
                 msg.next = p;
                 mMessages = msg;
+                needWake = mBlocked; // new head, might need to wake up
             } else {
                 Message prev = null;
                 while (p != null && p.when <= when) {
@@ -208,9 +216,12 @@
                 }
                 msg.next = prev.next;
                 prev.next = msg;
+                needWake = false; // still waiting on head, no need to wake up
             }
         }
-        nativeWake();
+        if (needWake) {
+            nativeWake(mPtr);
+        }
         return true;
     }
 
diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java
index 43c957a..9e800df 100644
--- a/core/java/android/view/InputQueue.java
+++ b/core/java/android/view/InputQueue.java
@@ -28,8 +28,21 @@
     
     private static final boolean DEBUG = false;
     
+    /**
+     * Interface to receive notification of when an InputQueue is associated
+     * and dissociated with a thread.
+     */
     public static interface Callback {
+        /**
+         * Called when the given InputQueue is now associated with the
+         * thread making this call, so it can start receiving events from it.
+         */
         void onInputQueueCreated(InputQueue queue);
+        
+        /**
+         * Called when the given InputQueue is no longer associated with
+         * the thread and thus not dispatching events.
+         */
         void onInputQueueDestroyed(InputQueue queue);
     }
 
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index ee57c6e..6260cdb 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2247,8 +2247,14 @@
                                 mTouchMode = TOUCH_MODE_OVERSCROLL;
                                 if (rawDeltaY > 0) {
                                     mEdgeGlowTop.onPull((float) overscroll / getHeight());
+                                    if (!mEdgeGlowBottom.isFinished()) {
+                                        mEdgeGlowBottom.onRelease();
+                                    }
                                 } else if (rawDeltaY < 0) {
                                     mEdgeGlowBottom.onPull((float) overscroll / getHeight());
+                                    if (!mEdgeGlowTop.isFinished()) {
+                                        mEdgeGlowTop.onRelease();
+                                    }
                                 }
                             }
                         }
@@ -2307,8 +2313,14 @@
                                         !contentFits())) {
                             if (rawDeltaY > 0) {
                                 mEdgeGlowTop.onPull((float) -incrementalDeltaY / getHeight());
+                                if (!mEdgeGlowBottom.isFinished()) {
+                                    mEdgeGlowBottom.onRelease();
+                                }
                             } else if (rawDeltaY < 0) {
                                 mEdgeGlowBottom.onPull((float) -incrementalDeltaY / getHeight());
+                                if (!mEdgeGlowTop.isFinished()) {
+                                    mEdgeGlowTop.onRelease();
+                                }
                             }
                             invalidate();
                         }
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 6a52f75..d38eef3 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -542,8 +542,14 @@
                         final int pulledToX = oldX + deltaX;
                         if (pulledToX < 0) {
                             mEdgeGlowLeft.onPull((float) deltaX / getWidth());
+                            if (!mEdgeGlowRight.isFinished()) {
+                                mEdgeGlowRight.onRelease();
+                            }
                         } else if (pulledToX > range) {
                             mEdgeGlowRight.onPull((float) deltaX / getWidth());
+                            if (!mEdgeGlowLeft.isFinished()) {
+                                mEdgeGlowLeft.onRelease();
+                            }
                         }
                     }
                 }
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 055ba87..76755de 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -109,6 +109,7 @@
     private Drawable mBelowAnchorBackgroundDrawable;
 
     private boolean mAboveAnchor;
+    private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
     
     private OnDismissListener mOnDismissListener;
     private boolean mIgnoreCheekPress = false;
@@ -623,6 +624,25 @@
     }
 
     /**
+     * Set the layout type for this window. Should be one of the TYPE constants defined in
+     * {@link WindowManager.LayoutParams}.
+     *
+     * @param layoutType Layout type for this window.
+     * @hide
+     */
+    public void setWindowLayoutType(int layoutType) {
+        mWindowLayoutType = layoutType;
+    }
+
+    /**
+     * @return The layout type for this window.
+     * @hide
+     */
+    public int getWindowLayoutType() {
+        return mWindowLayoutType;
+    }
+
+    /**
      * <p>Change the width and height measure specs that are given to the
      * window manager by the popup.  By default these are 0, meaning that
      * the current width or height is requested as an explicit size from
@@ -911,7 +931,7 @@
             p.format = PixelFormat.TRANSLUCENT;
         }
         p.flags = computeFlags(p.flags);
-        p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+        p.type = mWindowLayoutType;
         p.token = token;
         p.softInputMode = mSoftInputMode;
         p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 0337b5c..1daf2ab 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -537,8 +537,14 @@
                         final int pulledToY = oldY + deltaY;
                         if (pulledToY < 0) {
                             mEdgeGlowTop.onPull((float) deltaY / getHeight());
+                            if (!mEdgeGlowBottom.isFinished()) {
+                                mEdgeGlowBottom.onRelease();
+                            }
                         } else if (pulledToY > range) {
                             mEdgeGlowBottom.onPull((float) deltaY / getHeight());
+                            if (!mEdgeGlowTop.isFinished()) {
+                                mEdgeGlowTop.onRelease();
+                            }
                         }
                     }
                 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3ac179c..548002f 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -96,6 +96,7 @@
 import android.view.ViewParent;
 import android.view.ViewRoot;
 import android.view.ViewTreeObserver;
+import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AnimationUtils;
@@ -287,9 +288,13 @@
     }
     InputMethodState mInputMethodState;
 
-    private int mTextSelectHandleLeftRes;
-    private int mTextSelectHandleRightRes;
-    private int mTextSelectHandleRes;
+    int mTextSelectHandleLeftRes;
+    int mTextSelectHandleRightRes;
+    int mTextSelectHandleRes;
+
+    Drawable mSelectHandleLeft;
+    Drawable mSelectHandleRight;
+    Drawable mSelectHandleCenter;
 
     /*
      * Kick-start the font cache for the zygote process (to pay the cost of
@@ -7222,9 +7227,21 @@
         }
 
         // Two ints packed in a long
-        return (((long) start) << 32) | end;
+        return packRangeInLong(start, end);
     }
     
+    private static long packRangeInLong(int start, int end) {
+        return (((long) start) << 32) | end;
+    }
+
+    private static int extractRangeStartFromLong(long range) {
+        return (int) (range >>> 32);
+    }
+
+    private static int extractRangeEndFromLong(long range) {
+        return (int) (range & 0x00000000FFFFFFFFL);
+    }
+
     private void selectCurrentWord() {
         // In case selection mode is started after an orientation change or after a select all,
         // use the current selection instead of creating one
@@ -7242,14 +7259,14 @@
         
         long wordLimits = getWordLimitsAt(minOffset);
         if (wordLimits >= 0) {
-            selectionStart = (int) (wordLimits >>> 32);
+            selectionStart = extractRangeStartFromLong(wordLimits);
         } else {
             selectionStart = Math.max(minOffset - 5, 0);
         }
 
         wordLimits = getWordLimitsAt(maxOffset);
         if (wordLimits >= 0) {
-            selectionEnd = (int) (wordLimits & 0x00000000FFFFFFFFL);
+            selectionEnd = extractRangeEndFromLong(wordLimits);
         } else {
             selectionEnd = Math.min(maxOffset + 5, mText.length());
         }
@@ -7264,8 +7281,8 @@
 
         long wordLimits = getWordLimitsAt(mLastTouchOffset);
         if (wordLimits >= 0) {
-            int start = (int) (wordLimits >>> 32);
-            int end = (int) (wordLimits & 0x00000000FFFFFFFFL);
+            int start = extractRangeStartFromLong(wordLimits);
+            int end = extractRangeEndFromLong(wordLimits);
             return mTransformed.subSequence(start, end).toString();
         } else {
             return null;
@@ -7480,43 +7497,9 @@
                 CharSequence paste = clip.getText();
 
                 if (paste != null && paste.length() > 0) {
-                    // Paste adds/removes spaces before or after insertion as needed.
-
-                    if (Character.isSpaceChar(paste.charAt(0))) {
-                        if (min > 0 && Character.isSpaceChar(mTransformed.charAt(min - 1))) {
-                            // Two spaces at beginning of paste: remove one
-                            final int originalLength = mText.length();
-                            ((Editable) mText).replace(min - 1, min, "");
-                            // Due to filters, there is no garantee that exactly one character was
-                            // removed. Count instead.
-                            final int delta = mText.length() - originalLength;
-                            min += delta;
-                            max += delta;
-                        }
-                    } else {
-                        if (min > 0 && !Character.isSpaceChar(mTransformed.charAt(min - 1))) {
-                            // No space at beginning of paste: add one
-                            final int originalLength = mText.length();
-                            ((Editable) mText).replace(min, min, " ");
-                            // Taking possible filters into account as above.
-                            final int delta = mText.length() - originalLength;
-                            min += delta;
-                            max += delta;
-                        }
-                    }
-
-                    if (Character.isSpaceChar(paste.charAt(paste.length() - 1))) {
-                        if (max < mText.length() && Character.isSpaceChar(mTransformed.charAt(max))) {
-                            // Two spaces at end of paste: remove one
-                            ((Editable) mText).replace(max, max + 1, "");
-                        }
-                    } else {
-                        if (max < mText.length() && !Character.isSpaceChar(mTransformed.charAt(max))) {
-                            // No space at end of paste: add one
-                            ((Editable) mText).replace(max, max, " ");
-                        }
-                    }
-
+                    long minMax = prepareSpacesAroundPaste(min, max, paste);
+                    min = extractRangeStartFromLong(minMax);
+                    max = extractRangeEndFromLong(minMax);
                     Selection.setSelection((Spannable) mText, max);
                     ((Editable) mText).replace(min, max, paste);
                     stopTextSelectionMode();
@@ -7552,6 +7535,49 @@
         return false;
     }
 
+    /**
+     * Prepare text so that there are not zero or two spaces at beginning and end of region defined
+     * by [min, max] when replacing this region by paste.
+     */
+    private long prepareSpacesAroundPaste(int min, int max, CharSequence paste) {
+        // Paste adds/removes spaces before or after insertion as needed.
+        if (Character.isSpaceChar(paste.charAt(0))) {
+            if (min > 0 && Character.isSpaceChar(mTransformed.charAt(min - 1))) {
+                // Two spaces at beginning of paste: remove one
+                final int originalLength = mText.length();
+                ((Editable) mText).replace(min - 1, min, "");
+                // Due to filters, there is no garantee that exactly one character was
+                // removed. Count instead.
+                final int delta = mText.length() - originalLength;
+                min += delta;
+                max += delta;
+            }
+        } else {
+            if (min > 0 && !Character.isSpaceChar(mTransformed.charAt(min - 1))) {
+                // No space at beginning of paste: add one
+                final int originalLength = mText.length();
+                ((Editable) mText).replace(min, min, " ");
+                // Taking possible filters into account as above.
+                final int delta = mText.length() - originalLength;
+                min += delta;
+                max += delta;
+            }
+        }
+
+        if (Character.isSpaceChar(paste.charAt(paste.length() - 1))) {
+            if (max < mText.length() && Character.isSpaceChar(mTransformed.charAt(max))) {
+                // Two spaces at end of paste: remove one
+                ((Editable) mText).replace(max, max + 1, "");
+            }
+        } else {
+            if (max < mText.length() && !Character.isSpaceChar(mTransformed.charAt(max))) {
+                // No space at end of paste: add one
+                ((Editable) mText).replace(max, max, " ");
+            }
+        }
+        return packRangeInLong(min, max);
+    }
+
     @Override
     public boolean performLongClick() {
         if (super.performLongClick()) {
@@ -7651,26 +7677,75 @@
         private int mPositionY;
         private CursorController mController;
         private boolean mIsDragging;
-        private float mOffsetX;
-        private float mOffsetY;
+        private float mTouchToWindowOffsetX;
+        private float mTouchToWindowOffsetY;
         private float mHotspotX;
         private float mHotspotY;
+        private int mHeight;
+        private float mTouchOffsetY;
         private int mLastParentX;
         private int mLastParentY;
 
-        public HandleView(CursorController controller, Drawable handle) {
+        public static final int LEFT = 0;
+        public static final int CENTER = 1;
+        public static final int RIGHT = 2;
+
+        public HandleView(CursorController controller, int pos) {
             super(TextView.this.mContext);
             mController = controller;
-            mDrawable = handle;
             mContainer = new PopupWindow(TextView.this.mContext, null,
                     com.android.internal.R.attr.textSelectHandleWindowStyle);
             mContainer.setSplitTouchEnabled(true);
             mContainer.setClippingEnabled(false);
+            mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
 
-            final int handleWidth = mDrawable.getIntrinsicWidth();
+            setOrientation(pos);
+        }
+
+        public void setOrientation(int pos) {
+            int handleWidth;
+            switch (pos) {
+            case LEFT: {
+                if (mSelectHandleLeft == null) {
+                    mSelectHandleLeft = mContext.getResources().getDrawable(
+                            mTextSelectHandleLeftRes);
+                }
+                mDrawable = mSelectHandleLeft;
+                handleWidth = mDrawable.getIntrinsicWidth();
+                mHotspotX = handleWidth / 4 * 3;
+                break;
+            }
+
+            case RIGHT: {
+                if (mSelectHandleRight == null) {
+                    mSelectHandleRight = mContext.getResources().getDrawable(
+                            mTextSelectHandleRightRes);
+                }
+                mDrawable = mSelectHandleRight;
+                handleWidth = mDrawable.getIntrinsicWidth();
+                mHotspotX = handleWidth / 4;
+                break;
+            }
+
+            case CENTER:
+            default: {
+                if (mSelectHandleCenter == null) {
+                    mSelectHandleCenter = mContext.getResources().getDrawable(
+                            mTextSelectHandleRes);
+                }
+                mDrawable = mSelectHandleCenter;
+                handleWidth = mDrawable.getIntrinsicWidth();
+                mHotspotX = handleWidth / 2;
+                break;
+            }
+            }
+
             final int handleHeight = mDrawable.getIntrinsicHeight();
-            mHotspotX = handleWidth * 0.5f;
-            mHotspotY = -handleHeight * 0.2f;
+
+            mTouchOffsetY = -handleHeight * 0.3f;
+            mHotspotY = 0;
+            mHeight = handleHeight;
+            invalidate();
         }
 
         @Override
@@ -7735,10 +7810,10 @@
             final int[] coords = mTempCoords;
             hostView.getLocationInWindow(coords);
             final int posX = coords[0] + mPositionX + (int) mHotspotX;
-            final int posY = coords[1] + mPositionY;
+            final int posY = coords[1] + mPositionY + (int) mHotspotY;
 
             return posX >= clip.left && posX <= clip.right &&
-                    posY >= clip.top && posY + mHotspotY <= clip.bottom;
+                    posY >= clip.top && posY <= clip.bottom;
         }
 
         private void moveTo(int x, int y) {
@@ -7761,8 +7836,8 @@
                         TextView.this.getLocationInWindow(coords);
                     }
                     if (coords[0] != mLastParentX || coords[1] != mLastParentY) {
-                        mOffsetX += coords[0] - mLastParentX;
-                        mOffsetY += coords[1] - mLastParentY;
+                        mTouchToWindowOffsetX += coords[0] - mLastParentX;
+                        mTouchToWindowOffsetY += coords[1] - mLastParentY;
                         mLastParentX = coords[0];
                         mLastParentY = coords[1];
                     }
@@ -7791,8 +7866,8 @@
             case MotionEvent.ACTION_DOWN: {
                 final float rawX = ev.getRawX();
                 final float rawY = ev.getRawY();
-                mOffsetX = rawX - mPositionX;
-                mOffsetY = rawY - mPositionY;
+                mTouchToWindowOffsetX = rawX - mPositionX;
+                mTouchToWindowOffsetY = rawY - mPositionY;
                 final int[] coords = mTempCoords;
                 TextView.this.getLocationInWindow(coords);
                 mLastParentX = coords[0];
@@ -7803,8 +7878,8 @@
             case MotionEvent.ACTION_MOVE: {
                 final float rawX = ev.getRawX();
                 final float rawY = ev.getRawY();
-                final float newPosX = rawX - mOffsetX + mHotspotX;
-                final float newPosY = rawY - mOffsetY + mHotspotY;
+                final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX;
+                final float newPosY = rawY - mTouchToWindowOffsetY + mHotspotY + mTouchOffsetY;
 
                 mController.updatePosition(this, (int) Math.round(newPosX),
                         (int) Math.round(newPosY));
@@ -7830,9 +7905,9 @@
             final int lineBottom = mLayout.getLineBottom(line);
 
             final Rect bounds = sCursorControllerTempRect;
-            bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - width / 2.0)
+            bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - mHotspotX)
                 + TextView.this.mScrollX;
-            bounds.top = (bottom ? lineBottom : lineTop) + TextView.this.mScrollY;
+            bounds.top = (bottom ? lineBottom : lineTop - mHeight) + TextView.this.mScrollY;
 
             bounds.right = bounds.left + width;
             bounds.bottom = bounds.top + height;
@@ -7855,8 +7930,7 @@
         };
 
         InsertionPointCursorController() {
-            Resources res = mContext.getResources();
-            mHandle = new HandleView(this, res.getDrawable(mTextSelectHandleRes));
+            mHandle = new HandleView(this, HandleView.CENTER);
         }
 
         public void show() {
@@ -7931,9 +8005,8 @@
         };
 
         SelectionModifierCursorController() {
-            Resources res = mContext.getResources();
-            mStartHandle = new HandleView(this, res.getDrawable(mTextSelectHandleLeftRes));
-            mEndHandle = new HandleView(this, res.getDrawable(mTextSelectHandleRightRes));
+            mStartHandle = new HandleView(this, HandleView.LEFT);
+            mEndHandle = new HandleView(this, HandleView.RIGHT);
         }
 
         public void show() {
@@ -7974,31 +8047,25 @@
 
             // Handle the case where start and end are swapped, making sure start <= end
             if (handle == mStartHandle) {
-                if (offset <= selectionEnd) {
-                    if (selectionStart == offset) {
-                        return; // no change, no need to redraw;
-                    }
-                    selectionStart = offset;
-                } else {
-                    selectionStart = selectionEnd;
-                    selectionEnd = offset;
-                    HandleView temp = mStartHandle;
-                    mStartHandle = mEndHandle;
-                    mEndHandle = temp;
+                if (selectionStart == offset || offset > selectionEnd) {
+                    return; // no change, no need to redraw;
                 }
+                // If the user "closes" the selection entirely they were probably trying to
+                // select a single character. Help them out.
+                if (offset == selectionEnd) {
+                    offset = selectionEnd - 1;
+                }
+                selectionStart = offset;
             } else {
-                if (offset >= selectionStart) {
-                    if (selectionEnd == offset) {
-                        return; // no change, no need to redraw;
-                    }
-                    selectionEnd = offset;
-                } else {
-                    selectionEnd = selectionStart;
-                    selectionStart = offset;
-                    HandleView temp = mStartHandle;
-                    mStartHandle = mEndHandle;
-                    mEndHandle = temp;
+                if (selectionEnd == offset || offset < selectionStart) {
+                    return; // no change, no need to redraw;
                 }
+                // If the user "closes" the selection entirely they were probably trying to
+                // select a single character. Help them out.
+                if (offset == selectionStart) {
+                    offset = selectionStart + 1;
+                }
+                selectionEnd = offset;
             }
 
             Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
@@ -8016,9 +8083,7 @@
                 return;
             }
 
-            boolean oneLineSelection = mLayout.getLineForOffset(selectionStart) ==
-                    mLayout.getLineForOffset(selectionEnd);
-            mStartHandle.positionAtCursor(selectionStart, oneLineSelection);
+            mStartHandle.positionAtCursor(selectionStart, true);
             mEndHandle.positionAtCursor(selectionEnd, true);
             hideDelayed(DELAY_BEFORE_FADE_OUT);
         }
@@ -8144,7 +8209,7 @@
         final int previousLine = layout.getLineForOffset(previousOffset);
         final int previousLineTop = layout.getLineTop(previousLine);
         final int previousLineBottom = layout.getLineBottom(previousLine);
-        final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 6;
+        final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 8;
 
         // If new line is just before or after previous line and y position is less than
         // hysteresisThreshold away from previous line, keep cursor on previous line.
diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp
index 1b203ca..d2e5462 100644
--- a/core/jni/android_os_MessageQueue.cpp
+++ b/core/jni/android_os_MessageQueue.cpp
@@ -41,7 +41,7 @@
 
     inline sp<Looper> getLooper() { return mLooper; }
 
-    bool pollOnce(int timeoutMillis);
+    void pollOnce(int timeoutMillis);
     void wake();
 
 private:
@@ -61,8 +61,8 @@
 NativeMessageQueue::~NativeMessageQueue() {
 }
 
-bool NativeMessageQueue::pollOnce(int timeoutMillis) {
-    return mLooper->pollOnce(timeoutMillis) != ALOOPER_POLL_TIMEOUT;
+void NativeMessageQueue::pollOnce(int timeoutMillis) {
+    mLooper->pollOnce(timeoutMillis);
 }
 
 void NativeMessageQueue::wake() {
@@ -112,24 +112,14 @@
     jniThrowException(env, "java/lang/IllegalStateException", "Message queue not initialized");
 }
 
-static jboolean android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
-        jint timeoutMillis) {
-    NativeMessageQueue* nativeMessageQueue =
-            android_os_MessageQueue_getNativeMessageQueue(env, obj);
-    if (! nativeMessageQueue) {
-        throwQueueNotInitialized(env);
-        return false;
-    }
-    return nativeMessageQueue->pollOnce(timeoutMillis);
+static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
+        jint ptr, jint timeoutMillis) {
+    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
+    nativeMessageQueue->pollOnce(timeoutMillis);
 }
 
-static void android_os_MessageQueue_nativeWake(JNIEnv* env, jobject obj) {
-    NativeMessageQueue* nativeMessageQueue =
-            android_os_MessageQueue_getNativeMessageQueue(env, obj);
-    if (! nativeMessageQueue) {
-        throwQueueNotInitialized(env);
-        return;
-    }
+static void android_os_MessageQueue_nativeWake(JNIEnv* env, jobject obj, jint ptr) {
+    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
     return nativeMessageQueue->wake();
 }
 
@@ -139,8 +129,8 @@
     /* name, signature, funcPtr */
     { "nativeInit", "()V", (void*)android_os_MessageQueue_nativeInit },
     { "nativeDestroy", "()V", (void*)android_os_MessageQueue_nativeDestroy },
-    { "nativePollOnce", "(I)Z", (void*)android_os_MessageQueue_nativePollOnce },
-    { "nativeWake", "()V", (void*)android_os_MessageQueue_nativeWake }
+    { "nativePollOnce", "(II)V", (void*)android_os_MessageQueue_nativePollOnce },
+    { "nativeWake", "(I)V", (void*)android_os_MessageQueue_nativeWake }
 };
 
 #define FIND_CLASS(var, className) \
diff --git a/core/res/res/drawable-hdpi/popup_bottom_dark.9.png b/core/res/res/drawable-hdpi/popup_bottom_dark.9.png
index 26500bb..8b5d3d5 100755
--- a/core/res/res/drawable-hdpi/popup_bottom_dark.9.png
+++ b/core/res/res/drawable-hdpi/popup_bottom_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_bottom_medium.9.png b/core/res/res/drawable-hdpi/popup_bottom_medium.9.png
index 42e5ad5..26ede44 100755
--- a/core/res/res/drawable-hdpi/popup_bottom_medium.9.png
+++ b/core/res/res/drawable-hdpi/popup_bottom_medium.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_center_dark.9.png b/core/res/res/drawable-hdpi/popup_center_dark.9.png
index b0740a4..ac1f92df 100755
--- a/core/res/res/drawable-hdpi/popup_center_dark.9.png
+++ b/core/res/res/drawable-hdpi/popup_center_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_center_medium.9.png b/core/res/res/drawable-hdpi/popup_center_medium.9.png
index 1cca06d..1ce2a6d 100755
--- a/core/res/res/drawable-hdpi/popup_center_medium.9.png
+++ b/core/res/res/drawable-hdpi/popup_center_medium.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_full_dark.9.png b/core/res/res/drawable-hdpi/popup_full_dark.9.png
index 155d7df..8f2fdf0 100755
--- a/core/res/res/drawable-hdpi/popup_full_dark.9.png
+++ b/core/res/res/drawable-hdpi/popup_full_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/popup_top_dark.9.png b/core/res/res/drawable-hdpi/popup_top_dark.9.png
index 60e7e20..1108909 100755
--- a/core/res/res/drawable-hdpi/popup_top_dark.9.png
+++ b/core/res/res/drawable-hdpi/popup_top_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/screen_progress_frame.9.png b/core/res/res/drawable-hdpi/screen_progress_frame.9.png
deleted file mode 100644
index 3f9d738..0000000
--- a/core/res/res/drawable-hdpi/screen_progress_frame.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/screen_progress_inner.9.png b/core/res/res/drawable-hdpi/screen_progress_inner.9.png
deleted file mode 100644
index 10c7da5..0000000
--- a/core/res/res/drawable-hdpi/screen_progress_inner.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/text_select_handle_left.png b/core/res/res/drawable-hdpi/text_select_handle_left.png
old mode 100644
new mode 100755
index 985ae93..3743d91
--- a/core/res/res/drawable-hdpi/text_select_handle_left.png
+++ b/core/res/res/drawable-hdpi/text_select_handle_left.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/text_select_handle_middle.png b/core/res/res/drawable-hdpi/text_select_handle_middle.png
old mode 100644
new mode 100755
Binary files differ
diff --git a/core/res/res/drawable-hdpi/text_select_handle_right.png b/core/res/res/drawable-hdpi/text_select_handle_right.png
old mode 100644
new mode 100755
index 964c5ff..12a3dff
--- a/core/res/res/drawable-hdpi/text_select_handle_right.png
+++ b/core/res/res/drawable-hdpi/text_select_handle_right.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_emergency.png b/core/res/res/drawable-mdpi/ic_emergency.png
index 45d0f21..c6faf1e 100755
--- a/core/res/res/drawable-mdpi/ic_emergency.png
+++ b/core/res/res/drawable-mdpi/ic_emergency.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/screen_progress_frame.9.png b/core/res/res/drawable-mdpi/screen_progress_frame.9.png
deleted file mode 100644
index 0e92429..0000000
--- a/core/res/res/drawable-mdpi/screen_progress_frame.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/screen_progress_inner.9.png b/core/res/res/drawable-mdpi/screen_progress_inner.9.png
deleted file mode 100644
index 1799a53..0000000
--- a/core/res/res/drawable-mdpi/screen_progress_inner.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable/screen_progress.xml b/core/res/res/drawable/screen_progress.xml
deleted file mode 100644
index aed23a6..0000000
--- a/core/res/res/drawable/screen_progress.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* //device/apps/common/res/drawable/progress.xml
-**
-** Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
--->
-
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:drawable="@android:drawable/screen_progress_frame" />
-    <item>
-        <scale scaleWidth="100%" scaleGravity="0x3" drawable="@android:drawable/screen_progress_inner" />
-    </item>
-</layer-list>
-
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6e6dc26..340e23c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -352,4 +352,8 @@
 
     <!-- Enables SIP on WIFI only -->
     <bool name="config_sip_wifi_only">false</bool>
+
+    <!-- Boolean indicating if restoring network selection should be skipped -->
+    <!-- The restoring is handled by modem if it is true-->
+    <bool translatable="false" name="skip_restoring_network_selection">false</bool>
 </resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index e7cb593..040e732 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -137,8 +137,8 @@
         <item name="scrollbarTrackVertical">@null</item>
 
         <!-- Text selection handle attributes -->
-        <item name="textSelectHandleLeft">@android:drawable/text_select_handle_middle</item>
-        <item name="textSelectHandleRight">@android:drawable/text_select_handle_middle</item>
+        <item name="textSelectHandleLeft">@android:drawable/text_select_handle_left</item>
+        <item name="textSelectHandleRight">@android:drawable/text_select_handle_right</item>
         <item name="textSelectHandle">@android:drawable/text_select_handle_middle</item>
         <item name="textSelectHandleWindowStyle">@android:style/Widget.TextSelectHandle</item>
 
diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java
index e9311e0..2d0424d 100644
--- a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java
+++ b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java
@@ -133,14 +133,12 @@
     private class HeadsetServiceListener implements ServiceListener {
         private boolean mConnected = false;
 
-        @Override
         public void onServiceConnected() {
             synchronized (this) {
                 mConnected = true;
             }
         }
 
-        @Override
         public void onServiceDisconnected() {
             synchronized (this) {
                 mConnected = false;
@@ -321,6 +319,9 @@
         filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
         filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
         mContext.registerReceiver(mReceiver, filter);
+
+        mA2dp = new BluetoothA2dp(mContext);
+        mHeadset = new BluetoothHeadset(mContext, mHeadsetServiceListener);
     }
 
     public void close() {
diff --git a/include/media/stagefright/MediaErrors.h b/include/media/stagefright/MediaErrors.h
index 73d0f77..e44122d 100644
--- a/include/media/stagefright/MediaErrors.h
+++ b/include/media/stagefright/MediaErrors.h
@@ -39,6 +39,7 @@
 
     // Not technically an error.
     INFO_FORMAT_CHANGED    = MEDIA_ERROR_BASE - 12,
+    INFO_DISCONTINUITY     = MEDIA_ERROR_BASE - 13,
 };
 
 }  // namespace android
diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h
index ab2f11d..d2bd9f2 100644
--- a/include/media/stagefright/MetaData.h
+++ b/include/media/stagefright/MetaData.h
@@ -97,6 +97,8 @@
     kKeyAutoLoop          = 'autL',  // bool (int32_t)
 
     kKeyValidSamples      = 'valD',  // int32_t
+
+    kKeyIsUnreadable      = 'unre',  // bool (int32_t)
 };
 
 enum {
diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h
index 875bc5b..2bb7783 100644
--- a/include/media/stagefright/OMXCodec.h
+++ b/include/media/stagefright/OMXCodec.h
@@ -32,7 +32,8 @@
 struct OMXCodec : public MediaSource,
                   public MediaBufferObserver {
     enum CreationFlags {
-        kPreferSoftwareCodecs = 1,
+        kPreferSoftwareCodecs    = 1,
+        kIgnoreCodecSpecificData = 2
     };
     static sp<MediaSource> Create(
             const sp<IOMX> &omx,
@@ -103,6 +104,7 @@
         kSupportsMultipleFramesPerInputBuffer = 1024,
         kAvoidMemcopyInputRecordingFrames     = 2048,
         kRequiresLargerEncoderOutputBuffer    = 4096,
+        kOutputBuffersAreUnreadable           = 8192,
     };
 
     struct BufferInfo {
@@ -247,9 +249,10 @@
 
     void dumpPortStatus(OMX_U32 portIndex);
 
-    status_t configureCodec(const sp<MetaData> &meta);
+    status_t configureCodec(const sp<MetaData> &meta, uint32_t flags);
 
-    static uint32_t getComponentQuirks(const char *componentName);
+    static uint32_t getComponentQuirks(
+            const char *componentName, bool isEncoder);
 
     static void findMatchingCodecs(
             const char *mime,
diff --git a/include/utils/Looper.h b/include/utils/Looper.h
index 3f00b78..cc51490 100644
--- a/include/utils/Looper.h
+++ b/include/utils/Looper.h
@@ -20,9 +20,22 @@
 #include <utils/threads.h>
 #include <utils/RefBase.h>
 #include <utils/KeyedVector.h>
+#include <utils/Timers.h>
 
 #include <android/looper.h>
 
+// Currently using poll() instead of epoll_wait() since it does a better job of meeting a
+// timeout deadline.  epoll_wait() typically causes additional delays of up to 10ms
+// beyond the requested timeout.
+//#define LOOPER_USES_EPOLL
+//#define LOOPER_STATISTICS
+
+#ifdef LOOPER_USES_EPOLL
+#include <sys/epoll.h>
+#else
+#include <sys/poll.h>
+#endif
+
 /*
  * Declare a concrete type for the NDK's looper forward declaration.
  */
@@ -190,13 +203,54 @@
 
     const bool mAllowNonCallbacks; // immutable
 
-    int mEpollFd; // immutable
     int mWakeReadPipeFd;  // immutable
     int mWakeWritePipeFd; // immutable
+    Mutex mLock;
+
+#ifdef LOOPER_USES_EPOLL
+    int mEpollFd; // immutable
 
     // Locked list of file descriptor monitoring requests.
-    Mutex mLock;
-    KeyedVector<int, Request> mRequests;
+    KeyedVector<int, Request> mRequests;  // guarded by mLock
+#else
+    // The lock guards state used to track whether there is a poll() in progress and whether
+    // there are any other threads waiting in wakeAndLock().  The condition variables
+    // are used to transfer control among these threads such that all waiters are
+    // serviced before a new poll can begin.
+    // The wakeAndLock() method increments mWaiters, wakes the poll, blocks on mAwake
+    // until mPolling becomes false, then decrements mWaiters again.
+    // The poll() method blocks on mResume until mWaiters becomes 0, then sets
+    // mPolling to true, blocks until the poll completes, then resets mPolling to false
+    // and signals mResume if there are waiters.
+    bool mPolling;      // guarded by mLock
+    uint32_t mWaiters;  // guarded by mLock
+    Condition mAwake;   // guarded by mLock
+    Condition mResume;  // guarded by mLock
+
+    Vector<struct pollfd> mRequestedFds;  // must hold mLock and mPolling must be false to modify
+    Vector<Request> mRequests;            // must hold mLock and mPolling must be false to modify
+
+    ssize_t getRequestIndexLocked(int fd);
+    void wakeAndLock();
+#endif
+
+#ifdef LOOPER_STATISTICS
+    static const int SAMPLED_WAKE_CYCLES_TO_AGGREGATE = 100;
+    static const int SAMPLED_POLLS_TO_AGGREGATE = 1000;
+
+    nsecs_t mPendingWakeTime;
+    int mPendingWakeCount;
+
+    int mSampledWakeCycles;
+    int mSampledWakeCountSum;
+    nsecs_t mSampledWakeLatencySum;
+
+    int mSampledPolls;
+    int mSampledZeroPollCount;
+    int mSampledZeroPollLatencySum;
+    int mSampledTimeoutPollCount;
+    int mSampledTimeoutPollLatencySum;
+#endif
 
     // This state is only used privately by pollOnce and does not require a lock since
     // it runs on a single thread.
@@ -204,6 +258,8 @@
     size_t mResponseIndex;
 
     int pollInner(int timeoutMillis);
+    void awoken();
+    void pushResponse(int events, const Request& request);
 
     static void initTLSKey();
     static void threadDestructor(void *st);
diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp
index 8e173aa..7adc764 100644
--- a/libs/ui/InputReader.cpp
+++ b/libs/ui/InputReader.cpp
@@ -2447,7 +2447,7 @@
         yPrecision = mLocked.orientedYPrecision;
     } // release lock
 
-    getDispatcher()->notifyMotion(when, getDeviceId(), AINPUT_SOURCE_TOUCHSCREEN, policyFlags,
+    getDispatcher()->notifyMotion(when, getDeviceId(), getSources(), policyFlags,
             motionEventAction, 0, getContext()->getGlobalMetaState(), motionEventEdgeFlags,
             pointerCount, pointerIds, pointerCoords,
             xPrecision, yPrecision, mDownTime);
diff --git a/libs/utils/Looper.cpp b/libs/utils/Looper.cpp
index d2dd6eb..a5363d6 100644
--- a/libs/utils/Looper.cpp
+++ b/libs/utils/Looper.cpp
@@ -19,16 +19,17 @@
 
 #include <unistd.h>
 #include <fcntl.h>
-#include <sys/epoll.h>
 
 
 namespace android {
 
+#ifdef LOOPER_USES_EPOLL
 // Hint for number of file descriptors to be associated with the epoll instance.
 static const int EPOLL_SIZE_HINT = 8;
 
 // Maximum number of file descriptors for which to retrieve poll events each iteration.
 static const int EPOLL_MAX_EVENTS = 16;
+#endif
 
 static pthread_once_t gTLSOnce = PTHREAD_ONCE_INIT;
 static pthread_key_t gTLSKey = 0;
@@ -36,9 +37,6 @@
 Looper::Looper(bool allowNonCallbacks) :
         mAllowNonCallbacks(allowNonCallbacks),
         mResponseIndex(0) {
-    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
-    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);
-
     int wakeFds[2];
     int result = pipe(wakeFds);
     LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);
@@ -54,6 +52,11 @@
     LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",
             errno);
 
+#ifdef LOOPER_USES_EPOLL
+    // Allocate the epoll instance and register the wake pipe.
+    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
+    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);
+
     struct epoll_event eventItem;
     memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
     eventItem.events = EPOLLIN;
@@ -61,12 +64,45 @@
     result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
     LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",
             errno);
+#else
+    // Add the wake pipe to the head of the request list with a null callback.
+    struct pollfd requestedFd;
+    requestedFd.fd = mWakeReadPipeFd;
+    requestedFd.events = POLLIN;
+    mRequestedFds.push(requestedFd);
+
+    Request request;
+    request.fd = mWakeReadPipeFd;
+    request.callback = NULL;
+    request.ident = 0;
+    request.data = NULL;
+    mRequests.push(request);
+
+    mPolling = false;
+    mWaiters = 0;
+#endif
+
+#ifdef LOOPER_STATISTICS
+    mPendingWakeTime = -1;
+    mPendingWakeCount = 0;
+    mSampledWakeCycles = 0;
+    mSampledWakeCountSum = 0;
+    mSampledWakeLatencySum = 0;
+
+    mSampledPolls = 0;
+    mSampledZeroPollCount = 0;
+    mSampledZeroPollLatencySum = 0;
+    mSampledTimeoutPollCount = 0;
+    mSampledTimeoutPollLatencySum = 0;
+#endif
 }
 
 Looper::~Looper() {
     close(mWakeReadPipeFd);
     close(mWakeWritePipeFd);
+#ifdef LOOPER_USES_EPOLL
     close(mEpollFd);
+#endif
 }
 
 void Looper::initTLSKey() {
@@ -157,45 +193,61 @@
 #if DEBUG_POLL_AND_WAKE
     LOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis);
 #endif
+
+    int result = ALOOPER_POLL_WAKE;
+    mResponses.clear();
+    mResponseIndex = 0;
+
+#ifdef LOOPER_STATISTICS
+    nsecs_t pollStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
+#endif
+
+#ifdef LOOPER_USES_EPOLL
     struct epoll_event eventItems[EPOLL_MAX_EVENTS];
     int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
+    bool acquiredLock = false;
+#else
+    // Wait for wakeAndLock() waiters to run then set mPolling to true.
+    mLock.lock();
+    while (mWaiters != 0) {
+        mResume.wait(mLock);
+    }
+    mPolling = true;
+    mLock.unlock();
+
+    size_t requestedCount = mRequestedFds.size();
+    int eventCount = poll(mRequestedFds.editArray(), requestedCount, timeoutMillis);
+#endif
+
     if (eventCount < 0) {
         if (errno == EINTR) {
-            return ALOOPER_POLL_WAKE;
+            goto Done;
         }
 
         LOGW("Poll failed with an unexpected error, errno=%d", errno);
-        return ALOOPER_POLL_ERROR;
+        result = ALOOPER_POLL_ERROR;
+        goto Done;
     }
 
     if (eventCount == 0) {
 #if DEBUG_POLL_AND_WAKE
         LOGD("%p ~ pollOnce - timeout", this);
 #endif
-        return ALOOPER_POLL_TIMEOUT;
+        result = ALOOPER_POLL_TIMEOUT;
+        goto Done;
     }
 
-    int result = ALOOPER_POLL_WAKE;
-    mResponses.clear();
-    mResponseIndex = 0;
-
 #if DEBUG_POLL_AND_WAKE
     LOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount);
 #endif
-    bool acquiredLock = false;
+
+#ifdef LOOPER_USES_EPOLL
     for (int i = 0; i < eventCount; i++) {
         int fd = eventItems[i].data.fd;
         uint32_t epollEvents = eventItems[i].events;
         if (fd == mWakeReadPipeFd) {
             if (epollEvents & EPOLLIN) {
-#if DEBUG_POLL_AND_WAKE
-                LOGD("%p ~ pollOnce - awoken", this);
-#endif
-                char buffer[16];
-                ssize_t nRead;
-                do {
-                    nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
-                } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
+                awoken();
             } else {
                 LOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
             }
@@ -212,11 +264,7 @@
                 if (epollEvents & EPOLLOUT) events |= ALOOPER_EVENT_OUTPUT;
                 if (epollEvents & EPOLLERR) events |= ALOOPER_EVENT_ERROR;
                 if (epollEvents & EPOLLHUP) events |= ALOOPER_EVENT_HANGUP;
-
-                Response response;
-                response.events = events;
-                response.request = mRequests.valueAt(requestIndex);
-                mResponses.push(response);
+                pushResponse(events, mRequests.valueAt(requestIndex));
             } else {
                 LOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
                         "no longer registered.", epollEvents, fd);
@@ -226,6 +274,66 @@
     if (acquiredLock) {
         mLock.unlock();
     }
+Done: ;
+#else
+    for (size_t i = 0; i < requestedCount; i++) {
+        const struct pollfd& requestedFd = mRequestedFds.itemAt(i);
+
+        short pollEvents = requestedFd.revents;
+        if (pollEvents) {
+            if (requestedFd.fd == mWakeReadPipeFd) {
+                if (pollEvents & POLLIN) {
+                    awoken();
+                } else {
+                    LOGW("Ignoring unexpected poll events 0x%x on wake read pipe.", pollEvents);
+                }
+            } else {
+                int events = 0;
+                if (pollEvents & POLLIN) events |= ALOOPER_EVENT_INPUT;
+                if (pollEvents & POLLOUT) events |= ALOOPER_EVENT_OUTPUT;
+                if (pollEvents & POLLERR) events |= ALOOPER_EVENT_ERROR;
+                if (pollEvents & POLLHUP) events |= ALOOPER_EVENT_HANGUP;
+                if (pollEvents & POLLNVAL) events |= ALOOPER_EVENT_INVALID;
+                pushResponse(events, mRequests.itemAt(i));
+            }
+            if (--eventCount == 0) {
+                break;
+            }
+        }
+    }
+
+Done:
+    // Set mPolling to false and wake up the wakeAndLock() waiters.
+    mLock.lock();
+    mPolling = false;
+    if (mWaiters != 0) {
+        mAwake.broadcast();
+    }
+    mLock.unlock();
+#endif
+
+#ifdef LOOPER_STATISTICS
+    nsecs_t pollEndTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    mSampledPolls += 1;
+    if (timeoutMillis == 0) {
+        mSampledZeroPollCount += 1;
+        mSampledZeroPollLatencySum += pollEndTime - pollStartTime;
+    } else if (timeoutMillis > 0 && result == ALOOPER_POLL_TIMEOUT) {
+        mSampledTimeoutPollCount += 1;
+        mSampledTimeoutPollLatencySum += pollEndTime - pollStartTime
+                - milliseconds_to_nanoseconds(timeoutMillis);
+    }
+    if (mSampledPolls == SAMPLED_POLLS_TO_AGGREGATE) {
+        LOGD("%p ~ poll latency statistics: %0.3fms zero timeout, %0.3fms non-zero timeout", this,
+                0.000001f * float(mSampledZeroPollLatencySum) / mSampledZeroPollCount,
+                0.000001f * float(mSampledTimeoutPollLatencySum) / mSampledTimeoutPollCount);
+        mSampledPolls = 0;
+        mSampledZeroPollCount = 0;
+        mSampledZeroPollLatencySum = 0;
+        mSampledTimeoutPollCount = 0;
+        mSampledTimeoutPollLatencySum = 0;
+    }
+#endif
 
     for (size_t i = 0; i < mResponses.size(); i++) {
         const Response& response = mResponses.itemAt(i);
@@ -278,6 +386,13 @@
     LOGD("%p ~ wake", this);
 #endif
 
+#ifdef LOOPER_STATISTICS
+    // FIXME: Possible race with awoken() but this code is for testing only and is rarely enabled.
+    if (mPendingWakeCount++ == 0) {
+        mPendingWakeTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    }
+#endif
+
     ssize_t nWrite;
     do {
         nWrite = write(mWakeWritePipeFd, "W", 1);
@@ -290,23 +405,51 @@
     }
 }
 
+void Looper::awoken() {
+#if DEBUG_POLL_AND_WAKE
+    LOGD("%p ~ awoken", this);
+#endif
+
+#ifdef LOOPER_STATISTICS
+    if (mPendingWakeCount == 0) {
+        LOGD("%p ~ awoken: spurious!", this);
+    } else {
+        mSampledWakeCycles += 1;
+        mSampledWakeCountSum += mPendingWakeCount;
+        mSampledWakeLatencySum += systemTime(SYSTEM_TIME_MONOTONIC) - mPendingWakeTime;
+        mPendingWakeCount = 0;
+        mPendingWakeTime = -1;
+        if (mSampledWakeCycles == SAMPLED_WAKE_CYCLES_TO_AGGREGATE) {
+            LOGD("%p ~ wake statistics: %0.3fms wake latency, %0.3f wakes per cycle", this,
+                    0.000001f * float(mSampledWakeLatencySum) / mSampledWakeCycles,
+                    float(mSampledWakeCountSum) / mSampledWakeCycles);
+            mSampledWakeCycles = 0;
+            mSampledWakeCountSum = 0;
+            mSampledWakeLatencySum = 0;
+        }
+    }
+#endif
+
+    char buffer[16];
+    ssize_t nRead;
+    do {
+        nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
+    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
+}
+
+void Looper::pushResponse(int events, const Request& request) {
+    Response response;
+    response.events = events;
+    response.request = request;
+    mResponses.push(response);
+}
+
 int Looper::addFd(int fd, int ident, int events, ALooper_callbackFunc callback, void* data) {
 #if DEBUG_CALLBACKS
     LOGD("%p ~ addFd - fd=%d, ident=%d, events=0x%x, callback=%p, data=%p", this, fd, ident,
             events, callback, data);
 #endif
 
-    int epollEvents = 0;
-    if (events & ALOOPER_EVENT_INPUT) epollEvents |= EPOLLIN;
-    if (events & ALOOPER_EVENT_OUTPUT) epollEvents |= EPOLLOUT;
-    if (events & ALOOPER_EVENT_ERROR) epollEvents |= EPOLLERR;
-    if (events & ALOOPER_EVENT_HANGUP) epollEvents |= EPOLLHUP;
-
-    if (epollEvents == 0) {
-        LOGE("Invalid attempt to set a callback with no selected poll events.");
-        return -1;
-    }
-
     if (! callback) {
         if (! mAllowNonCallbacks) {
             LOGE("Invalid attempt to set NULL callback but not allowed for this looper.");
@@ -319,6 +462,11 @@
         }
     }
 
+#ifdef LOOPER_USES_EPOLL
+    int epollEvents = 0;
+    if (events & ALOOPER_EVENT_INPUT) epollEvents |= EPOLLIN;
+    if (events & ALOOPER_EVENT_OUTPUT) epollEvents |= EPOLLOUT;
+
     { // acquire lock
         AutoMutex _l(mLock);
 
@@ -350,6 +498,33 @@
             mRequests.replaceValueAt(requestIndex, request);
         }
     } // release lock
+#else
+    int pollEvents = 0;
+    if (events & ALOOPER_EVENT_INPUT) pollEvents |= POLLIN;
+    if (events & ALOOPER_EVENT_OUTPUT) pollEvents |= POLLOUT;
+
+    wakeAndLock(); // acquire lock
+
+    struct pollfd requestedFd;
+    requestedFd.fd = fd;
+    requestedFd.events = pollEvents;
+
+    Request request;
+    request.fd = fd;
+    request.ident = ident;
+    request.callback = callback;
+    request.data = data;
+    ssize_t index = getRequestIndexLocked(fd);
+    if (index < 0) {
+        mRequestedFds.push(requestedFd);
+        mRequests.push(request);
+    } else {
+        mRequestedFds.replaceAt(requestedFd, size_t(index));
+        mRequests.replaceAt(request, size_t(index));
+    }
+
+    mLock.unlock(); // release lock
+#endif
     return 1;
 }
 
@@ -358,6 +533,7 @@
     LOGD("%p ~ removeFd - fd=%d", this, fd);
 #endif
 
+#ifdef LOOPER_USES_EPOLL
     { // acquire lock
         AutoMutex _l(mLock);
         ssize_t requestIndex = mRequests.indexOfKey(fd);
@@ -372,8 +548,49 @@
         }
 
         mRequests.removeItemsAt(requestIndex);
-    } // request lock
+    } // release lock
     return 1;
+#else
+    wakeAndLock(); // acquire lock
+
+    ssize_t index = getRequestIndexLocked(fd);
+    if (index >= 0) {
+        mRequestedFds.removeAt(size_t(index));
+        mRequests.removeAt(size_t(index));
+    }
+
+    mLock.unlock(); // release lock
+    return index >= 0;
+#endif
 }
 
+#ifndef LOOPER_USES_EPOLL
+ssize_t Looper::getRequestIndexLocked(int fd) {
+    size_t requestCount = mRequestedFds.size();
+
+    for (size_t i = 0; i < requestCount; i++) {
+        if (mRequestedFds.itemAt(i).fd == fd) {
+            return i;
+        }
+    }
+
+    return -1;
+}
+
+void Looper::wakeAndLock() {
+    mLock.lock();
+
+    mWaiters += 1;
+    while (mPolling) {
+        wake();
+        mAwake.wait(mLock);
+    }
+
+    mWaiters -= 1;
+    if (mWaiters == 0) {
+        mResume.signal();
+    }
+}
+#endif
+
 } // namespace android
diff --git a/libs/utils/tests/Looper_test.cpp b/libs/utils/tests/Looper_test.cpp
index afc92f8..cea1313 100644
--- a/libs/utils/tests/Looper_test.cpp
+++ b/libs/utils/tests/Looper_test.cpp
@@ -354,14 +354,6 @@
             << "addFd should return 1 because FD was added";
 }
 
-TEST_F(LooperTest, AddFd_WhenEventsIsZero_ReturnsError) {
-    Pipe pipe;
-    int result = mLooper->addFd(pipe.receiveFd, 0, 0, NULL, NULL);
-
-    EXPECT_EQ(-1, result)
-            << "addFd should return -1 because arguments were invalid";
-}
-
 TEST_F(LooperTest, AddFd_WhenIdentIsNegativeAndCallbackIsNull_ReturnsError) {
     Pipe pipe;
     int result = mLooper->addFd(pipe.receiveFd, -1, ALOOPER_EVENT_INPUT, NULL, NULL);
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index bfc23d4..8c17aab 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -561,6 +561,39 @@
     postBufferingEvent_l();
 }
 
+void AwesomePlayer::partial_reset_l() {
+    // Only reset the video renderer and shut down the video decoder.
+    // Then instantiate a new video decoder and resume video playback.
+
+    mVideoRenderer.clear();
+
+    if (mLastVideoBuffer) {
+        mLastVideoBuffer->release();
+        mLastVideoBuffer = NULL;
+    }
+
+    if (mVideoBuffer) {
+        mVideoBuffer->release();
+        mVideoBuffer = NULL;
+    }
+
+    {
+        mVideoSource->stop();
+
+        // The following hack is necessary to ensure that the OMX
+        // component is completely released by the time we may try
+        // to instantiate it again.
+        wp<MediaSource> tmp = mVideoSource;
+        mVideoSource.clear();
+        while (tmp.promote() != NULL) {
+            usleep(1000);
+        }
+        IPCThreadState::self()->flushCommands();
+    }
+
+    CHECK_EQ(OK, initVideoDecoder(OMXCodec::kIgnoreCodecSpecificData));
+}
+
 void AwesomePlayer::onStreamDone() {
     // Posted whenever any stream finishes playing.
 
@@ -570,7 +603,21 @@
     }
     mStreamDoneEventPending = false;
 
-    if (mStreamDoneStatus != ERROR_END_OF_STREAM) {
+    if (mStreamDoneStatus == INFO_DISCONTINUITY) {
+        // This special status is returned because an http live stream's
+        // video stream switched to a different bandwidth at this point
+        // and future data may have been encoded using different parameters.
+        // This requires us to shutdown the video decoder and reinstantiate
+        // a fresh one.
+
+        LOGV("INFO_DISCONTINUITY");
+
+        CHECK(mVideoSource != NULL);
+
+        partial_reset_l();
+        postVideoEvent_l();
+        return;
+    } else if (mStreamDoneStatus != ERROR_END_OF_STREAM) {
         LOGV("MEDIA_ERROR %d", mStreamDoneStatus);
 
         notifyListener_l(
@@ -939,8 +986,7 @@
     mVideoTrack = source;
 }
 
-status_t AwesomePlayer::initVideoDecoder() {
-    uint32_t flags = 0;
+status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {
     mVideoSource = OMXCodec::Create(
             mClient.interface(), mVideoTrack->getFormat(),
             false, // createEncoder
@@ -1571,21 +1617,30 @@
 
     if (mLastVideoBuffer) {
         size_t size = mLastVideoBuffer->range_length();
+
         if (size) {
-            state->mLastVideoFrameSize = size;
-            state->mLastVideoFrame = malloc(size);
-            memcpy(state->mLastVideoFrame,
-                   (const uint8_t *)mLastVideoBuffer->data()
-                        + mLastVideoBuffer->range_offset(),
-                   size);
+            int32_t unreadable;
+            if (!mLastVideoBuffer->meta_data()->findInt32(
+                        kKeyIsUnreadable, &unreadable)
+                    || unreadable == 0) {
+                state->mLastVideoFrameSize = size;
+                state->mLastVideoFrame = malloc(size);
+                memcpy(state->mLastVideoFrame,
+                       (const uint8_t *)mLastVideoBuffer->data()
+                            + mLastVideoBuffer->range_offset(),
+                       size);
 
-            state->mVideoWidth = mVideoWidth;
-            state->mVideoHeight = mVideoHeight;
+                state->mVideoWidth = mVideoWidth;
+                state->mVideoHeight = mVideoHeight;
 
-            sp<MetaData> meta = mVideoSource->getFormat();
-            CHECK(meta->findInt32(kKeyColorFormat, &state->mColorFormat));
-            CHECK(meta->findInt32(kKeyWidth, &state->mDecodedWidth));
-            CHECK(meta->findInt32(kKeyHeight, &state->mDecodedHeight));
+                sp<MetaData> meta = mVideoSource->getFormat();
+                CHECK(meta->findInt32(kKeyColorFormat, &state->mColorFormat));
+                CHECK(meta->findInt32(kKeyWidth, &state->mDecodedWidth));
+                CHECK(meta->findInt32(kKeyHeight, &state->mDecodedHeight));
+            } else {
+                LOGV("Unable to save last video frame, we have no access to "
+                     "the decoded video data.");
+            }
         }
     }
 
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 76c8870..4648ad3 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -346,7 +346,8 @@
 }
 
 // static
-uint32_t OMXCodec::getComponentQuirks(const char *componentName) {
+uint32_t OMXCodec::getComponentQuirks(
+        const char *componentName, bool isEncoder) {
     uint32_t quirks = 0;
 
     if (!strcmp(componentName, "OMX.PV.avcdec")) {
@@ -404,6 +405,13 @@
         quirks |= kInputBufferSizesAreBogus;
     }
 
+    if (!strncmp(componentName, "OMX.SEC.", 8) && !isEncoder) {
+        // These output buffers contain no video data, just some
+        // opaque information that allows the overlay to display their
+        // contents.
+        quirks |= kOutputBuffersAreUnreadable;
+    }
+
     return quirks;
 }
 
@@ -490,13 +498,13 @@
             LOGV("Successfully allocated OMX node '%s'", componentName);
 
             sp<OMXCodec> codec = new OMXCodec(
-                    omx, node, getComponentQuirks(componentName),
+                    omx, node, getComponentQuirks(componentName, createEncoder),
                     createEncoder, mime, componentName,
                     source);
 
             observer->setCodec(codec);
 
-            err = codec->configureCodec(meta);
+            err = codec->configureCodec(meta, flags);
 
             if (err == OK) {
                 return codec;
@@ -509,93 +517,95 @@
     return NULL;
 }
 
-status_t OMXCodec::configureCodec(const sp<MetaData> &meta) {
-    uint32_t type;
-    const void *data;
-    size_t size;
-    if (meta->findData(kKeyESDS, &type, &data, &size)) {
-        ESDS esds((const char *)data, size);
-        CHECK_EQ(esds.InitCheck(), OK);
+status_t OMXCodec::configureCodec(const sp<MetaData> &meta, uint32_t flags) {
+    if (!(flags & kIgnoreCodecSpecificData)) {
+        uint32_t type;
+        const void *data;
+        size_t size;
+        if (meta->findData(kKeyESDS, &type, &data, &size)) {
+            ESDS esds((const char *)data, size);
+            CHECK_EQ(esds.InitCheck(), OK);
 
-        const void *codec_specific_data;
-        size_t codec_specific_data_size;
-        esds.getCodecSpecificInfo(
-                &codec_specific_data, &codec_specific_data_size);
+            const void *codec_specific_data;
+            size_t codec_specific_data_size;
+            esds.getCodecSpecificInfo(
+                    &codec_specific_data, &codec_specific_data_size);
 
-        addCodecSpecificData(
-                codec_specific_data, codec_specific_data_size);
-    } else if (meta->findData(kKeyAVCC, &type, &data, &size)) {
-        // Parse the AVCDecoderConfigurationRecord
+            addCodecSpecificData(
+                    codec_specific_data, codec_specific_data_size);
+        } else if (meta->findData(kKeyAVCC, &type, &data, &size)) {
+            // Parse the AVCDecoderConfigurationRecord
 
-        const uint8_t *ptr = (const uint8_t *)data;
+            const uint8_t *ptr = (const uint8_t *)data;
 
-        CHECK(size >= 7);
-        CHECK_EQ(ptr[0], 1);  // configurationVersion == 1
-        uint8_t profile = ptr[1];
-        uint8_t level = ptr[3];
+            CHECK(size >= 7);
+            CHECK_EQ(ptr[0], 1);  // configurationVersion == 1
+            uint8_t profile = ptr[1];
+            uint8_t level = ptr[3];
 
-        // There is decodable content out there that fails the following
-        // assertion, let's be lenient for now...
-        // CHECK((ptr[4] >> 2) == 0x3f);  // reserved
+            // There is decodable content out there that fails the following
+            // assertion, let's be lenient for now...
+            // CHECK((ptr[4] >> 2) == 0x3f);  // reserved
 
-        size_t lengthSize = 1 + (ptr[4] & 3);
+            size_t lengthSize = 1 + (ptr[4] & 3);
 
-        // commented out check below as H264_QVGA_500_NO_AUDIO.3gp
-        // violates it...
-        // CHECK((ptr[5] >> 5) == 7);  // reserved
+            // commented out check below as H264_QVGA_500_NO_AUDIO.3gp
+            // violates it...
+            // CHECK((ptr[5] >> 5) == 7);  // reserved
 
-        size_t numSeqParameterSets = ptr[5] & 31;
+            size_t numSeqParameterSets = ptr[5] & 31;
 
-        ptr += 6;
-        size -= 6;
+            ptr += 6;
+            size -= 6;
 
-        for (size_t i = 0; i < numSeqParameterSets; ++i) {
-            CHECK(size >= 2);
-            size_t length = U16_AT(ptr);
+            for (size_t i = 0; i < numSeqParameterSets; ++i) {
+                CHECK(size >= 2);
+                size_t length = U16_AT(ptr);
 
-            ptr += 2;
-            size -= 2;
+                ptr += 2;
+                size -= 2;
 
-            CHECK(size >= length);
+                CHECK(size >= length);
 
-            addCodecSpecificData(ptr, length);
+                addCodecSpecificData(ptr, length);
 
-            ptr += length;
-            size -= length;
-        }
+                ptr += length;
+                size -= length;
+            }
 
-        CHECK(size >= 1);
-        size_t numPictureParameterSets = *ptr;
-        ++ptr;
-        --size;
+            CHECK(size >= 1);
+            size_t numPictureParameterSets = *ptr;
+            ++ptr;
+            --size;
 
-        for (size_t i = 0; i < numPictureParameterSets; ++i) {
-            CHECK(size >= 2);
-            size_t length = U16_AT(ptr);
+            for (size_t i = 0; i < numPictureParameterSets; ++i) {
+                CHECK(size >= 2);
+                size_t length = U16_AT(ptr);
 
-            ptr += 2;
-            size -= 2;
+                ptr += 2;
+                size -= 2;
 
-            CHECK(size >= length);
+                CHECK(size >= length);
 
-            addCodecSpecificData(ptr, length);
+                addCodecSpecificData(ptr, length);
 
-            ptr += length;
-            size -= length;
-        }
+                ptr += length;
+                size -= length;
+            }
 
-        CODEC_LOGV(
-                "AVC profile = %d (%s), level = %d",
-                (int)profile, AVCProfileToString(profile), level);
+            CODEC_LOGV(
+                    "AVC profile = %d (%s), level = %d",
+                    (int)profile, AVCProfileToString(profile), level);
 
-        if (!strcmp(mComponentName, "OMX.TI.Video.Decoder")
-            && (profile != kAVCProfileBaseline || level > 30)) {
-            // This stream exceeds the decoder's capabilities. The decoder
-            // does not handle this gracefully and would clobber the heap
-            // and wreak havoc instead...
+            if (!strcmp(mComponentName, "OMX.TI.Video.Decoder")
+                && (profile != kAVCProfileBaseline || level > 30)) {
+                // This stream exceeds the decoder's capabilities. The decoder
+                // does not handle this gracefully and would clobber the heap
+                // and wreak havoc instead...
 
-            LOGE("Profile and/or level exceed the decoder's capabilities.");
-            return ERROR_UNSUPPORTED;
+                LOGE("Profile and/or level exceed the decoder's capabilities.");
+                return ERROR_UNSUPPORTED;
+            }
         }
     }
 
@@ -1747,6 +1757,10 @@
                     buffer->meta_data()->setInt32(kKeyIsCodecConfig, true);
                 }
 
+                if (mQuirks & kOutputBuffersAreUnreadable) {
+                    buffer->meta_data()->setInt32(kKeyIsUnreadable, true);
+                }
+
                 buffer->meta_data()->setPointer(
                         kKeyPlatformPrivate,
                         msg.u.extended_buffer_data.platform_private);
diff --git a/media/libstagefright/StagefrightMetadataRetriever.cpp b/media/libstagefright/StagefrightMetadataRetriever.cpp
index 9d89c20..af9c70c 100644
--- a/media/libstagefright/StagefrightMetadataRetriever.cpp
+++ b/media/libstagefright/StagefrightMetadataRetriever.cpp
@@ -159,6 +159,20 @@
 
     LOGV("successfully decoded video frame.");
 
+    int32_t unreadable;
+    if (buffer->meta_data()->findInt32(kKeyIsUnreadable, &unreadable)
+            && unreadable != 0) {
+        LOGV("video frame is unreadable, decoder does not give us access "
+             "to the video data.");
+
+        buffer->release();
+        buffer = NULL;
+
+        decoder->stop();
+
+        return NULL;
+    }
+
     int64_t timeUs;
     CHECK(buffer->meta_data()->findInt64(kKeyTime, &timeUs));
     if (thumbNailTime >= 0) {
diff --git a/media/libstagefright/httplive/LiveSource.cpp b/media/libstagefright/httplive/LiveSource.cpp
index 9103927..943a0fc 100644
--- a/media/libstagefright/httplive/LiveSource.cpp
+++ b/media/libstagefright/httplive/LiveSource.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+//#define LOG_NDEBUG 0
 #define LOG_TAG "LiveSource"
 #include <utils/Log.h>
 
@@ -22,18 +23,21 @@
 #include "include/NuHTTPDataSource.h"
 
 #include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/FileSource.h>
 #include <media/stagefright/MediaDebug.h>
 
 namespace android {
 
 LiveSource::LiveSource(const char *url)
-    : mURL(url),
+    : mMasterURL(url),
       mInitCheck(NO_INIT),
       mPlaylistIndex(0),
       mLastFetchTimeUs(-1),
       mSource(new NuHTTPDataSource),
       mSourceSize(0),
-      mOffsetBias(0) {
+      mOffsetBias(0),
+      mSignalDiscontinuity(false),
+      mPrevBandwidthIndex(-1) {
     if (switchToNext()) {
         mInitCheck = OK;
     }
@@ -46,21 +50,129 @@
     return mInitCheck;
 }
 
-bool LiveSource::loadPlaylist() {
+// static
+int LiveSource::SortByBandwidth(const BandwidthItem *a, const BandwidthItem *b) {
+    if (a->mBandwidth < b->mBandwidth) {
+        return -1;
+    } else if (a->mBandwidth == b->mBandwidth) {
+        return 0;
+    }
+
+    return 1;
+}
+
+static double uniformRand() {
+    return (double)rand() / RAND_MAX;
+}
+
+bool LiveSource::loadPlaylist(bool fetchMaster) {
+    mSignalDiscontinuity = false;
+
     mPlaylist.clear();
     mPlaylistIndex = 0;
 
-    sp<ABuffer> buffer;
-    status_t err = fetchM3U(mURL.c_str(), &buffer);
+    if (fetchMaster) {
+        mPrevBandwidthIndex = -1;
 
-    if (err != OK) {
-        return false;
+        sp<ABuffer> buffer;
+        status_t err = fetchM3U(mMasterURL.c_str(), &buffer);
+
+        if (err != OK) {
+            return false;
+        }
+
+        mPlaylist = new M3UParser(
+                mMasterURL.c_str(), buffer->data(), buffer->size());
+
+        if (mPlaylist->initCheck() != OK) {
+            return false;
+        }
+
+        if (mPlaylist->isVariantPlaylist()) {
+            for (size_t i = 0; i < mPlaylist->size(); ++i) {
+                BandwidthItem item;
+
+                sp<AMessage> meta;
+                mPlaylist->itemAt(i, &item.mURI, &meta);
+
+                unsigned long bandwidth;
+                CHECK(meta->findInt32("bandwidth", (int32_t *)&item.mBandwidth));
+
+                mBandwidthItems.push(item);
+            }
+            mPlaylist.clear();
+
+            // fall through
+            if (mBandwidthItems.size() == 0) {
+                return false;
+            }
+
+            mBandwidthItems.sort(SortByBandwidth);
+
+            for (size_t i = 0; i < mBandwidthItems.size(); ++i) {
+                const BandwidthItem &item = mBandwidthItems.itemAt(i);
+                LOGV("item #%d: %s", i, item.mURI.c_str());
+            }
+        }
     }
 
-    mPlaylist = new M3UParser(mURL.c_str(), buffer->data(), buffer->size());
+    if (mBandwidthItems.size() > 0) {
+#if 0
+        // Change bandwidth at random()
+        size_t index = uniformRand() * mBandwidthItems.size();
+#elif 0
+        // There's a 50% chance to stay on the current bandwidth and
+        // a 50% chance to switch to the next higher bandwidth (wrapping around
+        // to lowest)
+        size_t index;
+        if (uniformRand() < 0.5) {
+            index = mPrevBandwidthIndex < 0 ? 0 : (size_t)mPrevBandwidthIndex;
+        } else {
+            if (mPrevBandwidthIndex < 0) {
+                index = 0;
+            } else {
+                index = mPrevBandwidthIndex + 1;
+                if (index == mBandwidthItems.size()) {
+                    index = 0;
+                }
+            }
+        }
+#else
+        // Stay on the lowest bandwidth available.
+        size_t index = 0;  // Lowest bandwidth stream
+#endif
 
-    if (mPlaylist->initCheck() != OK) {
-        return false;
+        mURL = mBandwidthItems.editItemAt(index).mURI;
+
+        if (mPrevBandwidthIndex >= 0 && (size_t)mPrevBandwidthIndex != index) {
+            // If we switched streams because of bandwidth changes,
+            // we'll signal this discontinuity by inserting a
+            // special transport stream packet into the stream.
+            mSignalDiscontinuity = true;
+        }
+
+        mPrevBandwidthIndex = index;
+    } else {
+        mURL = mMasterURL;
+    }
+
+    if (mPlaylist == NULL) {
+        sp<ABuffer> buffer;
+        status_t err = fetchM3U(mURL.c_str(), &buffer);
+
+        if (err != OK) {
+            return false;
+        }
+
+        mPlaylist = new M3UParser(mURL.c_str(), buffer->data(), buffer->size());
+
+        if (mPlaylist->initCheck() != OK) {
+            return false;
+        }
+
+        if (mPlaylist->isVariantPlaylist()) {
+            return false;
+        }
     }
 
     if (!mPlaylist->meta()->findInt32(
@@ -79,6 +191,8 @@
 }
 
 bool LiveSource::switchToNext() {
+    mSignalDiscontinuity = false;
+
     mOffsetBias += mSourceSize;
     mSourceSize = 0;
 
@@ -87,7 +201,7 @@
         int32_t nextSequenceNumber =
             mPlaylistIndex + mFirstItemSequenceNumber;
 
-        if (!loadPlaylist()) {
+        if (!loadPlaylist(mLastFetchTimeUs < 0)) {
             LOGE("failed to reload playlist");
             return false;
         }
@@ -111,35 +225,62 @@
     }
 
     AString uri;
-    CHECK(mPlaylist->itemAt(mPlaylistIndex, &uri));
-    LOGI("switching to %s", uri.c_str());
+    sp<AMessage> itemMeta;
+    CHECK(mPlaylist->itemAt(mPlaylistIndex, &uri, &itemMeta));
+    LOGV("switching to %s", uri.c_str());
 
     if (mSource->connect(uri.c_str()) != OK
             || mSource->getSize(&mSourceSize) != OK) {
         return false;
     }
 
+    int32_t val;
+    if (itemMeta->findInt32("discontinuity", &val) && val != 0) {
+        mSignalDiscontinuity = true;
+    }
+
     mPlaylistIndex++;
     return true;
 }
 
+static const ssize_t kHeaderSize = 188;
+
 ssize_t LiveSource::readAt(off_t offset, void *data, size_t size) {
     CHECK(offset >= mOffsetBias);
     offset -= mOffsetBias;
 
-    if (offset >= mSourceSize) {
-        CHECK_EQ(offset, mSourceSize);
+    off_t delta = mSignalDiscontinuity ? kHeaderSize : 0;
 
-        offset -= mSourceSize;
+    if (offset >= mSourceSize + delta) {
+        CHECK_EQ(offset, mSourceSize + delta);
+
+        offset -= mSourceSize + delta;
         if (!switchToNext()) {
             return ERROR_END_OF_STREAM;
         }
+
+        if (mSignalDiscontinuity) {
+            LOGV("switchToNext changed streams");
+        } else {
+            LOGV("switchToNext stayed within the same stream");
+        }
+
+        mOffsetBias += delta;
+
+        delta = mSignalDiscontinuity ? kHeaderSize : 0;
+    }
+
+    if (offset < delta) {
+        size_t avail = delta - offset;
+        memset(data, 0, avail);
+        return avail;
     }
 
     size_t numRead = 0;
     while (numRead < size) {
         ssize_t n = mSource->readAt(
-                offset + numRead, (uint8_t *)data + numRead, size - numRead);
+                offset + numRead - delta,
+                (uint8_t *)data + numRead, size - numRead);
 
         if (n <= 0) {
             break;
@@ -154,14 +295,24 @@
 status_t LiveSource::fetchM3U(const char *url, sp<ABuffer> *out) {
     *out = NULL;
 
-    status_t err = mSource->connect(url);
+    sp<DataSource> source;
 
-    if (err != OK) {
-        return err;
+    if (!strncasecmp(url, "file://", 7)) {
+        source = new FileSource(url + 7);
+    } else {
+        CHECK(!strncasecmp(url, "http://", 7));
+
+        status_t err = mSource->connect(url);
+
+        if (err != OK) {
+            return err;
+        }
+
+        source = mSource;
     }
 
     off_t size;
-    err = mSource->getSize(&size);
+    status_t err = source->getSize(&size);
 
     if (err != OK) {
         return err;
@@ -170,7 +321,7 @@
     sp<ABuffer> buffer = new ABuffer(size);
     size_t offset = 0;
     while (offset < (size_t)size) {
-        ssize_t n = mSource->readAt(
+        ssize_t n = source->readAt(
                 offset, buffer->data() + offset, size - offset);
 
         if (n <= 0) {
diff --git a/media/libstagefright/httplive/M3UParser.cpp b/media/libstagefright/httplive/M3UParser.cpp
index edd8648..0d7daa9 100644
--- a/media/libstagefright/httplive/M3UParser.cpp
+++ b/media/libstagefright/httplive/M3UParser.cpp
@@ -74,7 +74,8 @@
 static bool MakeURL(const char *baseURL, const char *url, AString *out) {
     out->clear();
 
-    if (strncasecmp("http://", baseURL, 7)) {
+    if (strncasecmp("http://", baseURL, 7)
+            && strncasecmp("file://", baseURL, 7)) {
         // Base URL must be absolute
         return false;
     }
@@ -128,7 +129,12 @@
             line.setTo(&data[offset], offsetLF - offset);
         }
 
-        LOGI("#%s#", line.c_str());
+        // LOGI("#%s#", line.c_str());
+
+        if (line.empty()) {
+            offset = offsetLF + 1;
+            continue;
+        }
 
         if (lineNo == 0 && line == "#EXTM3U") {
             mIsExtM3U = true;
@@ -152,11 +158,20 @@
                     return ERROR_MALFORMED;
                 }
                 err = parseMetaData(line, &itemMeta, "duration");
+            } else if (line.startsWith("#EXT-X-DISCONTINUITY")) {
+                if (mIsVariantPlaylist) {
+                    return ERROR_MALFORMED;
+                }
+                if (itemMeta == NULL) {
+                    itemMeta = new AMessage;
+                }
+                itemMeta->setInt32("discontinuity", true);
             } else if (line.startsWith("#EXT-X-STREAM-INF")) {
                 if (mMeta != NULL) {
                     return ERROR_MALFORMED;
                 }
                 mIsVariantPlaylist = true;
+                err = parseStreamInf(line, &itemMeta);
             }
 
             if (err != OK) {
@@ -215,6 +230,61 @@
 }
 
 // static
+status_t M3UParser::parseStreamInf(
+        const AString &line, sp<AMessage> *meta) {
+    ssize_t colonPos = line.find(":");
+
+    if (colonPos < 0) {
+        return ERROR_MALFORMED;
+    }
+
+    size_t offset = colonPos + 1;
+
+    while (offset < line.size()) {
+        ssize_t end = line.find(",", offset);
+        if (end < 0) {
+            end = line.size();
+        }
+
+        AString attr(line, offset, end - offset);
+        attr.trim();
+
+        offset = end + 1;
+
+        ssize_t equalPos = attr.find("=");
+        if (equalPos < 0) {
+            continue;
+        }
+
+        AString key(attr, 0, equalPos);
+        key.trim();
+
+        AString val(attr, equalPos + 1, attr.size() - equalPos - 1);
+        val.trim();
+
+        LOGV("key=%s value=%s", key.c_str(), val.c_str());
+
+        if (!strcasecmp("bandwidth", key.c_str())) {
+            const char *s = val.c_str();
+            char *end;
+            unsigned long x = strtoul(s, &end, 10);
+
+            if (end == s || *end != '\0') {
+                // malformed
+                continue;
+            }
+
+            if (meta->get() == NULL) {
+                *meta = new AMessage;
+            }
+            (*meta)->setInt32("bandwidth", x);
+        }
+    }
+
+    return OK;
+}
+
+// static
 status_t M3UParser::ParseInt32(const char *s, int32_t *x) {
     char *end;
     long lval = strtol(s, &end, 10);
diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h
index 40ea37d..bbf482d 100644
--- a/media/libstagefright/include/AwesomePlayer.h
+++ b/media/libstagefright/include/AwesomePlayer.h
@@ -220,6 +220,7 @@
     status_t setDataSource_l(const sp<DataSource> &dataSource);
     status_t setDataSource_l(const sp<MediaExtractor> &extractor);
     void reset_l();
+    void partial_reset_l();
     status_t seekTo_l(int64_t timeUs);
     status_t pause_l(bool at_eos = false);
     void initRenderer_l();
@@ -231,7 +232,7 @@
     status_t initAudioDecoder();
 
     void setVideoSource(sp<MediaSource> source);
-    status_t initVideoDecoder();
+    status_t initVideoDecoder(uint32_t flags = 0);
 
     void onStreamDone();
 
diff --git a/media/libstagefright/include/LiveSource.h b/media/libstagefright/include/LiveSource.h
index c55508c..5e89581 100644
--- a/media/libstagefright/include/LiveSource.h
+++ b/media/libstagefright/include/LiveSource.h
@@ -44,6 +44,13 @@
     virtual ~LiveSource();
 
 private:
+    struct BandwidthItem {
+        AString mURI;
+        unsigned long mBandwidth;
+    };
+    Vector<BandwidthItem> mBandwidthItems;
+
+    AString mMasterURL;
     AString mURL;
     status_t mInitCheck;
 
@@ -56,10 +63,15 @@
     off_t mSourceSize;
     off_t mOffsetBias;
 
+    bool mSignalDiscontinuity;
+    ssize_t mPrevBandwidthIndex;
+
     status_t fetchM3U(const char *url, sp<ABuffer> *buffer);
 
+    static int SortByBandwidth(const BandwidthItem *a, const BandwidthItem *b);
+
     bool switchToNext();
-    bool loadPlaylist();
+    bool loadPlaylist(bool fetchMaster);
 
     DISALLOW_EVIL_CONSTRUCTORS(LiveSource);
 };
diff --git a/media/libstagefright/include/M3UParser.h b/media/libstagefright/include/M3UParser.h
index 36553de..69199ab 100644
--- a/media/libstagefright/include/M3UParser.h
+++ b/media/libstagefright/include/M3UParser.h
@@ -61,6 +61,9 @@
     static status_t parseMetaData(
             const AString &line, sp<AMessage> *meta, const char *key);
 
+    static status_t parseStreamInf(
+            const AString &line, sp<AMessage> *meta);
+
     static status_t ParseInt32(const char *s, int32_t *x);
 
     DISALLOW_EVIL_CONSTRUCTORS(M3UParser);
diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp
index bcaab9f..7c9b83a 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.cpp
+++ b/media/libstagefright/mpeg2ts/ATSParser.cpp
@@ -49,6 +49,8 @@
             unsigned pid, unsigned payload_unit_start_indicator,
             ABitReader *br);
 
+    void signalDiscontinuity();
+
     sp<MediaSource> getSource(SourceType type);
 
 private:
@@ -67,6 +69,8 @@
             unsigned payload_unit_start_indicator,
             ABitReader *br);
 
+    void signalDiscontinuity();
+
     sp<MediaSource> getSource(SourceType type);
 
 protected:
@@ -124,6 +128,12 @@
     return true;
 }
 
+void ATSParser::Program::signalDiscontinuity() {
+    for (size_t i = 0; i < mStreams.size(); ++i) {
+        mStreams.editValueAt(i)->signalDiscontinuity();
+    }
+}
+
 void ATSParser::Program::parseProgramMap(ABitReader *br) {
     unsigned table_id = br->getBits(8);
     LOGV("  table_id = %u", table_id);
@@ -271,6 +281,19 @@
     mBuffer->setRange(0, mBuffer->size() + payloadSizeBits / 8);
 }
 
+void ATSParser::Stream::signalDiscontinuity() {
+    LOGV("Stream discontinuity");
+    mPayloadStarted = false;
+    mBuffer->setRange(0, 0);
+
+    mQueue.clear();
+
+    if (mStreamType == 0x1b && mSource != NULL) {
+        // Don't signal discontinuities on audio streams.
+        mSource->queueDiscontinuity();
+    }
+}
+
 void ATSParser::Stream::parsePES(ABitReader *br) {
     unsigned packet_startcode_prefix = br->getBits(24);
 
@@ -459,7 +482,10 @@
                 mSource = new AnotherPacketSource(meta);
                 mSource->queueAccessUnit(accessUnit);
             }
-        } else {
+        } else if (mQueue.getFormat() != NULL) {
+            // After a discontinuity we invalidate the queue's format
+            // and won't enqueue any access units to the source until
+            // the queue has reestablished the new format.
             mSource->queueAccessUnit(accessUnit);
         }
     }
@@ -489,6 +515,12 @@
     parseTS(&br);
 }
 
+void ATSParser::signalDiscontinuity() {
+    for (size_t i = 0; i < mPrograms.size(); ++i) {
+        mPrograms.editItemAt(i)->signalDiscontinuity();
+    }
+}
+
 void ATSParser::parseProgramAssociationTable(ABitReader *br) {
     unsigned table_id = br->getBits(8);
     LOGV("  table_id = %u", table_id);
diff --git a/media/libstagefright/mpeg2ts/ATSParser.h b/media/libstagefright/mpeg2ts/ATSParser.h
index 1e22e7b..9ec6d7b 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.h
+++ b/media/libstagefright/mpeg2ts/ATSParser.h
@@ -33,6 +33,7 @@
     ATSParser();
 
     void feedTSPacket(const void *data, size_t size);
+    void signalDiscontinuity();
 
     enum SourceType {
         AVC_VIDEO,
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
index 3d51177..3f76820 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
@@ -59,21 +59,26 @@
 
     if (!mBuffers.empty()) {
         const sp<ABuffer> buffer = *mBuffers.begin();
-
-        uint64_t timeUs;
-        CHECK(buffer->meta()->findInt64(
-                    "time", (int64_t *)&timeUs));
-
-        MediaBuffer *mediaBuffer = new MediaBuffer(buffer->size());
-        mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs);
-
-        // hexdump(buffer->data(), buffer->size());
-
-        memcpy(mediaBuffer->data(), buffer->data(), buffer->size());
-        *out = mediaBuffer;
-
         mBuffers.erase(mBuffers.begin());
-        return OK;
+
+        int32_t discontinuity;
+        if (buffer->meta()->findInt32("discontinuity", &discontinuity)
+                && discontinuity) {
+            return INFO_DISCONTINUITY;
+        } else {
+            uint64_t timeUs;
+            CHECK(buffer->meta()->findInt64(
+                        "time", (int64_t *)&timeUs));
+
+            MediaBuffer *mediaBuffer = new MediaBuffer(buffer->size());
+            mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs);
+
+            // hexdump(buffer->data(), buffer->size());
+
+            memcpy(mediaBuffer->data(), buffer->data(), buffer->size());
+            *out = mediaBuffer;
+            return OK;
+        }
     }
 
     return mEOSResult;
@@ -91,6 +96,15 @@
     mCondition.signal();
 }
 
+void AnotherPacketSource::queueDiscontinuity() {
+    sp<ABuffer> buffer = new ABuffer(0);
+    buffer->meta()->setInt32("discontinuity", true);
+
+    Mutex::Autolock autoLock(mLock);
+    mBuffers.push_back(buffer);
+    mCondition.signal();
+}
+
 void AnotherPacketSource::signalEOS(status_t result) {
     CHECK(result != OK);
 
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.h b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
index ce83d21..6b43c4e 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.h
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
@@ -40,6 +40,7 @@
     bool hasBufferAvailable(status_t *finalResult);
 
     void queueAccessUnit(const sp<ABuffer> &buffer);
+    void queueDiscontinuity();
     void signalEOS(status_t result);
 
 protected:
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
index d87040b..4a75ee4 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.cpp
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -115,6 +115,11 @@
     return OK;
 }
 
+void ElementaryStreamQueue::clear() {
+    mBuffer->setRange(0, 0);
+    mFormat.clear();
+}
+
 status_t ElementaryStreamQueue::appendData(
         const void *data, size_t size, int64_t timeUs) {
     if (mBuffer == NULL || mBuffer->size() == 0) {
@@ -147,7 +152,7 @@
     if (mBuffer == NULL || neededSize > mBuffer->capacity()) {
         neededSize = (neededSize + 65535) & ~65535;
 
-        LOGI("resizing buffer to size %d", neededSize);
+        LOGV("resizing buffer to size %d", neededSize);
 
         sp<ABuffer> buffer = new ABuffer(neededSize);
         if (mBuffer != NULL) {
@@ -498,6 +503,8 @@
     meta->setInt32(kKeyWidth, width);
     meta->setInt32(kKeyHeight, height);
 
+    LOGI("found AVC codec config (%d x %d)", width, height);
+
     return meta;
 }
 
diff --git a/media/libstagefright/mpeg2ts/ESQueue.h b/media/libstagefright/mpeg2ts/ESQueue.h
index d2e87f2..246c390 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.h
+++ b/media/libstagefright/mpeg2ts/ESQueue.h
@@ -35,6 +35,7 @@
     ElementaryStreamQueue(Mode mode);
 
     status_t appendData(const void *data, size_t size, int64_t timeUs);
+    void clear();
 
     sp<ABuffer> dequeueAccessUnit();
 
diff --git a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
index c5257bb..0d96bd1 100644
--- a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
+++ b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
@@ -165,18 +165,26 @@
     LOGI("haveAudio=%d, haveVideo=%d", haveAudio, haveVideo);
 }
 
+static bool isDiscontinuity(const uint8_t *data, ssize_t size) {
+    return size == 188 && data[0] == 0x00;
+}
+
 status_t MPEG2TSExtractor::feedMore() {
     Mutex::Autolock autoLock(mLock);
 
     uint8_t packet[kTSPacketSize];
     ssize_t n = mDataSource->readAt(mOffset, packet, kTSPacketSize);
 
-    if (n < (ssize_t)kTSPacketSize) {
+    if (isDiscontinuity(packet, n)) {
+        LOGI("XXX discontinuity detected");
+        mParser->signalDiscontinuity();
+    } else if (n < (ssize_t)kTSPacketSize) {
         return (n < 0) ? (status_t)n : ERROR_END_OF_STREAM;
+    } else {
+        mParser->feedTSPacket(packet, kTSPacketSize);
     }
 
-    mOffset += kTSPacketSize;
-    mParser->feedTSPacket(packet, kTSPacketSize);
+    mOffset += n;
 
     return OK;
 }
diff --git a/native/include/android/looper.h b/native/include/android/looper.h
index a63b744..a9d8426 100644
--- a/native/include/android/looper.h
+++ b/native/include/android/looper.h
@@ -135,6 +135,15 @@
      * to specify this event flag in the requested event set.
      */
     ALOOPER_EVENT_HANGUP = 1 << 3,
+
+    /**
+     * The file descriptor is invalid.
+     * For example, the file descriptor was closed prematurely.
+     *
+     * The looper always sends notifications about invalid file descriptors; it is not necessary
+     * to specify this event flag in the requested event set.
+     */
+    ALOOPER_EVENT_INVALID = 1 << 4,
 };
 
 /**
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 06f9c41..3b2d836 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -79,8 +79,6 @@
 
     private static final String VOLD_TAG = "VoldConnector";
 
-    protected static final int MAX_OBBS = 8;
-
     /*
      * Internal vold volume state constants
      */
@@ -159,7 +157,6 @@
      * Mounted OBB tracking information. Used to track the current state of all
      * OBBs.
      */
-    final private Map<Integer, Integer> mObbUidUsage = new HashMap<Integer, Integer>();
     final private Map<IBinder, List<ObbState>> mObbMounts = new HashMap<IBinder, List<ObbState>>();
     final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>();
 
@@ -226,7 +223,6 @@
     private static final int OBB_MCS_BOUND = 2;
     private static final int OBB_MCS_UNBIND = 3;
     private static final int OBB_MCS_RECONNECT = 4;
-    private static final int OBB_MCS_GIVE_UP = 5;
 
     /*
      * Default Container Service information
@@ -1591,12 +1587,6 @@
             }
 
             final int callerUid = Binder.getCallingUid();
-
-            final Integer uidUsage = mObbUidUsage.get(callerUid);
-            if (uidUsage != null && uidUsage > MAX_OBBS) {
-                throw new IllegalStateException("Maximum number of OBBs mounted!");
-            }
-
             obbState = new ObbState(filename, token, callerUid);
             addObbState(obbState);
         }
@@ -1695,15 +1685,6 @@
             }
 
             mObbPathToStateMap.put(obbState.filename, obbState);
-
-            // Track the number of OBBs used by this UID.
-            final int uid = obbState.callerUid;
-            final Integer uidUsage = mObbUidUsage.get(uid);
-            if (uidUsage == null) {
-                mObbUidUsage.put(uid, 1);
-            } else {
-                mObbUidUsage.put(uid, uidUsage + 1);
-            }
         }
     }
 
@@ -1721,20 +1702,6 @@
             }
 
             mObbPathToStateMap.remove(obbState.filename);
-
-            // Track the number of OBBs used by this UID.
-            final int uid = obbState.callerUid;
-            final Integer uidUsage = mObbUidUsage.get(uid);
-            if (uidUsage == null) {
-                Slog.e(TAG, "Called removeObbState for UID that isn't in map: " + uid);
-            } else {
-                final int newUsage = uidUsage - 1;
-                if (newUsage == 0) {
-                    mObbUidUsage.remove(uid);
-                } else {
-                    mObbUidUsage.put(uid, newUsage);
-                }
-            }
         }
     }
 
@@ -1747,7 +1714,7 @@
 
     private class ObbActionHandler extends Handler {
         private boolean mBound = false;
-        private List<ObbAction> mActions = new LinkedList<ObbAction>();
+        private final List<ObbAction> mActions = new LinkedList<ObbAction>();
 
         ObbActionHandler(Looper l) {
             super(l);
@@ -1757,7 +1724,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case OBB_RUN_ACTION: {
-                    ObbAction action = (ObbAction) msg.obj;
+                    final ObbAction action = (ObbAction) msg.obj;
 
                     if (DEBUG_OBB)
                         Slog.i(TAG, "OBB_RUN_ACTION: " + action.toString());
@@ -1793,7 +1760,7 @@
                         }
                         mActions.clear();
                     } else if (mActions.size() > 0) {
-                        ObbAction action = mActions.get(0);
+                        final ObbAction action = mActions.get(0);
                         if (action != null) {
                             action.execute(this);
                         }
@@ -1841,13 +1808,6 @@
                     }
                     break;
                 }
-                case OBB_MCS_GIVE_UP: {
-                    if (DEBUG_OBB)
-                        Slog.i(TAG, "OBB_MCS_GIVE_UP");
-                    mActions.remove(0);
-                    mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
-                    break;
-                }
             }
         }
 
@@ -1887,7 +1847,7 @@
                 mRetries++;
                 if (mRetries > MAX_RETRIES) {
                     Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
-                    mObbActionHandler.sendEmptyMessage(OBB_MCS_GIVE_UP);
+                    mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
                     handleError();
                     return;
                 } else {
diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java
index cf89848..360d35e 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfo.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfo.java
@@ -26,9 +26,9 @@
 import android.text.TextUtils;
 import android.telephony.TelephonyManager;
 import android.telephony.PhoneNumberUtils;
-import android.util.Config;
 import android.util.Log;
 
+
 /**
  * Looks up caller information for the given phone number.
  *
@@ -36,6 +36,7 @@
  */
 public class CallerInfo {
     private static final String TAG = "CallerInfo";
+    private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
 
     public static final String UNKNOWN_NUMBER = "-1";
     public static final String PRIVATE_NUMBER = "-2";
@@ -128,7 +129,7 @@
         info.isCachedPhotoCurrent = false;
         info.contactExists = false;
 
-        if (Config.LOGV) Log.v(TAG, "construct callerInfo from cursor");
+        if (VDBG) Log.v(TAG, "construct callerInfo from cursor");
 
         if (cursor != null) {
             if (cursor.moveToFirst()) {
@@ -166,31 +167,30 @@
                 // Look for the person ID.
 
                 // TODO: This is pretty ugly now, see bug 2269240 for
-                // more details. With tel: URI the contact id is in
-                // col "_id" while when we use a
-                // content://contacts/data/phones URI, the contact id
-                // is col "contact_id". As a work around we use the
-                // type of the contact url to figure out which column
-                // we should look at to get the contact_id.
-
-                final String mimeType = context.getContentResolver().getType(contactRef);
+                // more details. The column to use depends upon the type of URL,
+                // for content://com.android.contacts/data/phones the "contact_id"
+                // column is used. For content/com.andriod.contacts/phone_lookup"
+                // the "_ID" column is used. If it is neither we leave columnIndex
+                // at -1 and no person ID will be available.
 
                 columnIndex = -1;
-                if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    // content://com.android.contacts/data/phones URL
+                String url = contactRef.toString();
+                if (url.startsWith("content://com.android.contacts/data/phones")) {
+                    if (VDBG) Log.v(TAG,
+                        "URL path starts with 'data/phones' using RawContacts.CONTACT_ID");
                     columnIndex = cursor.getColumnIndex(RawContacts.CONTACT_ID);
-                } else {
-                    // content://com.android.contacts/phone_lookup URL
-                    // TODO: mime type is null here so we cannot test
-                    // if we have the right url type. phone_lookup URL
-                    // should resolve to a mime type.
+                } else if (url.startsWith("content://com.android.contacts/phone_lookup")) {
+                    if (VDBG) Log.v(TAG,
+                        "URL path starts with 'phone_lookup' using PhoneLookup._ID");
                     columnIndex = cursor.getColumnIndex(PhoneLookup._ID);
+                } else {
+                    Log.e(TAG, "Bad contact URL '" + url + "'");
                 }
 
                 if (columnIndex != -1) {
                     info.person_id = cursor.getLong(columnIndex);
                 } else {
-                    Log.e(TAG, "Column missing for " + contactRef);
+                    Log.e(TAG, "person_id column missing for " + contactRef);
                 }
 
                 // look for the custom ringtone, create from the string stored
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
index 90ecbd7..6ddb312 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
@@ -358,8 +358,14 @@
                             EVENT_SIM_RECORDS_LOADED, null);
                     mNeedToRegForSimLoaded = false;
                 }
-                // restore the previous network selection.
-                phone.restoreSavedNetworkSelection(null);
+
+                boolean skipRestoringSelection = phone.getContext().getResources().getBoolean(
+                        com.android.internal.R.bool.skip_restoring_network_selection);
+
+                if (!skipRestoringSelection) {
+                    // restore the previous network selection.
+                    phone.restoreSavedNetworkSelection(null);
+                }
                 pollState();
                 // Signal strength polling stops when radio is off
                 queueNextSignalStrengthPoll();
diff --git a/telephony/mockril/Android.mk b/telephony/mockril/Android.mk
new file mode 100644
index 0000000..7c39cb1
--- /dev/null
+++ b/telephony/mockril/Android.mk
@@ -0,0 +1,31 @@
+#
+# Copyright (C) 2010 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_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := core framework
+
+LOCAL_STATIC_JAVA_LIBRARIES := librilproto-java
+
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE := mockrilcontroller
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/telephony/mockril/src/com/android/internal/telephony/mockril/MockRilController.java b/telephony/mockril/src/com/android/internal/telephony/mockril/MockRilController.java
new file mode 100644
index 0000000..9b6a850
--- /dev/null
+++ b/telephony/mockril/src/com/android/internal/telephony/mockril/MockRilController.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2010, 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.internal.telephony.mockril;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.telephony.PhoneNumberUtils;
+
+import com.android.internal.communication.MsgHeader;
+import com.android.internal.communication.Msg;
+import com.android.internal.telephony.RilChannel;
+import com.android.internal.telephony.ril_proto.RilCtrlCmds;
+import com.android.internal.telephony.ril_proto.RilCmds;
+import com.google.protobuf.micro.MessageMicro;
+
+import java.io.IOException;
+
+/**
+ * Contain a list of commands to control Mock RIL. Before using these commands the devices
+ * needs to be set with Mock RIL. Refer to hardware/ril/mockril/README.txt for details.
+ *
+ */
+public class MockRilController {
+    private static final String TAG = "MockRILController";
+    private RilChannel mRilChannel = null;
+    private Msg mMessage = null;
+
+    public MockRilController() throws IOException {
+        mRilChannel = RilChannel.makeRilChannel();
+    }
+
+    /**
+     * Close the channel after the communication is done.
+     * This method has to be called after the test is finished.
+     */
+    public void closeChannel() {
+        mRilChannel.close();
+    }
+
+    /**
+     * Send commands and return true on success
+     * @param cmd for MsgHeader
+     * @param token for MsgHeader
+     * @param status for MsgHeader
+     * @param pbData for Msg data
+     * @return true if command is sent successfully, false if it fails
+     */
+    private boolean sendCtrlCommand(int cmd, long token, int status, MessageMicro pbData) {
+        try {
+            Msg.send(mRilChannel, cmd, token, status, pbData);
+        } catch (IOException e) {
+            Log.v(TAG, "send command : %d failed: " + e.getStackTrace());
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Get control response
+     * @return Msg if response is received, else return null.
+     */
+    private Msg getCtrlResponse() {
+        Msg response = null;
+        try {
+            response = Msg.recv(mRilChannel);
+        } catch (IOException e) {
+            Log.v(TAG, "receive response for getRadioState() error: " + e.getStackTrace());
+            return null;
+        }
+        return response;
+    }
+
+    /**
+     * @return the radio state if it is valid, otherwise return -1
+     */
+    public int getRadioState() {
+        if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_GET_RADIO_STATE, 0, 0, null)) {
+            return -1;
+        }
+        Msg response = getCtrlResponse();
+        if (response == null) {
+            Log.v(TAG, "failed to get response");
+            return -1;
+        }
+        response.printHeader(TAG);
+        RilCtrlCmds.CtrlRspRadioState resp =
+            response.getDataAs(RilCtrlCmds.CtrlRspRadioState.class);
+        int state = resp.getState();
+        if ((state >= RilCmds.RADIOSTATE_OFF) && (state <= RilCmds.RADIOSTATE_NV_READY))
+            return state;
+        else
+            return -1;
+    }
+
+    /**
+     * Set the radio state of mock ril to the given state
+     * @param state for given radio state
+     * @return true if the state is set successful, false if it fails
+     */
+    public boolean setRadioState(int state) {
+        RilCtrlCmds.CtrlReqRadioState req = new RilCtrlCmds.CtrlReqRadioState();
+        if (state < 0 || state > RilCmds.RADIOSTATE_NV_READY) {
+            Log.v(TAG, "the give radio state is not valid.");
+            return false;
+        }
+        req.setState(state);
+        if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_SET_RADIO_STATE, 0, 0, req)) {
+            Log.v(TAG, "send set radio state request failed.");
+            return false;
+        }
+        Msg response = getCtrlResponse();
+        if (response == null) {
+            Log.v(TAG, "failed to get response for setRadioState");
+            return false;
+        }
+        response.printHeader(TAG);
+        RilCtrlCmds.CtrlRspRadioState resp =
+            response.getDataAs(RilCtrlCmds.CtrlRspRadioState.class);
+        int curstate = resp.getState();
+        return curstate == state;
+    }
+
+
+
+    /**
+     * Set an MT call
+     *
+     * @param phoneNumber for the number shown
+     */
+    public boolean setMTCall(String phoneNumber) {
+        RilCtrlCmds.CtrlReqSetMTCall req = new RilCtrlCmds.CtrlReqSetMTCall();
+
+        // Check whether it is a valid number
+        req.setPhoneNumber(phoneNumber);
+        if (!sendCtrlCommand(RilCtrlCmds.CTRL_CMD_SET_MT_CALL, 0, 0, req)) {
+            Log.v(TAG, "send CMD_SET_MT_CALL request failed");
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/telephony/tests/telephonytests/Android.mk b/telephony/tests/telephonytests/Android.mk
index 45e265a..98e4403 100644
--- a/telephony/tests/telephonytests/Android.mk
+++ b/telephony/tests/telephonytests/Android.mk
@@ -5,6 +5,8 @@
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
+LOCAL_STATIC_JAVA_LIBRARIES := librilproto-java
+
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
 LOCAL_PACKAGE_NAME := FrameworksTelephonyTests
diff --git a/telephony/tests/telephonytests/AndroidManifest.xml b/telephony/tests/telephonytests/AndroidManifest.xml
index 6a97423..ba1d957 100644
--- a/telephony/tests/telephonytests/AndroidManifest.xml
+++ b/telephony/tests/telephonytests/AndroidManifest.xml
@@ -32,6 +32,13 @@
         android:targetPackage="com.android.frameworks.telephonytests"
         android:label="Frameworks Telephony Tests">
     </instrumentation>
+
+    <instrumentation android:name=".TelephonyMockRilTestRunner"
+        android:targetPackage="com.android.frameworks.telephonytests"
+        android:label="Test Runner for Mock Ril Tests"
+    />
+
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
 
 </manifest>
diff --git a/telephony/tests/telephonytests/src/com/android/frameworks/telephonytests/TelephonyMockRilTestRunner.java b/telephony/tests/telephonytests/src/com/android/frameworks/telephonytests/TelephonyMockRilTestRunner.java
new file mode 100644
index 0000000..9192f57
--- /dev/null
+++ b/telephony/tests/telephonytests/src/com/android/frameworks/telephonytests/TelephonyMockRilTestRunner.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010, 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.frameworks.telephonytests;
+
+import android.os.Bundle;
+
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+import android.util.Log;
+
+import java.io.IOException;
+
+import com.android.internal.telephony.RilChannel;
+import com.android.internal.telephony.mockril.MockRilTest;
+
+import junit.framework.TestSuite;
+
+public class TelephonyMockRilTestRunner extends InstrumentationTestRunner {
+
+    public RilChannel mMockRilChannel;
+
+    @Override
+    public TestSuite getAllTests() {
+        log("getAllTests E");
+        TestSuite suite = new InstrumentationTestSuite(this);
+        suite.addTestSuite(MockRilTest.class);
+        log("getAllTests X");
+        return suite;
+    }
+
+    @Override
+    public ClassLoader getLoader() {
+        log("getLoader EX");
+        return TelephonyMockRilTestRunner.class.getClassLoader();
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        log("onCreate E");
+        try {
+            mMockRilChannel = RilChannel.makeRilChannel();
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        log("onCreate X");
+
+        super.onCreate(icicle);
+    }
+
+    @Override
+    public void onDestroy() {
+        // I've not seen this called
+        log("onDestroy EX");
+        super.onDestroy();
+    }
+
+    @Override
+    public void onStart() {
+        // Called when the instrumentation thread is started.
+        // At the moment we don't need the thread so return
+        // which will shut down this unused thread.
+        log("onStart EX");
+        super.onStart();
+    }
+
+    @Override
+    public void finish(int resultCode, Bundle results) {
+        // Called when complete so I ask the mMockRilChannel to quit.
+        log("finish E");
+        mMockRilChannel.close();
+        log("finish X");
+        super.finish(resultCode, results);
+    }
+
+    private void log(String s) {
+        Log.e("TelephonyMockRilTestRunner", s);
+    }
+}
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/mockril/MockRilTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/mockril/MockRilTest.java
new file mode 100644
index 0000000..f0d5b31
--- /dev/null
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/mockril/MockRilTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2010 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.internal.telephony.mockril;
+
+import android.util.Log;
+import android.test.InstrumentationTestCase;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import com.android.internal.communication.MsgHeader;
+import com.android.internal.communication.Msg;
+import com.android.internal.telephony.RilChannel;
+import com.android.internal.telephony.ril_proto.RilCtrlCmds;
+import com.android.internal.telephony.ril_proto.RilCmds;
+
+import com.android.frameworks.telephonytests.TelephonyMockRilTestRunner;
+import com.google.protobuf.micro.InvalidProtocolBufferMicroException;
+
+// Test suite for test ril
+public class MockRilTest extends InstrumentationTestCase {
+    private static final String TAG = "MockRilTest";
+
+    RilChannel mMockRilChannel;
+    TelephonyMockRilTestRunner mRunner;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mRunner = (TelephonyMockRilTestRunner)getInstrumentation();
+        mMockRilChannel = mRunner.mMockRilChannel;
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    static void log(String s) {
+        Log.v(TAG, s);
+    }
+
+    /**
+     * Test protobuf serialization and deserialization
+     * @throws InvalidProtocolBufferMicroException
+     */
+    public void testProtobufSerDes() throws InvalidProtocolBufferMicroException {
+        log("testProtobufSerdes E");
+
+        RilCtrlCmds.CtrlRspRadioState rs = new RilCtrlCmds.CtrlRspRadioState();
+        assertTrue(String.format("expected rs.state == 0 was %d", rs.getState()),
+                rs.getState() == 0);
+        rs.setState(1);
+        assertTrue(String.format("expected rs.state == 1 was %d", rs.getState()),
+                rs.getState() == 1);
+
+        byte[] rs_ser = rs.toByteArray();
+        RilCtrlCmds.CtrlRspRadioState rsNew = RilCtrlCmds.CtrlRspRadioState.parseFrom(rs_ser);
+        assertTrue(String.format("expected rsNew.state == 1 was %d", rs.getState()),
+                rs.getState() == 1);
+
+        log("testProtobufSerdes X");
+    }
+
+    /**
+     * Test echo command works using writeMsg & readMsg
+     */
+    public void testEchoMsg() throws IOException {
+        log("testEchoMsg E");
+
+        MsgHeader mh = new MsgHeader();
+        mh.setCmd(0);
+        mh.setToken(1);
+        mh.setStatus(2);
+        ByteBuffer data = ByteBuffer.allocate(3);
+        data.put((byte)3);
+        data.put((byte)4);
+        data.put((byte)5);
+        Msg.send(mMockRilChannel, mh, data);
+
+        Msg respMsg = Msg.recv(mMockRilChannel);
+        assertTrue(String.format("expected mhd.header.cmd == 0 was %d",respMsg.getCmd()),
+                respMsg.getCmd() == 0);
+        assertTrue(String.format("expected mhd.header.token == 1 was %d",respMsg.getToken()),
+                respMsg.getToken() == 1);
+        assertTrue(String.format("expected mhd.header.status == 2 was %d", respMsg.getStatus()),
+                respMsg.getStatus() == 2);
+        assertTrue(String.format("expected mhd.data[0] == 3 was %d", respMsg.getData(0)),
+                respMsg.getData(0) == 3);
+        assertTrue(String.format("expected mhd.data[1] == 4 was %d", respMsg.getData(1)),
+                respMsg.getData(1) == 4);
+        assertTrue(String.format("expected mhd.data[2] == 5 was %d", respMsg.getData(2)),
+                respMsg.getData(2) == 5);
+
+        log("testEchoMsg X");
+    }
+
+    /**
+     * Test get as
+     */
+    public void testGetAs() {
+        log("testGetAs E");
+
+        // Use a message header as the protobuf data content
+        MsgHeader mh = new MsgHeader();
+        mh.setCmd(12345);
+        mh.setToken(9876);
+        mh.setStatus(7654);
+        mh.setLengthData(4321);
+        byte[] data = mh.toByteArray();
+        MsgHeader mhResult = Msg.getAs(MsgHeader.class, data);
+
+        assertTrue(String.format("expected cmd == 12345 was %d", mhResult.getCmd()),
+                mhResult.getCmd() == 12345);
+        assertTrue(String.format("expected token == 9876 was %d", mhResult.getToken()),
+                mhResult.getToken() == 9876);
+        assertTrue(String.format("expected status == 7654 was %d", mhResult.getStatus()),
+                mhResult.getStatus() == 7654);
+        assertTrue(String.format("expected lengthData == 4321 was %d", mhResult.getLengthData()),
+                mhResult.getLengthData() == 4321);
+
+        Msg msg = Msg.obtain();
+        msg.setData(ByteBuffer.wrap(data));
+
+        mhResult = msg.getDataAs(MsgHeader.class);
+
+        assertTrue(String.format("expected cmd == 12345 was %d", mhResult.getCmd()),
+                mhResult.getCmd() == 12345);
+        assertTrue(String.format("expected token == 9876 was %d", mhResult.getToken()),
+                mhResult.getToken() == 9876);
+        assertTrue(String.format("expected status == 7654 was %d", mhResult.getStatus()),
+                mhResult.getStatus() == 7654);
+        assertTrue(String.format("expected lengthData == 4321 was %d", mhResult.getLengthData()),
+                mhResult.getLengthData() == 4321);
+
+        log("testGetAs X");
+    }
+
+    public void testGetRadioState() throws IOException {
+        log("testGetRadioState E");
+
+        Msg.send(mMockRilChannel, 1, 9876, 0, null);
+
+        Msg resp = Msg.recv(mMockRilChannel);
+        //resp.printHeader("testGetRadioState");
+
+        assertTrue(String.format("expected cmd == 1 was %d", resp.getCmd()),
+                resp.getCmd() == 1);
+        assertTrue(String.format("expected token == 9876 was %d", resp.getToken()),
+                resp.getToken() == 9876);
+        assertTrue(String.format("expected status == 0 was %d", resp.getStatus()),
+                resp.getStatus() == 0);
+
+        RilCtrlCmds.CtrlRspRadioState rsp = resp.getDataAs(RilCtrlCmds.CtrlRspRadioState.class);
+
+        int state = rsp.getState();
+        log("testGetRadioState state=" + state);
+        assertTrue(String.format("expected RadioState >= 0 && RadioState <= 9 was %d", state),
+                ((state >= 0) && (state <= 9)));
+
+        log("testGetRadioState X");
+    }
+
+    public void testSetRadioState() throws IOException {
+        log("testSetRadioState E");
+
+        RilCtrlCmds.CtrlReqRadioState cmdrs = new RilCtrlCmds.CtrlReqRadioState();
+        assertEquals(0, cmdrs.getState());
+
+        cmdrs.setState(RilCmds.RADIOSTATE_SIM_NOT_READY);
+        assertEquals(2, cmdrs.getState());
+
+        Msg.send(mMockRilChannel, RilCtrlCmds.CTRL_CMD_SET_RADIO_STATE, 0, 0, cmdrs);
+
+        Msg resp = Msg.recv(mMockRilChannel);
+
+        RilCtrlCmds.CtrlRspRadioState rsp = resp.getDataAs(RilCtrlCmds.CtrlRspRadioState.class);
+
+        int state = rsp.getState();
+        log("get response for testSetRadioState: " + state);
+        assertTrue(RilCmds.RADIOSTATE_SIM_NOT_READY == state);
+    }
+}
diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java
index 80c35fb..8c32aa0 100644
--- a/voip/java/android/net/sip/SipManager.java
+++ b/voip/java/android/net/sip/SipManager.java
@@ -65,6 +65,13 @@
     public static final String EXTRA_OFFER_SD = "android:sipOfferSD";
 
     /**
+     * Action to broadcast when SipService is up.
+     * Internal use only.
+     * @hide
+     */
+    public static final String ACTION_SIP_SERVICE_UP =
+            "android.net.sip.SIP_SERVICE_UP";
+    /**
      * Action string for the incoming call intent for the Phone app.
      * Internal use only.
      * @hide
diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java
index d8a1572..1fa2400 100644
--- a/voip/java/com/android/server/sip/SipService.java
+++ b/voip/java/com/android/server/sip/SipService.java
@@ -100,6 +100,7 @@
     public static void start(Context context) {
         if (SipManager.isApiSupported(context)) {
             ServiceManager.addService("sip", new SipService(context));
+            context.sendBroadcast(new Intent(SipManager.ACTION_SIP_SERVICE_UP));
             Log.i(TAG, "SIP service started");
         }
     }
diff --git a/voip/jni/rtp/EchoSuppressor.cpp b/voip/jni/rtp/EchoSuppressor.cpp
index a1a7aed..ad63cd6 100644
--- a/voip/jni/rtp/EchoSuppressor.cpp
+++ b/voip/jni/rtp/EchoSuppressor.cpp
@@ -157,11 +157,12 @@
     if (correlation > 0.3f) {
         float factor = 1.0f - correlation;
         factor *= factor;
+        factor /= 2.0; // suppress harder
         for (int i = 0; i < mSampleCount; ++i) {
             recorded[i] *= factor;
         }
     }
-//    LOGI("latency %5d, correlation %.10f", latency, correlation);
+    //LOGI("latency %5d, correlation %.10f", latency, correlation);
 
 
     // Increase RecordOffset.