merge in ics-mr1-release history after reset to ics-mr1
diff --git a/api/current.txt b/api/current.txt
index 808cb6f..4e41c29 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -18813,6 +18813,7 @@
     method public boolean areDefaultsEnforced();
     method public java.lang.String getDefaultEngine();
     method public java.util.List<android.speech.tts.TextToSpeech.EngineInfo> getEngines();
+    method public java.util.Set<java.lang.String> getFeatures(java.util.Locale);
     method public java.util.Locale getLanguage();
     method public int isLanguageAvailable(java.util.Locale);
     method public boolean isSpeaking();
@@ -18858,6 +18859,8 @@
     field public static final java.lang.String EXTRA_VOICE_DATA_FILES_INFO = "dataFilesInfo";
     field public static final java.lang.String EXTRA_VOICE_DATA_ROOT_DIRECTORY = "dataRoot";
     field public static final java.lang.String INTENT_ACTION_TTS_SERVICE = "android.intent.action.TTS_SERVICE";
+    field public static final java.lang.String KEY_FEATURE_EMBEDDED_SYNTHESIS = "embeddedTts";
+    field public static final java.lang.String KEY_FEATURE_NETWORK_SYNTHESIS = "networkTts";
     field public static final java.lang.String KEY_PARAM_PAN = "pan";
     field public static final java.lang.String KEY_PARAM_STREAM = "streamType";
     field public static final java.lang.String KEY_PARAM_UTTERANCE_ID = "utteranceId";
@@ -18883,6 +18886,7 @@
   public abstract class TextToSpeechService extends android.app.Service {
     ctor public TextToSpeechService();
     method public android.os.IBinder onBind(android.content.Intent);
+    method protected java.util.Set<java.lang.String> onGetFeaturesForLanguage(java.lang.String, java.lang.String, java.lang.String);
     method protected abstract java.lang.String[] onGetLanguage();
     method protected abstract int onIsLanguageAvailable(java.lang.String, java.lang.String, java.lang.String);
     method protected abstract int onLoadLanguage(java.lang.String, java.lang.String, java.lang.String);
diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c
index 30aed33..ca66a4e 100644
--- a/cmds/dumpstate/dumpstate.c
+++ b/cmds/dumpstate/dumpstate.c
@@ -209,6 +209,8 @@
 
     run_command("LIST OF OPEN FILES", 10, "su", "root", "lsof", NULL);
 
+    for_each_pid(do_showmap, "SMAPS OF ALL PROCESSES");
+
 #ifdef BOARD_HAS_DUMPSTATE
     printf("========================================================\n");
     printf("== Board\n");
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 597ab1f..6d66b1b 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -45,6 +45,9 @@
 /* Displays a blocked processes in-kernel wait channel */
 void show_wchan(int pid, const char *name);
 
+/* Runs "showmap" for a process */
+void do_showmap(int pid, const char *name);
+
 /* Play a sound via Stagefright */
 void play_sound(const char* path);
 
diff --git a/cmds/dumpstate/utils.c b/cmds/dumpstate/utils.c
index b2f9e80..14984ec 100644
--- a/cmds/dumpstate/utils.c
+++ b/cmds/dumpstate/utils.c
@@ -96,6 +96,15 @@
     return;
 }
 
+void do_showmap(int pid, const char *name) {
+    char title[255];
+    char arg[255];
+
+    sprintf(title, "SHOW MAP %d (%s)", pid, name);
+    sprintf(arg, "%d", pid);
+    run_command(title, 10, "su", "root", "showmap", arg, NULL);
+}
+
 /* prints the contents of a file */
 int dump_file(const char *title, const char* path) {
     char buffer[32768];
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 3eb7647..8541748d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -153,10 +153,14 @@
     public static final int GET_PERMISSIONS               = 0x00001000;
 
     /**
-     * Flag parameter to retrieve all applications(even uninstalled ones) with data directories.
-     * This state could have resulted if applications have been deleted with flag
-     * DONT_DELETE_DATA
-     * with a possibility of being replaced or reinstalled in future
+     * Flag parameter to retrieve some information about all applications (even
+     * uninstalled ones) which have data directories. This state could have
+     * resulted if applications have been deleted with flag
+     * {@code DONT_DELETE_DATA} with a possibility of being replaced or
+     * reinstalled in future.
+     * <p>
+     * Note: this flag may cause less information about currently installed
+     * applications to be returned.
      */
     public static final int GET_UNINSTALLED_PACKAGES = 0x00002000;
 
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index be87946..6ecc640 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -105,6 +105,18 @@
     void removeRoute(String iface, in RouteInfo route);
 
     /**
+     * Add the specified route to a secondary interface
+     * This will go into a special route table to be accessed
+     * via ip rules
+     */
+    void addSecondaryRoute(String iface, in RouteInfo route);
+
+    /**
+     * Remove the specified secondary route.
+     */
+    void removeSecondaryRoute(String iface, in RouteInfo route);
+
+    /**
      * Shuts down the service
      */
     void shutdown();
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index cc2fa85..99f58a0 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -116,6 +116,14 @@
     private static final boolean IS_ENG_BUILD = "eng".equals(Build.TYPE);
 
     /**
+     * Boolean system property to disable strict mode checks outright.
+     * Set this to 'true' to force disable; 'false' has no effect on other
+     * enable/disable policy.
+     * @hide
+     */
+    public static final String DISABLE_PROPERTY = "persist.sys.strictmode.disable";
+
+    /**
      * The boolean system property to control screen flashes on violations.
      *
      * @hide
@@ -891,16 +899,24 @@
      * @hide
      */
     public static boolean conditionallyEnableDebugLogging() {
-        boolean doFlashes = !amTheSystemServerProcess() &&
-                SystemProperties.getBoolean(VISUAL_PROPERTY, IS_ENG_BUILD);
+        boolean doFlashes = SystemProperties.getBoolean(VISUAL_PROPERTY, false)
+                && !amTheSystemServerProcess();
+        final boolean suppress = SystemProperties.getBoolean(DISABLE_PROPERTY, false);
 
         // For debug builds, log event loop stalls to dropbox for analysis.
         // Similar logic also appears in ActivityThread.java for system apps.
-        if (IS_USER_BUILD && !doFlashes) {
+        if (!doFlashes && (IS_USER_BUILD || suppress)) {
             setCloseGuardEnabled(false);
             return false;
         }
 
+        // Eng builds have flashes on all the time.  The suppression property
+        // overrides this, so we force the behavior only after the short-circuit
+        // check above.
+        if (IS_ENG_BUILD) {
+            doFlashes = true;
+        }
+
         // Thread policy controls BlockGuard.
         int threadPolicyMask = StrictMode.DETECT_DISK_WRITE |
                 StrictMode.DETECT_DISK_READ |
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 4bc0892..ff28596 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -4666,6 +4666,13 @@
          * @hide
          */
         public static final String CONTENT_TYPE = "vnd.android.cursor.dir/phone_lookup";
+
+       /**
+        * Boolean parameter that is used to look up a SIP address.
+        *
+        * @hide
+        */
+        public static final String QUERY_PARAMETER_SIP_ADDRESS = "sip";
     }
 
     /**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5754e60..a0652f7 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3620,6 +3620,13 @@
                 "pdp_watchdog_max_pdp_reset_fail_count";
 
         /**
+         * The number of milliseconds to delay when checking for data stalls
+         * @hide
+         */
+        public static final String DATA_STALL_ALARM_DELAY_IN_MS =
+                "data_stall_alarm_delay_in_ms";
+
+        /**
          * The interval in milliseconds at which to check gprs registration
          * after the first registration mismatch of gprs and voice service,
          * to detect possible data network registration problems.
diff --git a/core/java/android/speech/tts/ITextToSpeechService.aidl b/core/java/android/speech/tts/ITextToSpeechService.aidl
index ff3fa11..1a8c1fb 100644
--- a/core/java/android/speech/tts/ITextToSpeechService.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechService.aidl
@@ -114,6 +114,21 @@
     int isLanguageAvailable(in String lang, in String country, in String variant);
 
     /**
+     * Returns a list of features available for a given language. Elements of the returned
+     * string array can be passed in as keys to {@link TextToSpeech#speak} and
+     * {@link TextToSpeech#synthesizeToFile} to select a given feature or features to be
+     * used during synthesis.
+     *
+     * @param lang ISO-3 language code.
+     * @param country ISO-3 country code. May be empty or null.
+     * @param variant Language variant. May be empty or null.
+     * @return An array of strings containing the set of features supported for
+     *         the supplied locale. The array of strings must not contain 
+     *         duplicates.
+     */
+    String[] getFeaturesForLanguage(in String lang, in String country, in String variant);
+
+    /**
      * Notifies the engine that it should load a speech synthesis language.
      *
      * @param lang ISO-3 language code.
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 98ab310..954a6fe 100755
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -31,10 +31,13 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 
 /**
  *
@@ -147,7 +150,25 @@
     }
 
     /**
-     * Constants and parameter names for controlling text-to-speech.
+     * Constants and parameter names for controlling text-to-speech. These include:
+     *
+     * <ul>
+     *     <li>
+     *         Intents to ask engine to install data or check its data and
+     *         extras for a TTS engine's check data activity.
+     *     </li>
+     *     <li>
+     *         Keys for the parameters passed with speak commands, e.g.
+     *         {@link Engine#KEY_PARAM_UTTERANCE_ID}, {@link Engine#KEY_PARAM_STREAM}.
+     *     </li>
+     *     <li>
+     *         A list of feature strings that engines might support, e.g
+     *         {@link Engine#KEY_FEATURE_NETWORK_SYNTHESIS}). These values may be passed in to
+     *         {@link TextToSpeech#speak} and {@link TextToSpeech#synthesizeToFile} to modify
+     *         engine behaviour. The engine can be queried for the set of features it supports
+     *         through {@link TextToSpeech#getFeatures(java.util.Locale)}.
+     *     </li>
+     * </ul>
      */
     public class Engine {
 
@@ -435,6 +456,25 @@
          */
         public static final String KEY_PARAM_PAN = "pan";
 
+        /**
+         * Feature key for network synthesis. See {@link TextToSpeech#getFeatures(Locale)}
+         * for a description of how feature keys work. If set (and supported by the engine
+         * as per {@link TextToSpeech#getFeatures(Locale)}, the engine must
+         * use network based synthesis.
+         *
+         * @see TextToSpeech#speak(String, int, java.util.HashMap)
+         * @see TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)
+         * @see TextToSpeech#getFeatures(java.util.Locale)
+         */
+        public static final String KEY_FEATURE_NETWORK_SYNTHESIS = "networkTts";
+
+        /**
+         * Feature key for embedded synthesis. See {@link TextToSpeech#getFeatures(Locale)}
+         * for a description of how feature keys work. If set and supported by the engine
+         * as per {@link TextToSpeech#getFeatures(Locale)}, the engine must synthesize
+         * text on-device (without making network requests).
+         */
+        public static final String KEY_FEATURE_EMBEDDED_SYNTHESIS = "embeddedTts";
     }
 
     private final Context mContext;
@@ -812,6 +852,36 @@
     }
 
     /**
+     * Queries the engine for the set of features it supports for a given locale.
+     * Features can either be framework defined, e.g.
+     * {@link TextToSpeech.Engine#KEY_FEATURE_NETWORK_SYNTHESIS} or engine specific.
+     * Engine specific keys must be prefixed by the name of the engine they
+     * are intended for. These keys can be used as parameters to
+     * {@link TextToSpeech#speak(String, int, java.util.HashMap)} and
+     * {@link TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)}.
+     *
+     * Features are boolean flags, and their values in the synthesis parameters
+     * must be behave as per {@link Boolean#parseBoolean(String)}.
+     *
+     * @param locale The locale to query features for.
+     */
+    public Set<String> getFeatures(final Locale locale) {
+        return runAction(new Action<Set<String>>() {
+            @Override
+            public Set<String> run(ITextToSpeechService service) throws RemoteException {
+                String[] features = service.getFeaturesForLanguage(
+                        locale.getISO3Language(), locale.getISO3Country(), locale.getVariant());
+                if (features != null) {
+                    final Set<String> featureSet = new HashSet<String>();
+                    Collections.addAll(featureSet, features);
+                    return featureSet;
+                }
+                return null;
+            }
+        }, null, "getFeatures");
+    }
+
+    /**
      * Checks whether the TTS engine is busy speaking. Note that a speech item is
      * considered complete once it's audio data has been sent to the audio mixer, or
      * written to a file. There might be a finite lag between this point, and when
@@ -1017,6 +1087,9 @@
             copyFloatParam(bundle, params, Engine.KEY_PARAM_VOLUME);
             copyFloatParam(bundle, params, Engine.KEY_PARAM_PAN);
 
+            // Copy feature strings defined by the framework.
+            copyStringParam(bundle, params, Engine.KEY_FEATURE_NETWORK_SYNTHESIS);
+
             // Copy over all parameters that start with the name of the
             // engine that we are currently connected to. The engine is
             // free to interpret them as it chooses.
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 48739ba..245271d 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -36,6 +36,7 @@
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Locale;
+import java.util.Set;
 
 
 /**
@@ -67,7 +68,6 @@
  * any. Any pending data from the current synthesis will be discarded.
  *
  */
-// TODO: Add a link to the sample TTS engine once it's done.
 public abstract class TextToSpeechService extends Service {
 
     private static final boolean DBG = false;
@@ -196,6 +196,18 @@
     protected abstract void onSynthesizeText(SynthesisRequest request,
             SynthesisCallback callback);
 
+    /**
+     * Queries the service for a set of features supported for a given language.
+     *
+     * @param lang ISO-3 language code.
+     * @param country ISO-3 country code. May be empty or null.
+     * @param variant Language variant. May be empty or null.
+     * @return A list of features supported for the given language.
+     */
+    protected Set<String> onGetFeaturesForLanguage(String lang, String country, String variant) {
+        return null;
+    }
+
     private int getDefaultSpeechRate() {
         return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE);
     }
@@ -778,6 +790,13 @@
             return onIsLanguageAvailable(lang, country, variant);
         }
 
+        public String[] getFeaturesForLanguage(String lang, String country, String variant) {
+            Set<String> features = onGetFeaturesForLanguage(lang, country, variant);
+            String[] featuresArray = new String[features.size()];
+            features.toArray(featuresArray);
+            return featuresArray;
+        }
+
         /*
          * There is no point loading a non default language if defaults
          * are enforced.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 24423c3..f7078ec 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -218,6 +218,7 @@
     boolean mNewSurfaceNeeded;
     boolean mHasHadWindowFocus;
     boolean mLastWasImTarget;
+    InputEventMessage mPendingInputEvents = null;
 
     boolean mWindowAttributesChanged = false;
     int mWindowAttributesChangesFlag = 0;
@@ -832,10 +833,24 @@
         }
     }
 
+    private void processInputEvents(boolean outOfOrder) {
+        while (mPendingInputEvents != null) {
+            handleMessage(mPendingInputEvents.mMessage);
+            InputEventMessage tmpMessage = mPendingInputEvents;
+            mPendingInputEvents = mPendingInputEvents.mNext;
+            tmpMessage.recycle();
+            if (outOfOrder) {
+                removeMessages(PROCESS_INPUT_EVENTS);
+            }
+        }
+    }
+
     private void performTraversals() {
         // cache mView since it is used so much below...
         final View host = mView;
 
+        processInputEvents(true);
+
         if (DBG) {
             System.out.println("======================================");
             System.out.println("performTraversals");
@@ -2336,6 +2351,7 @@
     public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 1021;
     public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 1022;
     public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT = 1023;
+    public final static int PROCESS_INPUT_EVENTS = 1024;
 
     @Override
     public String getMessageName(Message message) {
@@ -2388,7 +2404,9 @@
                 return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
             case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT:
                 return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT";
-                                                                                                                                                                                                                                    
+            case PROCESS_INPUT_EVENTS:
+                return "PROCESS_INPUT_EVENTS";
+
         }
         return super.getMessageName(message);
     }
@@ -2447,6 +2465,9 @@
         case DISPATCH_GENERIC_MOTION:
             deliverGenericMotionEvent((MotionEvent) msg.obj, msg.arg1 != 0);
             break;
+        case PROCESS_INPUT_EVENTS:
+            processInputEvents(false);
+            break;
         case DISPATCH_APP_VISIBILITY:
             handleAppVisibility(msg.arg1 != 0);
             break;
@@ -3744,7 +3765,7 @@
         msg.obj = ri;
         sendMessage(msg);
     }
-    
+
     private long mInputEventReceiveTimeNanos;
     private long mInputEventDeliverTimeNanos;
     private long mInputEventDeliverPostImeTimeNanos;
@@ -3762,6 +3783,78 @@
         }
     };
 
+    /**
+     * Utility class used to queue up input events which are then handled during
+     * performTraversals(). Doing it this way allows us to ensure that we are up to date with
+     * all input events just prior to drawing, instead of placing those events on the regular
+     * handler queue, potentially behind a drawing event.
+     */
+    static class InputEventMessage {
+        Message mMessage;
+        InputEventMessage mNext;
+
+        private static final Object sPoolSync = new Object();
+        private static InputEventMessage sPool;
+        private static int sPoolSize = 0;
+
+        private static final int MAX_POOL_SIZE = 10;
+
+        private InputEventMessage(Message m) {
+            mMessage = m;
+            mNext = null;
+        }
+
+        /**
+         * Return a new Message instance from the global pool. Allows us to
+         * avoid allocating new objects in many cases.
+         */
+        public static InputEventMessage obtain(Message msg) {
+            synchronized (sPoolSync) {
+                if (sPool != null) {
+                    InputEventMessage m = sPool;
+                    sPool = m.mNext;
+                    m.mNext = null;
+                    sPoolSize--;
+                    m.mMessage = msg;
+                    return m;
+                }
+            }
+            return new InputEventMessage(msg);
+        }
+
+        /**
+         * Return the message to the pool.
+         */
+        public void recycle() {
+            mMessage.recycle();
+            synchronized (sPoolSync) {
+                if (sPoolSize < MAX_POOL_SIZE) {
+                    mNext = sPool;
+                    sPool = this;
+                    sPoolSize++;
+                }
+            }
+
+        }
+    }
+
+    /**
+     * Place the input event message at the end of the current pending list
+     */
+    private void enqueueInputEvent(Message msg, long when) {
+        InputEventMessage inputMessage = InputEventMessage.obtain(msg);
+        if (mPendingInputEvents == null) {
+            mPendingInputEvents = inputMessage;
+        } else {
+            InputEventMessage currMessage = mPendingInputEvents;
+            while (currMessage.mNext != null) {
+                currMessage = currMessage.mNext;
+            }
+            currMessage.mNext = inputMessage;
+        }
+        sendEmptyMessageAtTime(PROCESS_INPUT_EVENTS, when);
+    }
+
     public void dispatchKey(KeyEvent event) {
         dispatchKey(event, false);
     }
@@ -3786,7 +3879,7 @@
         if (LOCAL_LOGV) Log.v(
             TAG, "sending key " + event + " to " + mView);
 
-        sendMessageAtTime(msg, event.getEventTime());
+        enqueueInputEvent(msg, event.getEventTime());
     }
     
     private void dispatchMotion(MotionEvent event, boolean sendDone) {
@@ -3804,21 +3897,21 @@
         Message msg = obtainMessage(DISPATCH_POINTER);
         msg.obj = event;
         msg.arg1 = sendDone ? 1 : 0;
-        sendMessageAtTime(msg, event.getEventTime());
+        enqueueInputEvent(msg, event.getEventTime());
     }
 
     private void dispatchTrackball(MotionEvent event, boolean sendDone) {
         Message msg = obtainMessage(DISPATCH_TRACKBALL);
         msg.obj = event;
         msg.arg1 = sendDone ? 1 : 0;
-        sendMessageAtTime(msg, event.getEventTime());
+        enqueueInputEvent(msg, event.getEventTime());
     }
 
     private void dispatchGenericMotion(MotionEvent event, boolean sendDone) {
         Message msg = obtainMessage(DISPATCH_GENERIC_MOTION);
         msg.obj = event;
         msg.arg1 = sendDone ? 1 : 0;
-        sendMessageAtTime(msg, event.getEventTime());
+        enqueueInputEvent(msg, event.getEventTime());
     }
 
     public void dispatchAppVisibility(boolean visible) {
diff --git a/core/java/android/webkit/HTML5VideoInline.java b/core/java/android/webkit/HTML5VideoInline.java
index 42581c2..fe5908e 100644
--- a/core/java/android/webkit/HTML5VideoInline.java
+++ b/core/java/android/webkit/HTML5VideoInline.java
@@ -74,11 +74,13 @@
     public SurfaceTexture getSurfaceTexture(int videoLayerId) {
         // Create the surface texture.
         if (videoLayerId != mVideoLayerUsingSurfaceTexture
-            || mSurfaceTexture == null) {
-            if (mTextureNames == null) {
-                mTextureNames = new int[1];
-                GLES20.glGenTextures(1, mTextureNames, 0);
+            || mSurfaceTexture == null
+            || mTextureNames == null) {
+            if (mTextureNames != null) {
+                GLES20.glDeleteTextures(1, mTextureNames, 0);
             }
+            mTextureNames = new int[1];
+            GLES20.glGenTextures(1, mTextureNames, 0);
             mSurfaceTexture = new SurfaceTexture(mTextureNames[0]);
         }
         mVideoLayerUsingSurfaceTexture = videoLayerId;
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index 399d217..e84ae97 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -151,6 +151,14 @@
         }
     };
 
+    private Runnable mReleaseCursorRunnable = new Runnable() {
+        public void run() {
+            if (mSuggestionsAdapter != null && mSuggestionsAdapter instanceof SuggestionsAdapter) {
+                mSuggestionsAdapter.changeCursor(null);
+            }
+        }
+    };
+
     // For voice searching
     private final Intent mVoiceWebSearchIntent;
     private final Intent mVoiceAppSearchIntent;
@@ -759,6 +767,7 @@
     @Override
     protected void onDetachedFromWindow() {
         removeCallbacks(mUpdateDrawableStateRunnable);
+        post(mReleaseCursorRunnable);
         super.onDetachedFromWindow();
     }
 
@@ -1028,7 +1037,9 @@
             }
         }
         mQueryTextView.setInputType(inputType);
