Merge "Fix issue #5470311: Activity with android:configChanges defined..." into ics-mr1
diff --git a/api/current.txt b/api/current.txt
index daf1036..7c4bae2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -17432,7 +17432,6 @@
     field public static final java.lang.String LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED = "lock_pattern_tactile_feedback_enabled";
     field public static final java.lang.String LOCK_PATTERN_VISIBLE = "lock_pattern_visible_pattern";
     field public static final deprecated java.lang.String LOGGING_ID = "logging_id";
-    field public static final java.lang.String MESSAGING_APP_NOTIFICATIONS = "messaging_app_notifications";
     field public static final java.lang.String NETWORK_PREFERENCE = "network_preference";
     field public static final java.lang.String PARENTAL_CONTROL_ENABLED = "parental_control_enabled";
     field public static final java.lang.String PARENTAL_CONTROL_LAST_UPDATE = "parental_control_last_update";
@@ -24830,6 +24829,7 @@
   }
 
   public class SpellCheckerSession {
+    method public void cancel();
     method public void close();
     method public android.view.textservice.SpellCheckerInfo getSpellChecker();
     method public void getSuggestions(android.view.textservice.TextInfo, int);
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 48adfad..3becec0 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -22,9 +22,12 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.SystemProperties;
 import android.util.Log;
 import android.view.Surface;
 import android.view.SurfaceHolder;
@@ -154,6 +157,7 @@
     private boolean mOneShot;
     private boolean mWithBuffer;
     private boolean mFaceDetectionRunning = false;
+    private boolean mReleased = false;
 
     /**
      * Broadcast Action:  A new picture is taken by the camera, and the entry of
@@ -303,7 +307,7 @@
     }
 
     protected void finalize() {
-        native_release();
+        release();
     }
 
     private native final void native_setup(Object camera_this, int cameraId);
@@ -318,6 +322,15 @@
     public final void release() {
         native_release();
         mFaceDetectionRunning = false;
+        if (mCameraSoundPlayers != null) {
+            for (CameraSoundPlayer csp: mCameraSoundPlayers) {
+                if (csp != null) {
+                    csp.release();
+                }
+            }
+            mCameraSoundPlayers = null;
+        }
+        mReleased = true;
     }
 
     /**
@@ -2354,7 +2367,7 @@
          *
          * <p>The reference code is as follows.
          *
-	 * <pre>
+         * <pre>
          * public void onOrientationChanged(int orientation) {
          *     if (orientation == ORIENTATION_UNKNOWN) return;
          *     android.hardware.Camera.CameraInfo info =
@@ -2369,7 +2382,7 @@
          *     }
          *     mParameters.setRotation(rotation);
          * }
-	 * </pre>
+         * </pre>
          *
          * @param rotation The rotation angle in degrees relative to the
          *                 orientation of the camera. Rotation can only be 0,
@@ -3452,4 +3465,194 @@
             return result;
         }
     };
+
+    /**
+     * <p>The set of default system sounds for camera actions. Use this with
+     * {@link #playSound} to play an appropriate sound when implementing a
+     * custom still or video recording mechanism through the preview
+     * callbacks.</p>
+     *
+     * <p>There is no need to play sounds when using {@link #takePicture} or
+     * {@link android.media.MediaRecorder} for still images or video,
+     * respectively, as these play their own sounds when needed.</p>
+     *
+     * @see #playSound
+     * @hide
+     */
+    public static class Sound {
+        /**
+         * The sound used by {@link android.hardware.Camera#takePicture} to
+         * indicate still image capture.
+         */
+        public static final int SHUTTER_CLICK         = 0;
+
+        /**
+         * A sound to indicate that focusing has completed. Because deciding
+         * when this occurs is application-dependent, this sound is not used by
+         * any methods in the Camera class.
+         */
+        public static final int FOCUS_COMPLETE        = 1;
+
+        /**
+         * The sound used by {@link android.media.MediaRecorder#start} to
+         * indicate the start of video recording.
+         */
+        public static final int START_VIDEO_RECORDING = 2;
+
+        /**
+         * The sound used by {@link android.media.MediaRecorder#stop} to
+         * indicate the end of video recording.
+         */
+        public static final int STOP_VIDEO_RECORDING  = 3;
+
+        private static final int NUM_SOUNDS           = 4;
+    };
+
+    /**
+     * <p>Play one of the predefined platform sounds for camera actions.</p>
+     *
+     * <p>Use this method to play a platform-specific sound for various camera
+     * actions. The sound playing is done asynchronously, with the same behavior
+     * and content as the sounds played by {@link #takePicture takePicture},
+     * {@link android.media.MediaRecorder#start MediaRecorder.start}, and
+     * {@link android.media.MediaRecorder#stop MediaRecorder.stop}.</p>
+     *
+     * <p>Using this method makes it easy to match the default device sounds
+     * when recording or capturing data through the preview callbacks
+     * ({@link #setPreviewCallback setPreviewCallback},
+     * {@link #setPreviewTexture setPreviewTexture}).</p>
+     *
+     * @param soundId The type of sound to play, selected from the options in
+     *   {@link android.hardware.Camera.Sound}
+     * @see android.hardware.Camera.Sound
+     * @see #takePicture
+     * @see android.media.MediaRecorder
+     * @hide
+     */
+    public void playSound(int soundId) {
+        if (mReleased) return;
+        if (mCameraSoundPlayers == null) {
+            mCameraSoundPlayers = new CameraSoundPlayer[Sound.NUM_SOUNDS];
+        }
+        if (mCameraSoundPlayers[soundId] == null) {
+            mCameraSoundPlayers[soundId] = new CameraSoundPlayer(soundId);
+        }
+        mCameraSoundPlayers[soundId].play();
+    }
+
+    private CameraSoundPlayer[] mCameraSoundPlayers;
+
+    private static class CameraSoundPlayer implements Runnable {
+        private int mSoundId;
+        private int mAudioStreamType;
+        private MediaPlayer mPlayer;
+        private Thread mThread;
+        private boolean mExit;
+        private int mPlayCount;
+
+        private static final String mShutterSound    =
+                "/system/media/audio/ui/camera_click.ogg";
+        private static final String mFocusSound      =
+                "/system/media/audio/ui/camera_focus.ogg";
+        private static final String mVideoStartSound =
+                "/system/media/audio/ui/VideoRecord.ogg";
+        private static final String mVideoStopSound  =
+                "/system/media/audio/ui/VideoRecord.ogg";
+
+        @Override
+        public void run() {
+            String soundFilePath;
+            switch (mSoundId) {
+                case Sound.SHUTTER_CLICK:
+                    soundFilePath = mShutterSound;
+                    break;
+                case Sound.FOCUS_COMPLETE:
+                    soundFilePath = mFocusSound;
+                    break;
+                case Sound.START_VIDEO_RECORDING:
+                    soundFilePath = mVideoStartSound;
+                    break;
+                case Sound.STOP_VIDEO_RECORDING:
+                    soundFilePath = mVideoStopSound;
+                    break;
+                default:
+                    Log.e(TAG, "Unknown sound " + mSoundId + " requested.");
+                    return;
+            }
+            mPlayer = new MediaPlayer();
+            try {
+                mPlayer.setAudioStreamType(mAudioStreamType);
+                mPlayer.setDataSource(soundFilePath);
+                mPlayer.setLooping(false);
+                mPlayer.prepare();
+            } catch(IOException e) {
+                Log.e(TAG, "Error setting up sound " + mSoundId, e);
+                return;
+            }
+
+            while(true) {
+                try {
+                    synchronized (this) {
+                        while(true) {
+                            if (mExit) {
+                                return;
+                            } else if (mPlayCount <= 0) {
+                                wait();
+                            } else {
+                                mPlayCount--;
+                                break;
+                            }
+                        }
+                    }
+                    mPlayer.start();
+                } catch (Exception e) {
+                    Log.e(TAG, "Error playing sound " + mSoundId, e);
+                }
+            }
+        }
+
+        public CameraSoundPlayer(int soundId) {
+            mSoundId = soundId;
+            if (SystemProperties.get("ro.camera.sound.forced", "0").equals("0")) {
+                mAudioStreamType = AudioManager.STREAM_MUSIC;
+            } else {
+                mAudioStreamType = AudioManager.STREAM_SYSTEM_ENFORCED;
+            }
+        }
+
+        public void play() {
+            if (mThread == null) {
+                mThread = new Thread(this);
+                mThread.start();
+            }
+            synchronized (this) {
+                mPlayCount++;
+                notifyAll();
+            }
+        }
+
+        public void release() {
+            if (mThread != null) {
+                synchronized (this) {
+                    mExit = true;
+                    notifyAll();
+                }
+                try {
+                    mThread.join();
+                } catch (InterruptedException e) {
+                }
+                mThread = null;
+            }
+            if (mPlayer != null) {
+                mPlayer.release();
+                mPlayer = null;
+            }
+        }
+
+        @Override
+        protected void finalize() {
+            release();
+        }
+    }
+
 }
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 33310df..fe0106d 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -768,6 +768,61 @@
     }
 
     /**
+     * TODO: Remove this once pre-built apk's (Maps, Youtube etc) are updated
+     * @deprecated use {@link CreateNdefMessageCallback} or {@link OnNdefPushCompleteCallback}
+     * @hide
+     */
+    @Deprecated
+    public interface NdefPushCallback {
+        /**
+         * @deprecated use {@link CreateNdefMessageCallback} instead
+         */
+        @Deprecated
+        NdefMessage createMessage();
+        /**
+         * @deprecated use{@link OnNdefPushCompleteCallback} instead
+         */
+        @Deprecated
+        void onMessagePushed();
+    }
+
+    /**
+     * TODO: Remove this
+     * Converts new callbacks to old callbacks.
+     */
+    static final class LegacyCallbackWrapper implements CreateNdefMessageCallback,
+            OnNdefPushCompleteCallback {
+        final NdefPushCallback mLegacyCallback;
+        LegacyCallbackWrapper(NdefPushCallback legacyCallback) {
+            mLegacyCallback = legacyCallback;
+        }
+        @Override
+        public void onNdefPushComplete(NfcEvent event) {
+            mLegacyCallback.onMessagePushed();
+        }
+        @Override
+        public NdefMessage createNdefMessage(NfcEvent event) {
+            return mLegacyCallback.createMessage();
+        }
+    }
+
+    /**
+     * TODO: Remove this once pre-built apk's (Maps, Youtube etc) are updated
+     * @deprecated use {@link #setNdefPushMessageCallback} instead
+     * @hide
+     */
+    @Deprecated
+    public void enableForegroundNdefPush(Activity activity, final NdefPushCallback callback) {
+        if (activity == null || callback == null) {
+            throw new NullPointerException();
+        }
+        enforceResumed(activity);
+        LegacyCallbackWrapper callbackWrapper = new LegacyCallbackWrapper(callback);
+        mNfcActivityManager.setNdefPushMessageCallback(activity, callbackWrapper);
+        mNfcActivityManager.setOnNdefPushCompleteCallback(activity, callbackWrapper);
+    }
+
+    /**
      * Enable NDEF Push feature.
      * <p>This API is for the Settings application.
      * @hide
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 769776e..b032169 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4077,13 +4077,6 @@
                 "contacts_preauth_uri_expiration";
 
         /**
-         * Whether the Messaging app posts notifications.
-         * 0=disabled. 1=enabled.
-         */
-        public static final String MESSAGING_APP_NOTIFICATIONS = "messaging_app_notifications";
-
-
-        /**
          * This are the settings to be backed up.
          *
          * NOTE: Settings are backed up and restored in the order they appear
@@ -4120,8 +4113,7 @@
             MOUNT_UMS_NOTIFY_ENABLED,
             UI_NIGHT_MODE,
             LOCK_SCREEN_OWNER_INFO,
-            LOCK_SCREEN_OWNER_INFO_ENABLED,
-            MESSAGING_APP_NOTIFICATIONS
+            LOCK_SCREEN_OWNER_INFO_ENABLED
         };
 
         /**
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 8eb9da1..0e6d07d 100755
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -1838,15 +1838,5 @@
         public static final String EXTRA_PLMN       = "plmn";
         public static final String EXTRA_SHOW_SPN   = "showSpn";
         public static final String EXTRA_SPN        = "spn";
-
-        /**
-         * Activity Action: Shows a dialog to turn off Messaging app notification.
-         * <p>Input: Nothing.
-         * <p>Output: Nothing.
-         */
-        @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-        public static final String ACTION_MESSAGING_APP_NOTIFICATIONS =
-            "android.provider.Telephony.MESSAGING_APP_NOTIFICATIONS";
-
     }
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 62e6ebd..dc46d42 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -11402,8 +11402,12 @@
      *        {@link SystemClock#uptimeMillis} timebase.
      */
     public void scheduleDrawable(Drawable who, Runnable what, long when) {
-        if (verifyDrawable(who) && what != null && mAttachInfo != null) {
-            mAttachInfo.mHandler.postAtTime(what, who, when);
+        if (verifyDrawable(who) && what != null) {
+            if (mAttachInfo != null) {
+                mAttachInfo.mHandler.postAtTime(what, who, when);
+            } else {
+                ViewRootImpl.getRunQueue().postDelayed(what, when - SystemClock.uptimeMillis());
+            }
         }
     }
 
@@ -11414,8 +11418,12 @@
      * @param what the action to cancel
      */
     public void unscheduleDrawable(Drawable who, Runnable what) {
-        if (verifyDrawable(who) && what != null && mAttachInfo != null) {
-            mAttachInfo.mHandler.removeCallbacks(what, who);
+        if (verifyDrawable(who) && what != null) {
+            if (mAttachInfo != null) {
+                mAttachInfo.mHandler.removeCallbacks(what, who);
+            } else {
+                ViewRootImpl.getRunQueue().removeCallbacks(what);
+            }
         }
     }
 
diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java
index 01b114c..0eb6e27 100644
--- a/core/java/android/view/textservice/SpellCheckerSession.java
+++ b/core/java/android/view/textservice/SpellCheckerSession.java
@@ -146,6 +146,13 @@
     }
 
     /**
+     * Cancel pending and running spell check tasks
+     */
+    public void cancel() {
+        mSpellCheckerSessionListenerImpl.cancel();
+    }
+
+    /**
      * Finish this session and allow TextServicesManagerService to disconnect the bound spell
      * checker.
      */
@@ -242,6 +249,13 @@
             }
         }
 
+        public void cancel() {
+            if (DBG) {
+                Log.w(TAG, "cancel");
+            }
+            processOrEnqueueTask(new SpellCheckerParams(TASK_CANCEL, null, 0, false));
+        }
+
         public void getSuggestionsMultiple(
                 TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
             if (DBG) {
@@ -275,8 +289,22 @@
             if (DBG) {
                 Log.d(TAG, "process or enqueue task: " + mISpellCheckerSession);
             }
+            SpellCheckerParams closeTask = null;
             if (mISpellCheckerSession == null) {
+                if (scp.mWhat == TASK_CANCEL) {
+                    while (!mPendingTasks.isEmpty()) {
+                        final SpellCheckerParams tmp = mPendingTasks.poll();
+                        if (tmp.mWhat == TASK_CLOSE) {
+                            // Only one close task should be processed, while we need to remove all
+                            // close tasks from the queue
+                            closeTask = tmp;
+                        }
+                    }
+                }
                 mPendingTasks.offer(scp);
+                if (closeTask != null) {
+                    mPendingTasks.offer(closeTask);
+                }
             } else {
                 processTask(scp);
             }
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index 388920c..c194559 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -1404,4 +1404,17 @@
     native void nativeSslClientCert(int handle,
                                     byte[] pkcs8EncodedPrivateKey,
                                     byte[][] asn1DerEncodedCertificateChain);
+
+    /**
+     * Returns true when the contents of the frame is an RTL or vertical-rl
+     * page. This is used for determining whether a frame should be initially
+     * scrolled right-most as opposed to left-most.
+     * @return true when the frame should be initially scrolled right-most
+     * based on the text direction and writing mode.
+     */
+    /* package */ boolean getShouldStartScrolledRight() {
+        return nativeGetShouldStartScrolledRight(mNativeFrame);
+    }
+
+    private native boolean nativeGetShouldStartScrolledRight(int nativeBrowserFrame);
 }
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 35efabc..03d6511 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -3254,6 +3254,26 @@
             if (mHTML5VideoViewProxy != null) {
                 mHTML5VideoViewProxy.pauseAndDispatch();
             }
+            if (mNativeClass != 0) {
+                nativeSetPauseDrawing(mNativeClass, true);
+            }
+        }
+    }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        updateDrawingState();
+    }
+
+    void updateDrawingState() {
+        if (mNativeClass == 0 || mIsPaused) return;
+        if (getWindowVisibility() != VISIBLE) {
+            nativeSetPauseDrawing(mNativeClass, true);
+        } else if (getVisibility() != VISIBLE) {
+            nativeSetPauseDrawing(mNativeClass, true);
+        } else {
+            nativeSetPauseDrawing(mNativeClass, false);
         }
     }
 