-
+        if (mSuggestionsAdapter != null) {
+            mSuggestionsAdapter.changeCursor(null);
+        }
         // attach the suggestions adapter, if suggestions are available
         // The existence of a suggestions authority is the proxy for "suggestions available here"
         if (mSearchable.getSuggestAuthority() != null) {
@@ -1177,7 +1188,6 @@
     public void onActionViewCollapsed() {
         clearFocus();
         updateViewsVisibility(true);
-        mQueryTextView.setText("");
         mQueryTextView.setImeOptions(mCollapsedImeOptions);
         mExpandedInActionView = false;
     }
@@ -1192,6 +1202,7 @@
         mExpandedInActionView = true;
         mCollapsedImeOptions = mQueryTextView.getImeOptions();
         mQueryTextView.setImeOptions(mCollapsedImeOptions | EditorInfo.IME_FLAG_NO_FULLSCREEN);
+        mQueryTextView.setText("");
         setIconified(false);
     }
 
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 5fbbe4d..e929e7d 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -84,9 +84,14 @@
     private void setLocale(Locale locale) {
         final TextServicesManager textServicesManager = (TextServicesManager)
                 mTextView.getContext().getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
-        mSpellCheckerSession = textServicesManager.newSpellCheckerSession(
-                null /* Bundle not currently used by the textServicesManager */,
-                locale, this, false /* means any available languages from current spell checker */);
+        if (!textServicesManager.isSpellCheckerEnabled()) {
+            mSpellCheckerSession = null;
+        } else {
+            mSpellCheckerSession = textServicesManager.newSpellCheckerSession(
+                    null /* Bundle not currently used by the textServicesManager */,
+                    locale, this,
+                    false /* means any available languages from current spell checker */);
+        }
         mCurrentLocale = locale;
 
         // Restore SpellCheckSpans in pool
diff --git a/core/java/android/widget/SuggestionsAdapter.java b/core/java/android/widget/SuggestionsAdapter.java
index 9e32c9a..c44d431 100644
--- a/core/java/android/widget/SuggestionsAdapter.java
+++ b/core/java/android/widget/SuggestionsAdapter.java
@@ -29,9 +29,7 @@
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.database.Cursor;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.StateListDrawable;
 import android.net.Uri;
 import android.os.Bundle;
 import android.text.Spannable;
@@ -39,7 +37,6 @@
 import android.text.TextUtils;
 import android.text.style.TextAppearanceSpan;
 import android.util.Log;
-import android.util.SparseArray;
 import android.util.TypedValue;
 import android.view.View;
 import android.view.ViewGroup;
@@ -113,7 +110,6 @@
 
         mOutsideDrawablesCache = outsideDrawablesCache;
         
-
         // mStartSpinnerRunnable = new Runnable() {
         // public void run() {
         // // mSearchView.setWorking(true); // TODO:
@@ -185,6 +181,10 @@
          * the results.
          */
         Cursor cursor = null;
+        if (mSearchView.getVisibility() != View.VISIBLE
+                || mSearchView.getWindowVisibility() != View.VISIBLE) {
+            return null;
+        }
         //mSearchView.getWindow().getDecorView().post(mStartSpinnerRunnable); // TODO:
         try {
             cursor = mSearchManager.getSuggestions(mSearchable, query, QUERY_LIMIT);
diff --git a/core/res/res/layout/search_view.xml b/core/res/res/layout/search_view.xml
index f699a46..ca1dc88 100644
--- a/core/res/res/layout/search_view.xml
+++ b/core/res/res/layout/search_view.xml
@@ -93,7 +93,7 @@
                 android:singleLine="true"
                 android:ellipsize="end"
                 android:background="@null"
-                android:inputType="text|textAutoComplete"
+                android:inputType="text|textAutoComplete|textNoSuggestions"
                 android:imeOptions="actionSearch"
                 android:dropDownHeight="wrap_content"
                 android:dropDownAnchor="@id/search_edit_frame"
diff --git a/docs/html/guide/topics/admin/device-admin.jd b/docs/html/guide/topics/admin/device-admin.jd
index 7bbf5e6..820c3c0 100644
--- a/docs/html/guide/topics/admin/device-admin.jd
+++ b/docs/html/guide/topics/admin/device-admin.jd
@@ -27,6 +27,12 @@
       <li>{@link android.app.admin.DevicePolicyManager}</li>
       <li>{@link android.app.admin.DeviceAdminInfo}</li>
     </ol>
+    <h2>Related samples</h2>
+    <ol>
+      <li><a
+href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.html">
+DeviceAdminSample</a></li>
+</ol>
 </div>
 </div>
 
@@ -201,6 +207,16 @@
 <td>Specifies that the storage area should be encrypted, if the device supports it. 
 Introduced in Android 3.0.</td> </tr>
 
+<tr>
+  <td>Disable camera</td>
+  
+  <td>Specifies that the camera should be disabled. Note that this doesn't have
+to be a permanent disabling. The camera can be enabled/disabled dynamically
+based on context, time, and so on. Introduced in Android 4.0.</td>
+  
+</tr>
+
+
 </table>
 
 <h4>Other features</h4>
@@ -247,6 +263,7 @@
 locks.</li>
   <li>Make the device lock immediately.</li>
   <li>Wipe the device's data (that is, restore factory settings).</li>
+  <li>Disable the camera.</li>
   
 </ul>
 
@@ -280,46 +297,38 @@
   <li>A declaration of security policies used in metadata.</li>
 </ul>
 <p>Here is an excerpt from the Device Administration sample manifest:</p>
-<pre>&lt;activity android:name=&quot;.app.DeviceAdminSample$Controller&quot;
-          android:label=&quot;&#64;string/activity_sample_device_admin&quot;&gt;
-    &lt;intent-filter&gt;
-        &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;
-        &lt;category android:name=&quot;android.intent.category.SAMPLE_CODE&quot; /&gt;
-    &lt;/intent-filter&gt;
+<pre>&lt;activity android:name=&quot;.app.DeviceAdminSample&quot;
+            android:label=&quot;&#64;string/activity_sample_device_admin&quot;&gt;
+    &lt;intent-filter&gt;
+        &lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;
+        &lt;category android:name=&quot;android.intent.category.SAMPLE_CODE&quot; /&gt;
+    &lt;/intent-filter&gt;
 &lt;/activity&gt;
-
-&lt;receiver android:name=&quot;.app.DeviceAdminSample&quot;
-          android:label=&quot;&#64;string/sample_device_admin&quot;
-          android:description=&quot;&#64;string/sample_device_admin_description&quot;
-          android:permission=&quot;android.permission.BIND_DEVICE_ADMIN&quot;&gt;
-    &lt;meta-data android:name=&quot;android.app.device_admin&quot;
-               android:resource=&quot;&#64;xml/device_admin_sample&quot; /&gt;
-    &lt;intent-filter&gt;
-        &lt;action android:name=&quot;android.app.action.DEVICE_ADMIN_ENABLED&quot; /&gt;
-    &lt;/intent-filter&gt;
+&lt;receiver android:name=&quot;.app.DeviceAdminSample$DeviceAdminSampleReceiver&quot;
+        android:label=&quot;&#64;string/sample_device_admin&quot;
+        android:description=&quot;&#64;string/sample_device_admin_description&quot;
+        android:permission=&quot;android.permission.BIND_DEVICE_ADMIN&quot;&gt;
+    &lt;meta-data android:name=&quot;android.app.device_admin&quot;
+            android:resource=&quot;&#64;xml/device_admin_sample&quot; /&gt;
+    &lt;intent-filter&gt;
+        &lt;action android:name=&quot;android.app.action.DEVICE_ADMIN_ENABLED&quot; /&gt;
+    &lt;/intent-filter&gt;
 &lt;/receiver&gt;</pre>
 
  <p>Note that:</p>
 <ul>
-  <li>The activity in the sample application is an {@link android.app.Activity}
-subclass called <code>Controller</code>. The syntax
-<code>&quot;.app.DeviceAdminSample$Controller&quot;</code>  indicates that
-<code>Controller</code> is an inner class that is nested inside the
-<code>DeviceAdminSample</code> class. Note that an Activity does not need to be
-an inner class; it just is in this example.</li>
-
 <li>The following attributes refer to string resources that for the sample application reside in
 <code>ApiDemos/res/values/strings.xml</code>. For more information about resources, see
 <a
 href="{@docRoot}guide/topics/resources/index.html">Application Resources</a>.
 <ul>
-<li><code>android:label=&quot;@string/activity_sample_device_admin&quot;</code> refers to the
+<li><code>android:label=&quot;&#64;string/activity_sample_device_admin&quot;</code> refers to the
 user-readable label for the activity.</li>
 
-<li><code>android:label=&quot;@string/sample_device_admin&quot;</code> refers to the
+<li><code>android:label=&quot;&#64;string/sample_device_admin&quot;</code> refers to the
 user-readable label for the permission.</li>
 
-<li><code>android:description=&quot;@string/sample_device_admin_description&quot;</code> refers to
+<li><code>android:description=&quot;&#64;string/sample_device_admin_description&quot;</code> refers to
 the user-readable description of the permission. A descripton is typically longer and more
 informative than
 a label.</li>
@@ -357,6 +366,9 @@
     &lt;reset-password /&gt;
     &lt;force-lock /&gt;
     &lt;wipe-data /&gt;
+    &lt;expire-password /&gt;
+    &lt;encrypted-storage /&gt;
+    &lt;disable-camera /&gt;
   &lt;/uses-policies&gt;
 &lt;/device-admin&gt;
 </pre>
@@ -401,33 +413,34 @@
 events. For example:</p>
 <pre>public class DeviceAdminSample extends DeviceAdminReceiver {
 
-...
+    void showToast(Context context, String msg) {
+        String status = context.getString(R.string.admin_receiver_status, msg);
+        Toast.makeText(context, status, Toast.LENGTH_SHORT).show();
+    }
+
     &#64;Override
-    public void onEnabled(Context context, Intent intent) {
-        showToast(context, &quot;Sample Device Admin: enabled&quot;);
-    }
+    public void onEnabled(Context context, Intent intent) {
+        showToast(context, context.getString(R.string.admin_receiver_status_enabled));
+    }
 
-    &#64;Override
-    public CharSequence onDisableRequested(Context context, Intent intent) {
-        return &quot;This is an optional message to warn the user about disabling.&quot;;
-    }
+    &#64;Override
+    public CharSequence onDisableRequested(Context context, Intent intent) {
+        return context.getString(R.string.admin_receiver_status_disable_warning);
+    }
 
-    &#64;Override
-    public void onDisabled(Context context, Intent intent) {
-        showToast(context, &quot;Sample Device Admin: disabled&quot;);
-    }
+    &#64;Override
+    public void onDisabled(Context context, Intent intent) {
+        showToast(context, context.getString(R.string.admin_receiver_status_disabled));
+    }
 
-    &#64;Override
-    public void onPasswordChanged(Context context, Intent intent) {
-        showToast(context, &quot;Sample Device Admin: pw changed&quot;);
-    }
-
-    void showToast(Context context, CharSequence msg) {
-        Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
+    &#64;Override
+    public void onPasswordChanged(Context context, Intent intent) {
+        showToast(context, context.getString(R.string.admin_receiver_status_pw_changed));
     }
 ...
 }</pre>
 
+
 <h4 id="enabling">Enabling the application</h4>
 <p>One of the major events a device admin application has to handle is the user
 enabling the application. The user must explicitly enable the application for
@@ -438,43 +451,50 @@
 action that triggers the {@link android.app.admin.DevicePolicyManager#ACTION_ADD_DEVICE_ADMIN}
 intent. In the
 sample application, this happens when the user clicks the <strong>Enable
-Admin</strong> button. </p>
-<p>When the user clicks the <strong>Enable Admin</strong> button, the display
-changes to prompt the user to enable the device admin application, as shown in figure
+Admin</strong> checkbox. </p>
+<p>When the user clicks the <strong>Enable Admin</strong> checkbox, the display
+changes to prompt the user to activate the device admin application, as shown in figure
 2.</p>
 
 <img src="{@docRoot}images/admin/device-admin-activate-prompt.png"/>
 <p class="img-caption"><strong>Figure 2.</strong> Sample Application: Activating the Application</p>
-<p>Below  is the code that gets executed when the user clicks the <strong>Enable
-Admin</strong> button shown in figure 1. </p>
 
-<pre> private OnClickListener mEnableListener = new OnClickListener() {
-    public void onClick(View v) {
-        // Launch the activity to have the user enable our admin.
-        Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
-        intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
-               mDeviceAdminSample);
-        intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,
-               &quot;Additional text explaining why this needs to be added.&quot;);
-        startActivityForResult(intent, RESULT_ENABLE);
-    }
-};
+<p>Below  is the code that gets executed when the user clicks the <strong>Enable Admin</strong> checkbox. This has the effect of triggering the 
+{@link android.preference.Preference.OnPreferenceChangeListener#onPreferenceChange(android.preference.Preference, java.lang.Object) onPreferenceChange()} 
+callback. This callback is invoked when the value of this  {@link android.preference.Preference} has been changed by the user and is about to be set and/or persisted. If the user is enabling the application, the display
+changes to prompt the user to activate the device admin application, as shown in figure
+2. Otherwise, the device admin application is disabled. </p>
 
-...
-// This code checks whether the device admin app was successfully enabled.
-&#64;Override
-protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-    switch (requestCode) {
-        case RESULT_ENABLE:
-            if (resultCode == Activity.RESULT_OK) {
-                Log.i(&quot;DeviceAdminSample&quot;, &quot;Administration enabled!&quot;);
-            } else {
-                Log.i(&quot;DeviceAdminSample&quot;, &quot;Administration enable FAILED!&quot;);
-            }
-            return;
-    }
-    super.onActivityResult(requestCode, resultCode, data);
-}</pre>
+<pre>&#64;Override
+        public boolean onPreferenceChange(Preference preference, Object newValue) {
+            if (super.onPreferenceChange(preference, newValue)) {
+                return true;
+            }
+            boolean value = (Boolean) newValue;
+            if (preference == mEnableCheckbox) {
+                if (value != mAdminActive) {
+                    if (value) {
+                        // Launch the activity to have the user enable our admin.
+                        Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
+                        intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mDeviceAdminSample);
+                        intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,
+                                mActivity.getString(R.string.add_admin_extra_app_text));
+                        startActivityForResult(intent, REQUEST_CODE_ENABLE_ADMIN);
+                        // return false - don't update checkbox until we're really active
+                        return false;
+                    } else {
+                        mDPM.removeActiveAdmin(mDeviceAdminSample);
+                        enableDeviceCapabilitiesArea(false);
+                        mAdminActive = false;
+                    }
+                }
+            } else if (preference == mDisableCameraCheckbox) {
+                mDPM.setCameraDisabled(mDeviceAdminSample, value);
+                ...
+            }
+            return true;
+        }</pre>
+
 
 <p>The line
 <code>intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