@@ -3265,6 +3285,9 @@
         if (mIsPaused) {
             mIsPaused = false;
             mWebViewCore.sendMessage(EventHub.ON_RESUME);
+            if (mNativeClass != 0) {
+                nativeSetPauseDrawing(mNativeClass, false);
+            }
         }
     }
 
@@ -5599,6 +5622,7 @@
         if (visibility != View.VISIBLE && mZoomManager != null) {
             mZoomManager.dismissZoomPicker();
         }
+        updateDrawingState();
     }
 
     /**
@@ -8403,6 +8427,9 @@
                         setNewPicture(mDelaySetPicture, true);
                         mDelaySetPicture = null;
                     }
+                    if (mIsPaused) {
+                        nativeSetPauseDrawing(mNativeClass, true);
+                    }
                     break;
                 case UPDATE_TEXTFIELD_TEXT_MSG_ID:
                     // Make sure that the textfield is currently focused
@@ -8733,27 +8760,6 @@
                     isPictureAfterFirstLayout, registerPageSwapCallback);
         }
         final Point viewSize = draw.mViewSize;
-        if (isPictureAfterFirstLayout) {
-            // Reset the last sent data here since dealing with new page.
-            mLastWidthSent = 0;
-            mZoomManager.onFirstLayout(draw);
-            if (!mDrawHistory) {
-                // Do not send the scroll event for this particular
-                // scroll message.  Note that a scroll event may
-                // still be fired if the user scrolls before the
-                // message can be handled.
-                mSendScrollEvent = false;
-                setContentScrollTo(viewState.mScrollX, viewState.mScrollY);
-                mSendScrollEvent = true;
-
-                // As we are on a new page, remove the WebTextView. This
-                // is necessary for page loads driven by webkit, and in
-                // particular when the user was on a password field, so
-                // the WebTextView was visible.
-                clearTextEntry();
-            }
-        }
-
         // We update the layout (i.e. request a layout from the
         // view system) if the last view size that we sent to
         // WebCore matches the view size of the picture we just
@@ -8766,7 +8772,25 @@
         mSendScrollEvent = false;
         recordNewContentSize(draw.mContentSize.x,
                 draw.mContentSize.y, updateLayout);
+
+        if (isPictureAfterFirstLayout) {
+            // Reset the last sent data here since dealing with new page.
+            mLastWidthSent = 0;
+            mZoomManager.onFirstLayout(draw);
+            int scrollX = viewState.mShouldStartScrolledRight
+                    ? getContentWidth() : viewState.mScrollX;
+            int scrollY = viewState.mScrollY;
+            setContentScrollTo(scrollX, scrollY);
+            if (!mDrawHistory) {
+                // As we are on a new page, remove the WebTextView. This
+                // is necessary for page loads driven by webkit, and in
+                // particular when the user was on a password field, so
+                // the WebTextView was visible.
+                clearTextEntry();
+            }
+        }
         mSendScrollEvent = true;
+
         if (DebugFlags.WEB_VIEW) {
             Rect b = draw.mInvalRegion.getBounds();
             Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" +
@@ -9584,4 +9608,5 @@
      * See {@link ComponentCallbacks2} for the trim levels and descriptions
      */
     private static native void     nativeOnTrimMemory(int level);
+    private static native void nativeSetPauseDrawing(int instance, boolean pause);
 }
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 754d6e9..cd61481 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -1982,6 +1982,7 @@
         int mScrollY;
         boolean mMobileSite;
         boolean mIsRestored;
+        boolean mShouldStartScrolledRight;
     }
 
     static class DrawData {
@@ -2382,6 +2383,7 @@
             viewState.mMobileSite = false;
             // for non-mobile site, we don't need minPrefWidth, set it as 0
             viewState.mScrollX = 0;
+            viewState.mShouldStartScrolledRight = false;
             Message.obtain(mWebView.mPrivateHandler,
                     WebView.UPDATE_ZOOM_RANGE, viewState).sendToTarget();
             return;
@@ -2412,6 +2414,11 @@
         mInitialViewState.mDefaultScale = adjust;
         mInitialViewState.mScrollX = mRestoredX;
         mInitialViewState.mScrollY = mRestoredY;
+        mInitialViewState.mShouldStartScrolledRight = (mRestoredX == 0)
+                && (mRestoredY == 0)
+                && (mBrowserFrame != null)
+                && mBrowserFrame.getShouldStartScrolledRight();
+
         mInitialViewState.mMobileSite = (0 == mViewportWidth);
         if (mIsRestored) {
             mInitialViewState.mIsRestored = true;
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index b24dd69..1e2d0d1 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -1035,4 +1035,28 @@
             mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8);
         }
     }
+
+    @Override
+    public void setVisibility(int visibility) {
+        super.setVisibility(visibility);
+        if (mDrawable != null) {
+            mDrawable.setVisible(visibility == VISIBLE, false);
+        }
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (mDrawable != null) {
+            mDrawable.setVisible(getVisibility() == VISIBLE, false);
+        }
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (mDrawable != null) {
+            mDrawable.setVisible(false, false);
+        }
+    }
 }
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 87c3e9b..ebb2604 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -76,7 +76,7 @@
         mIds = new int[size];
         mSpellCheckSpans = new SpellCheckSpan[size];
 
-        setLocale(mTextView.getLocale());
+        setLocale(mTextView.getTextServicesLocale());
 
         mCookie = hashCode();
     }