@@ -489,18 +509,17 @@
 {@link android.app.admin.DevicePolicyManager#isAdminActive(android.content.ComponentName) isAdminActive()}. Notice that the {@link android.app.admin.DevicePolicyManager}
 method {@link android.app.admin.DevicePolicyManager#isAdminActive(android.content.ComponentName) isAdminActive()} takes a {@link android.app.admin.DeviceAdminReceiver}
 component as its argument:</p>
+
 <pre>
 DevicePolicyManager mDPM;
 ...
-boolean active = mDPM.isAdminActive(mDeviceAdminSample);
-if (active) {
-    // Admin app is active, so do some admin stuff
-               ...
-} else {
-    // do something else
+private boolean isActiveAdmin() {
+    return mDPM.isAdminActive(mDeviceAdminSample);
 }
 </pre>
 
+
+
 <h3 id="admin_ops">Managing policies</h3>
 <p>{@link android.app.admin.DevicePolicyManager} is a public class for managing policies
 enforced on a device. {@link android.app.admin.DevicePolicyManager} manages policies for one
@@ -618,49 +637,6 @@
 ...
 mDPM.setPasswordExpirationTimeout(mDeviceAdminSample, pwExpiration);
 </pre>
-
-<p>From the <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/DeviceAdminSample.html"
->Device Administration API sample</a>, here is the code
-that updates the password expiration status:</p>
-
-<pre>
-DevicePolicyManager mDPM;
-ComponentName mDeviceAdminSample;
-private TextView mPasswordExpirationStatus;
-...
-void updatePasswordExpirationStatus() {
-    boolean active = mDPM.isAdminActive(mDeviceAdminSample);
-    String statusText;
-    if (active) {
-        long now = System.currentTimeMillis();
-        // Query the DevicePolicyManager twice - first for the expiration values
-        // set by the sample app, and later, for the system values (which may be different
-        // if there is another administrator active.)
-        long expirationDate = mDPM.getPasswordExpiration(mDeviceAdminSample);
-        long mSecUntilExpiration = expirationDate - now;
-        if (mSecUntilExpiration &gt;= 0) {
-            statusText = &quot;Expiration in &quot; + countdownString(mSecUntilExpiration);
-        } else {
-            statusText = &quot;Expired &quot; + countdownString(-mSecUntilExpiration) + &quot; ago&quot;;
-        }
-
-        // expirationTimeout is the cycle time between required password refresh
-        long expirationTimeout = mDPM.getPasswordExpirationTimeout(mDeviceAdminSample);
-        statusText += &quot; / timeout period &quot; + countdownString(expirationTimeout);
-
-        // Now report the aggregate (global) expiration time
-        statusText += &quot; / Aggregate &quot;;
-        expirationDate = mDPM.getPasswordExpiration(null);
-        mSecUntilExpiration = expirationDate - now;
-        if (mSecUntilExpiration &gt;= 0) {
-            statusText += &quot;expiration in &quot; + countdownString(mSecUntilExpiration);
-        } else {
-            statusText += &quot;expired &quot; + countdownString(-mSecUntilExpiration) + &quot; ago&quot;;
-        }
-    } else {
-        statusText = &quot;&lt;inactive&gt;&quot;;
-    }
-    mPasswordExpirationStatus.setText(statusText);</pre>
     
 <h5 id="history">Restrict password based on history</h5>
 
@@ -718,6 +694,19 @@
 <p>The {@link android.app.admin.DevicePolicyManager#wipeData wipeData()} method takes as its parameter a bit mask of
 additional options. Currently the value must be 0. </p>
 
+<h4>Disable camera</h4>
+<p>Beginning with Android 4.0, you can disable the camera. Note that this doesn't have to be a permanent disabling. The camera can be enabled/disabled dynamically based on context, time, and so on. </p>
+<p>You control whether the camera is disabled by using the 
+{@link android.app.admin.DevicePolicyManager#setCameraDisabled(android.content.ComponentName, boolean) setCameraDisabled()} method. For example, this snippet sets the camera to be enabled or disabled based on a checkbox setting:</p>
+
+<pre>private CheckBoxPreference mDisableCameraCheckbox;
+DevicePolicyManager mDPM;
+ComponentName mDeviceAdminSample;
+...
+mDPM.setCameraDisabled(mDeviceAdminSample, mDisableCameraCheckbox.isChecked());<br />
+</pre>
+
+
 <h4 id=storage">Storage encryption</h4>
 <p>Beginning with Android 3.0, you can use the 
 {@link android.app.admin.DevicePolicyManager#setStorageEncryption(android.content.ComponentName,boolean) setStorageEncryption()} 
diff --git a/docs/html/images/admin/device-admin-activate-prompt.png b/docs/html/images/admin/device-admin-activate-prompt.png
index 2851194..3786788 100644
--- a/docs/html/images/admin/device-admin-activate-prompt.png
+++ b/docs/html/images/admin/device-admin-activate-prompt.png
Binary files differ
diff --git a/docs/html/images/admin/device-admin-app.png b/docs/html/images/admin/device-admin-app.png
index c96defc..6b23aba 100644
--- a/docs/html/images/admin/device-admin-app.png
+++ b/docs/html/images/admin/device-admin-app.png
Binary files differ
diff --git a/docs/html/resources/dashboard/opengl.jd b/docs/html/resources/dashboard/opengl.jd
index 9089937..07a0e43 100644
--- a/docs/html/resources/dashboard/opengl.jd
+++ b/docs/html/resources/dashboard/opengl.jd
@@ -57,7 +57,7 @@
 <div class="dashboard-panel">
 
 <img alt="" width="400" height="250"
-src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=GL%201.1|GL%202.0%20%26%201.1&chd=t%3A9.2,90.8" />
+src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=GL%201.1|GL%202.0%20%26%201.1&chd=t%3A9.8,90.2" />
 
 <table>
 <tr>
@@ -66,14 +66,14 @@
 </tr>
 <tr>
 <td>1.1</th>
-<td>9.2%</td>
+<td>9.8%</td>
 </tr>
 <tr>
 <td>2.0</th>
-<td>90.8%</td>
+<td>90.2%</td>
 </tr>
 </table>
 
-<p><em>Data collected during a 7-day period ending on October 3, 2011</em></p>
+<p><em>Data collected during a 7-day period ending on November 3, 2011</em></p>
 </div>
 
diff --git a/docs/html/resources/dashboard/platform-versions.jd b/docs/html/resources/dashboard/platform-versions.jd
index 135c6f2..8041096 100644
--- a/docs/html/resources/dashboard/platform-versions.jd
+++ b/docs/html/resources/dashboard/platform-versions.jd
@@ -52,7 +52,7 @@
 <div class="dashboard-panel">
 
 <img alt="" height="250" width="470"
-src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:1.1,1.4,11.7,45.3,0.5,38.2,0.2,0.9,0.7&chl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3|Android%202.3.3|Android%203.0|Android%203.1|Android%203.2&chco=c4df9b,6fad0c" />
+src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:0.9,1.4,10.7,40.7,0.5,43.9,0.1,0.9,0.9&chl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3|Android%202.3.3|Android%203.0|Android%203.1|Android%203.2&chco=c4df9b,6fad0c" />
 
 <table>
 <tr>
@@ -61,21 +61,21 @@
   <th>API Level</th>
   <th>Distribution</th>
 </tr>
-<tr><td><a href="{@docRoot}sdk/android-1.5.html">Android 1.5</a></td><td>Cupcake</td>  <td>3</td><td>1.1%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-1.5.html">Android 1.5</a></td><td>Cupcake</td>  <td>3</td><td>0.9%</td></tr>
 <tr><td><a href="{@docRoot}sdk/android-1.6.html">Android 1.6</a></td><td>Donut</td>    <td>4</td><td>1.4%</td></tr>
-<tr><td><a href="{@docRoot}sdk/android-2.1.html">Android 2.1</a></td><td>Eclair</td>   <td>7</td><td>11.7%</td></tr>
-<tr><td><a href="{@docRoot}sdk/android-2.2.html">Android 2.2</a></td><td>Froyo</td>    <td>8</td><td>45.3%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-2.1.html">Android 2.1</a></td><td>Eclair</td>   <td>7</td><td>10.7%</td></tr>
+<tr><td><a href="{@docRoot}sdk/android-2.2.html">Android 2.2</a></td><td>Froyo</td>    <td>8</td><td>40.7%</td></tr>
 <tr><td><a href="{@docRoot}sdk/android-2.3.html">Android 2.3 -<br/>
                              Android 2.3.2</a></td><td rowspan="2">Gingerbread</td>    <td>9</td><td>0.5%</td></tr>
 <tr><td><a href="{@docRoot}sdk/android-2.3.3.html">Android 2.3.3 -<br/>
-      Android 2.3.7</a></td><!-- Gingerbread -->                                       <td>10</td><td>38.2%</td></tr>
+      Android 2.3.7</a></td><!-- Gingerbread -->                                       <td>10</td><td>43.9%</td></tr>
 <tr><td><a href="{@docRoot}sdk/android-3.0.html">Android 3.0</a></td>
-                                                   <td rowspan="3">Honeycomb</td>      <td>11</td><td>0.2%</td></tr>
+                                                   <td rowspan="3">Honeycomb</td>      <td>11</td><td>0.1%</td></tr>
 <tr><td><a href="{@docRoot}sdk/android-3.1.html">Android 3.1</a></td><!-- Honeycomb --><td>12</td><td>0.9%</td></tr>
-<tr><td><a href="{@docRoot}sdk/android-3.2.html">Android 3.2</a></td><!-- Honeycomb --><td>13</td><td>0.7%</td></tr> 
+<tr><td><a href="{@docRoot}sdk/android-3.2.html">Android 3.2</a></td><!-- Honeycomb --><td>13</td><td>0.9%</td></tr> 
 </table>
 
-<p><em>Data collected during a 14-day period ending on October 3, 2011</em></p>
+<p><em>Data collected during a 14-day period ending on November 3, 2011</em></p>
 <!--
 <p style="font-size:.9em">* <em>Other: 0.1% of devices running obsolete versions</em></p>
 -->
@@ -104,9 +104,9 @@
 <div class="dashboard-panel">
 
 <img alt="" height="250" width="660" style="padding:5px;background:#fff"
-src="http://chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A%7C04/01%7C04/15%7C05/01%7C05/15%7C06/01%7C06/15%7C07/01%7C07/15%7C08/01%7C08/15%7C09/01%7C09/15%7C10/01%7C1%3A%7C2011%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C2011%7C2%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C3%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:99.7,99.6,99.6,99.5,99.4,99.3,99.2,99.0,98.8,98.7,98.5,98.5,98.2|97.0,97.1,97.3,97.5,97.5,97.5,97.7,97.6,97.5,97.5,97.5,97.5,97.1|93.5,93.9,94.3,94.8,95.0,95.2,95.5,95.5,95.5,95.6,95.7,95.8,95.6|66.4,68.0,69.8,71.5,73.9,75.4,77.6,79.0,80.2,81.1,82.4,83.3,83.8|2.5,3.1,4.0,6.1,9.5,13.6,17.8,20.6,24.3,27.5,31.2,34.7,38.3|1.7,2.2,3.0,5.1,8.4,12.6,16.8,20.0,23.7,26.9,30.6,34.1,37.8&chm=b,c3df9b,0,1,0|b,b4db77,1,2,0|tAndroid 2.1,547a19,2,0,15,,t::-5|b,a5db51,2,3,0|tAndroid 2.2,3f5e0e,3,0,15,,t::-5|b,96dd28,3,4,0|b,83c916,4,5,0|tAndroid 2.3.3,131d02,5,5,15,,t::-5|B,6fad0c,5,6,0&chg=7,25&chdl=Android 1.5|Android 1.6|Android 2.1|Android 2.2|Android 2.3|Android 2.3.3&chco=add274,9dd14f,8ece2a,7ab61c,659b11,507d08" />
+src="http://chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A%7C05/01%7C05/15%7C06/01%7C06/15%7C07/01%7C07/15%7C08/01%7C08/15%7C09/01%7C09/15%7C10/01%7C10/15%7C11/01%7C1%3A%7C2011%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C%7C2011%7C2%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C3%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:99.6,99.5,99.4,99.3,99.2,99.0,98.8,98.7,98.5,98.5,98.2,98.1,98.0|97.3,97.5,97.5,97.5,97.7,97.6,97.5,97.5,97.5,97.5,97.1,97.1,97.1|94.3,94.8,95.0,95.2,95.5,95.5,95.5,95.6,95.7,95.8,95.6,95.9,95.7|69.8,71.5,73.9,75.4,77.6,79.0,80.2,81.1,82.4,83.3,83.8,84.9,85.0|4.0,6.1,9.5,13.6,17.8,20.6,24.3,27.5,31.2,34.7,38.3,41.3,44.0|3.0,5.1,8.4,12.6,16.8,20.0,23.7,26.9,30.6,34.1,37.8,40.8,43.5&chm=b,c3df9b,0,1,0|b,b4db77,1,2,0|tAndroid 2.1,547a19,2,0,15,,t::-5|b,a5db51,2,3,0|tAndroid 2.2,3f5e0e,3,0,15,,t::-5|b,96dd28,3,4,0|b,83c916,4,5,0|tAndroid 2.3.3,131d02,5,3,15,,t::-5|B,6fad0c,5,6,0&chg=7,25&chdl=Android 1.5|Android 1.6|Android 2.1|Android 2.2|Android 2.3|Android 2.3.3&chco=add274,9dd14f,8ece2a,7ab61c,659b11,507d08" />
 
-<p><em>Last historical dataset collected during a 14-day period ending on October 3, 2011</em></p>
+<p><em>Last historical dataset collected during a 14-day period ending on November 3, 2011</em></p>
 
 
 </div><!-- end dashboard-panel -->
diff --git a/docs/html/resources/dashboard/screens.jd b/docs/html/resources/dashboard/screens.jd
index 67b47d0..ec3034d 100644
--- a/docs/html/resources/dashboard/screens.jd
+++ b/docs/html/resources/dashboard/screens.jd
@@ -60,7 +60,7 @@
 <div class="dashboard-panel">
 
 <img alt="" width="400" height="250"
-src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=Xlarge%20/%20mdpi|Large%20/%20ldpi|Large%20/%20mdpi|Normal%20/%20hdpi|Normal%20/%20ldpi|Normal%20/%20mdpi|Small%20/%20hdpi|Small%20/%20ldpi&chd=t%3A2.6,0.1,3.0,71.9,0.9,17.6,2.7,1.2" />
+src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=Xlarge%20/%20mdpi|Large%20/%20ldpi|Large%20/%20mdpi|Normal%20/%20hdpi|Normal%20/%20ldpi|Normal%20/%20mdpi|Small%20/%20hdpi|Small%20/%20ldpi&chd=t%3A2.9,0.1,3.1,70.8,1.0,17.7,3.0,1.3" />
 
 <table>
 <tr>
@@ -71,31 +71,31 @@
 <th scope="col">xhdpi</th>
 </tr>
 <tr><th scope="row">small</th> 
-<td>1.2%</td>     <!-- small/ldpi -->
+<td>1.3%</td>     <!-- small/ldpi -->
 <td></td>     <!-- small/mdpi -->
-<td>2.7%</td> <!-- small/hdpi -->
+<td>3.0%</td> <!-- small/hdpi -->
 <td></td>     <!-- small/xhdpi -->
 </tr> 
 <tr><th scope="row">normal</th> 
-<td>0.9%</td>  <!-- normal/ldpi -->
-<td>17.6%</td> <!-- normal/mdpi -->
-<td>71.9%</td> <!-- normal/hdpi -->
+<td>1.0%</td>  <!-- normal/ldpi -->
+<td>17.7%</td> <!-- normal/mdpi -->
+<td>70.8%</td> <!-- normal/hdpi -->
 <td></td>      <!-- normal/xhdpi -->
 </tr> 
 <tr><th scope="row">large</th> 
 <td>0.1%</td>     <!-- large/ldpi -->
-<td>3.0%</td> <!-- large/mdpi -->
+<td>3.1%</td> <!-- large/mdpi -->
 <td></td>     <!-- large/hdpi -->
 <td></td>     <!-- large/xhdpi -->
 </tr> 
 <tr><th scope="row">xlarge</th> 
 <td></td>     <!-- xlarge/ldpi -->
-<td>2.6%</td> <!-- xlarge/mdpi -->
+<td>2.9%</td> <!-- xlarge/mdpi -->
 <td></td>     <!-- xlarge/hdpi -->
 <td></td>     <!-- xlarge/xhdpi -->
 </tr> 
 </table>
 
-<p><em>Data collected during a 7-day period ending on October 3, 2011</em></p>
+<p><em>Data collected during a 7-day period ending on November 3, 2011</em></p>
 </div>
 
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index bc7e906..18b8bc7 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -202,18 +202,30 @@
 
     @Override
     public void setAlpha(int alpha) {
+        if (mPaint == null && alpha == 0xFF) {
+            // Fast common case -- leave at normal alpha.
+            return;
+        }
         getPaint().setAlpha(alpha);
         invalidateSelf();
     }
     
     @Override
     public void setColorFilter(ColorFilter cf) {
+        if (mPaint == null && cf == null) {
+            // Fast common case -- leave at no color filter.
+            return;
+        }
         getPaint().setColorFilter(cf);
         invalidateSelf();
     }
 
     @Override
     public void setDither(boolean dither) {
+        if (mPaint == null && dither == DEFAULT_DITHER) {
+            // Fast common case -- leave at default dither.
+            return;
+        }
         getPaint().setDither(dither);
         invalidateSelf();
     }
diff --git a/include/binder/Parcel.h b/include/binder/Parcel.h
index 3fa2acb..33b2f00 100644
--- a/include/binder/Parcel.h
+++ b/include/binder/Parcel.h
@@ -110,7 +110,8 @@
     
     // Place a file descriptor into the parcel.  The given fd must remain
     // valid for the lifetime of the parcel.
-    status_t            writeFileDescriptor(int fd);
+    // The Parcel does not take ownership of the given fd unless you ask it to.
+    status_t            writeFileDescriptor(int fd, bool takeOwnership = false);
     
     // Place a file descriptor into the parcel.  A dup of the fd is made, which
     // will be closed once the parcel is destroyed.
diff --git a/include/utils/BlobCache.h b/include/utils/BlobCache.h
index dc45ff0..4f342a2 100644
--- a/include/utils/BlobCache.h
+++ b/include/utils/BlobCache.h
@@ -19,19 +19,21 @@
 
 #include <stddef.h>
 
+#include <utils/Flattenable.h>
 #include <utils/RefBase.h>
 #include <utils/SortedVector.h>
 #include <utils/threads.h>
 
 namespace android {
 
-// A BlobCache is an in-memory cache for binary key/value pairs. All the public
-// methods are thread-safe.
+// A BlobCache is an in-memory cache for binary key/value pairs.  A BlobCache
+// does NOT provide any thread-safety guarantees.
 //
-// The cache contents can be serialized to a file and reloaded in a subsequent
-// execution of the program. This serialization is non-portable and should only
-// be loaded by the device that generated it.
-class BlobCache : public RefBase {
+// The cache contents can be serialized to an in-memory buffer or mmap'd file
+// and then reloaded in a subsequent execution of the program.  This
+// serialization is non-portable and the data should only be used by the device
+// that generated it.
+class BlobCache : public RefBase, public Flattenable {
 public:
 
     // Create an empty blob cache. The blob cache will cache key/value pairs
@@ -58,14 +60,13 @@
     void set(const void* key, size_t keySize, const void* value,
             size_t valueSize);
 
-    // The get function retrieves from the cache the binary value associated
-    // with a given binary key.  If the key is present in the cache then the
-    // length of the binary value associated with that key is returned.  If the
-    // value argument is non-NULL and the size of the cached value is less than
-    // valueSize bytes then the cached value is copied into the buffer pointed
-    // to by the value argument.  If the key is not present in the cache then 0
-    // is returned and the buffer pointed to by the value argument is not
-    // modified.
+    // get retrieves from the cache the binary value associated with a given
+    // binary key.  If the key is present in the cache then the length of the
+    // binary value associated with that key is returned.  If the value argument
+    // is non-NULL and the size of the cached value is less than valueSize bytes
+    // then the cached value is copied into the buffer pointed to by the value
+    // argument.  If the key is not present in the cache then 0 is returned and
+    // the buffer pointed to by the value argument is not modified.
     //
     // Note that when calling get multiple times with the same key, the later
     // calls may fail, returning 0, even if earlier calls succeeded.  The return
@@ -77,6 +78,37 @@
     //   0 <= valueSize
     size_t get(const void* key, size_t keySize, void* value, size_t valueSize);
 
+    // getFlattenedSize returns the number of bytes needed to store the entire
+    // serialized cache.
+    virtual size_t getFlattenedSize() const;
+
+    // getFdCount returns the number of file descriptors that will result from
+    // flattening the cache.  This will always return 0 so as to allow the
+    // flattened cache to be saved to disk and then later restored.
+    virtual size_t getFdCount() const;
+
+    // flatten serializes the current contents of the cache into the memory
+    // pointed to by 'buffer'.  The serialized cache contents can later be
+    // loaded into a BlobCache object using the unflatten method.  The contents
+    // of the BlobCache object will not be modified.
+    //
+    // Preconditions:
+    //   size >= this.getFlattenedSize()
+    //   count == 0
+    virtual status_t flatten(void* buffer, size_t size, int fds[],
+            size_t count) const;
+
+    // unflatten replaces the contents of the cache with the serialized cache
+    // contents in the memory pointed to by 'buffer'.  The previous contents of
+    // the BlobCache will be evicted from the cache.  If an error occurs while
+    // unflattening the serialized cache contents then the BlobCache will be
+    // left in an empty state.
+    //
+    // Preconditions:
+    //   count == 0
+    virtual status_t unflatten(void const* buffer, size_t size, int fds[],
+            size_t count);
+
 private:
     // Copying is disallowed.
     BlobCache(const BlobCache&);
@@ -144,6 +176,46 @@
         sp<Blob> mValue;
     };
 
+    // A Header is the header for the entire BlobCache serialization format. No
+    // need to make this portable, so we simply write the struct out.
+    struct Header {
+        // mMagicNumber is the magic number that identifies the data as
+        // serialized BlobCache contents.  It must always contain 'Blb$'.
+        uint32_t mMagicNumber;
+
+        // mBlobCacheVersion is the serialization format version.
+        uint32_t mBlobCacheVersion;
+
+        // mDeviceVersion is the device-specific version of the cache.  This can
+        // be used to invalidate the cache.
+        uint32_t mDeviceVersion;
+
+        // mNumEntries is number of cache entries following the header in the
+        // data.
+        size_t mNumEntries;
+    };
+
+    // An EntryHeader is the header for a serialized cache entry.  No need to
+    // make this portable, so we simply write the struct out.  Each EntryHeader
+    // is followed imediately by the key data and then the value data.
+    //
+    // The beginning of each serialized EntryHeader is 4-byte aligned, so the
+    // number of bytes that a serialized cache entry will occupy is:
+    //
+    //   ((sizeof(EntryHeader) + keySize + valueSize) + 3) & ~3
+    //
+    struct EntryHeader {
+        // mKeySize is the size of the entry key in bytes.
+        size_t mKeySize;
+
+        // mValueSize is the size of the entry value in bytes.
+        size_t mValueSize;
+
+        // mData contains both the key and value data for the cache entry.  The
+        // key comes first followed immediately by the value.
+        uint8_t mData[];
+    };
+
     // mMaxKeySize is the maximum key size that will be cached. Calls to
     // BlobCache::set with a keySize parameter larger than mMaxKeySize will
     // simply not add the key/value pair to the cache.
@@ -166,17 +238,12 @@
     size_t mTotalSize;
 
     // mRandState is the pseudo-random number generator state. It is passed to
-    // nrand48 to generate random numbers when needed. It must be protected by
-    // mMutex.
+    // nrand48 to generate random numbers when needed.
     unsigned short mRandState[3];
 
     // mCacheEntries stores all the cache entries that are resident in memory.
     // Cache entries are added to it by the 'set' method.
     SortedVector<CacheEntry> mCacheEntries;
-
-    // mMutex is used to synchronize access to all member variables.  It must be
-    // locked any time the member variables are written or read.
-    Mutex mMutex;
 };
 
 }
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index c7180ce..6b4c1a6 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -710,24 +710,19 @@
     return err;
 }
 
-status_t Parcel::writeFileDescriptor(int fd)
+status_t Parcel::writeFileDescriptor(int fd, bool takeOwnership)
 {
     flat_binder_object obj;
     obj.type = BINDER_TYPE_FD;
     obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
     obj.handle = fd;
-    obj.cookie = (void*)0;
+    obj.cookie = (void*) (takeOwnership ? 1 : 0);
     return writeObject(obj, true);
 }
 
 status_t Parcel::writeDupFileDescriptor(int fd)
 {
-    flat_binder_object obj;
-    obj.type = BINDER_TYPE_FD;
-    obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
-    obj.handle = dup(fd);
-    obj.cookie = (void*)1;
-    return writeObject(obj, true);
+    return writeFileDescriptor(dup(fd), true /*takeOwnership*/);
 }
 
 status_t Parcel::writeBlob(size_t len, WritableBlob* outBlob)
@@ -764,7 +759,7 @@
             } else {
                 status = writeInt32(1);
                 if (!status) {
-                    status = writeFileDescriptor(fd);
+                    status = writeFileDescriptor(fd, true /*takeOwnership*/);
                     if (!status) {
                         outBlob->init(true /*mapped*/, ptr, len);
                         return NO_ERROR;
diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp
index c72a45b..6f84206 100644
--- a/libs/gui/SurfaceTexture.cpp
+++ b/libs/gui/SurfaceTexture.cpp
@@ -116,7 +116,7 @@
     // Choose a name using the PID and a process-unique ID.
     mName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());
 
-    ST_LOGV("SurfaceTexture::SurfaceTexture");
+    ST_LOGV("SurfaceTexture");
     sp<ISurfaceComposer> composer(ComposerService::getComposerService());
     mGraphicBufferAlloc = composer->createGraphicBufferAlloc();
     mNextCrop.makeInvalid();
@@ -125,7 +125,7 @@
 }
 
 SurfaceTexture::~SurfaceTexture() {
-    ST_LOGV("SurfaceTexture::~SurfaceTexture");
+    ST_LOGV("~SurfaceTexture");
     freeAllBuffersLocked();
 }
 
@@ -169,7 +169,7 @@
 }
 
 status_t SurfaceTexture::setBufferCount(int bufferCount) {
-    ST_LOGV("SurfaceTexture::setBufferCount");
+    ST_LOGV("setBufferCount: count=%d", bufferCount);
     Mutex::Autolock lock(mMutex);
 
     if (mAbandoned) {
@@ -217,6 +217,7 @@
 
 status_t SurfaceTexture::setDefaultBufferSize(uint32_t w, uint32_t h)
 {
+    ST_LOGV("setDefaultBufferSize: w=%d, h=%d", w, h);
     if (!w || !h) {
         ST_LOGE("setDefaultBufferSize: dimensions cannot be 0 (w=%d, h=%d)",
                 w, h);
@@ -230,7 +231,7 @@
 }
 
 status_t SurfaceTexture::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
-    ST_LOGV("SurfaceTexture::requestBuffer");
+    ST_LOGV("requestBuffer: slot=%d", slot);
     Mutex::Autolock lock(mMutex);
     if (mAbandoned) {
         ST_LOGE("requestBuffer: SurfaceTexture has been abandoned!");
@@ -248,7 +249,7 @@
 
 status_t SurfaceTexture::dequeueBuffer(int *outBuf, uint32_t w, uint32_t h,
         uint32_t format, uint32_t usage) {
-    ST_LOGV("SurfaceTexture::dequeueBuffer");
+    ST_LOGV("dequeueBuffer: w=%d h=%d fmt=%#x usage=%#x", w, h, format, usage);
 
     if ((w && !h) || (!w && h)) {
         ST_LOGE("dequeueBuffer: invalid size: w=%u, h=%u", w, h);
@@ -342,6 +343,8 @@
         // clients are not allowed to dequeue more than one buffer
         // if they didn't set a buffer count.
         if (!mClientBufferCount && dequeuedCount) {
+            ST_LOGE("dequeueBuffer: can't dequeue multiple buffers without "
+                    "setting the buffer count");
             return -EINVAL;
         }
 
@@ -375,6 +378,8 @@
     }
 
     if (found == INVALID_BUFFER_SLOT) {
+        // This should not happen.
+        ST_LOGE("dequeueBuffer: no available buffer slots");
         return -EBUSY;
     }
 
@@ -427,10 +432,13 @@
         }
         returnFlags |= ISurfaceTexture::BUFFER_NEEDS_REALLOCATION;
     }
+    ST_LOGV("dequeueBuffer: returning slot=%d buf=%p flags=%#x", buf,
+            mSlots[buf].mGraphicBuffer->handle, returnFlags);
     return returnFlags;
 }
 
 status_t SurfaceTexture::setSynchronousMode(bool enabled) {
+    ST_LOGV("setSynchronousMode: enabled=%d", enabled);
     Mutex::Autolock lock(mMutex);
 
     if (mAbandoned) {
@@ -462,7 +470,7 @@
 
 status_t SurfaceTexture::queueBuffer(int buf, int64_t timestamp,
         uint32_t* outWidth, uint32_t* outHeight, uint32_t* outTransform) {
-    ST_LOGV("SurfaceTexture::queueBuffer");
+    ST_LOGV("queueBuffer: slot=%d time=%lld", buf, timestamp);
 
     sp<FrameAvailableListener> listener;
 
@@ -534,7 +542,7 @@
 }
 
 void SurfaceTexture::cancelBuffer(int buf) {
-    ST_LOGV("SurfaceTexture::cancelBuffer");
+    ST_LOGV("cancelBuffer: slot=%d", buf);
     Mutex::Autolock lock(mMutex);
 
     if (mAbandoned) {
@@ -556,7 +564,9 @@
 }
 
 status_t SurfaceTexture::setCrop(const Rect& crop) {
-    ST_LOGV("SurfaceTexture::setCrop");
+    ST_LOGV("setCrop: crop=[%d,%d,%d,%d]", crop.left, crop.top, crop.right,
+            crop.bottom);
+
     Mutex::Autolock lock(mMutex);
     if (mAbandoned) {
         ST_LOGE("setCrop: SurfaceTexture has been abandoned!");
@@ -567,7 +577,7 @@
 }
 
 status_t SurfaceTexture::setTransform(uint32_t transform) {
-    ST_LOGV("SurfaceTexture::setTransform");
+    ST_LOGV("setTransform: xform=%#x", transform);
     Mutex::Autolock lock(mMutex);
     if (mAbandoned) {
         ST_LOGE("setTransform: SurfaceTexture has been abandoned!");
@@ -579,7 +589,7 @@
 
 status_t SurfaceTexture::connect(int api,
         uint32_t* outWidth, uint32_t* outHeight, uint32_t* outTransform) {
-    ST_LOGV("SurfaceTexture::connect(this=%p, %d)", this, api);
+    ST_LOGV("connect: api=%d", api);
     Mutex::Autolock lock(mMutex);
 
     if (mAbandoned) {
@@ -612,7 +622,7 @@
 }
 
 status_t SurfaceTexture::disconnect(int api) {
-    ST_LOGV("SurfaceTexture::disconnect(this=%p, %d)", this, api);
+    ST_LOGV("disconnect: api=%d", api);
     Mutex::Autolock lock(mMutex);
 
     if (mAbandoned) {
@@ -640,6 +650,7 @@
             }
             break;
         default:
+            ST_LOGE("disconnect: unknown API %d", api);
             err = -EINVAL;
             break;
     }
@@ -647,13 +658,14 @@
 }
 
 status_t SurfaceTexture::setScalingMode(int mode) {
-    ST_LOGV("SurfaceTexture::setScalingMode(%d)", mode);
+    ST_LOGV("setScalingMode: mode=%d", mode);
 
     switch (mode) {
         case NATIVE_WINDOW_SCALING_MODE_FREEZE:
         case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW:
             break;
         default:
+            ST_LOGE("unknown scaling mode: %d", mode);
             return BAD_VALUE;
     }
 
@@ -663,7 +675,7 @@
 }
 
 status_t SurfaceTexture::updateTexImage() {
-    ST_LOGV("SurfaceTexture::updateTexImage");
+    ST_LOGV("updateTexImage");
     Mutex::Autolock lock(mMutex);
 
     if (mAbandoned) {
@@ -713,6 +725,10 @@
             return -EINVAL;
         }
 
+        ST_LOGV("updateTexImage: (slot=%d buf=%p) -> (slot=%d buf=%p)", mCurrentTexture,
+                mCurrentTextureBuf != NULL ? mCurrentTextureBuf->handle : 0, buf,
+                mSlots[buf].mGraphicBuffer->handle);
+
         if (mCurrentTexture != INVALID_BUFFER_SLOT) {
             // The current buffer becomes FREE if it was still in the queued
             // state. If it has already been given to the client
@@ -771,7 +787,7 @@
 }
 
 void SurfaceTexture::computeCurrentTransformMatrix() {
-    ST_LOGV("SurfaceTexture::computeCurrentTransformMatrix");
+    ST_LOGV("computeCurrentTransformMatrix");
 
     float xform[16];
     for (int i = 0; i < 16; i++) {
@@ -862,14 +878,14 @@
 }
 
 nsecs_t SurfaceTexture::getTimestamp() {
-    ST_LOGV("SurfaceTexture::getTimestamp");
+    ST_LOGV("getTimestamp");
     Mutex::Autolock lock(mMutex);
     return mCurrentTimestamp;
 }
 
 void SurfaceTexture::setFrameAvailableListener(
         const sp<FrameAvailableListener>& listener) {
-    ST_LOGV("SurfaceTexture::setFrameAvailableListener");
+    ST_LOGV("setFrameAvailableListener");
     Mutex::Autolock lock(mMutex);
     mFrameAvailableListener = listener;
 }
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index 24ec4e8..75b07de 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -170,8 +170,11 @@
             patchCache.clear();
             dropShadowCache.clear();
             gradientCache.clear();
+            fontRenderer.clear();
             // fall through
         case kFlushMode_Moderate:
+            fontRenderer.flush();
+            textureCache.flush();
             pathCache.clear();
             roundRectShapeCache.clear();
             circleShapeCache.clear();
diff --git a/libs/hwui/GammaFontRenderer.cpp b/libs/hwui/GammaFontRenderer.cpp
index e8362dc..eb863e9 100644
--- a/libs/hwui/GammaFontRenderer.cpp
+++ b/libs/hwui/GammaFontRenderer.cpp
@@ -67,20 +67,63 @@
     const float whiteGamma = 1.0f / gamma;
 
     for (uint32_t i = 0; i <= 255; i++) {
-        mDefault[i] = i;
+        mGammaTable[i] = i;
 
         const float v = i / 255.0f;
         const float black = pow(v, blackGamma);
         const float white = pow(v, whiteGamma);
 
-        mBlackGamma[i] = uint8_t((float)::floor(black * 255.0f + 0.5f));
-        mWhiteGamma[i] = uint8_t((float)::floor(white * 255.0f + 0.5f));
+        mGammaTable[256 + i] = uint8_t((float)::floor(black * 255.0f + 0.5f));
+        mGammaTable[512 + i] = uint8_t((float)::floor(white * 255.0f + 0.5f));
     }
 
-    // Configure the font renderers
-    mDefaultRenderer.setGammaTable(&mDefault[0]);
-    mBlackGammaRenderer.setGammaTable(&mBlackGamma[0]);
-    mWhiteGammaRenderer.setGammaTable(&mWhiteGamma[0]);
+    memset(mRenderers, 0, sizeof(FontRenderer*) * kGammaCount);
+    memset(mRenderersUsageCount, 0, sizeof(uint32_t) * kGammaCount);
+}
+
+GammaFontRenderer::~GammaFontRenderer() {
+    for (int i = 0; i < kGammaCount; i++) {
+        delete mRenderers[i];
+    }
+}
+
+void GammaFontRenderer::clear() {
+    for (int i = 0; i < kGammaCount; i++) {
+        delete mRenderers[i];
+        mRenderers[i] = NULL;
+    }
+}
+
+void GammaFontRenderer::flush() {
+    int count = 0;
+    int min = -1;
+    uint32_t minCount = UINT_MAX;
+
+    for (int i = 0; i < kGammaCount; i++) {
+        if (mRenderers[i]) {
+            count++;
+            if (mRenderersUsageCount[i] < minCount) {
+                minCount = mRenderersUsageCount[i];
+                min = i;
+            }
+        }
+    }
+
+    if (count <= 1 || min < 0) return;
+
+    delete mRenderers[min];
+    mRenderers[min] = NULL;
+}
+
+FontRenderer* GammaFontRenderer::getRenderer(Gamma gamma) {
+    FontRenderer* renderer = mRenderers[gamma];
+    if (!renderer) {
+        renderer = new FontRenderer();
+        mRenderers[gamma] = renderer;
+        renderer->setGammaTable(&mGammaTable[gamma * 256]);
+    }
+    mRenderersUsageCount[gamma]++;
+    return renderer;
 }
 
 FontRenderer& GammaFontRenderer::getFontRenderer(const SkPaint* paint) {
@@ -92,12 +135,12 @@
         const int luminance = (r * 2 + g * 5 + b) >> 3;
 
         if (luminance <= mBlackThreshold) {
-            return mBlackGammaRenderer;
+            return *getRenderer(kGammaBlack);
         } else if (luminance >= mWhiteThreshold) {
-            return mWhiteGammaRenderer;
+            return *getRenderer(kGammaWhite);
         }
     }
-    return mDefaultRenderer;
+    return *getRenderer(kGammaDefault);
 }
 
 }; // namespace uirenderer
diff --git a/libs/hwui/GammaFontRenderer.h b/libs/hwui/GammaFontRenderer.h
index 96d960c..54c208e 100644
--- a/libs/hwui/GammaFontRenderer.h
+++ b/libs/hwui/GammaFontRenderer.h
@@ -26,36 +26,43 @@
 
 struct GammaFontRenderer {
     GammaFontRenderer();
+    ~GammaFontRenderer();
+
+    enum Gamma {
+        kGammaDefault = 0,
+        kGammaBlack = 1,
+        kGammaWhite = 2,
+        kGammaCount = 3
+    };
+
+    void clear();
+    void flush();
 
     FontRenderer& getFontRenderer(const SkPaint* paint);
 
     uint32_t getFontRendererCount() const {
-        return 3;
+        return kGammaCount;
     }
 
     uint32_t getFontRendererSize(uint32_t fontRenderer) const {
-        switch (fontRenderer) {
-            case 0:
-                return mDefaultRenderer.getCacheHeight() * mDefaultRenderer.getCacheWidth();
-            case 1:
-                return mBlackGammaRenderer.getCacheHeight() * mBlackGammaRenderer.getCacheWidth();
-            case 2:
-                return mWhiteGammaRenderer.getCacheHeight() * mWhiteGammaRenderer.getCacheWidth();
-        }
-        return 0;
+        if (fontRenderer >= kGammaCount) return 0;
+
+        FontRenderer* renderer = mRenderers[fontRenderer];
+        if (!renderer) return 0;
+
+        return renderer->getCacheHeight() * renderer->getCacheWidth();
     }
 
 private:
-    FontRenderer mDefaultRenderer;
-    FontRenderer mBlackGammaRenderer;
-    FontRenderer mWhiteGammaRenderer;
+    FontRenderer* getRenderer(Gamma gamma);
+
+    uint32_t mRenderersUsageCount[kGammaCount];
+    FontRenderer* mRenderers[kGammaCount];
 
     int mBlackThreshold;
     int mWhiteThreshold;
 
-    uint8_t mDefault[256];
-    uint8_t mBlackGamma[256];
-    uint8_t mWhiteGamma[256];
+    uint8_t mGammaTable[256 * kGammaCount];
 };
 
 }; // namespace uirenderer
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 5bd0d4f..8c01e3a 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -61,6 +61,9 @@
 #define PROPERTY_DROP_SHADOW_CACHE_SIZE "ro.hwui.drop_shadow_cache_size"
 #define PROPERTY_FBO_CACHE_SIZE "ro.hwui.fbo_cache_size"
 
+// These properties are defined in percentage (range 0..1)
+#define PROPERTY_TEXTURE_CACHE_FLUSH_RATE "ro.hwui.texture_cache_flush_rate"
+
 // These properties are defined in pixels
 #define PROPERTY_TEXT_CACHE_WIDTH "ro.hwui.text_cache_width"
 #define PROPERTY_TEXT_CACHE_HEIGHT "ro.hwui.text_cache_height"
@@ -82,6 +85,8 @@
 #define DEFAULT_DROP_SHADOW_CACHE_SIZE 2.0f
 #define DEFAULT_FBO_CACHE_SIZE 16
 
+#define DEFAULT_TEXTURE_CACHE_FLUSH_RATE 0.6f
+
 #define DEFAULT_TEXT_GAMMA 1.4f
 #define DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD 64
 #define DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD 192
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index fbdbf92..018ce3e 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -34,7 +34,8 @@
 
 TextureCache::TextureCache():
         mCache(GenerationCache<SkBitmap*, Texture*>::kUnlimitedCapacity),
-        mSize(0), mMaxSize(MB(DEFAULT_TEXTURE_CACHE_SIZE)) {
+        mSize(0), mMaxSize(MB(DEFAULT_TEXTURE_CACHE_SIZE)),
+        mFlushRate(DEFAULT_TEXTURE_CACHE_FLUSH_RATE) {
     char property[PROPERTY_VALUE_MAX];
     if (property_get(PROPERTY_TEXTURE_CACHE_SIZE, property, NULL) > 0) {
         INIT_LOGD("  Setting texture cache size to %sMB", property);
@@ -43,6 +44,15 @@
         INIT_LOGD("  Using default texture cache size of %.2fMB", DEFAULT_TEXTURE_CACHE_SIZE);
     }
 
+    if (property_get(PROPERTY_TEXTURE_CACHE_FLUSH_RATE, property, NULL) > 0) {
+        float flushRate = atof(property);
+        INIT_LOGD("  Setting texture cache flush rate to %.2f%%", flushRate * 100.0f);
+        setFlushRate(flushRate);
+    } else {
+        INIT_LOGD("  Using default texture cache flush rate of %.2f%%",
+                DEFAULT_TEXTURE_CACHE_FLUSH_RATE * 100.0f);
+    }
+
     init();
 }
 
@@ -84,6 +94,10 @@
     }
 }
 
+void TextureCache::setFlushRate(float flushRate) {
+    mFlushRate = fmaxf(0.0f, fminf(1.0f, flushRate));
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Callbacks
 ///////////////////////////////////////////////////////////////////////////////
@@ -168,6 +182,21 @@
     TEXTURE_LOGD("TextureCache:clear(), mSize = %d", mSize);
 }
 
+void TextureCache::flush() {
+    if (mFlushRate >= 1.0f || mCache.size() == 0) return;
+    if (mFlushRate <= 0.0f) {
+        clear();
+        return;
+    }
+
+    uint32_t targetSize = uint32_t(mSize * mFlushRate);
+    TEXTURE_LOGD("TextureCache::flush: target size: %d", targetSize);
+
+    while (mSize > targetSize) {
+        mCache.removeOldest();
+    }
+}
+
 void TextureCache::generateTexture(SkBitmap* bitmap, Texture* texture, bool regenerate) {
     SkAutoLockPixels alp(*bitmap);
 
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index f7707f7..ce924b4 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -98,6 +98,17 @@
      */
     uint32_t getSize();
 
+    /**
+     * Partially flushes the cache. The amount of memory freed by a flush
+     * is defined by the flush rate.
+     */
+    void flush();
+    /**
+     * Indicates the percentage of the cache to retain when a
+     * memory trim is requested (see Caches::flush).
+     */
+    void setFlushRate(float flushRate);
+
 private:
     /**
      * Generates the texture from a bitmap into the specified texture structure.
@@ -119,6 +130,8 @@
     uint32_t mMaxSize;
     GLint mMaxTextureSize;
 
+    float mFlushRate;
+
     bool mDebugEnabled;
 
     Vector<SkBitmap*> mGarbage;
diff --git a/libs/utils/BlobCache.cpp b/libs/utils/BlobCache.cpp
index 590576a..d38aae9 100644
--- a/libs/utils/BlobCache.cpp
+++ b/libs/utils/BlobCache.cpp
@@ -21,10 +21,20 @@
 #include <string.h>
 
 #include <utils/BlobCache.h>
+#include <utils/Errors.h>
 #include <utils/Log.h>
 
 namespace android {
 
+// BlobCache::Header::mMagicNumber value
+static const uint32_t blobCacheMagic = '_Bb$';
+
+// BlobCache::Header::mBlobCacheVersion value
+static const uint32_t blobCacheVersion = 1;
+
+// BlobCache::Header::mDeviceVersion value
+static const uint32_t blobCacheDeviceVersion = 1;
+
 BlobCache::BlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize):
         mMaxKeySize(maxKeySize),
         mMaxValueSize(maxValueSize),
@@ -67,12 +77,10 @@
         return;
     }
 
-    Mutex::Autolock lock(mMutex);
     sp<Blob> dummyKey(new Blob(key, keySize, false));
     CacheEntry dummyEntry(dummyKey, NULL);
 
     while (true) {
-
         ssize_t index = mCacheEntries.indexOf(dummyEntry);
         if (index < 0) {
             // Create a new cache entry.
@@ -129,7 +137,6 @@
                 keySize, mMaxKeySize);
         return 0;
     }
-    Mutex::Autolock lock(mMutex);
     sp<Blob> dummyKey(new Blob(key, keySize, false));
     CacheEntry dummyEntry(dummyKey, NULL);
     ssize_t index = mCacheEntries.indexOf(dummyEntry);
@@ -152,6 +159,133 @@
     return valueBlobSize;
 }
 
+static inline size_t align4(size_t size) {
+    return (size + 3) & ~3;
+}
+
+size_t BlobCache::getFlattenedSize() const {
+    size_t size = sizeof(Header);
+    for (size_t i = 0; i < mCacheEntries.size(); i++) {
+        const CacheEntry& e(mCacheEntries[i]);
+        sp<Blob> keyBlob = e.getKey();
+        sp<Blob> valueBlob = e.getValue();
+        size = align4(size);
+        size += sizeof(EntryHeader) + keyBlob->getSize() +
+                valueBlob->getSize();
+    }
+    return size;
+}
+
+size_t BlobCache::getFdCount() const {
+    return 0;
+}
+
+status_t BlobCache::flatten(void* buffer, size_t size, int fds[], size_t count)
+        const {
+    if (count != 0) {
+        LOGE("flatten: nonzero fd count: %d", count);
+        return BAD_VALUE;
+    }
+
+    // Write the cache header
+    if (size < sizeof(Header)) {
+        LOGE("flatten: not enough room for cache header");
+        return BAD_VALUE;
+    }
+    Header* header = reinterpret_cast<Header*>(buffer);
+    header->mMagicNumber = blobCacheMagic;
+    header->mBlobCacheVersion = blobCacheVersion;
+    header->mDeviceVersion = blobCacheDeviceVersion;
+    header->mNumEntries = mCacheEntries.size();
+
+    // Write cache entries
+    uint8_t* byteBuffer = reinterpret_cast<uint8_t*>(buffer);
+    off_t byteOffset = align4(sizeof(Header));
+    for (size_t i = 0; i < mCacheEntries.size(); i++) {
+        const CacheEntry& e(mCacheEntries[i]);
+        sp<Blob> keyBlob = e.getKey();
+        sp<Blob> valueBlob = e.getValue();
+        size_t keySize = keyBlob->getSize();
+        size_t valueSize = valueBlob->getSize();
+
+        size_t entrySize = sizeof(EntryHeader) + keySize + valueSize;
+        if (byteOffset + entrySize > size) {
+            LOGE("flatten: not enough room for cache entries");
+            return BAD_VALUE;
+        }
+
+        EntryHeader* eheader = reinterpret_cast<EntryHeader*>(
+            &byteBuffer[byteOffset]);
+        eheader->mKeySize = keySize;
+        eheader->mValueSize = valueSize;
+
+        memcpy(eheader->mData, keyBlob->getData(), keySize);
+        memcpy(eheader->mData + keySize, valueBlob->getData(), valueSize);
+
+        byteOffset += align4(entrySize);
+    }
+
+    return OK;
+}
+
+status_t BlobCache::unflatten(void const* buffer, size_t size, int fds[],
+        size_t count) {
+    // All errors should result in the BlobCache being in an empty state.
+    mCacheEntries.clear();
+
+    if (count != 0) {
+        LOGE("unflatten: nonzero fd count: %d", count);
+        return BAD_VALUE;
+    }
+
+    // Read the cache header
+    if (size < sizeof(Header)) {
+        LOGE("unflatten: not enough room for cache header");
+        return BAD_VALUE;
+    }
+    const Header* header = reinterpret_cast<const Header*>(buffer);
+    if (header->mMagicNumber != blobCacheMagic) {
+        LOGE("unflatten: bad magic number: %d", header->mMagicNumber);
+        return BAD_VALUE;
+    }
+    if (header->mBlobCacheVersion != blobCacheVersion ||
+            header->mDeviceVersion != blobCacheDeviceVersion) {
+        // We treat version mismatches as an empty cache.
+        return OK;
+    }
+
+    // Read cache entries
+    const uint8_t* byteBuffer = reinterpret_cast<const uint8_t*>(buffer);
+    off_t byteOffset = align4(sizeof(Header));
+    size_t numEntries = header->mNumEntries;
+    for (size_t i = 0; i < numEntries; i++) {
+        if (byteOffset + sizeof(EntryHeader) > size) {
+            mCacheEntries.clear();
+            LOGE("unflatten: not enough room for cache entry headers");
+            return BAD_VALUE;
+        }
+
+        const EntryHeader* eheader = reinterpret_cast<const EntryHeader*>(
+                &byteBuffer[byteOffset]);
+        size_t keySize = eheader->mKeySize;
+        size_t valueSize = eheader->mValueSize;
+        size_t entrySize = sizeof(EntryHeader) + keySize + valueSize;
+
+        if (byteOffset + entrySize > size) {
+            mCacheEntries.clear();
+            LOGE("unflatten: not enough room for cache entry headers");
+            return BAD_VALUE;
+        }
+
+        const uint8_t* data = eheader->mData;
+        set(data, keySize, data + keySize, valueSize);
+
+        byteOffset += align4(entrySize);
+    }
+
+    return OK;
+}
+
 long int BlobCache::blob_random() {
 #ifdef _WIN32
     return rand();
@@ -179,7 +313,7 @@
         mData(copyData ? malloc(size) : data),
         mSize(size),
         mOwnsData(copyData) {
-    if (copyData) {
+    if (data != NULL && copyData) {
         memcpy(const_cast<void*>(mData), data, size);
     }
 }
diff --git a/libs/utils/tests/BlobCache_test.cpp b/libs/utils/tests/BlobCache_test.cpp
index 653ea5e..b64cc39 100644
--- a/libs/utils/tests/BlobCache_test.cpp
+++ b/libs/utils/tests/BlobCache_test.cpp
@@ -14,9 +14,13 @@
  ** limitations under the License.
  */
 
+#include <fcntl.h>
+#include <stdio.h>
+
 #include <gtest/gtest.h>
 
 #include <utils/BlobCache.h>
+#include <utils/Errors.h>
 
 namespace android {
 
@@ -254,4 +258,164 @@
     ASSERT_EQ(maxEntries/2 + 1, numCached);
 }
 
+class BlobCacheFlattenTest : public BlobCacheTest {
+protected:
+    virtual void SetUp() {
+        BlobCacheTest::SetUp();
+        mBC2 = new BlobCache(MAX_KEY_SIZE, MAX_VALUE_SIZE, MAX_TOTAL_SIZE);
+    }
+
+    virtual void TearDown() {
+        mBC2.clear();
+        BlobCacheTest::TearDown();
+    }
+
+    void roundTrip() {
+        size_t size = mBC->getFlattenedSize();
+        uint8_t* flat = new uint8_t[size];
+        ASSERT_EQ(OK, mBC->flatten(flat, size, NULL, 0));
+        ASSERT_EQ(OK, mBC2->unflatten(flat, size, NULL, 0));
+        delete[] flat;
+    }
+
+    sp<BlobCache> mBC2;
+};
+
+TEST_F(BlobCacheFlattenTest, FlattenOneValue) {
+    char buf[4] = { 0xee, 0xee, 0xee, 0xee };
+    mBC->set("abcd", 4, "efgh", 4);
+    roundTrip();
+    ASSERT_EQ(size_t(4), mBC2->get("abcd", 4, buf, 4));
+    ASSERT_EQ('e', buf[0]);
+    ASSERT_EQ('f', buf[1]);
+    ASSERT_EQ('g', buf[2]);
+    ASSERT_EQ('h', buf[3]);
+}
+
+TEST_F(BlobCacheFlattenTest, FlattenFullCache) {
+    // Fill up the entire cache with 1 char key/value pairs.
+    const int maxEntries = MAX_TOTAL_SIZE / 2;
+    for (int i = 0; i < maxEntries; i++) {
+        uint8_t k = i;
+        mBC->set(&k, 1, &k, 1);
+    }
+
+    roundTrip();
+
+    // Verify the deserialized cache
+    for (int i = 0; i < maxEntries; i++) {
+        uint8_t k = i;
+        uint8_t v = 0xee;
+        ASSERT_EQ(size_t(1), mBC2->get(&k, 1, &v, 1));
+        ASSERT_EQ(k, v);
+    }
+}
+
+TEST_F(BlobCacheFlattenTest, FlattenDoesntChangeCache) {
+    // Fill up the entire cache with 1 char key/value pairs.
+    const int maxEntries = MAX_TOTAL_SIZE / 2;
+    for (int i = 0; i < maxEntries; i++) {
+        uint8_t k = i;
+        mBC->set(&k, 1, &k, 1);
+    }
+
+    size_t size = mBC->getFlattenedSize();
+    uint8_t* flat = new uint8_t[size];
+    ASSERT_EQ(OK, mBC->flatten(flat, size, NULL, 0));
+    delete[] flat;
+
+    // Verify the cache that we just serialized
+    for (int i = 0; i < maxEntries; i++) {
+        uint8_t k = i;
+        uint8_t v = 0xee;
+        ASSERT_EQ(size_t(1), mBC->get(&k, 1, &v, 1));
+        ASSERT_EQ(k, v);
+    }
+}
+
+TEST_F(BlobCacheFlattenTest, FlattenCatchesBufferTooSmall) {
+    // Fill up the entire cache with 1 char key/value pairs.
+    const int maxEntries = MAX_TOTAL_SIZE / 2;
+    for (int i = 0; i < maxEntries; i++) {
+        uint8_t k = i;
+        mBC->set(&k, 1, &k, 1);
+    }
+
+    size_t size = mBC->getFlattenedSize() - 1;
+    uint8_t* flat = new uint8_t[size];
+    ASSERT_EQ(BAD_VALUE, mBC->flatten(flat, size, NULL, 0));
+    delete[] flat;
+}
+
+TEST_F(BlobCacheFlattenTest, UnflattenCatchesBadMagic) {
+    char buf[4] = { 0xee, 0xee, 0xee, 0xee };
+    mBC->set("abcd", 4, "efgh", 4);
+
+    size_t size = mBC->getFlattenedSize();
+    uint8_t* flat = new uint8_t[size];
+    ASSERT_EQ(OK, mBC->flatten(flat, size, NULL, 0));
+    flat[1] = ~flat[1];
+
+    // Bad magic should cause an error.
+    ASSERT_EQ(BAD_VALUE, mBC2->unflatten(flat, size, NULL, 0));
+    delete[] flat;
+
+    // The error should cause the unflatten to result in an empty cache
+    ASSERT_EQ(size_t(0), mBC2->get("abcd", 4, buf, 4));
+}
+
+TEST_F(BlobCacheFlattenTest, UnflattenCatchesBadBlobCacheVersion) {
+    char buf[4] = { 0xee, 0xee, 0xee, 0xee };
+    mBC->set("abcd", 4, "efgh", 4);
+
+    size_t size = mBC->getFlattenedSize();
+    uint8_t* flat = new uint8_t[size];
+    ASSERT_EQ(OK, mBC->flatten(flat, size, NULL, 0));
+    flat[5] = ~flat[5];
+
+    // Version mismatches shouldn't cause errors, but should not use the
+    // serialized entries
+    ASSERT_EQ(OK, mBC2->unflatten(flat, size, NULL, 0));
+    delete[] flat;
+
+    // The version mismatch should cause the unflatten to result in an empty
+    // cache
+    ASSERT_EQ(size_t(0), mBC2->get("abcd", 4, buf, 4));
+}
+
+TEST_F(BlobCacheFlattenTest, UnflattenCatchesBadBlobCacheDeviceVersion) {
+    char buf[4] = { 0xee, 0xee, 0xee, 0xee };
+    mBC->set("abcd", 4, "efgh", 4);
+
+    size_t size = mBC->getFlattenedSize();
+    uint8_t* flat = new uint8_t[size];
+    ASSERT_EQ(OK, mBC->flatten(flat, size, NULL, 0));
+    flat[10] = ~flat[10];
+
+    // Version mismatches shouldn't cause errors, but should not use the
+    // serialized entries
+    ASSERT_EQ(OK, mBC2->unflatten(flat, size, NULL, 0));
+    delete[] flat;
+
+    // The version mismatch should cause the unflatten to result in an empty
+    // cache
+    ASSERT_EQ(size_t(0), mBC2->get("abcd", 4, buf, 4));
+}
+
+TEST_F(BlobCacheFlattenTest, UnflattenCatchesBufferTooSmall) {
+    char buf[4] = { 0xee, 0xee, 0xee, 0xee };
+    mBC->set("abcd", 4, "efgh", 4);
+
+    size_t size = mBC->getFlattenedSize();
+    uint8_t* flat = new uint8_t[size];
+    ASSERT_EQ(OK, mBC->flatten(flat, size, NULL, 0));
+
+    // A buffer truncation shouldt cause an error
+    ASSERT_EQ(BAD_VALUE, mBC2->unflatten(flat, size-1, NULL, 0));
+    delete[] flat;
+
+    // The error should cause the unflatten to result in an empty cache
+    ASSERT_EQ(size_t(0), mBC2->get("abcd", 4, buf, 4));
+}
+
 } // namespace android
diff --git a/media/jni/android_media_MediaScanner.cpp b/media/jni/android_media_MediaScanner.cpp
index b88296f..09152f5 100644
--- a/media/jni/android_media_MediaScanner.cpp
+++ b/media/jni/android_media_MediaScanner.cpp
@@ -56,6 +56,53 @@
     return OK;
 }
 
+// stolen from dalvik/vm/checkJni.cpp
+static bool isValidUtf8(const char* bytes) {
+    while (*bytes != '\0') {
+        unsigned char utf8 = *(bytes++);
+        // Switch on the high four bits.
+        switch (utf8 >> 4) {
+        case 0x00:
+        case 0x01:
+        case 0x02:
+        case 0x03:
+        case 0x04:
+        case 0x05:
+        case 0x06:
+        case 0x07:
+            // Bit pattern 0xxx. No need for any extra bytes.
+            break;
+        case 0x08:
+        case 0x09:
+        case 0x0a:
+        case 0x0b:
+        case 0x0f:
+            /*
+             * Bit pattern 10xx or 1111, which are illegal start bytes.
+             * Note: 1111 is valid for normal UTF-8, but not the
+             * modified UTF-8 used here.
+             */
+            return false;
+        case 0x0e:
+            // Bit pattern 1110, so there are two additional bytes.
+            utf8 = *(bytes++);
+            if ((utf8 & 0xc0) != 0x80) {
+                return false;
+            }
+            // Fall through to take care of the final byte.
+        case 0x0c:
+        case 0x0d:
+            // Bit pattern 110x, so there is one additional byte.
+            utf8 = *(bytes++);
+            if ((utf8 & 0xc0) != 0x80) {
+                return false;
+            }
+            break;
+        }
+    }
+    return true;
+}
+
 class MyMediaScannerClient : public MediaScannerClient
 {
 public:
@@ -123,7 +170,22 @@
             mEnv->ExceptionClear();
             return NO_MEMORY;
         }
-        if ((valueStr = mEnv->NewStringUTF(value)) == NULL) {
+        char *cleaned = NULL;
+        if (!isValidUtf8(value)) {
+            cleaned = strdup(value);
+            char *chp = cleaned;
+            char ch;
+            while ((ch = *chp)) {
+                if (ch & 0x80) {
+                    *chp = '?';
+                }
+                chp++;
+            }
+            value = cleaned;
+        }
+        valueStr = mEnv->NewStringUTF(value);
+        free(cleaned);
+        if (valueStr == NULL) {
             mEnv->DeleteLocalRef(nameStr);
             mEnv->ExceptionClear();
             return NO_MEMORY;
diff --git a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
index efa1c45..5a1e93a 100644
--- a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
+++ b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
@@ -49,6 +49,16 @@
         }\
     }
 
+
+static inline int16_t clamp16(int32_t sample)
+{
+    // check overflow for both positive and negative values:
+    // all bits above short range must me equal to sign bit
+    if ((sample>>15) ^ (sample>>31))
+        sample = 0x7FFF ^ (sample>>31);
+    return sample;
+}
+
 // Namespaces
 namespace android {
 namespace {
@@ -707,13 +717,6 @@
 }   /* end LvmBundle_init */
 
 
-static inline int16_t clamp16(int32_t sample)
-{
-    if ((sample>>15) ^ (sample>>31))
-        sample = 0x7FFF ^ (sample>>31);
-    return sample;
-}
-
 //----------------------------------------------------------------------------
 // LvmBundle_process()
 //----------------------------------------------------------------------------
@@ -2459,6 +2462,9 @@
     LOGV("\tEffect_setEnabled() type %d, enabled %d", pContext->EffectType, enabled);
 
     if (enabled) {
+        // Bass boost or Virtualizer can be temporarily disabled if playing over device speaker due
+        // to their nature.
+        bool tempDisabled = false;
         switch (pContext->EffectType) {
             case LVM_BASS_BOOST:
                 if (pContext->pBundledContext->bBassEnabled == LVM_TRUE) {
@@ -2471,6 +2477,7 @@
                 pContext->pBundledContext->SamplesToExitCountBb =
                      (LVM_INT32)(pContext->pBundledContext->SamplesPerSecond*0.1);
                 pContext->pBundledContext->bBassEnabled = LVM_TRUE;
+                tempDisabled = pContext->pBundledContext->bBassTempDisabled;
                 break;
             case LVM_EQUALIZER:
                 if (pContext->pBundledContext->bEqualizerEnabled == LVM_TRUE) {
@@ -2495,6 +2502,7 @@
                 pContext->pBundledContext->SamplesToExitCountVirt =
                      (LVM_INT32)(pContext->pBundledContext->SamplesPerSecond*0.1);
                 pContext->pBundledContext->bVirtualizerEnabled = LVM_TRUE;
+                tempDisabled = pContext->pBundledContext->bVirtualizerTempDisabled;
                 break;
             case LVM_VOLUME:
                 if (pContext->pBundledContext->bVolumeEnabled == LVM_TRUE) {
@@ -2508,7 +2516,9 @@
                 LOGV("\tEffect_setEnabled() invalid effect type");
                 return -EINVAL;
         }
-        LvmEffect_enable(pContext);
+        if (!tempDisabled) {
+            LvmEffect_enable(pContext);
+        }
     } else {
         switch (pContext->EffectType) {
             case LVM_BASS_BOOST:
@@ -2683,12 +2693,19 @@
             LOGV("\tLVM_ERROR : LvmBundle_process returned error %d", lvmStatus);
             return lvmStatus;
         }
-    }else{
+    } else {
         //LOGV("\tEffect_process Not Calling process with %d effects enabled, %d called: Effect %d",
         //pContext->pBundledContext->NumberEffectsEnabled,
         //pContext->pBundledContext->NumberEffectsCalled, pContext->EffectType);
         // 2 is for stereo input
-        memcpy(outBuffer->raw, inBuffer->raw, outBuffer->frameCount*sizeof(LVM_INT16)*2);
+        if (pContext->config.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE) {
+            for (size_t i=0; i < outBuffer->frameCount*2; i++){
+                outBuffer->s16[i] =
+                        clamp16((LVM_INT32)outBuffer->s16[i] + (LVM_INT32)inBuffer->s16[i]);
+            }
+        } else {
+            memcpy(outBuffer->raw, inBuffer->raw, outBuffer->frameCount*sizeof(LVM_INT16)*2);
+        }
     }
 
     return status;
@@ -3047,9 +3064,10 @@
             LOGV("\tEffect_command cmdCode Case: EFFECT_CMD_SET_DEVICE start");
             uint32_t device = *(uint32_t *)pCmdData;
 
-            if(pContext->EffectType == LVM_BASS_BOOST){
-                if((device == AUDIO_DEVICE_OUT_SPEAKER)||(device == AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT)||
-                   (device == AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)){
+            if (pContext->EffectType == LVM_BASS_BOOST) {
+                if((device == AUDIO_DEVICE_OUT_SPEAKER) ||
+                        (device == AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT) ||
+                        (device == AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)){
                     LOGV("\tEFFECT_CMD_SET_DEVICE device is invalid for LVM_BASS_BOOST %d",
                           *(int32_t *)pCmdData);
                     LOGV("\tEFFECT_CMD_SET_DEVICE temporary disable LVM_BAS_BOOST");
@@ -3058,30 +3076,31 @@
                     // the effect must still report its original state as this can only be changed
                     // by the ENABLE/DISABLE command
 
-                    if(pContext->pBundledContext->bBassEnabled == LVM_TRUE){
+                    if (pContext->pBundledContext->bBassEnabled == LVM_TRUE) {
                         LOGV("\tEFFECT_CMD_SET_DEVICE disable LVM_BASS_BOOST %d",
                              *(int32_t *)pCmdData);
                         android::LvmEffect_disable(pContext);
-                        pContext->pBundledContext->bBassTempDisabled = LVM_TRUE;
                     }
-                }else{
+                    pContext->pBundledContext->bBassTempDisabled = LVM_TRUE;
+                } else {
                     LOGV("\tEFFECT_CMD_SET_DEVICE device is valid for LVM_BASS_BOOST %d",
                          *(int32_t *)pCmdData);
 
                     // If a device supports bassboost and the effect has been temporarily disabled
                     // previously then re-enable it
 
-                    if(pContext->pBundledContext->bBassTempDisabled == LVM_TRUE){
+                    if (pContext->pBundledContext->bBassEnabled == LVM_TRUE) {
                         LOGV("\tEFFECT_CMD_SET_DEVICE re-enable LVM_BASS_BOOST %d",
                              *(int32_t *)pCmdData);
                         android::LvmEffect_enable(pContext);
-                        pContext->pBundledContext->bBassTempDisabled = LVM_FALSE;
                     }
+                    pContext->pBundledContext->bBassTempDisabled = LVM_FALSE;
                 }
             }
-            if(pContext->EffectType == LVM_VIRTUALIZER){
-                if((device == AUDIO_DEVICE_OUT_SPEAKER)||(device == AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT)||
-                   (device == AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)){
+            if (pContext->EffectType == LVM_VIRTUALIZER) {
+                if((device == AUDIO_DEVICE_OUT_SPEAKER)||
+                        (device == AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT)||
+                        (device == AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)){
                     LOGV("\tEFFECT_CMD_SET_DEVICE device is invalid for LVM_VIRTUALIZER %d",
                           *(int32_t *)pCmdData);
                     LOGV("\tEFFECT_CMD_SET_DEVICE temporary disable LVM_VIRTUALIZER");
@@ -3090,25 +3109,25 @@
                     // the effect must still report its original state as this can only be changed
                     // by the ENABLE/DISABLE command
 
-                    if(pContext->pBundledContext->bVirtualizerEnabled == LVM_TRUE){
+                    if (pContext->pBundledContext->bVirtualizerEnabled == LVM_TRUE) {
                         LOGV("\tEFFECT_CMD_SET_DEVICE disable LVM_VIRTUALIZER %d",
                               *(int32_t *)pCmdData);
                         android::LvmEffect_disable(pContext);
-                        pContext->pBundledContext->bVirtualizerTempDisabled = LVM_TRUE;
                     }
-                }else{
+                    pContext->pBundledContext->bVirtualizerTempDisabled = LVM_TRUE;
+                } else {
                     LOGV("\tEFFECT_CMD_SET_DEVICE device is valid for LVM_VIRTUALIZER %d",
                           *(int32_t *)pCmdData);
 
                     // If a device supports virtualizer and the effect has been temporarily disabled
                     // previously then re-enable it
 
-                    if(pContext->pBundledContext->bVirtualizerTempDisabled == LVM_TRUE){
+                    if(pContext->pBundledContext->bVirtualizerEnabled == LVM_TRUE){
                         LOGV("\tEFFECT_CMD_SET_DEVICE re-enable LVM_VIRTUALIZER %d",
                               *(int32_t *)pCmdData);
                         android::LvmEffect_enable(pContext);
-                        pContext->pBundledContext->bVirtualizerTempDisabled = LVM_FALSE;
                     }
+                    pContext->pBundledContext->bVirtualizerTempDisabled = LVM_FALSE;
                 }
             }
             LOGV("\tEffect_command cmdCode Case: EFFECT_CMD_SET_DEVICE end");
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index 7cdb76c..70208f8 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -282,7 +282,7 @@
 
                 if (err == -EWOULDBLOCK) {
                     if (mSource->feedMoreTSData() == OK) {
-                        msg->post();
+                        msg->post(10000ll);
                     }
                 }
             } else if (what == ACodec::kWhatEOS) {
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaBassBoostTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaBassBoostTest.java
index e3aa8cf..1fa5c0d 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaBassBoostTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaBassBoostTest.java
@@ -44,13 +44,7 @@
  */
 public class MediaBassBoostTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
     private String TAG = "MediaBassBoostTest";
-    private final static int MIN_ENERGY_RATIO_2 = 3;
     private final static short TEST_STRENGTH = 500;
-    private final static int TEST_VOLUME = 4;
-    // Implementor UUID for volume controller effect defined in
-    // frameworks/base/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
-    private final static UUID VOLUME_EFFECT_UUID =
-        UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b");
 
     private BassBoost mBassBoost = null;
     private int mSession = -1;
@@ -184,85 +178,6 @@
     }
 
     //-----------------------------------------------------------------
-    // 2 - Effect action
-    //----------------------------------
-
-    //Test case 2.0: test actual bass boost influence on sound
-    @LargeTest
-    public void test2_0SoundModification() throws Exception {
-        boolean result = false;
-        String msg = "test2_0SoundModification()";
-        EnergyProbe probe = null;
-        AudioEffect vc = null;
-        MediaPlayer mp = null;
-        AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
-        int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-        am.setStreamVolume(AudioManager.STREAM_MUSIC,
-                           TEST_VOLUME,
-                           0);
-
-        try {
-            probe = new EnergyProbe(0);
-            // creating a volume controller on output mix ensures that ro.audio.silent mutes
-            // audio after the effects and not before
-            vc = new AudioEffect(
-                                AudioEffect.EFFECT_TYPE_NULL,
-                                VOLUME_EFFECT_UUID,
-                                0,
-                                0);
-            vc.setEnabled(true);
-
-            mp = new MediaPlayer();
-            mp.setDataSource(MediaNames.SINE_200_1000);
-            mp.setLooping(true);
-            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
-            getBassBoost(mp.getAudioSessionId());
-            mp.prepare();
-            mp.start();
-            Thread.sleep(200);
-            // measure reference energy around 1kHz
-            int refEnergy200 = probe.capture(200);
-            int refEnergy1000 = probe.capture(1000);
-            mBassBoost.setStrength((short)1000);
-            mBassBoost.setEnabled(true);
-            Thread.sleep(4000);
-            // measure energy around 1kHz with band level at min
-            int energy200 = probe.capture(200);
-            int energy1000 = probe.capture(1000);
-            // verify that the energy ration between low and high frequencies is at least
-            // MIN_ENERGY_RATIO_2 times higher with bassboost on.
-            assertTrue(msg + ": bass boost has no effect",
-                    ((float)energy200/(float)energy1000) >
-                    (MIN_ENERGY_RATIO_2 * ((float)refEnergy200/(float)refEnergy1000)));
-            result = true;
-        } catch (IllegalArgumentException e) {
-            msg = msg.concat(": Bad parameter value");
-            loge(msg, "Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            msg = msg.concat(": get parameter() rejected");
-            loge(msg, "get parameter() rejected");
-        } catch (IllegalStateException e) {
-            msg = msg.concat("get parameter() called in wrong state");
-            loge(msg, "get parameter() called in wrong state");
-        } catch (InterruptedException e) {
-            loge(msg, "sleep() interrupted");
-        }
-        finally {
-            releaseBassBoost();
-            if (mp != null) {
-                mp.release();
-            }
-            if (vc != null) {
-                vc.release();
-            }
-            if (probe != null) {
-                probe.release();
-            }
-            am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
-        }
-        assertTrue(msg, result);
-    }
-    //-----------------------------------------------------------------
     // private methods
     //----------------------------------
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaEqualizerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaEqualizerTest.java
index ee91bbb..da9089d 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaEqualizerTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaEqualizerTest.java
@@ -49,11 +49,6 @@
     private final static int MAX_BAND_LEVEL = 1500;
     private final static int TEST_FREQUENCY_MILLIHERTZ = 1000000;
     private final static int MIN_NUMBER_OF_PRESETS = 4;
-    private final static int TEST_VOLUME = 4;
-    // Implementor UUID for volume controller effect defined in
-    // frameworks/base/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
-    private final static UUID VOLUME_EFFECT_UUID =
-        UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b");
 
     private Equalizer mEqualizer = null;
     private int mSession = -1;
@@ -252,80 +247,6 @@
     }
 
     //-----------------------------------------------------------------
-    // 2 - Effect action
-    //----------------------------------
-
-    //Test case 2.0: test that the equalizer actually alters the sound
-    @LargeTest
-    public void test2_0SoundModification() throws Exception {
-        boolean result = false;
-        String msg = "test2_0SoundModification()";
-        EnergyProbe probe = null;
-        AudioEffect vc = null;
-        MediaPlayer mp = null;
-        AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
-        int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-        am.setStreamVolume(AudioManager.STREAM_MUSIC,
-                           TEST_VOLUME,
-                           0);
-        try {
-            probe = new EnergyProbe(0);
-            // creating a volume controller on output mix ensures that ro.audio.silent mutes
-            // audio after the effects and not before
-            vc = new AudioEffect(
-                                AudioEffect.EFFECT_TYPE_NULL,
-                                VOLUME_EFFECT_UUID,
-                                0,
-                                0);
-            vc.setEnabled(true);
-
-            mp = new MediaPlayer();
-            mp.setDataSource(MediaNames.SINE_200_1000);
-            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
-            getEqualizer(mp.getAudioSessionId());
-            mp.prepare();
-            mp.start();
-            Thread.sleep(500);
-            // measure reference energy around 1kHz
-            int refEnergy = probe.capture(1000);
-            short band = mEqualizer.getBand(1000000);
-            short[] levelRange = mEqualizer.getBandLevelRange();
-            mEqualizer.setBandLevel(band, levelRange[0]);
-            mEqualizer.setEnabled(true);
-            Thread.sleep(500);
-            // measure energy around 1kHz with band level at min
-            int energy = probe.capture(1000);
-            assertTrue(msg + ": equalizer has no effect at 1kHz", energy < refEnergy/4);
-            result = true;
-        } catch (IllegalArgumentException e) {
-            msg = msg.concat(": Bad parameter value");
-            loge(msg, "Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            msg = msg.concat(": get parameter() rejected");
-            loge(msg, "get parameter() rejected");
-        } catch (IllegalStateException e) {
-            msg = msg.concat("get parameter() called in wrong state");
-            loge(msg, "get parameter() called in wrong state");
-        } catch (InterruptedException e) {
-            loge(msg, "sleep() interrupted");
-        }
-        finally {
-            releaseEqualizer();
-            if (mp != null) {
-                mp.release();
-            }
-            if (vc != null) {
-                vc.release();
-            }
-            if (probe != null) {
-                probe.release();
-            }
-            am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
-        }
-        assertTrue(msg, result);
-    }
-
-    //-----------------------------------------------------------------
     // private methods
     //----------------------------------
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVirtualizerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVirtualizerTest.java
index b74e525..122545f 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVirtualizerTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaVirtualizerTest.java
@@ -44,13 +44,7 @@
  */
 public class MediaVirtualizerTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
     private String TAG = "MediaVirtualizerTest";
-    private final static int MIN_ENERGY_RATIO_2 = 2;
     private final static short TEST_STRENGTH = 500;
-    private final static int TEST_VOLUME = 4;
-    // Implementor UUID for volume controller effect defined in
-    // frameworks/base/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
-    private final static UUID VOLUME_EFFECT_UUID =
-        UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b");
 
     private Virtualizer mVirtualizer = null;
     private int mSession = -1;
@@ -185,89 +179,6 @@
     }
 
     //-----------------------------------------------------------------
-    // 2 - Effect action
-    //----------------------------------
-
-    //Test case 2.0: test actual virtualizer influence on sound
-    @LargeTest
-    public void test2_0SoundModification() throws Exception {
-        boolean result = false;
-        String msg = "test2_0SoundModification()";
-        EnergyProbe probe = null;
-        AudioEffect vc = null;
-        MediaPlayer mp = null;
-        AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
-        int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-        am.setStreamVolume(AudioManager.STREAM_MUSIC,
-                           TEST_VOLUME,
-                           0);
-
-        try {
-            probe = new EnergyProbe(0);
-            // creating a volume controller on output mix ensures that ro.audio.silent mutes
-            // audio after the effects and not before
-            vc = new AudioEffect(
-                                AudioEffect.EFFECT_TYPE_NULL,
-                                VOLUME_EFFECT_UUID,
-                                0,
-                                0);
-            vc.setEnabled(true);
-
-            mp = new MediaPlayer();
-            mp.setDataSource(MediaNames.SINE_200_1000);
-            mp.setLooping(true);
-            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
-            getVirtualizer(mp.getAudioSessionId());
-            mp.prepare();
-            mp.start();
-            Thread.sleep(200);
-            // measure reference energy around 1kHz
-            int refEnergy200 = probe.capture(200);
-            int refEnergy1000 = probe.capture(1000);
-            mVirtualizer.setStrength((short)1000);
-            mVirtualizer.setEnabled(true);
-            Thread.sleep(4000);
-            // measure energy around 1kHz with band level at min
-            int energy200 = probe.capture(200);
-            int energy1000 = probe.capture(1000);
-            // verify that the energy ration between low and high frequencies is at least
-            // MIN_ENERGY_RATIO_2 times higher with virtualizer on.
-            // NOTE: this is what is observed with current virtualizer implementation and the test
-            // audio file but is not the primary effect of the virtualizer. A better way would
-            // be to have a stereo PCM capture and check that a strongly paned input is centered
-            // when output. However, we cannot capture stereo with the visualizer.
-            assertTrue(msg + ": virtualizer has no effect",
-                    ((float)energy200/(float)energy1000) >
-                    (MIN_ENERGY_RATIO_2 * ((float)refEnergy200/(float)refEnergy1000)));
-            result = true;
-        } catch (IllegalArgumentException e) {
-            msg = msg.concat(": Bad parameter value");
-            loge(msg, "Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            msg = msg.concat(": get parameter() rejected");
-            loge(msg, "get parameter() rejected");
-        } catch (IllegalStateException e) {
-            msg = msg.concat("get parameter() called in wrong state");
-            loge(msg, "get parameter() called in wrong state");
-        } catch (InterruptedException e) {
-            loge(msg, "sleep() interrupted");
-        }
-        finally {
-            releaseVirtualizer();
-            if (mp != null) {
-                mp.release();
-            }
-            if (vc != null) {
-                vc.release();
-            }
-            if (probe != null) {
-                probe.release();
-            }
-            am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
-        }
-        assertTrue(msg, result);
-    }
-    //-----------------------------------------------------------------
     // private methods
     //----------------------------------
 
diff --git a/opengl/libs/Android.mk b/opengl/libs/Android.mk
index 3e66a13..5855b63 100644
--- a/opengl/libs/Android.mk
+++ b/opengl/libs/Android.mk
@@ -8,6 +8,7 @@
 
 LOCAL_SRC_FILES:= 	       \
 	EGL/egl_tls.cpp        \
+	EGL/egl_cache.cpp      \
 	EGL/egl_display.cpp    \
 	EGL/egl_object.cpp     \
 	EGL/egl.cpp 	       \
@@ -157,4 +158,3 @@
 include $(BUILD_SHARED_LIBRARY)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/opengl/libs/EGL/eglApi.cpp b/opengl/libs/EGL/eglApi.cpp
index 1f9ce68..60ac34b 100644
--- a/opengl/libs/EGL/eglApi.cpp
+++ b/opengl/libs/EGL/eglApi.cpp
@@ -858,10 +858,17 @@
         return  NULL;
     }
 
+    // The EGL_ANDROID_blob_cache extension should not be exposed to
+    // applications.  It is used internally by the Android EGL layer.
+    if (!strcmp(procname, "eglSetBlobCacheFuncs")) {
+        return NULL;
+    }
+
     __eglMustCastToProperFunctionPointerType addr;
     addr = findProcAddress(procname, sExtentionMap, NELEM(sExtentionMap));
     if (addr) return addr;
 
+
     // this protects accesses to sGLExtentionMap and sGLExtentionSlot
     pthread_mutex_lock(&sExtensionMapMutex);
 
diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp
new file mode 100644
index 0000000..1e64302
--- /dev/null
+++ b/opengl/libs/EGL/egl_cache.cpp
@@ -0,0 +1,95 @@
+/*
+ ** Copyright 2011, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ **     http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+#include "egl_cache.h"
+#include "egl_display.h"
+#include "egl_impl.h"
+#include "egldefs.h"
+
+// ----------------------------------------------------------------------------
+namespace android {
+// ----------------------------------------------------------------------------
+
+#define BC_EXT_STR "EGL_ANDROID_blob_cache"
+
+//
+// EGL_ANDROID_blob_cache types and functions
+//
+typedef khronos_ssize_t EGLsizei;
+
+typedef void (*EGLSetBlobFunc) (const void* key, EGLsizei keySize,
+        const void* value, EGLsizei valueSize);
+
+typedef EGLsizei (*EGLGetBlobFunc) (const void* key, EGLsizei keySize,
+        void* value, EGLsizei valueSize);
+
+typedef void (EGLAPIENTRYP PFNEGLSETBLOBCACHEFUNCSPROC) (EGLDisplay dpy,
+        EGLSetBlobFunc set, EGLGetBlobFunc get);
+
+//
+// egl_cache_t definition
+//
+static void setBlob(const void* key, EGLsizei keySize, const void* value,
+        EGLsizei valueSize) {
+}
+
+static EGLsizei getBlob(const void* key, EGLsizei keySize, void* value,
+        EGLsizei valueSize) {
+    return 0;
+}
+
+egl_cache_t* egl_cache_t::get() {
+    static egl_cache_t theCache;
+    return &theCache;
+}
+
+void egl_cache_t::initialize(egl_display_t *display) {
+    for (int i = 0; i < IMPL_NUM_IMPLEMENTATIONS; i++) {
+        egl_connection_t* const cnx = &gEGLImpl[i];
+        if (cnx->dso && cnx->major >= 0 && cnx->minor >= 0) {
+            const char* exts = display->disp[i].queryString.extensions;
+            size_t bcExtLen = strlen(BC_EXT_STR);
+            size_t extsLen = strlen(exts);
+            bool equal = !strcmp(BC_EXT_STR, exts);
+            bool atStart = !strncmp(BC_EXT_STR " ", exts, bcExtLen+1);
+            bool atEnd = (bcExtLen+1) < extsLen &&
+                    !strcmp(" " BC_EXT_STR, exts + extsLen - (bcExtLen+1));
+            bool inMiddle = strstr(" " BC_EXT_STR " ", exts);
+            if (equal || atStart || atEnd || inMiddle) {
+                PFNEGLSETBLOBCACHEFUNCSPROC eglSetBlobCacheFuncs;
+                eglSetBlobCacheFuncs =
+                        reinterpret_cast<PFNEGLSETBLOBCACHEFUNCSPROC>(
+                            cnx->egl.eglGetProcAddress("eglSetBlobCacheFuncs"));
+                if (eglSetBlobCacheFuncs == NULL) {
+                    LOGE("EGL_ANDROID_blob_cache advertised by display %d, "
+                            "but unable to get eglSetBlobCacheFuncs", i);
+                    continue;
+                }
+
+                eglSetBlobCacheFuncs(display->disp[i].dpy, setBlob, getBlob);
+                EGLint err = cnx->egl.eglGetError();
+                if (err != EGL_SUCCESS) {
+                    LOGE("eglSetBlobCacheFuncs resulted in an error: %#x",
+                            err);
+                }
+            }
+        }
+    }
+}
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+// ----------------------------------------------------------------------------
diff --git a/opengl/libs/EGL/egl_cache.h b/opengl/libs/EGL/egl_cache.h
new file mode 100644
index 0000000..1fcfacc
--- /dev/null
+++ b/opengl/libs/EGL/egl_cache.h
@@ -0,0 +1,33 @@
+/*
+ ** Copyright 2011, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ **     http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+// ----------------------------------------------------------------------------
+namespace android {
+// ----------------------------------------------------------------------------
+
+class egl_display_t;
+
+class egl_cache_t {
+public:
+
+    static egl_cache_t* get();
+
+    void initialize(egl_display_t* display);
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+// ----------------------------------------------------------------------------
diff --git a/opengl/libs/EGL/egl_display.cpp b/opengl/libs/EGL/egl_display.cpp
index 83aafa6..0f92864 100644
--- a/opengl/libs/EGL/egl_display.cpp
+++ b/opengl/libs/EGL/egl_display.cpp
@@ -14,6 +14,7 @@
  ** limitations under the License.
  */
 
+#include "egl_cache.h"
 #include "egl_display.h"
 #include "egl_object.h"
 #include "egl_tls.h"
@@ -170,6 +171,8 @@
         }
     }
 
+    egl_cache_t::get()->initialize(this);
+
     EGLBoolean res = EGL_FALSE;
     for (int i = 0; i < IMPL_NUM_IMPLEMENTATIONS; i++) {
         egl_connection_t* const cnx = &gEGLImpl[i];
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java
index eb75ebc..2af5103 100644
--- a/services/java/com/android/server/AppWidgetService.java
+++ b/services/java/com/android/server/AppWidgetService.java
@@ -751,10 +751,13 @@
                 return;
             }
             ArrayList<AppWidgetId> instances = p.instances;
+            final int callingUid = getCallingUid();
             final int N = instances.size();
             for (int i=0; i<N; i++) {
                 AppWidgetId id = instances.get(i);
-                updateAppWidgetInstanceLocked(id, views);
+                if (canAccessAppWidgetId(id, callingUid)) {
+                    updateAppWidgetInstanceLocked(id, views);
+                }
             }
         }
     }
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 851cb33..8c42f31 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -171,6 +171,12 @@
     private static final int ENABLED  = 1;
     private static final int DISABLED = 0;
 
+    private static final boolean ADD = true;
+    private static final boolean REMOVE = false;
+
+    private static final boolean TO_DEFAULT_TABLE = true;
+    private static final boolean TO_SECONDARY_TABLE = false;
+
     // Share the event space with NetworkStateTracker (which can't see this
     // internal class but sends us events).  If you change these, change
     // NetworkStateTracker.java too.
@@ -501,7 +507,7 @@
         IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
         INetworkManagementService nmService = INetworkManagementService.Stub.asInterface(b);
 
-        mTethering = new Tethering(mContext, nmService, statsService, mHandler.getLooper());
+        mTethering = new Tethering(mContext, nmService, statsService, this, mHandler.getLooper());
         mTetheringConfigValid = ((mTethering.getTetherableUsbRegexs().length != 0 ||
                                   mTethering.getTetherableWifiRegexs().length != 0 ||
                                   mTethering.getTetherableBluetoothRegexs().length != 0) &&
@@ -1146,23 +1152,24 @@
         return false;
     }
 
-    private boolean addRoute(LinkProperties p, RouteInfo r) {
-        return modifyRoute(p.getInterfaceName(), p, r, 0, true);
+    private boolean addRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable) {
+        return modifyRoute(p.getInterfaceName(), p, r, 0, ADD, toDefaultTable);
     }
 
-    private boolean removeRoute(LinkProperties p, RouteInfo r) {
-        return modifyRoute(p.getInterfaceName(), p, r, 0, false);
+    private boolean removeRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable) {
+        return modifyRoute(p.getInterfaceName(), p, r, 0, REMOVE, toDefaultTable);
     }
 
     private boolean addRouteToAddress(LinkProperties lp, InetAddress addr) {
-        return modifyRouteToAddress(lp, addr, true);
+        return modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE);
     }
 
     private boolean removeRouteToAddress(LinkProperties lp, InetAddress addr) {
-        return modifyRouteToAddress(lp, addr, false);
+        return modifyRouteToAddress(lp, addr, REMOVE, TO_DEFAULT_TABLE);
     }
 
-    private boolean modifyRouteToAddress(LinkProperties lp, InetAddress addr, boolean doAdd) {
+    private boolean modifyRouteToAddress(LinkProperties lp, InetAddress addr, boolean doAdd,
+            boolean toDefaultTable) {
         RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getRoutes(), addr);
         if (bestRoute == null) {
             bestRoute = RouteInfo.makeHostRoute(addr);
@@ -1176,15 +1183,15 @@
                 bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway());
             }
         }
-        return modifyRoute(lp.getInterfaceName(), lp, bestRoute, 0, doAdd);
+        return modifyRoute(lp.getInterfaceName(), lp, bestRoute, 0, doAdd, toDefaultTable);
     }
 
     private boolean modifyRoute(String ifaceName, LinkProperties lp, RouteInfo r, int cycleCount,
-            boolean doAdd) {
+            boolean doAdd, boolean toDefaultTable) {
         if ((ifaceName == null) || (lp == null) || (r == null)) return false;
 
         if (cycleCount > MAX_HOSTROUTE_CYCLE_COUNT) {
-            loge("Error adding route - too much recursion");
+            loge("Error modifying route - too much recursion");
             return false;
         }
 
@@ -1199,14 +1206,18 @@
                     // route to it's gateway
                     bestRoute = RouteInfo.makeHostRoute(r.getGateway(), bestRoute.getGateway());
                 }
-                modifyRoute(ifaceName, lp, bestRoute, cycleCount+1, doAdd);
+                modifyRoute(ifaceName, lp, bestRoute, cycleCount+1, doAdd, toDefaultTable);
             }
         }
         if (doAdd) {
             if (VDBG) log("Adding " + r + " for interface " + ifaceName);
-            mAddedRoutes.add(r);
             try {
-                mNetd.addRoute(ifaceName, r);
+                if (toDefaultTable) {
+                    mAddedRoutes.add(r);  // only track default table - only one apps can effect
+                    mNetd.addRoute(ifaceName, r);
+                } else {
+                    mNetd.addSecondaryRoute(ifaceName, r);
+                }
             } catch (Exception e) {
                 // never crash - catch them all
                 if (VDBG) loge("Exception trying to add a route: " + e);
@@ -1215,18 +1226,29 @@
         } else {
             // if we remove this one and there are no more like it, then refcount==0 and
             // we can remove it from the table
-            mAddedRoutes.remove(r);
-            if (mAddedRoutes.contains(r) == false) {
+            if (toDefaultTable) {
+                mAddedRoutes.remove(r);
+                if (mAddedRoutes.contains(r) == false) {
+                    if (VDBG) log("Removing " + r + " for interface " + ifaceName);
+                    try {
+                        mNetd.removeRoute(ifaceName, r);
+                    } catch (Exception e) {
+                        // never crash - catch them all
+                        if (VDBG) loge("Exception trying to remove a route: " + e);
+                        return false;
+                    }
+                } else {
+                    if (VDBG) log("not removing " + r + " as it's still in use");
+                }
+            } else {
                 if (VDBG) log("Removing " + r + " for interface " + ifaceName);
                 try {
-                    mNetd.removeRoute(ifaceName, r);
+                    mNetd.removeSecondaryRoute(ifaceName, r);
                 } catch (Exception e) {
                     // never crash - catch them all
                     if (VDBG) loge("Exception trying to remove a route: " + e);
                     return false;
                 }
-            } else {
-                if (VDBG) log("not removing " + r + " as it's still in use");
             }
         }
         return true;
@@ -1862,14 +1884,21 @@
 
         for (RouteInfo r : routeDiff.removed) {
             if (isLinkDefault || ! r.isDefaultRoute()) {
-                removeRoute(curLp, r);
+                removeRoute(curLp, r, TO_DEFAULT_TABLE);
+            }
+            if (isLinkDefault == false) {
+                // remove from a secondary route table
+                removeRoute(curLp, r, TO_SECONDARY_TABLE);
             }
         }
 
         for (RouteInfo r :  routeDiff.added) {
             if (isLinkDefault || ! r.isDefaultRoute()) {
-                addRoute(newLp, r);
+                addRoute(newLp, r, TO_DEFAULT_TABLE);
             } else {
+                // add to a secondary route table
+                addRoute(newLp, r, TO_SECONDARY_TABLE);
+
                 // many radios add a default route even when we don't want one.
                 // remove the default route unless somebody else has asked for it
                 String ifaceName = newLp.getInterfaceName();
@@ -2450,12 +2479,6 @@
         int defaultVal = (SystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1);
         boolean tetherEnabledInSettings = (Settings.Secure.getInt(mContext.getContentResolver(),
                 Settings.Secure.TETHER_SUPPORTED, defaultVal) != 0);
-        // Short term disabling of Tethering if DUN is required.
-        // TODO - fix multi-connection tethering using policy-base routing
-        int[] upstreamConnTypes = mTethering.getUpstreamIfaceTypes();
-        for (int i : upstreamConnTypes) {
-            if (i == ConnectivityManager.TYPE_MOBILE_DUN) return false;
-        }
         return tetherEnabledInSettings && mTetheringConfigValid;
     }
 
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 4e4fe4a..6887de3 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -59,7 +59,11 @@
 import java.io.PrintWriter;
 import java.net.Inet4Address;
 import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.NoSuchElementException;
 import java.util.StringTokenizer;
@@ -77,6 +81,9 @@
     private static final int ADD = 1;
     private static final int REMOVE = 2;
 
+    private static final String DEFAULT = "default";
+    private static final String SECONDARY = "secondary";
+
     /**
      * Name representing {@link #setGlobalAlert(long)} limit when delivered to
      * {@link INetworkManagementEventObserver#limitReached(String, String)}.
@@ -505,15 +512,25 @@
 
     public void addRoute(String interfaceName, RouteInfo route) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
-        modifyRoute(interfaceName, ADD, route);
+        modifyRoute(interfaceName, ADD, route, DEFAULT);
     }
 
     public void removeRoute(String interfaceName, RouteInfo route) {
         mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
-        modifyRoute(interfaceName, REMOVE, route);
+        modifyRoute(interfaceName, REMOVE, route, DEFAULT);
     }
 
-    private void modifyRoute(String interfaceName, int action, RouteInfo route) {
+    public void addSecondaryRoute(String interfaceName, RouteInfo route) {
+        mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
+        modifyRoute(interfaceName, ADD, route, SECONDARY);
+    }
+
+    public void removeSecondaryRoute(String interfaceName, RouteInfo route) {
+        mContext.enforceCallingOrSelfPermission(CHANGE_NETWORK_STATE, TAG);
+        modifyRoute(interfaceName, REMOVE, route, SECONDARY);
+    }
+
+    private void modifyRoute(String interfaceName, int action, RouteInfo route, String type) {
         ArrayList<String> rsp;
 
         StringBuilder cmd;
@@ -521,12 +538,12 @@
         switch (action) {
             case ADD:
             {
-                cmd = new StringBuilder("interface route add " + interfaceName);
+                cmd = new StringBuilder("interface route add " + interfaceName + " " + type);
                 break;
             }
             case REMOVE:
             {
-                cmd = new StringBuilder("interface route remove " + interfaceName);
+                cmd = new StringBuilder("interface route remove " + interfaceName + " " + type);
                 break;
             }
             default:
@@ -833,14 +850,33 @@
         }
     }
 
+    private void modifyNat(String cmd, String internalInterface, String externalInterface)
+            throws SocketException {
+        cmd = String.format("nat %s %s %s", cmd, internalInterface, externalInterface);
+
+        NetworkInterface internalNetworkInterface =
+                NetworkInterface.getByName(internalInterface);
+        Collection<InterfaceAddress>interfaceAddresses =
+                internalNetworkInterface.getInterfaceAddresses();
+        cmd += " " + interfaceAddresses.size();
+        for (InterfaceAddress ia : interfaceAddresses) {
+            InetAddress addr = NetworkUtils.getNetworkPart(ia.getAddress(),
+                    ia.getNetworkPrefixLength());
+            cmd = cmd + " " + addr.getHostAddress() + "/" + ia.getNetworkPrefixLength();
+        }
+
+        mConnector.doCommand(cmd);
+    }
+
     public void enableNat(String internalInterface, String externalInterface)
             throws IllegalStateException {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
+        if (DBG) Log.d(TAG, "enableNat(" + internalInterface + ", " + externalInterface + ")");
         try {
-            mConnector.doCommand(
-                    String.format("nat enable %s %s", internalInterface, externalInterface));
-        } catch (NativeDaemonConnectorException e) {
+            modifyNat("enable", internalInterface, externalInterface);
+        } catch (Exception e) {
+            Log.e(TAG, "enableNat got Exception " + e.toString());
             throw new IllegalStateException(
                     "Unable to communicate to native daemon for enabling NAT interface");
         }
@@ -850,10 +886,11 @@
             throws IllegalStateException {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.CHANGE_NETWORK_STATE, "NetworkManagementService");
+        if (DBG) Log.d(TAG, "disableNat(" + internalInterface + ", " + externalInterface + ")");
         try {
-            mConnector.doCommand(
-                    String.format("nat disable %s %s", internalInterface, externalInterface));
-        } catch (NativeDaemonConnectorException e) {
+            modifyNat("disable", internalInterface, externalInterface);
+        } catch (Exception e) {
+            Log.e(TAG, "disableNat got Exception " + e.toString());
             throw new IllegalStateException(
                     "Unable to communicate to native daemon for disabling NAT interface");
         }
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 5bfe6f8..01eade1 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -921,18 +921,14 @@
                     Slog.d(TAG, "ACTION_SCREEN_ON");
                 }
                 mAlarmManager.cancel(mIdleIntent);
-                mDeviceIdle = false;
                 mScreenOff = false;
-                // Once the screen is on, we are not keeping WIFI running
-                // because of any locks so clear that tracking immediately.
-                reportStartWorkSource();
                 evaluateTrafficStatsPolling();
                 mWifiStateMachine.enableRssiPolling(true);
                 if (mBackgroundScanSupported) {
                     mWifiStateMachine.enableBackgroundScanCommand(false);
                 }
                 mWifiStateMachine.enableAllNetworks();
-                updateWifiState();
+                setDeviceIdleAndUpdateWifi(false);
             } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                 if (DBG) {
                     Slog.d(TAG, "ACTION_SCREEN_OFF");
@@ -950,36 +946,17 @@
                  * or plugged in to AC).
                  */
                 if (!shouldWifiStayAwake(stayAwakeConditions, mPluggedType)) {
-                    WifiInfo info = mWifiStateMachine.syncRequestConnectionInfo();
-                    if (info.getSupplicantState() != SupplicantState.COMPLETED) {
-                        // we used to go to sleep immediately, but this caused some race conditions
-                        // we don't have time to track down for this release.  Delay instead,
-                        // but not as long as we would if connected (below)
-                        // TODO - fix the race conditions and switch back to the immediate turn-off
-                        long triggerTime = System.currentTimeMillis() + (2*60*1000); // 2 min
-                        if (DBG) {
-                            Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for 120,000 ms");
-                        }
-                        mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent);
-                        //  // do not keep Wifi awake when screen is off if Wifi is not associated
-                        //  mDeviceIdle = true;
-                        //  updateWifiState();
+                    //Delayed shutdown if wifi is connected
+                    if (mNetworkInfo.getDetailedState() == DetailedState.CONNECTED) {
+                        if (DBG) Slog.d(TAG, "setting ACTION_DEVICE_IDLE: " + idleMillis + " ms");
+                        mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
+                                + idleMillis, mIdleIntent);
                     } else {
-                        long triggerTime = System.currentTimeMillis() + idleMillis;
-                        if (DBG) {
-                            Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for " + idleMillis
-                                    + "ms");
-                        }
-                        mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent);
+                        setDeviceIdleAndUpdateWifi(true);
                     }
                 }
             } else if (action.equals(ACTION_DEVICE_IDLE)) {
-                if (DBG) {
-                    Slog.d(TAG, "got ACTION_DEVICE_IDLE");
-                }
-                mDeviceIdle = true;
-                reportStartWorkSource();
-                updateWifiState();
+                setDeviceIdleAndUpdateWifi(true);
             } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
                 /*
                  * Set a timer to put Wi-Fi to sleep, but only if the screen is off
@@ -1056,6 +1033,12 @@
         }
     };
 
+    private void setDeviceIdleAndUpdateWifi(boolean deviceIdle) {
+        mDeviceIdle = deviceIdle;
+        reportStartWorkSource();
+        updateWifiState();
+    }
+
     private synchronized void reportStartWorkSource() {
         mTmpWorkSource.clear();
         if (mDeviceIdle) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 0c42926..4fe8119 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -120,6 +120,7 @@
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.File;
@@ -128,8 +129,10 @@
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.lang.IllegalStateException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -857,9 +860,11 @@
     static final int SHOW_COMPAT_MODE_DIALOG_MSG = 30;
     static final int DISPATCH_FOREGROUND_ACTIVITIES_CHANGED = 31;
     static final int DISPATCH_PROCESS_DIED = 32;
+    static final int REPORT_MEM_USAGE = 33;
 
     AlertDialog mUidAlert;
     CompatModeDialog mCompatModeDialog;
+    long mLastMemUsageReportTime = 0;
 
     final Handler mHandler = new Handler() {
         //public Handler() {
@@ -1199,6 +1204,56 @@
                 dispatchProcessDied(pid, uid);
                 break;
             }
+            case REPORT_MEM_USAGE: {
+                boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"));
+                if (!isDebuggable) {
+                    return;
+                }
+                synchronized (ActivityManagerService.this) {
+                    long now = SystemClock.uptimeMillis();
+                    if (now < (mLastMemUsageReportTime+10000)) {
+                        // Don't report more than every 10 seconds to somewhat
+                        // avoid spamming.
+                        return;
+                    }
+                    mLastMemUsageReportTime = now;
+                }
+                Thread thread = new Thread() {
+                    @Override public void run() {
+                        try {
+                            java.lang.Process proc = Runtime.getRuntime().exec(new String[] {
+                                    "procrank", });
+                            final InputStreamReader converter = new InputStreamReader(
+                                    proc.getInputStream());
+                            BufferedReader in = new BufferedReader(converter);
+                            String line;
+                            while (true) {
+                                line = in.readLine();
+                                if (line == null) {
+                                    break;
+                                }
+                                if (line.length() > 0) {
+                                    Slog.i(TAG, line);
+                                }
+                            }
+                            converter.close();
+                        } catch (IOException e) {
+                        }
+                        StringWriter sw = new StringWriter();
+                        PrintWriter pw = new PrintWriter(sw);
+                        dumpApplicationMemoryUsage(null, pw, "  ", new String[] { }, true);
+                        Slog.i(TAG, sw.toString());
+                        synchronized (ActivityManagerService.this) {
+                            long now = SystemClock.uptimeMillis();
+                            if (mLastMemUsageReportTime < now) {
+                                mLastMemUsageReportTime = now;
+                            }
+                        }
+                    }
+                };
+                thread.start();
+                break;
+            }
             }
         }
     };
@@ -1339,7 +1394,7 @@
                 return;
             }
 
-            mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, "  ", args);
+            mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, "  ", args, false);
         }
     }
 
@@ -2768,6 +2823,7 @@
                             addProcessToGcListLocked(rec);
                         }
                     }
+                    mHandler.sendEmptyMessage(REPORT_MEM_USAGE);
                     scheduleAppGcsLocked();
                 }
             }
@@ -9242,7 +9298,7 @@
     }
 
     final void dumpApplicationMemoryUsage(FileDescriptor fd,
-            PrintWriter pw, String prefix, String[] args) {
+            PrintWriter pw, String prefix, String[] args, boolean brief) {
         boolean dumpAll = false;
         
         int opti = 0;
@@ -9382,15 +9438,19 @@
                 }
             }
 
-            pw.println();
-            pw.println("Total PSS by process:");
-            dumpMemItems(pw, "  ", procMems, true);
-            pw.println();
+            if (!brief) {
+                pw.println();
+                pw.println("Total PSS by process:");
+                dumpMemItems(pw, "  ", procMems, true);
+                pw.println();
+            }
             pw.println("Total PSS by OOM adjustment:");
             dumpMemItems(pw, "  ", oomMems, false);
-            pw.println();
-            pw.println("Total PSS by category:");
-            dumpMemItems(pw, "  ", catMems, true);
+            if (!brief) {
+                pw.println();
+                pw.println("Total PSS by category:");
+                dumpMemItems(pw, "  ", catMems, true);
+            }
             pw.println();
             pw.print("Total PSS: "); pw.print(totalPss); pw.println(" Kb");
         }
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index 7bd29d9..e49acaf 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -91,6 +91,7 @@
 
     private final INetworkManagementService mNMService;
     private final INetworkStatsService mStatsService;
+    private final IConnectivityManager mConnService;
     private Looper mLooper;
     private HandlerThread mThread;
 
@@ -127,10 +128,11 @@
                                          // when RNDIS is enabled
 
     public Tethering(Context context, INetworkManagementService nmService,
-            INetworkStatsService statsService, Looper looper) {
+            INetworkStatsService statsService, IConnectivityManager connService, Looper looper) {
         mContext = context;
         mNMService = nmService;
         mStatsService = statsService;
+        mConnService = connService;
         mLooper = looper;
 
         mIfaces = new HashMap<String, TetherInterfaceSM>();
@@ -347,10 +349,8 @@
     }
 
     private void sendTetherStateChangedBroadcast() {
-        IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
-        IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b);
         try {
-            if (!cm.isTetheringSupported()) return;
+            if (!mConnService.isTetheringSupported()) return;
         } catch (RemoteException e) {
             return;
         }
@@ -910,6 +910,7 @@
                 try {
                     mNMService.tetherInterface(mIfaceName);
                 } catch (Exception e) {
+                    Log.e(TAG, "Error Tethering: " + e.toString());
                     setLastError(ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR);
 
                     transitionTo(mInitialState);
@@ -987,6 +988,7 @@
                             try {
                                 mNMService.enableNat(mIfaceName, newUpstreamIfaceName);
                             } catch (Exception e) {
+                                Log.e(TAG, "Exception enabling Nat: " + e.toString());
                                 try {
                                     mNMService.untetherInterface(mIfaceName);
                                 } catch (Exception ee) {}
@@ -1150,13 +1152,11 @@
                 boolean retValue = true;
                 if (apnType == ConnectivityManager.TYPE_NONE) return false;
                 if (apnType != mMobileApnReserved) turnOffUpstreamMobileConnection();
-                IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
-                IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b);
                 int result = Phone.APN_REQUEST_FAILED;
                 String enableString = enableString(apnType);
                 if (enableString == null) return false;
                 try {
-                    result = cm.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+                    result = mConnService.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
                             enableString, new Binder());
                 } catch (Exception e) {
                 }
@@ -1178,10 +1178,8 @@
             }
             protected boolean turnOffUpstreamMobileConnection() {
                 if (mMobileApnReserved != ConnectivityManager.TYPE_NONE) {
-                    IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
-                    IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b);
                     try {
-                        cm.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+                        mConnService.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
                                 enableString(mMobileApnReserved));
                     } catch (Exception e) {
                         return false;
@@ -1234,8 +1232,6 @@
             }
 
             protected void chooseUpstreamType(boolean tryCell) {
-                IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
-                IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b);
                 int upType = ConnectivityManager.TYPE_NONE;
                 String iface = null;
 
@@ -1251,7 +1247,7 @@
                 for (Integer netType : mUpstreamIfaceTypes) {
                     NetworkInfo info = null;
                     try {
-                        info = cm.getNetworkInfo(netType.intValue());
+                        info = mConnService.getNetworkInfo(netType.intValue());
                     } catch (RemoteException e) { }
                     if ((info != null) && info.isConnected()) {
                         upType = netType.intValue();
@@ -1283,7 +1279,7 @@
                 } else {
                     LinkProperties linkProperties = null;
                     try {
-                        linkProperties = cm.getLinkProperties(upType);
+                        linkProperties = mConnService.getLinkProperties(upType);
                     } catch (RemoteException e) { }
                     if (linkProperties != null) iface = linkProperties.getInterfaceName();
                 }
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 2cd3062..8ed7966 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -8682,7 +8682,8 @@
         if (needRelayout) {
             requestAnimationLocked(0);
         } else if (animating) {
-            requestAnimationLocked(currentTime+(1000/60)-SystemClock.uptimeMillis());
+            final int refreshTimeUs = (int)(1000 / mDisplay.getRefreshRate());
+            requestAnimationLocked(currentTime + refreshTimeUs - SystemClock.uptimeMillis());
         }
 
         // Finally update all input windows now that the window changes have stabilized.
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 5b74fb8..d2d2d8b 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -471,14 +471,20 @@
     if (mInitCheck != NO_ERROR)
         return mInitCheck;
 
+    SensorInterface* sensor = mSensorMap.valueFor(handle);
+    if (!sensor)
+        return BAD_VALUE;
+
     if (ns < 0)
         return BAD_VALUE;
 
+    if (ns == 0) {
+        ns = sensor->getSensor().getMinDelayNs();
+    }
+
     if (ns < MINIMUM_EVENTS_PERIOD)
         ns = MINIMUM_EVENTS_PERIOD;
 
-    SensorInterface* sensor = mSensorMap.valueFor(handle);
-    if (!sensor) return BAD_VALUE;
     return sensor->setDelay(connection.get(), handle, ns);
 }
 
diff --git a/services/surfaceflinger/Android.mk b/services/surfaceflinger/Android.mk
index 61a8358..b916bd7 100644
--- a/services/surfaceflinger/Android.mk
+++ b/services/surfaceflinger/Android.mk
@@ -30,6 +30,10 @@
 	LOCAL_CFLAGS += -DHAS_CONTEXT_PRIORITY -DNEVER_DEFAULT_TO_ASYNC_MODE
 endif
 
+ifneq (,$(findstring $(TARGET_DEVICE),tuna toro maguro))
+	LOCAL_CFLAGS += -DREFRESH_RATE=48
+endif
+
 
 LOCAL_SHARED_LIBRARIES := \
 	libcutils \
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
index f4be168..329c052 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
@@ -141,6 +141,17 @@
     mDpiY = mNativeWindow->ydpi;
     mRefreshRate = fbDev->fps;
 
+
+/* FIXME: this is a temporary HACK until we are able to report the refresh rate
+ * properly from the HAL. The WindowManagerService now relies on this value.
+ */
+#ifndef REFRESH_RATE
+    mRefreshRate = fbDev->fps;
+#else
+    mRefreshRate = REFRESH_RATE;
+#warning "refresh rate set via makefile to REFRESH_RATE"
+#endif
+
     EGLint w, h, dummy;
     EGLint numConfigs=0;
     EGLSurface surface;
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
index 97e7aa3..5b13603 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -27,12 +27,14 @@
 import android.net.LinkCapabilities;
 import android.net.LinkProperties;
 import android.net.NetworkInfo;
+import android.net.TrafficStats;
 import android.net.wifi.WifiManager;
 import android.os.AsyncResult;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.os.Messenger;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
@@ -56,6 +58,7 @@
  */
 public abstract class DataConnectionTracker extends Handler {
     protected static final boolean DBG = true;
+    protected static final boolean VDBG = false;
 
     /**
      * IDLE: ready to start data connection setup, default state
@@ -114,8 +117,8 @@
     protected static final int EVENT_RESTORE_DEFAULT_APN = BASE + 14;
     protected static final int EVENT_DISCONNECT_DONE = BASE + 15;
     protected static final int EVENT_DATA_CONNECTION_ATTACHED = BASE + 16;
-    protected static final int EVENT_START_NETSTAT_POLL = BASE + 17;
-    protected static final int EVENT_START_RECOVERY = BASE + 18;
+    protected static final int EVENT_DATA_STALL_ALARM = BASE + 17;
+    protected static final int EVENT_DO_RECOVERY = BASE + 18;
     protected static final int EVENT_APN_CHANGED = BASE + 19;
     protected static final int EVENT_CDMA_DATA_DETACHED = BASE + 20;
     protected static final int EVENT_NV_READY = BASE + 21;
@@ -189,19 +192,16 @@
 
     /**
      * After detecting a potential connection problem, this is the max number
-     * of subsequent polls before attempting a radio reset.  At this point,
-     * poll interval is 5 seconds (POLL_NETSTAT_SLOW_MILLIS), so set this to
-     * poll for about 2 more minutes.
+     * of subsequent polls before attempting recovery.
      */
     protected static final int NO_RECV_POLL_LIMIT = 24;
-
     // 1 sec. default polling interval when screen is on.
     protected static final int POLL_NETSTAT_MILLIS = 1000;
     // 10 min. default polling interval when screen is off.
     protected static final int POLL_NETSTAT_SCREEN_OFF_MILLIS = 1000*60*10;
     // 2 min for round trip time
     protected static final int POLL_LONGEST_RTT = 120 * 1000;
-    // 10 for packets without ack
+    // Default sent packets without ack which triggers initial recovery steps
     protected static final int NUMBER_SENT_PACKETS_OF_HANG = 10;
     // how long to wait before switching back to default APN
     protected static final int RESTORE_DEFAULT_APN_DELAY = 1 * 60 * 1000;
@@ -210,6 +210,13 @@
     // represents an invalid IP address
     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;
+    // 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
+    protected static final String DATA_STALL_ALARM_TAG_EXTRA = "data.stall.alram.tag";
+
     // TODO: See if we can remove INTENT_RECONNECT_ALARM
     //       having to have different values for GSM and
     //       CDMA. If so we can then remove the need for
@@ -240,11 +247,19 @@
 
     protected long mTxPkts;
     protected long mRxPkts;
-    protected long mSentSinceLastRecv;
     protected int mNetStatPollPeriod;
-    protected int mNoRecvPollCount = 0;
     protected boolean mNetStatPollEnabled = false;
 
+    protected TxRxSum mDataStallTxRxSum = new TxRxSum(0, 0);
+    // Used to track stale data stall alarms.
+    protected int mDataStallAlarmTag = (int) SystemClock.elapsedRealtime();
+    // The current data stall alarm intent
+    protected PendingIntent mDataStallAlarmIntent = null;
+    // Number of packets sent since the last received packet
+    protected long mSentSinceLastRecv;
+    // Controls when a simple recovery attempt it to be tried
+    protected int mNoRecvPollCount = 0;
+
     // wifi connection status will be updated by sticky intent
     protected boolean mIsWifiConnected = false;
 
@@ -313,7 +328,8 @@
             } else if (action.startsWith(getActionIntentReconnectAlarm())) {
                 log("Reconnect alarm. Previous state was " + mState);
                 onActionIntentReconnectAlarm(intent);
-
+            } else if (action.equals(getActionIntentDataStallAlarm())) {
+                onActionIntentDataStallAlarm(intent);
             } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
                 final android.net.NetworkInfo networkInfo = (NetworkInfo)
                         intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
@@ -363,6 +379,71 @@
         }
     }
 
+    /**
+     * Maintian the sum of transmit and receive packets.
+     *
+     * The packet counts are initizlied and reset to -1 and
+     * remain -1 until they can be updated.
+     */
+    public class TxRxSum {
+        public long txPkts;
+        public long rxPkts;
+
+        public TxRxSum() {
+            reset();
+        }
+
+        public TxRxSum(long txPkts, long rxPkts) {
+            this.txPkts = txPkts;
+            this.rxPkts = rxPkts;
+        }
+
+        public TxRxSum(TxRxSum sum) {
+            txPkts = sum.txPkts;
+            rxPkts = sum.rxPkts;
+        }
+
+        public void reset() {
+            txPkts = -1;
+            rxPkts = -1;
+        }
+
+        public String toString() {
+            return "{txSum=" + txPkts + " rxSum=" + rxPkts + "}";
+        }
+
+        public void updateTxRxSum() {
+            boolean txUpdated = false, rxUpdated = false;
+            long txSum = 0, rxSum = 0;
+            for (ApnContext apnContext : mApnContexts.values()) {
+                if (apnContext.getState() == State.CONNECTED) {
+                    DataConnectionAc dcac = apnContext.getDataConnectionAc();
+                    if (dcac == null) continue;
+
+                    LinkProperties linkProp = dcac.getLinkPropertiesSync();
+                    if (linkProp == null) continue;
+
+                    String iface = linkProp.getInterfaceName();
+
+                    if (iface != null) {
+                        long stats = TrafficStats.getTxPackets(iface);
+                        if (stats > 0) {
+                            txUpdated = true;
+                            txSum += stats;
+                        }
+                        stats = TrafficStats.getRxPackets(iface);
+                        if (stats > 0) {
+                            rxUpdated = true;
+                            rxSum += stats;
+                        }
+                    }
+                }
+            }
+            if (txUpdated) this.txPkts = txSum;
+            if (rxUpdated) this.rxPkts = rxSum;
+        }
+    }
+
     protected boolean isDataSetupCompleteOk(AsyncResult ar) {
         if (ar.exception != null) {
             if (DBG) log("isDataSetupCompleteOk return false, ar.result=" + ar.result);
@@ -394,6 +475,13 @@
         sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA));
     }
 
+    protected void onActionIntentDataStallAlarm(Intent intent) {
+        if (VDBG) log("onActionIntentDataStallAlarm: action=" + intent.getAction());
+        Message msg = obtainMessage(EVENT_DATA_STALL_ALARM, intent.getAction());
+        msg.arg1 = intent.getIntExtra(DATA_STALL_ALARM_TAG_EXTRA, 0);
+        sendMessage(msg);
+    }
+
     /**
      * Default constructor
      */
@@ -529,6 +617,7 @@
 
     // abstract methods
     protected abstract String getActionIntentReconnectAlarm();
+    protected abstract String getActionIntentDataStallAlarm();
     protected abstract void startNetStatPoll();
     protected abstract void stopNetStatPoll();
     protected abstract void restartRadio();
@@ -553,6 +642,10 @@
     protected abstract void onCleanUpAllConnections(String cause);
     protected abstract boolean isDataPossible(String apnType);
 
+    protected void onDataStallAlarm(int tag) {
+        loge("onDataStallAlarm: not impleted tag=" + tag);
+    }
+
     @Override
     public void handleMessage(Message msg) {
         switch (msg.what) {
@@ -575,6 +668,10 @@
                 onTrySetupData(reason);
                 break;
 
+            case EVENT_DATA_STALL_ALARM:
+                onDataStallAlarm(msg.arg1);
+                break;
+
             case EVENT_ROAMING_OFF:
                 if (getDataOnRoamingEnabled() == false) {
                     resetAllRetryCounts();
diff --git a/telephony/java/com/android/internal/telephony/EventLogTags.logtags b/telephony/java/com/android/internal/telephony/EventLogTags.logtags
index 9be7b80..427e5da 100644
--- a/telephony/java/com/android/internal/telephony/EventLogTags.logtags
+++ b/telephony/java/com/android/internal/telephony/EventLogTags.logtags
@@ -56,3 +56,18 @@
 
 # Bad IP address
 50117 bad_ip_address (ip_address|3)
+
+# Data Stall Recovery mode DATA_STALL_RECOVERY_GET_DATA_CALL_LIST
+50118 data_stall_recovery_get_data_call_list (out_packet_count|1|1)
+
+# Data Stall Recovery mode DATA_STALL_RECOVERY_CLEANUP
+50119 data_stall_recovery_cleanup (out_packet_count|1|1)
+
+# Data Stall Recovery mode DATA_STALL_RECOVERY_REREGISTER
+50120 data_stall_recovery_reregister (out_packet_count|1|1)
+
+# Data Stall Recovery mode DATA_STALL_RECOVERY_RADIO_RESTART
+50121 data_stall_recovery_radio_restart (out_packet_count|1|1)
+
+# Data Stall Recovery mode DATA_STALL_RECOVERY_RADIO_RESTART_WITH_PROP
+50122 data_stall_recovery_radio_restart_with_prop (out_packet_count|1|1)
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
index f5d05a1..5889372 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java
@@ -69,6 +69,10 @@
     private static final String INTENT_RECONNECT_ALARM =
         "com.android.internal.telephony.cdma-reconnect";
 
+    private static final String INTENT_DATA_STALL_ALARM =
+        "com.android.internal.telephony.cdma-data-stall";
+
+
     /**
      * Constants for the data connection activity:
      * physical link down/up
@@ -149,6 +153,11 @@
     }
 
     @Override
+    protected String getActionIntentDataStallAlarm() {
+        return INTENT_DATA_STALL_ALARM;
+    }
+
+    @Override
     protected void setState(State s) {
         if (DBG) log ("setState: " + s);
         if (mState != s) {
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index aa475e5..865caf6 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -96,22 +96,37 @@
     private boolean mReregisterOnReconnectFailure = false;
     private ContentResolver mResolver;
 
-    // Count of PDP reset attempts; reset when we see incoming,
-    // call reRegisterNetwork, or pingTest succeeds.
-    private int mPdpResetCount = 0;
-
     // Recovery action taken in case of data stall
-    enum RecoveryAction {REREGISTER, RADIO_RESTART, RADIO_RESET};
-    private RecoveryAction mRecoveryAction = RecoveryAction.REREGISTER;
-
+    class RecoveryAction {
+        public static final int GET_DATA_CALL_LIST      = 0;
+        public static final int CLEANUP                 = 1;
+        public static final int REREGISTER              = 2;
+        public static final int RADIO_RESTART           = 3;
+        public static final int RADIO_RESTART_WITH_PROP = 4;
+    }
+    public int getRecoveryAction() {
+        int action = Settings.System.getInt(mPhone.getContext().getContentResolver(),
+                "radio.data.stall.recovery.action", RecoveryAction.GET_DATA_CALL_LIST);
+        if (VDBG) log("getRecoveryAction: " + action);
+        return action;
+    }
+    public void putRecoveryAction(int action) {
+        Settings.System.putInt(mPhone.getContext().getContentResolver(),
+                "radio.data.stall.recovery.action", action);
+        if (VDBG) log("putRecoveryAction: " + action);
+    }
 
     //***** Constants
 
     private static final int POLL_PDP_MILLIS = 5 * 1000;
 
-    private static final String INTENT_RECONNECT_ALARM = "com.android.internal.telephony.gprs-reconnect";
+    private static final String INTENT_RECONNECT_ALARM =
+        "com.android.internal.telephony.gprs-reconnect";
     private static final String INTENT_RECONNECT_ALARM_EXTRA_TYPE = "type";
 
+    private static final String INTENT_DATA_STALL_ALARM =
+        "com.android.internal.telephony.gprs-data-stall";
+
     static final Uri PREFERAPN_URI = Uri.parse("content://telephony/carriers/preferapn");
     static final String APN_ID = "apn_id";
     private boolean canSetPreferApn = false;
@@ -163,6 +178,11 @@
         p.getServiceStateTracker().registerForPsRestrictedDisabled(this,
                 EVENT_PS_RESTRICT_DISABLED, null);
 
+        // install reconnect intent filter for this data connection.
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(INTENT_DATA_STALL_ALARM);
+        p.getContext().registerReceiver(mIntentReceiver, filter, null, p);
+
         mDataConnectionTracker = this;
         mResolver = mPhone.getContext().getContentResolver();
 
@@ -241,6 +261,11 @@
         return INTENT_RECONNECT_ALARM;
     }
 
+    @Override
+    protected String getActionIntentDataStallAlarm() {
+        return INTENT_DATA_STALL_ALARM;
+    }
+
     private ApnContext addApnContext(String type) {
         ApnContext apnContext = new ApnContext(type, LOG_TAG);
         apnContext.setDependencyMet(false);
@@ -552,6 +577,7 @@
          */
         if (DBG) log ("onDataConnectionDetached: stop polling and notify detached");
         stopNetStatPoll();
+        stopDataStallAlarm();
         notifyDataConnection(Phone.REASON_DATA_DETACHED);
     }
 
@@ -560,6 +586,7 @@
         if (getOverallState() == State.CONNECTED) {
             if (DBG) log("onDataConnectionAttached: start polling notify attached");
             startNetStatPoll();
+            startDataStallAlarm();
             notifyDataConnection(Phone.REASON_DATA_ATTACHED);
         } else {
             // update APN availability so that APN can be enabled.
@@ -764,6 +791,8 @@
         }
 
         stopNetStatPoll();
+        stopDataStallAlarm();
+
         // TODO: Do we need mRequestedApnType?
         mRequestedApnType = Phone.APN_TYPE_DEFAULT;
     }
@@ -1236,6 +1265,7 @@
         // setState(State.CONNECTED);
         mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
         startNetStatPoll();
+        startDataStallAlarm();
         // reset reconnect timer
         apnContext.getDataConnection().resetRetryCount();
     }
@@ -1250,59 +1280,62 @@
     private void resetPollStats() {
         mTxPkts = -1;
         mRxPkts = -1;
-        mSentSinceLastRecv = 0;
         mNetStatPollPeriod = POLL_NETSTAT_MILLIS;
-        mNoRecvPollCount = 0;
     }
 
     private void doRecovery() {
         if (getOverallState() == State.CONNECTED) {
-            int maxPdpReset = Settings.Secure.getInt(mResolver,
-                    Settings.Secure.PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT,
-                    DEFAULT_MAX_PDP_RESET_FAIL);
-            if (mPdpResetCount < maxPdpReset) {
-                mPdpResetCount++;
-                EventLog.writeEvent(EventLogTags.PDP_RADIO_RESET, mSentSinceLastRecv);
-                if (DBG) log("doRecovery() cleanup all connections mPdpResetCount < max");
+            // Go through a series of recovery steps, each action transitions to the next action
+            int recoveryAction = getRecoveryAction();
+            switch (recoveryAction) {
+            case RecoveryAction.GET_DATA_CALL_LIST:
+                EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_GET_DATA_CALL_LIST,
+                        mSentSinceLastRecv);
+                if (DBG) log("doRecovery() get data call list");
+                mPhone.mCM.getDataCallList(obtainMessage(EVENT_DATA_STATE_CHANGED));
+                putRecoveryAction(RecoveryAction.CLEANUP);
+                break;
+            case RecoveryAction.CLEANUP:
+                EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_CLEANUP, mSentSinceLastRecv);
+                if (DBG) log("doRecovery() cleanup all connections");
                 cleanUpAllConnections(true, Phone.REASON_PDP_RESET);
-            } else {
-                mPdpResetCount = 0;
-                switch (mRecoveryAction) {
-                case REREGISTER:
-                    EventLog.writeEvent(EventLogTags.PDP_REREGISTER_NETWORK, mSentSinceLastRecv);
-                    if (DBG) log("doRecovery() re-register getting preferred network type");
-                    mPhone.getServiceStateTracker().reRegisterNetwork(null);
-                    mRecoveryAction = RecoveryAction.RADIO_RESTART;
-                    break;
-                case RADIO_RESTART:
-                    EventLog.writeEvent(EventLogTags.PDP_RADIO_RESET, mSentSinceLastRecv);
-                    if (DBG) log("restarting radio");
-                    mRecoveryAction = RecoveryAction.RADIO_RESET;
-                    restartRadio();
-                    break;
-                case RADIO_RESET:
-                    // This is in case radio restart has not recovered the data.
-                    // It will set an additional "gsm.radioreset" property to tell
-                    // RIL or system to take further action.
-                    // The implementation of hard reset recovery action is up to OEM product.
-                    // Once gsm.radioreset property is consumed, it is expected to set back
-                    // to false by RIL.
-                    EventLog.writeEvent(EventLogTags.PDP_RADIO_RESET, -1);
-                    if (DBG) log("restarting radio with reset indication");
-                    SystemProperties.set("gsm.radioreset", "true");
-                    // give 1 sec so property change can be notified.
-                    try {
-                        Thread.sleep(1000);
-                    } catch (InterruptedException e) {}
-                    restartRadio();
-                    break;
-                default:
-                    throw new RuntimeException("doRecovery: Invalid mRecoveryAction " +
-                        mRecoveryAction);
-                }
+                putRecoveryAction(RecoveryAction.REREGISTER);
+                break;
+            case RecoveryAction.REREGISTER:
+                EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_REREGISTER,
+                        mSentSinceLastRecv);
+                if (DBG) log("doRecovery() re-register");
+                mPhone.getServiceStateTracker().reRegisterNetwork(null);
+                putRecoveryAction(RecoveryAction.RADIO_RESTART);
+                break;
+            case RecoveryAction.RADIO_RESTART:
+                EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_RADIO_RESTART,
+                        mSentSinceLastRecv);
+                if (DBG) log("restarting radio");
+                putRecoveryAction(RecoveryAction.RADIO_RESTART_WITH_PROP);
+                restartRadio();
+                break;
+            case RecoveryAction.RADIO_RESTART_WITH_PROP:
+                // This is in case radio restart has not recovered the data.
+                // It will set an additional "gsm.radioreset" property to tell
+                // RIL or system to take further action.
+                // The implementation of hard reset recovery action is up to OEM product.
+                // Once gsm.radioreset property is consumed, it is expected to set back
+                // to false by RIL.
+                EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_RADIO_RESTART_WITH_PROP, -1);
+                if (DBG) log("restarting radio with gsm.radioreset to true");
+                SystemProperties.set("gsm.radioreset", "true");
+                // give 1 sec so property change can be notified.
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {}
+                restartRadio();
+                putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
+                break;
+            default:
+                throw new RuntimeException("doRecovery: Invalid recoveryAction=" +
+                    recoveryAction);
             }
-        } else {
-            if (DBG) log("doRecovery(): ignore, we're not connected");
         }
     }
 
@@ -1340,119 +1373,130 @@
         SystemProperties.set("net.ppp.reset-by-timeout", String.valueOf(reset+1));
     }
 
+
+    private void updateDataStallInfo() {
+        long sent, received;
+
+        TxRxSum preTxRxSum = new TxRxSum(mDataStallTxRxSum);
+        mDataStallTxRxSum.updateTxRxSum();
+
+        if (VDBG) {
+            log("updateDataStallInfo: mDataStallTxRxSum=" + mDataStallTxRxSum +
+                    " preTxRxSum=" + preTxRxSum);
+        }
+
+        sent = mDataStallTxRxSum.txPkts - preTxRxSum.txPkts;
+        received = mDataStallTxRxSum.rxPkts - preTxRxSum.rxPkts;
+
+        if (VDBG) {
+            if (SystemProperties.getBoolean("radio.test.data.stall", false)) {
+                log("updateDataStallInfo: radio.test.data.stall true received = 0;");
+                received = 0;
+            }
+        }
+        if ( sent > 0 && received > 0 ) {
+            if (VDBG) log("updateDataStallInfo: IN/OUT");
+            mSentSinceLastRecv = 0;
+            putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
+        } else if (sent > 0 && received == 0) {
+            if (mPhone.getState() == Phone.State.IDLE) {
+                mSentSinceLastRecv += sent;
+            } else {
+                mSentSinceLastRecv = 0;
+            }
+            if (DBG) {
+                log("updateDataStallInfo: OUT sent=" + sent +
+                        " mSentSinceLastRecv=" + mSentSinceLastRecv);
+            }
+        } else if (sent == 0 && received > 0) {
+            if (VDBG) log("updateDataStallInfo: IN");
+            mSentSinceLastRecv = 0;
+            putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
+        } else {
+            if (VDBG) log("updateDataStallInfo: NONE");
+        }
+    }
+
+    @Override
+    protected void onDataStallAlarm(int tag) {
+        if (mDataStallAlarmTag != tag) {
+            if (DBG) {
+                log("onDataStallAlarm: ignore, tag=" + tag + " expecting " + mDataStallAlarmTag);
+            }
+            return;
+        }
+        updateDataStallInfo();
+
+        int hangWatchdogTrigger = Settings.Secure.getInt(mResolver,
+                Settings.Secure.PDP_WATCHDOG_TRIGGER_PACKET_COUNT,
+                NUMBER_SENT_PACKETS_OF_HANG);
+
+        if (mSentSinceLastRecv >= hangWatchdogTrigger) {
+            if (DBG) {
+                log("onDataStallAlarm: tag=" + tag + " do recovery action=" + getRecoveryAction());
+            }
+            sendMessage(obtainMessage(EVENT_DO_RECOVERY));
+        } else {
+            if (VDBG) {
+                log("onDataStallAlarm: tag=" + tag + " Sent " + String.valueOf(mSentSinceLastRecv) +
+                    " pkts since last received, < watchdogTrigger=" + hangWatchdogTrigger);
+            }
+        }
+        startDataStallAlarm();
+    }
+
+
+    private void updateDataActivity() {
+        long sent, received;
+
+        Activity newActivity;
+
+        TxRxSum preTxRxSum = new TxRxSum(mTxPkts, mRxPkts);
+        TxRxSum curTxRxSum = new TxRxSum();
+        curTxRxSum.updateTxRxSum();
+        mTxPkts = curTxRxSum.txPkts;
+        mRxPkts = curTxRxSum.rxPkts;
+
+        if (VDBG) {
+            log("updateDataActivity: curTxRxSum=" + curTxRxSum + " preTxRxSum=" + preTxRxSum);
+        }
+
+        if (mNetStatPollEnabled && (preTxRxSum.txPkts > 0 || preTxRxSum.rxPkts > 0)) {
+            sent = mTxPkts - preTxRxSum.txPkts;
+            received = mRxPkts - preTxRxSum.rxPkts;
+
+            if (VDBG) log("updateDataActivity: sent=" + sent + " received=" + received);
+            if ( sent > 0 && received > 0 ) {
+                newActivity = Activity.DATAINANDOUT;
+            } else if (sent > 0 && received == 0) {
+                newActivity = Activity.DATAOUT;
+            } else if (sent == 0 && received > 0) {
+                newActivity = Activity.DATAIN;
+            } else {
+                newActivity = Activity.NONE;
+            }
+
+            if (mActivity != newActivity && mIsScreenOn) {
+                if (VDBG) log("updateDataActivity: newActivity=" + newActivity);
+                mActivity = newActivity;
+                mPhone.notifyDataActivity();
+            }
+        }
+    }
+
     private Runnable mPollNetStat = new Runnable()
     {
-
+        @Override
         public void run() {
-            long sent, received;
-            long preTxPkts = -1, preRxPkts = -1;
+            updateDataActivity();
 
-            Activity newActivity;
-
-            preTxPkts = mTxPkts;
-            preRxPkts = mRxPkts;
-
-            long txSum = 0, rxSum = 0;
-            for (ApnContext apnContext : mApnContexts.values()) {
-                if (apnContext.getState() == State.CONNECTED) {
-                    DataConnectionAc dcac = apnContext.getDataConnectionAc();
-                    if (dcac == null) continue;
-
-                    LinkProperties linkProp = dcac.getLinkPropertiesSync();
-                    if (linkProp == null) continue;
-
-                    String iface = linkProp.getInterfaceName();
-
-                    if (iface != null) {
-                        long stats = TrafficStats.getTxPackets(iface);
-                        if (stats > 0) txSum += stats;
-                        stats = TrafficStats.getRxPackets(iface);
-                        if (stats > 0) rxSum += stats;
-                    }
-                }
-            }
-
-            mTxPkts = txSum;
-            mRxPkts = rxSum;
-
-            // log("tx " + mTxPkts + " rx " + mRxPkts);
-
-            if (mNetStatPollEnabled && (preTxPkts > 0 || preRxPkts > 0)) {
-                sent = mTxPkts - preTxPkts;
-                received = mRxPkts - preRxPkts;
-
-                if ( sent > 0 && received > 0 ) {
-                    mSentSinceLastRecv = 0;
-                    newActivity = Activity.DATAINANDOUT;
-                    mPdpResetCount = 0;
-                    mRecoveryAction = RecoveryAction.REREGISTER;
-                } else if (sent > 0 && received == 0) {
-                    if (mPhone.getState() == Phone.State.IDLE) {
-                        mSentSinceLastRecv += sent;
-                    } else {
-                        mSentSinceLastRecv = 0;
-                    }
-                    newActivity = Activity.DATAOUT;
-                } else if (sent == 0 && received > 0) {
-                    mSentSinceLastRecv = 0;
-                    newActivity = Activity.DATAIN;
-                    mPdpResetCount = 0;
-                    mRecoveryAction = RecoveryAction.REREGISTER;
-                } else if (sent == 0 && received == 0) {
-                    newActivity = Activity.NONE;
-                } else {
-                    mSentSinceLastRecv = 0;
-                    newActivity = Activity.NONE;
-                }
-
-                if (mActivity != newActivity && mIsScreenOn) {
-                    mActivity = newActivity;
-                    mPhone.notifyDataActivity();
-                }
-            }
-
-            int watchdogTrigger = Settings.Secure.getInt(mResolver,
-                    Settings.Secure.PDP_WATCHDOG_TRIGGER_PACKET_COUNT,
-                    NUMBER_SENT_PACKETS_OF_HANG);
-
-            if (mSentSinceLastRecv >= watchdogTrigger) {
-                // we already have NUMBER_SENT_PACKETS sent without ack
-                if (mNoRecvPollCount == 0) {
-                    EventLog.writeEvent(EventLogTags.PDP_RADIO_RESET_COUNTDOWN_TRIGGERED,
-                            mSentSinceLastRecv);
-                }
-
-                int noRecvPollLimit = Settings.Secure.getInt(mResolver,
-                        Settings.Secure.PDP_WATCHDOG_ERROR_POLL_COUNT, NO_RECV_POLL_LIMIT);
-
-                if (mNoRecvPollCount < noRecvPollLimit) {
-                    // It's possible the PDP context went down and we weren't notified.
-                    // Start polling the context list in an attempt to recover.
-                    if (DBG) log("Polling: no DATAIN in a while; polling PDP");
-                    mPhone.mCM.getDataCallList(obtainMessage(EVENT_DATA_STATE_CHANGED));
-
-                    mNoRecvPollCount++;
-
-                    // Slow down the poll interval to let things happen
-                    mNetStatPollPeriod = Settings.Secure.getInt(mResolver,
-                            Settings.Secure.PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS,
-                            POLL_NETSTAT_SLOW_MILLIS);
-                } else {
-                    if (DBG) log("Polling: Sent " + String.valueOf(mSentSinceLastRecv) +
-                                        " pkts since last received start recovery process");
-                    mNoRecvPollCount = 0;
-                    sendMessage(obtainMessage(EVENT_START_RECOVERY));
-                }
+            if (mIsScreenOn) {
+                mNetStatPollPeriod = Settings.Secure.getInt(mResolver,
+                        Settings.Secure.PDP_WATCHDOG_POLL_INTERVAL_MS, POLL_NETSTAT_MILLIS);
             } else {
-                mNoRecvPollCount = 0;
-                if (mIsScreenOn) {
-                    mNetStatPollPeriod = Settings.Secure.getInt(mResolver,
-                            Settings.Secure.PDP_WATCHDOG_POLL_INTERVAL_MS, POLL_NETSTAT_MILLIS);
-                } else {
-                    mNetStatPollPeriod = Settings.Secure.getInt(mResolver,
-                            Settings.Secure.PDP_WATCHDOG_LONG_POLL_INTERVAL_MS,
-                            POLL_NETSTAT_SCREEN_OFF_MILLIS);
-                }
+                mNetStatPollPeriod = Settings.Secure.getInt(mResolver,
+                        Settings.Secure.PDP_WATCHDOG_LONG_POLL_INTERVAL_MS,
+                        POLL_NETSTAT_SCREEN_OFF_MILLIS);
             }
 
             if (mNetStatPollEnabled) {
@@ -1564,6 +1608,41 @@
 
     }
 
+    private void startDataStallAlarm() {
+        int delayInMs = Settings.Secure.getInt(mResolver,
+                            Settings.Secure.DATA_STALL_ALARM_DELAY_IN_MS,
+                            DATA_STALL_ALARM_DELAY_IN_MS_DEFAULT);
+        mDataStallAlarmTag += 1;
+        if (DBG) {
+            log("startDataStallAlarm: tag=" + mDataStallAlarmTag +
+                    " delay=" + (delayInMs / 1000) + "s");
+        }
+        AlarmManager am =
+            (AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
+
+        Intent intent = new Intent(INTENT_DATA_STALL_ALARM);
+        intent.putExtra(DATA_STALL_ALARM_TAG_EXTRA, mDataStallAlarmTag);
+        mDataStallAlarmIntent = PendingIntent.getBroadcast(mPhone.getContext(), 0, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT);
+        am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                SystemClock.elapsedRealtime() + delayInMs, mDataStallAlarmIntent);
+    }
+
+    private void stopDataStallAlarm() {
+        AlarmManager am =
+            (AlarmManager) mPhone.getContext().getSystemService(Context.ALARM_SERVICE);
+
+        if (DBG) {
+            log("stopDataStallAlarm: current tag=" + mDataStallAlarmTag +
+                    " mDataStallAlarmIntent=" + mDataStallAlarmIntent);
+        }
+        mDataStallAlarmTag += 1;
+        if (mDataStallAlarmIntent != null) {
+            am.cancel(mDataStallAlarmIntent);
+            mDataStallAlarmIntent = null;
+        }
+    }
+
     private void notifyNoData(GsmDataConnection.FailCause lastFailCauseCode,
                               ApnContext apnContext) {
         if (DBG) log( "notifyNoData: type=" + apnContext.getApnType());
@@ -1928,6 +2007,7 @@
         if (isConnected() && ! mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
             if (DBG) log("onVoiceCallStarted stop polling");
             stopNetStatPoll();
+            stopDataStallAlarm();
             notifyDataConnection(Phone.REASON_VOICE_CALL_STARTED);
         }
     }
@@ -1938,6 +2018,7 @@
         if (isConnected()) {
             if (!mPhone.getServiceStateTracker().isConcurrentVoiceAndDataAllowed()) {
                 startNetStatPoll();
+                startDataStallAlarm();
                 notifyDataConnection(Phone.REASON_VOICE_CALL_ENDED);
             } else {
                 // clean slate after call end.
@@ -2249,11 +2330,7 @@
                 onPollPdp();
                 break;
 
-            case EVENT_START_NETSTAT_POLL:
-                startNetStatPoll();
-                break;
-
-            case EVENT_START_RECOVERY:
+            case EVENT_DO_RECOVERY:
                 doRecovery();
                 break;
 
@@ -2270,6 +2347,7 @@
                  */
                 if (DBG) log("EVENT_PS_RESTRICT_ENABLED " + mIsPsRestricted);
                 stopNetStatPoll();
+                stopDataStallAlarm();
                 mIsPsRestricted = true;
                 break;
 
@@ -2282,6 +2360,7 @@
                 mIsPsRestricted  = false;
                 if (isConnected()) {
                     startNetStatPoll();
+                    startDataStallAlarm();
                 } else {
                     // TODO: Should all PDN states be checked to fail?
                     if (mState == State.FAILED) {
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index e981da7..00fae2e 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -211,7 +211,7 @@
     static final int CMD_STOP_SUPPLICANT                  = BASE + 12;
     /* Start the driver */
     static final int CMD_START_DRIVER                     = BASE + 13;
-    /* Start the driver */
+    /* Stop the driver */
     static final int CMD_STOP_DRIVER                      = BASE + 14;
     /* Indicates Static IP succeded */
     static final int CMD_STATIC_IP_SUCCESS                = BASE + 15;
@@ -219,6 +219,9 @@
     static final int CMD_STATIC_IP_FAILURE                = BASE + 16;
     /* Indicates supplicant stop failed */
     static final int CMD_STOP_SUPPLICANT_FAILED           = BASE + 17;
+    /* Delayed stop to avoid shutting down driver too quick*/
+    static final int CMD_DELAYED_STOP_DRIVER              = BASE + 18;
+
 
     /* Start the soft access point */
     static final int CMD_START_AP                         = BASE + 21;
@@ -389,6 +392,13 @@
     private static final int MIN_INTERVAL_ENABLE_ALL_NETWORKS_MS = 10 * 60 * 1000; /* 10 minutes */
     private long mLastEnableAllNetworksTime;
 
+    /**
+     * Starting and shutting down driver too quick causes problems leading to driver
+     * being in a bad state. Delay driver stop.
+     */
+    private static final int DELAYED_DRIVER_STOP_MS = 2 * 60 * 1000; /* 2 minutes */
+    private int mDelayedStopCounter;
+    private boolean mInDelayedStop = false;
 
     private static final int MIN_RSSI = -200;
     private static final int MAX_RSSI = 256;
@@ -1651,6 +1661,7 @@
             mDhcpInfoInternal = dhcpInfoInternal;
         }
         mLastSignalLevel = -1; // force update of signal strength
+        mReconnectCount = 0; //Reset IP failure tracking
         WifiConfigStore.setIpConfiguration(mLastNetworkId, dhcpInfoInternal);
         InetAddress addr = NetworkUtils.numericToInetAddress(dhcpInfoInternal.ipAddress);
         mWifiInfo.setInetAddress(addr);
@@ -1779,6 +1790,7 @@
                 case CMD_STOP_SUPPLICANT_FAILED:
                 case CMD_START_DRIVER:
                 case CMD_STOP_DRIVER:
+                case CMD_DELAYED_STOP_DRIVER:
                 case CMD_START_AP:
                 case CMD_START_AP_SUCCESS:
                 case CMD_START_AP_FAILURE:
@@ -2441,6 +2453,7 @@
             EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
 
             mIsRunning = true;
+            mInDelayedStop = false;
             updateBatteryWorkSource(null);
 
             /**
@@ -2520,6 +2533,30 @@
                     WifiNative.setBluetoothCoexistenceScanModeCommand(mBluetoothConnectionActive);
                     break;
                 case CMD_STOP_DRIVER:
+                    /* Already doing a delayed stop */
+                    if (mInDelayedStop) {
+                        if (DBG) log("Already in delayed stop");
+                        break;
+                    }
+                    mInDelayedStop = true;
+                    mDelayedStopCounter++;
+                    if (DBG) log("Delayed stop message " + mDelayedStopCounter);
+                    sendMessageDelayed(obtainMessage(CMD_DELAYED_STOP_DRIVER, mDelayedStopCounter,
+                            0), DELAYED_DRIVER_STOP_MS);
+                    break;
+                case CMD_START_DRIVER:
+                    if (mInDelayedStop) {
+                        mInDelayedStop = false;
+                        mDelayedStopCounter++;
+                        if (DBG) log("Delayed stop ignored due to start");
+                    }
+                    break;
+                case CMD_DELAYED_STOP_DRIVER:
+                    if (message.arg1 != mDelayedStopCounter) break;
+                    if (getCurrentState() != mDisconnectedState) {
+                        WifiNative.disconnectCommand();
+                        handleNetworkDisconnect();
+                    }
                     mWakeLock.acquire();
                     WifiNative.stopDriverCommand();
                     transitionTo(mDriverStoppingState);
@@ -2878,10 +2915,6 @@
                   /* Ignore */
               case WifiMonitor.NETWORK_CONNECTION_EVENT:
                   break;
-              case CMD_STOP_DRIVER:
-                  sendMessage(CMD_DISCONNECT);
-                  deferMessage(message);
-                  break;
               case CMD_SET_SCAN_MODE:
                   if (message.arg1 == SCAN_ONLY_MODE) {
                       sendMessage(CMD_DISCONNECT);
@@ -2936,10 +2969,6 @@
                     WifiNative.disconnectCommand();
                     transitionTo(mDisconnectingState);
                     break;
-                case CMD_STOP_DRIVER:
-                    sendMessage(CMD_DISCONNECT);
-                    deferMessage(message);
-                    break;
                 case CMD_REQUEST_CM_WAKELOCK:
                     checkAndSetConnectivityInstance();
                     mCm.requestNetworkTransitionWakelock(TAG);
@@ -3035,9 +3064,6 @@
         public boolean processMessage(Message message) {
             if (DBG) log(getName() + message.toString() + "\n");
             switch (message.what) {
-                case CMD_STOP_DRIVER: /* Stop driver only after disconnect handled */
-                    deferMessage(message);
-                    break;
                 case CMD_SET_SCAN_MODE:
                     if (message.arg1 == SCAN_ONLY_MODE) {
                         deferMessage(message);