@@ -173,7 +173,7 @@
     }
 
     public void spellCheck(int start, int end) {
-        final Locale locale = mTextView.getLocale();
+        final Locale locale = mTextView.getTextServicesLocale();
         if (mCurrentLocale == null || (!(mCurrentLocale.equals(locale)))) {
             setLocale(locale);
             // Re-check the entire text
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 5833afd..bc8721a 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -36,7 +36,6 @@
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.inputmethodservice.ExtractEditText;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -101,7 +100,6 @@
 import android.util.TypedValue;
 import android.view.ActionMode;
 import android.view.ActionMode.Callback;
-import android.view.ContextMenu;
 import android.view.DragEvent;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
@@ -133,6 +131,8 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.InputMethodSubtype;
+import android.view.textservice.SpellCheckerSubtype;
+import android.view.textservice.TextServicesManager;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.RemoteViews.RemoteView;
 
@@ -8355,10 +8355,12 @@
                         // When the cursor moves, the word that was typed may need spell check
                         mSpellChecker.onSelectionChanged();
                     }
-                    if (isCursorInsideEasyCorrectionSpan()) {
-                        showSuggestions();
-                    } else if (hasInsertionController()) {
-                        getInsertionController().show();
+                    if (!extractedTextModeWillBeStarted()) {
+                        if (isCursorInsideEasyCorrectionSpan()) {
+                            showSuggestions();
+                        } else if (hasInsertionController()) {
+                            getInsertionController().show();
+                        }
                     }
                 }
 
@@ -8904,21 +8906,18 @@
     /**
      * This is a temporary method. Future versions may support multi-locale text.
      *
-     * @return The current locale used in this TextView, based on the current IME's locale,
-     * or the system default locale if this is not defined.
+     * @return The locale that should be used for a word iterator and a spell checker
+     * in this TextView, based on the current spell checker settings,
+     * the current IME's locale, or the system default locale.
      * @hide
      */
-    public Locale getLocale() {
+    public Locale getTextServicesLocale() {
         Locale locale = Locale.getDefault();
-        final InputMethodManager imm = InputMethodManager.peekInstance();
-        if (imm != null) {
-            final InputMethodSubtype currentInputMethodSubtype = imm.getCurrentInputMethodSubtype();
-            if (currentInputMethodSubtype != null) {
-                String localeString = currentInputMethodSubtype.getLocale();
-                if (!TextUtils.isEmpty(localeString)) {
-                    locale = new Locale(localeString);
-                }
-            }
+        final TextServicesManager textServicesManager = (TextServicesManager)
+                mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
+        final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
+        if (subtype != null) {
+            locale = new Locale(subtype.getLocale());
         }
         return locale;
     }
@@ -8933,7 +8932,7 @@
      */
     public WordIterator getWordIterator() {
         if (mWordIterator == null) {
-            mWordIterator = new WordIterator(getLocale());
+            mWordIterator = new WordIterator(getTextServicesLocale());
         }
         return mWordIterator;
     }
@@ -10113,27 +10112,35 @@
             }
         }
 
-        final InputMethodManager imm = InputMethodManager.peekInstance();
-        boolean extractedTextModeWillBeStartedFullScreen = !(this instanceof ExtractEditText) &&
-                imm != null && imm.isFullscreenMode();
+        boolean willExtract = extractedTextModeWillBeStarted();
 
         // Do not start the action mode when extracted text will show up full screen, thus
         // immediately hiding the newly created action bar, which would be visually distracting.
-        if (!extractedTextModeWillBeStartedFullScreen) {
+        if (!willExtract) {
             ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
             mSelectionActionMode = startActionMode(actionModeCallback);
         }
-        final boolean selectionStarted = mSelectionActionMode != null ||
-                extractedTextModeWillBeStartedFullScreen;
 
-        if (selectionStarted && !mTextIsSelectable && imm != null && mSoftInputShownOnFocus) {
+        final boolean selectionStarted = mSelectionActionMode != null || willExtract;
+        if (selectionStarted && !mTextIsSelectable && mSoftInputShownOnFocus) {
             // Show the IME to be able to replace text, except when selecting non editable text.
-            imm.showSoftInput(this, 0, null);
+            final InputMethodManager imm = InputMethodManager.peekInstance();
+            if (imm != null) {
+                imm.showSoftInput(this, 0, null);
+            }
         }
 
         return selectionStarted;
     }
 
+    private boolean extractedTextModeWillBeStarted() {
+        if (!(this instanceof ExtractEditText)) {
+            final InputMethodManager imm = InputMethodManager.peekInstance();
+            return  imm != null && imm.isFullscreenMode();
+        }
+        return false;
+    }
+
     private void stopSelectionActionMode() {
         if (mSelectionActionMode != null) {
             // This will hide the mSelectionModifierCursorController
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 17b8acf..89f9d4e 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -25,14 +25,11 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.hardware.Camera;
-import android.hardware.Camera.CameraInfo;
 import android.os.FileObserver;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.storage.IMountService;
 import android.provider.Settings;
 import android.security.KeyStore;
@@ -41,7 +38,6 @@
 import android.util.Log;
 import android.view.View;
 import android.widget.Button;
-import android.widget.TextView;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -968,6 +964,11 @@
                 com.android.internal.R.bool.config_enable_puk_unlock_screen);
     }
 
+    public boolean isEmergencyCallEnabledWhileSimLocked() {
+        return mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_enable_emergency_call_while_sim_locked);
+    }
+
     /**
      * @return A formatted string of the next alarm (for showing on the lock screen),
      *   or null if there is no next alarm.
@@ -1031,12 +1032,10 @@
      *  {@link TelephonyManager#CALL_STATE_IDLE}
      *  {@link TelephonyManager#CALL_STATE_RINGING}
      *  {@link TelephonyManager#CALL_STATE_OFFHOOK}
-     * @param showIfCapable indicates whether the button should be shown if emergency calls are
-     *                      possible on the device
+     * @param shown indicates whether the given screen wants the emergency button to show at all
      */
-    public void updateEmergencyCallButtonState(Button button, int  phoneState,
-            boolean showIfCapable) {
-        if (isEmergencyCallCapable() && showIfCapable) {
+    public void updateEmergencyCallButtonState(Button button, int  phoneState, boolean shown) {
+        if (isEmergencyCallCapable() && shown) {
             button.setVisibility(View.VISIBLE);
         } else {
             button.setVisibility(View.GONE);
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
old mode 100644
new mode 100755
index 8b07e34..767cafe
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -453,6 +453,11 @@
          If unlock screen is disabled, the puk should be unlocked through Emergency Dialer -->
     <bool name="config_enable_puk_unlock_screen">true</bool>
 
+    <!-- Enable emergency call when sim is locked or puk locked. Some countries/carriers do not
+         allow emergency calls to be placed without the IMSI, which is locked in the SIM.
+         If so, this should be set to 'false' in an overlay. -->
+    <bool name="config_enable_emergency_call_while_sim_locked">true</bool>
+
     <!-- Control the behavior when the user long presses the home button.
             0 - Nothing
             1 - Recent apps dialog
diff --git a/data/sounds/AudioPackage5.mk b/data/sounds/AudioPackage5.mk
index 550f990..5961f06 100755
--- a/data/sounds/AudioPackage5.mk
+++ b/data/sounds/AudioPackage5.mk
@@ -20,6 +20,7 @@
 	$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
 	$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
+	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
 	$(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
 	$(LOCAL_PATH)/effects/Dock.ogg:system/media/audio/ui/Dock.ogg \
 	$(LOCAL_PATH)/effects/Undock.ogg:system/media/audio/ui/Undock.ogg \
diff --git a/data/sounds/AudioPackage6.mk b/data/sounds/AudioPackage6.mk
index 610e821..d113a29 100755
--- a/data/sounds/AudioPackage6.mk
+++ b/data/sounds/AudioPackage6.mk
@@ -19,6 +19,7 @@
 	$(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
+	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
 	$(LOCAL_PATH)/effects/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
 	$(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \
 	$(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg \
diff --git a/data/sounds/AudioPackage7.mk b/data/sounds/AudioPackage7.mk
index 93e0fa0..6ae624e 100755
--- a/data/sounds/AudioPackage7.mk
+++ b/data/sounds/AudioPackage7.mk
@@ -21,6 +21,7 @@
 	$(LOCAL_PATH)/effects/ogg/KeypressReturn_120.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
+	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
 	$(LOCAL_PATH)/effects/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
 	$(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \
 	$(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg \
diff --git a/data/sounds/effects/ogg/camera_focus.ogg b/data/sounds/effects/ogg/camera_focus.ogg
new file mode 100644
index 0000000..0db2683
--- /dev/null
+++ b/data/sounds/effects/ogg/camera_focus.ogg
Binary files differ
diff --git a/docs/html/resources/resources-data.js b/docs/html/resources/resources-data.js
index 237e18c..41a5a51 100644
--- a/docs/html/resources/resources-data.js
+++ b/docs/html/resources/resources-data.js
@@ -578,7 +578,7 @@
     }
   },
   {
-    tags: ['sample', 'newfeature', 'performance', 'gamedev', 'gl'],
+    tags: ['sample', 'newfeature', 'performance', 'gamedev', 'gl', 'updated'],
     path: 'samples/RenderScript/index.html',
     title: {
       en: 'RenderScript'
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 2f32bd8..fe15605 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -521,6 +521,9 @@
         ensureValidDirection(direction);
         ensureValidStreamType(streamType);
 
+        // use stream type alias here so that streams with same alias have the same behavior,
+        // including with regard to silent mode control (e.g the use of STREAM_RING below and in
+        // checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION)
         int streamTypeAlias = STREAM_VOLUME_ALIAS[streamType];
         VolumeStreamState streamState = mStreamStates[streamTypeAlias];
         final int oldIndex = (streamState.muteCount() != 0) ? streamState.mLastAudibleIndex : streamState.mIndex;
@@ -529,9 +532,8 @@
         // If either the client forces allowing ringer modes for this adjustment,
         // or the stream type is one that is affected by ringer modes
         if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
-             (!mVoiceCapable && streamType != AudioSystem.STREAM_VOICE_CALL &&
-               streamType != AudioSystem.STREAM_BLUETOOTH_SCO) ||
-                (mVoiceCapable && streamTypeAlias == AudioSystem.STREAM_RING)) {
+                streamTypeAlias == AudioSystem.STREAM_RING ||
+                (!mVoiceCapable && streamTypeAlias == AudioSystem.STREAM_MUSIC)) {
             // do not vibrate if already in vibrate mode
             if (mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
                 flags &= ~AudioManager.FLAG_VIBRATE;
@@ -545,10 +547,19 @@
         int index;
         if (streamState.muteCount() != 0) {
             if (adjustVolume) {
-                streamState.adjustLastAudibleIndex(direction);
-                // Post a persist volume msg
-                sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, streamType,
-                        SENDMSG_REPLACE, 0, 1, streamState, PERSIST_DELAY);
+                // adjust volume on all stream types sharing the same alias otherwise a query
+                // on last audible index for an alias would not give the correct value
+                int numStreamTypes = AudioSystem.getNumStreamTypes();
+                for (int i = numStreamTypes - 1; i >= 0; i--) {
+                    if (STREAM_VOLUME_ALIAS[i] == streamTypeAlias) {
+                        VolumeStreamState s = mStreamStates[i];
+
+                        s.adjustLastAudibleIndex(direction);
+                        // Post a persist volume msg
+                        sendMsg(mAudioHandler, MSG_PERSIST_VOLUME, i,
+                                SENDMSG_REPLACE, 0, 1, s, PERSIST_DELAY);
+                    }
+                }
             }
             index = streamState.mLastAudibleIndex;
         } else {
diff --git a/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp b/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp
index ddced5f..aa07e57 100644
--- a/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp
+++ b/media/libstagefright/codecs/m4v_h263/dec/SoftMPEG4.cpp
@@ -421,8 +421,13 @@
 
         int32_t bufferSize = inHeader->nFilledLen;
 
+        // The PV decoder is lying to us, sometimes it'll claim to only have
+        // consumed a subset of the buffer when it clearly consumed all of it.
+        // ignore whatever it says...
+        int32_t tmp = bufferSize;
+
         if (PVDecodeVideoFrame(
-                    mHandle, &bitstream, &timestamp, &bufferSize,
+                    mHandle, &bitstream, &timestamp, &tmp,
                     &useExtTimestamp,
                     outHeader->pBuffer) != PV_TRUE) {
             LOGE("failed to decode video frame.");
diff --git a/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp b/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp
index 740c957..dede3ac 100644
--- a/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp
+++ b/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp
@@ -76,7 +76,8 @@
       mPicId(0),
       mHeadersDecoded(false),
       mEOSStatus(INPUT_DATA_AVAILABLE),
-      mOutputPortSettingsChange(NONE) {
+      mOutputPortSettingsChange(NONE),
+      mSignalledError(false) {
     initPorts();
     CHECK_EQ(initDecoder(), (status_t)OK);
 }
@@ -287,7 +288,7 @@
 }
 
 void SoftAVC::onQueueFilled(OMX_U32 portIndex) {
-    if (mOutputPortSettingsChange != NONE) {
+    if (mSignalledError || mOutputPortSettingsChange != NONE) {
         return;
     }
 
@@ -298,7 +299,6 @@
     List<BufferInfo *> &inQueue = getPortQueue(kInputPortIndex);
     List<BufferInfo *> &outQueue = getPortQueue(kOutputPortIndex);
     H264SwDecRet ret = H264SWDEC_PIC_RDY;
-    status_t err = OK;
     bool portSettingsChanged = false;
     while ((mEOSStatus != INPUT_DATA_AVAILABLE || !inQueue.empty())
             && outQueue.size() == kNumOutputBuffers) {
@@ -372,7 +372,12 @@
                 inPicture.dataLen = 0;
                 if (ret < 0) {
                     LOGE("Decoder failed: %d", ret);
-                    err = ERROR_MALFORMED;
+
+                    notify(OMX_EventError, OMX_ErrorUndefined,
+                           ERROR_MALFORMED, NULL);
+
+                    mSignalledError = true;
+                    return;
                 }
             }
         }
@@ -400,10 +405,6 @@
             uint8_t *data = (uint8_t *) decodedPicture.pOutputPicture;
             drainOneOutputBuffer(picId, data);
         }
-
-        if (err != OK) {
-            notify(OMX_EventError, OMX_ErrorUndefined, err, NULL);
-        }
     }
 }
 
diff --git a/media/libstagefright/codecs/on2/h264dec/SoftAVC.h b/media/libstagefright/codecs/on2/h264dec/SoftAVC.h
index 1cc85e8..879b014 100644
--- a/media/libstagefright/codecs/on2/h264dec/SoftAVC.h
+++ b/media/libstagefright/codecs/on2/h264dec/SoftAVC.h
@@ -88,6 +88,8 @@
     };
     OutputPortSettingChange mOutputPortSettingsChange;
 
+    bool mSignalledError;
+
     void initPorts();
     status_t initDecoder();
     void updatePortDefinitions();
diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h
index af7dd23..6a5efa4 100644
--- a/media/libstagefright/rtsp/MyHandler.h
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -44,12 +44,14 @@
 
 // If no access units are received within 5 secs, assume that the rtp
 // stream has ended and signal end of stream.
-static int64_t kAccessUnitTimeoutUs = 5000000ll;
+static int64_t kAccessUnitTimeoutUs = 10000000ll;
 
 // If no access units arrive for the first 10 secs after starting the
 // stream, assume none ever will and signal EOS or switch transports.
 static int64_t kStartupTimeoutUs = 10000000ll;
 
+static int64_t kDefaultKeepAliveTimeoutUs = 60000000ll;
+
 namespace android {
 
 static void MakeUserAgentString(AString *s) {
@@ -130,7 +132,9 @@
           mTryFakeRTCP(false),
           mReceivedFirstRTCPPacket(false),
           mReceivedFirstRTPPacket(false),
-          mSeekable(false) {
+          mSeekable(false),
+          mKeepAliveTimeoutUs(kDefaultKeepAliveTimeoutUs),
+          mKeepAliveGeneration(0) {
         mNetLooper->setName("rtsp net");
         mNetLooper->start(false /* runOnCallingThread */,
                           false /* canCallJava */,
@@ -371,6 +375,8 @@
 
             case 'disc':
             {
+                ++mKeepAliveGeneration;
+
                 int32_t reconnect;
                 if (msg->findInt32("reconnect", &reconnect) && reconnect) {
                     sp<AMessage> reply = new AMessage('conn', id());
@@ -502,6 +508,34 @@
                         CHECK_GE(i, 0);
 
                         mSessionID = response->mHeaders.valueAt(i);
+
+                        mKeepAliveTimeoutUs = kDefaultKeepAliveTimeoutUs;
+                        AString timeoutStr;
+                        if (GetAttribute(
+                                    mSessionID.c_str(), "timeout", &timeoutStr)) {
+                            char *end;
+                            unsigned long timeoutSecs =
+                                strtoul(timeoutStr.c_str(), &end, 10);
+
+                            if (end == timeoutStr.c_str() || *end != '\0') {
+                                LOGW("server specified malformed timeout '%s'",
+                                     timeoutStr.c_str());
+
+                                mKeepAliveTimeoutUs = kDefaultKeepAliveTimeoutUs;
+                            } else if (timeoutSecs < 15) {
+                                LOGW("server specified too short a timeout "
+                                     "(%lu secs), using default.",
+                                     timeoutSecs);
+
+                                mKeepAliveTimeoutUs = kDefaultKeepAliveTimeoutUs;
+                            } else {
+                                mKeepAliveTimeoutUs = timeoutSecs * 1000000ll;
+
+                                LOGI("server specified timeout of %lu secs.",
+                                     timeoutSecs);
+                            }
+                        }
+
                         i = mSessionID.find(";");
                         if (i >= 0) {
                             // Remove options, i.e. ";timeout=90"
@@ -555,6 +589,9 @@
                 if (index < mSessionDesc->countTracks()) {
                     setupTrack(index);
                 } else if (mSetupTracksSuccessful) {
+                    ++mKeepAliveGeneration;
+                    postKeepAlive();
+
                     AString request = "PLAY ";
                     request.append(mSessionURL);
                     request.append(" RTSP/1.0\r\n");
@@ -606,6 +643,51 @@
                 break;
             }
 
+            case 'aliv':
+            {
+                int32_t generation;
+                CHECK(msg->findInt32("generation", &generation));
+
+                if (generation != mKeepAliveGeneration) {
+                    // obsolete event.
+                    break;
+                }
+
+                AString request;
+                request.append("OPTIONS ");
+                request.append(mSessionURL);
+                request.append(" RTSP/1.0\r\n");
+                request.append("Session: ");
+                request.append(mSessionID);
+                request.append("\r\n");
+                request.append("\r\n");
+
+                sp<AMessage> reply = new AMessage('opts', id());
+                reply->setInt32("generation", mKeepAliveGeneration);
+                mConn->sendRequest(request.c_str(), reply);
+                break;
+            }
+
+            case 'opts':
+            {
+                int32_t result;
+                CHECK(msg->findInt32("result", &result));
+
+                LOGI("OPTIONS completed with result %d (%s)",
+                     result, strerror(-result));
+
+                int32_t generation;
+                CHECK(msg->findInt32("generation", &generation));
+
+                if (generation != mKeepAliveGeneration) {
+                    // obsolete event.
+                    break;
+                }
+
+                postKeepAlive();
+                break;
+            }
+
             case 'abor':
             {
                 for (size_t i = 0; i < mTracks.size(); ++i) {
@@ -952,6 +1034,12 @@
         }
     }
 
+    void postKeepAlive() {
+        sp<AMessage> msg = new AMessage('aliv', id());
+        msg->setInt32("generation", mKeepAliveGeneration);
+        msg->post((mKeepAliveTimeoutUs * 9) / 10);
+    }
+
     void postAccessUnitTimeoutCheck() {
         if (mCheckPending) {
             return;
@@ -1120,6 +1208,8 @@
     bool mReceivedFirstRTCPPacket;
     bool mReceivedFirstRTPPacket;
     bool mSeekable;
+    int64_t mKeepAliveTimeoutUs;
+    int32_t mKeepAliveGeneration;
 
     Vector<TrackInfo> mTracks;
 
diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp
index aa40d58..522421b 100644
--- a/opengl/libs/EGL/egl_cache.cpp
+++ b/opengl/libs/EGL/egl_cache.cpp
@@ -34,6 +34,9 @@
 static const char* cacheFileMagic = "EGL$";
 static const size_t cacheFileHeaderSize = 8;
 
+// The time in seconds to wait before saving newly inserted cache entries.
+static const unsigned int deferredSaveDelay = 4;
+
 // ----------------------------------------------------------------------------
 namespace android {
 // ----------------------------------------------------------------------------
@@ -128,6 +131,30 @@
     if (mInitialized) {
         sp<BlobCache> bc = getBlobCacheLocked();
         bc->set(key, keySize, value, valueSize);
+
+        if (!mSavePending) {
+            class DeferredSaveThread : public Thread {
+            public:
+                DeferredSaveThread() : Thread(false) {}
+
+                virtual bool threadLoop() {
+                    sleep(deferredSaveDelay);
+                    egl_cache_t* c = egl_cache_t::get();
+                    Mutex::Autolock lock(c->mMutex);
+                    if (c->mInitialized) {
+                        c->saveBlobCacheLocked();
+                    }
+                    c->mSavePending = false;
+                    return false;
+                }
+            };
+
+            // The thread will hold a strong ref to itself until it has finished
+            // running, so there's no need to keep a ref around.
+            sp<Thread> deferredSaveThread(new DeferredSaveThread());
+            mSavePending = true;
+            deferredSaveThread->run();
+        }
     }
 }
 
diff --git a/opengl/libs/EGL/egl_cache.h b/opengl/libs/EGL/egl_cache.h
index 05d5873..4389623 100644
--- a/opengl/libs/EGL/egl_cache.h
+++ b/opengl/libs/EGL/egl_cache.h
@@ -108,6 +108,13 @@
     // from disk.
     String8 mFilename;
 
+    // mSavePending indicates whether or not a deferred save operation is
+    // pending.  Each time a key/value pair is inserted into the cache via
+    // setBlob, a deferred save is initiated if one is not already pending.
+    // This will wait some amount of time and then trigger a save of the cache
+    // contents to disk.
+    bool mSavePending;
+
     // mMutex is the mutex used to prevent concurrent access to the member
     // variables. It must be locked whenever the member variables are accessed.
     mutable Mutex mMutex;
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 0891525..7a98615 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -133,8 +133,4 @@
     <bool name="def_dtmf_tones_enabled">true</bool>
     <!-- Default for UI touch sounds enabled -->
     <bool name="def_sound_effects_enabled">true</bool>
-
-    <!-- Default for Messaging app notifications enabled -->
-    <bool name="def_messaging_app_notifications_on">true</bool>
-
 </resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 44194f0..5495d08 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -1472,10 +1472,6 @@
 
             loadBooleanSetting(stmt, Settings.Secure.TOUCH_EXPLORATION_ENABLED,
                     R.bool.def_touch_exploration_enabled);
-
-            loadBooleanSetting(stmt, Settings.Secure.MESSAGING_APP_NOTIFICATIONS,
-                    R.bool.def_messaging_app_notifications_on);
-
         } finally {
             if (stmt != null) stmt.close();
         }
diff --git a/packages/SystemUI/res/drawable-hdpi/scrubber_control_disabled_holo.png b/packages/SystemUI/res/drawable-hdpi/scrubber_control_disabled_holo.png
deleted file mode 100644
index 55f6aa1..0000000
--- a/packages/SystemUI/res/drawable-hdpi/scrubber_control_disabled_holo.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/scrubber_control_holo.png b/packages/SystemUI/res/drawable-hdpi/scrubber_control_holo.png
deleted file mode 100644
index 19dae07..0000000
--- a/packages/SystemUI/res/drawable-hdpi/scrubber_control_holo.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/scrubber_track_holo_dark.9.png b/packages/SystemUI/res/drawable-hdpi/scrubber_track_holo_dark.9.png
deleted file mode 100644
index 8811df5..0000000
--- a/packages/SystemUI/res/drawable-hdpi/scrubber_track_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/scrubber_control_disabled_holo.png b/packages/SystemUI/res/drawable-mdpi/scrubber_control_disabled_holo.png
deleted file mode 100644
index 2b8768b..0000000
--- a/packages/SystemUI/res/drawable-mdpi/scrubber_control_disabled_holo.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/scrubber_control_holo.png b/packages/SystemUI/res/drawable-mdpi/scrubber_control_holo.png
deleted file mode 100644
index 0672564..0000000
--- a/packages/SystemUI/res/drawable-mdpi/scrubber_control_holo.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/scrubber_track_holo_dark.9.png b/packages/SystemUI/res/drawable-mdpi/scrubber_track_holo_dark.9.png
deleted file mode 100644
index 511d5c3..0000000
--- a/packages/SystemUI/res/drawable-mdpi/scrubber_track_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/scrubber_control_disabled_holo.png b/packages/SystemUI/res/drawable-xhdpi/scrubber_control_disabled_holo.png
deleted file mode 100644
index 551c6dc..0000000
--- a/packages/SystemUI/res/drawable-xhdpi/scrubber_control_disabled_holo.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/scrubber_control_holo.png b/packages/SystemUI/res/drawable-xhdpi/scrubber_control_holo.png
deleted file mode 100644
index 11f9c51..0000000
--- a/packages/SystemUI/res/drawable-xhdpi/scrubber_control_holo.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/scrubber_track_holo_dark.9.png b/packages/SystemUI/res/drawable-xhdpi/scrubber_track_holo_dark.9.png
deleted file mode 100644
index b28dddf..0000000
--- a/packages/SystemUI/res/drawable-xhdpi/scrubber_track_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ToggleSlider.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ToggleSlider.java
index 0eb2be6..fe2ec69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ToggleSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ToggleSlider.java
@@ -80,10 +80,13 @@
         Drawable slider;
         final Resources res = getContext().getResources();
         if (checked) {
-            thumb = res.getDrawable(R.drawable.scrubber_control_disabled_holo);
-            slider = res.getDrawable(R.drawable.status_bar_settings_slider_disabled);
+            thumb = res.getDrawable(
+                    com.android.internal.R.drawable.scrubber_control_disabled_holo);
+            slider = res.getDrawable(
+                    R.drawable.status_bar_settings_slider_disabled);
         } else {
-            thumb = res.getDrawable(R.drawable.scrubber_control_holo);
+            thumb = res.getDrawable(
+                    com.android.internal.R.drawable.scrubber_control_selector_holo);
             slider = res.getDrawable(
                     com.android.internal.R.drawable.scrubber_progress_horizontal_holo_dark);
         }
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java b/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java
index dafbdcf..a7da96e 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardStatusViewManager.java
@@ -91,7 +91,7 @@
     private LockPatternUtils mLockPatternUtils;
     private KeyguardUpdateMonitor mUpdateMonitor;
     private Button mEmergencyCallButton;
-    private boolean mUnlockDisabledDueToSimState;
+    private boolean mEmergencyButtonEnabledBecauseSimLocked;
 
     // Shadowed text values
     private CharSequence mCarrierText;
@@ -101,9 +101,10 @@
     private CharSequence mOwnerInfoText;
     private boolean mShowingStatus;
     private KeyguardScreenCallback mCallback;
-    private final boolean mShowEmergencyButtonByDefault;
+    private final boolean mEmergencyCallButtonEnabledInScreen;
     private CharSequence mPlmn;
     private CharSequence mSpn;
+    protected int mPhoneState;
 
     private class TransientTextManager {
         private TextView mTextView;
@@ -154,9 +155,17 @@
         }
     };
 
+    /**
+     *
+     * @param view the containing view of all widgets
+     * @param updateMonitor the update monitor to use
+     * @param lockPatternUtils lock pattern util object
+     * @param callback used to invoke emergency dialer
+     * @param emergencyButtonEnabledInScreen whether emergency button is enabled by default
+     */
     public KeyguardStatusViewManager(View view, KeyguardUpdateMonitor updateMonitor,
                 LockPatternUtils lockPatternUtils, KeyguardScreenCallback callback,
-                boolean showEmergencyButtonByDefault) {
+                boolean emergencyButtonEnabledInScreen) {
         if (DEBUG) Log.v(TAG, "KeyguardStatusViewManager()");
         mContainer = view;
         mDateFormatString = getContext().getString(R.string.full_wday_month_day_no_year);
@@ -171,7 +180,7 @@
         mOwnerInfoView = (TextView) findViewById(R.id.propertyOf);
         mTransportView = (TransportControlView) findViewById(R.id.transport);
         mEmergencyCallButton = (Button) findViewById(R.id.emergencyCallButton);
-        mShowEmergencyButtonByDefault = showEmergencyButtonByDefault;
+        mEmergencyCallButtonEnabledInScreen = emergencyButtonEnabledInScreen;
 
         // Hide transport control view until we know we need to show it.
         if (mTransportView != null) {
@@ -452,12 +461,12 @@
      *
      * @param simState
      */
-    private void updateCarrierTextWithSimStatus(State simState) {
+    private void updateCarrierStateWithSimStatus(State simState) {
         if (DEBUG) Log.d(TAG, "updateCarrierTextWithSimStatus(), simState = " + simState);
 
         CharSequence carrierText = null;
         int carrierHelpTextId = 0;
-        mUnlockDisabledDueToSimState = false;
+        mEmergencyButtonEnabledBecauseSimLocked = false;
         mStatus = getStatusForIccState(simState);
         mSimState = simState;
         switch (mStatus) {
@@ -479,32 +488,35 @@
             case SimPermDisabled:
                 carrierText = getContext().getText(R.string.lockscreen_missing_sim_message_short);
                 carrierHelpTextId = R.string.lockscreen_permanent_disabled_sim_instructions;
-                mUnlockDisabledDueToSimState = true;
+                mEmergencyButtonEnabledBecauseSimLocked = true;
                 break;
 
             case SimMissingLocked:
                 carrierText = makeCarierString(mPlmn,
                         getContext().getText(R.string.lockscreen_missing_sim_message_short));
                 carrierHelpTextId = R.string.lockscreen_missing_sim_instructions;
-                mUnlockDisabledDueToSimState = true;
+                mEmergencyButtonEnabledBecauseSimLocked = true;
                 break;
 
             case SimLocked:
                 carrierText = makeCarierString(mPlmn,
                         getContext().getText(R.string.lockscreen_sim_locked_message));
+                mEmergencyButtonEnabledBecauseSimLocked = true;
                 break;
 
             case SimPukLocked:
                 carrierText = makeCarierString(mPlmn,
                         getContext().getText(R.string.lockscreen_sim_puk_locked_message));
                 if (!mLockPatternUtils.isPukUnlockScreenEnable()) {
-                    mUnlockDisabledDueToSimState = true;
+                    // This means we're showing the PUK unlock screen
+                    mEmergencyButtonEnabledBecauseSimLocked = true;
                 }
                 break;
         }
 
         setCarrierText(carrierText);
         setCarrierHelpText(carrierHelpTextId);
+        updateEmergencyCallButtonState(mPhoneState);
     }
 
     private View findViewById(int id) {
@@ -569,9 +581,12 @@
 
     private void updateEmergencyCallButtonState(int phoneState) {
         if (mEmergencyCallButton != null) {
-            boolean showIfCapable = mShowEmergencyButtonByDefault || mUnlockDisabledDueToSimState;
+            boolean enabledBecauseSimLocked =
+                    mLockPatternUtils.isEmergencyCallEnabledWhileSimLocked()
+                    && mEmergencyButtonEnabledBecauseSimLocked;
+            boolean shown = mEmergencyCallButtonEnabledInScreen || enabledBecauseSimLocked;
             mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton,
-                    phoneState, showIfCapable);
+                    phoneState, shown);
         }
     }
 
@@ -594,7 +609,7 @@
         public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) {
             mPlmn = plmn;
             mSpn = spn;
-            updateCarrierTextWithSimStatus(mSimState);
+            updateCarrierStateWithSimStatus(mSimState);
         }
 
         public void onRingerModeChanged(int state) {
@@ -602,6 +617,7 @@
         }
 
         public void onPhoneStateChanged(int phoneState) {
+            mPhoneState = phoneState;
             updateEmergencyCallButtonState(phoneState);
         }
 
@@ -618,7 +634,7 @@
     private SimStateCallback mSimStateCallback = new SimStateCallback() {
 
         public void onSimStateChanged(State simState) {
-            updateCarrierTextWithSimStatus(simState);
+            updateCarrierStateWithSimStatus(simState);
         }
     };
 
diff --git a/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java b/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java
index 6acd1c5..47a7157 100644
--- a/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java
@@ -106,7 +106,7 @@
         mHeaderText.setSelected(true);
 
         mKeyguardStatusViewManager = new KeyguardStatusViewManager(this, updateMonitor,
-                lockpatternutils, callback, true);
+                lockpatternutils, callback, false);
 
         mPinText.setFocusableInTouchMode(true);
         mPinText.setOnFocusChangeListener(this);
diff --git a/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java b/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java
index 184748a..99e1ce1 100644
--- a/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java
@@ -100,7 +100,7 @@
         mOkButton.setOnClickListener(this);
 
         mKeyguardStatusViewManager = new KeyguardStatusViewManager(this, updateMonitor,
-                lockpatternutils, callback, true);
+                lockpatternutils, callback, false);
 
         setFocusableInTouchMode(true);
     }
diff --git a/services/java/com/android/server/TextServicesManagerService.java b/services/java/com/android/server/TextServicesManagerService.java
index b042da6..af9152d 100644
--- a/services/java/com/android/server/TextServicesManagerService.java
+++ b/services/java/com/android/server/TextServicesManagerService.java
@@ -40,6 +40,8 @@
 import android.service.textservice.SpellCheckerService;
 import android.text.TextUtils;
 import android.util.Slog;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
 import android.view.textservice.SpellCheckerInfo;
 import android.view.textservice.SpellCheckerSubtype;
 
@@ -222,20 +224,40 @@
             if (hashCode == 0 && !allowImplicitlySelectedSubtype) {
                 return null;
             }
-            final String systemLocale =
-                    mContext.getResources().getConfiguration().locale.toString();
+            String candidateLocale = null;
+            if (hashCode == 0) {
+                // Spell checker language settings == "auto"
+                final InputMethodManager imm =
+                        (InputMethodManager)mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+                if (imm != null) {
+                    final InputMethodSubtype currentInputMethodSubtype =
+                            imm.getCurrentInputMethodSubtype();
+                    if (currentInputMethodSubtype != null) {
+                        final String localeString = currentInputMethodSubtype.getLocale();
+                        if (!TextUtils.isEmpty(localeString)) {
+                            // 1. Use keyboard locale if available in the spell checker
+                            candidateLocale = localeString;
+                        }
+                    }
+                }
+                if (candidateLocale == null) {
+                    // 2. Use System locale if available in the spell checker
+                    candidateLocale = mContext.getResources().getConfiguration().locale.toString();
+                }
+            }
             SpellCheckerSubtype candidate = null;
             for (int i = 0; i < sci.getSubtypeCount(); ++i) {
                 final SpellCheckerSubtype scs = sci.getSubtypeAt(i);
                 if (hashCode == 0) {
-                    if (systemLocale.equals(locale)) {
+                    if (candidateLocale.equals(locale)) {
                         return scs;
                     } else if (candidate == null) {
                         final String scsLocale = scs.getLocale();
-                        if (systemLocale.length() >= 2
+                        if (candidateLocale.length() >= 2
                                 && scsLocale.length() >= 2
-                                && systemLocale.substring(0, 2).equals(
+                                && candidateLocale.substring(0, 2).equals(
                                         scsLocale.substring(0, 2))) {
+                            // Fall back to the applicable language
                             candidate = scs;
                         }
                     }
@@ -244,9 +266,13 @@
                         Slog.w(TAG, "Return subtype " + scs.hashCode() + ", input= " + locale
                                 + ", " + scs.getLocale());
                     }
+                    // 3. Use the user specified spell check language
                     return scs;
                 }
             }
+            // 4. Fall back to the applicable language and return it if not null
+            // 5. Simply just return it even if it's null which means we could find no suitable
+            // spell check languages
             return candidate;
         }
     }
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index bfe6613..36442a0 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -63,6 +63,8 @@
 import java.util.HashSet;
 import java.util.Iterator;
 
+import libcore.io.IoUtils;
+
 /**
  * Holds information about dynamic settings.
  */
@@ -998,8 +1000,8 @@
                 FileUtils.sync(fstr);
                 str.close();
                 journal.commit();
-            }
-            catch (Exception  e) {
+            } catch (Exception e) {
+                IoUtils.closeQuietly(str);
                 journal.rollback();
             }
 
diff --git a/services/java/com/android/server/wm/InputManager.java b/services/java/com/android/server/wm/InputManager.java
index df7e0e1..a4f0a0c2 100644
--- a/services/java/com/android/server/wm/InputManager.java
+++ b/services/java/com/android/server/wm/InputManager.java
@@ -675,7 +675,13 @@
             } catch (NumberFormatException e) {
             }
             if (result < 1) {
-                result = 55;
+                // This number equates to the refresh rate * 1.5. The rate should be at least
+                // equal to the screen refresh rate. We increase the rate by 50% to compensate for
+                // the discontinuity between the actual rate that events come in at (they do
+                // not necessarily come in constantly and are not handled synchronously).
+                // Ideally, we would use Display.getRefreshRate(), but as this does not necessarily
+                // return a sensible result, we use '60' as our default assumed refresh rate.
+                result = 90;
             }
             return result;
         }
diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp
index d82a7e2..7575ebd 100644
--- a/services/sensorservice/SensorDevice.cpp
+++ b/services/sensorservice/SensorDevice.cpp
@@ -166,7 +166,11 @@
 
 ssize_t SensorDevice::poll(sensors_event_t* buffer, size_t count) {
     if (!mSensorDevice) return NO_INIT;
-    return mSensorDevice->poll(mSensorDevice, buffer, count);
+    ssize_t c;
+    do {
+        c = mSensorDevice->poll(mSensorDevice, buffer, count);
+    } while (c == -EINTR);
+    return c;
 }
 
 status_t SensorDevice::activate(void* ident, int handle, int enabled)
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index c2c6b4d..6202143 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -286,7 +286,8 @@
         }
     } while (count >= 0 || Thread::exitPending());
 
-    LOGW("Exiting SensorService::threadLoop!");
+    LOGW("Exiting SensorService::threadLoop => aborting...");
+    abort();
     return false;
 }
 
diff --git a/services/surfaceflinger/Android.mk b/services/surfaceflinger/Android.mk
index b916bd7..53502db 100644
--- a/services/surfaceflinger/Android.mk
+++ b/services/surfaceflinger/Android.mk
@@ -31,7 +31,7 @@
 endif
 
 ifneq (,$(findstring $(TARGET_DEVICE),tuna toro maguro))
-	LOCAL_CFLAGS += -DREFRESH_RATE=48
+	LOCAL_CFLAGS += -DREFRESH_RATE=59
 endif
 
 
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
index 410e961..6d9a2c2 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -213,7 +213,7 @@
     protected static final String NULL_IP = "0.0.0.0";
 
     // Default for the data stall alarm
-    protected static final int DATA_STALL_ALARM_DELAY_IN_MS_DEFAULT = 1000 * 60 * 3;
+    protected static final int DATA_STALL_ALARM_DELAY_IN_MS_DEFAULT = 1000 * 60 * 6;
     // If attempt is less than this value we're doing first level recovery
     protected static final int DATA_STALL_NO_RECV_POLL_LIMIT = 1;
     // Tag for tracking stale alarms
diff --git a/telephony/java/com/android/internal/telephony/cat/ComprehensionTlv.java b/telephony/java/com/android/internal/telephony/cat/ComprehensionTlv.java
index 99f662d..e5a2d31 100644
--- a/telephony/java/com/android/internal/telephony/cat/ComprehensionTlv.java
+++ b/telephony/java/com/android/internal/telephony/cat/ComprehensionTlv.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006 The Android Open Source Project
+ * Copyright (C) 2011 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@
  * {@hide}
  */
 class ComprehensionTlv {
+    private static final String LOG_TAG = "ComprehensionTlv";
     private int mTag;
     private boolean mCr;
     private int mLength;
@@ -88,8 +89,13 @@
         int endIndex = data.length;
         while (startIndex < endIndex) {
             ComprehensionTlv ctlv = ComprehensionTlv.decode(data, startIndex);
-            items.add(ctlv);
-            startIndex = ctlv.mValueIndex + ctlv.mLength;
+            if (ctlv != null) {
+                items.add(ctlv);
+                startIndex = ctlv.mValueIndex + ctlv.mLength;
+            } else {
+                CatLog.d(LOG_TAG, "decodeMany: ctlv is null, stop decoding");
+                break;
+            }
         }
 
         return items;