Merge "Fully deferred displaylist replay" into jb-mr2-dev
diff --git a/api/current.txt b/api/current.txt
index 3132a91..1668170 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5919,7 +5919,6 @@
     field public static final java.lang.String ACTION_USER_INITIALIZE = "android.intent.action.USER_INITIALIZE";
     field public static final java.lang.String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT";
     field public static final java.lang.String ACTION_VIEW = "android.intent.action.VIEW";
-    field public static final java.lang.String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
     field public static final java.lang.String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
     field public static final deprecated java.lang.String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
     field public static final java.lang.String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 9fa7dbb..1c02960 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -63,6 +63,7 @@
 
     private int mRepeat = 0;
     private int mUserId;
+    private String mReceiverPermission;
 
     private String mProfileFile;
 
@@ -332,6 +333,8 @@
                 mStartFlags |= ActivityManager.START_FLAG_OPENGL_TRACES;
             } else if (opt.equals("--user")) {
                 mUserId = parseUserArg(nextArgRequired());
+            } else if (opt.equals("--receiver-permission")) {
+                mReceiverPermission = nextArgRequired();
             } else {
                 System.err.println("Error: Unknown option: " + opt);
                 return null;
@@ -608,7 +611,7 @@
         Intent intent = makeIntent(UserHandle.USER_ALL);
         IntentReceiver receiver = new IntentReceiver();
         System.out.println("Broadcasting: " + intent);
-        mAm.broadcastIntent(null, intent, null, receiver, 0, null, null, null,
+        mAm.broadcastIntent(null, intent, null, receiver, 0, null, null, mReceiverPermission,
                 android.app.AppOpsManager.OP_NONE, true, false, mUserId);
         receiver.waitForFinish();
     }
@@ -1408,6 +1411,7 @@
                 "am broadcast: send a broadcast Intent.  Options are:\n" +
                 "    --user <USER_ID> | all | current: Specify which user to send to; if not\n" +
                 "        specified then send to all users.\n" +
+                "    --receiver-permission <PERMISSION>: Require receiver to hold permission.\n" +
                 "\n" +
                 "am instrument: start an Instrumentation.  Typically this target <COMPONENT>\n" +
                 "  is the form <TEST_PACKAGE>/<RUNNER_CLASS>.  Options are:\n" +
diff --git a/cmds/app_process/app_main.cpp b/cmds/app_process/app_main.cpp
index 6fe358c..0668be6 100644
--- a/cmds/app_process/app_main.cpp
+++ b/cmds/app_process/app_main.cpp
@@ -13,7 +13,9 @@
 #include <cutils/process_name.h>
 #include <cutils/memory.h>
 #include <android_runtime/AndroidRuntime.h>
+#include <sys/personality.h>
 
+#include <stdlib.h>
 #include <stdio.h>
 #include <unistd.h>
 
@@ -128,8 +130,32 @@
     strlcpy(const_cast<char *>(argv0), newArgv0, strlen(argv0));
 }
 
-int main(int argc, const char* const argv[])
+int main(int argc, char* const argv[])
 {
+#ifdef __arm__
+    /*
+     * b/7188322 - Temporarily revert to the compat memory layout
+     * to avoid breaking third party apps.
+     *
+     * THIS WILL GO AWAY IN A FUTURE ANDROID RELEASE.
+     *
+     * http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=commitdiff;h=7dbaa466
+     * changes the kernel mapping from bottom up to top-down.
+     * This breaks some programs which improperly embed
+     * an out of date copy of Android's linker.
+     */
+    if (getenv("NO_ADDR_COMPAT_LAYOUT_FIXUP") == NULL) {
+        int current = personality(0xFFFFFFFF);
+        if ((current & ADDR_COMPAT_LAYOUT) == 0) {
+            personality(current | ADDR_COMPAT_LAYOUT);
+            setenv("NO_ADDR_COMPAT_LAYOUT_FIXUP", "1", 1);
+            execv("/system/bin/app_process", argv);
+            return -1;
+        }
+    }
+    unsetenv("NO_ADDR_COMPAT_LAYOUT_FIXUP");
+#endif
+
     // These are global variables in ProcessState.cpp
     mArgC = argc;
     mArgV = argv;
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 6aac723..f8b7a0c 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -18,9 +18,11 @@
 
 import android.app.Activity;
 import android.content.Intent;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.IntentFilter;
 import android.content.BroadcastReceiver;
+import android.content.res.Resources;
 import android.database.SQLException;
 import android.os.Bundle;
 import android.os.Handler;
@@ -44,6 +46,7 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import com.android.internal.R;
 import com.google.android.collect.Maps;
 
 /**
@@ -1777,8 +1780,11 @@
                                     };
                                     // have many accounts, launch the chooser
                                     Intent intent = new Intent();
-                                    intent.setClassName("android",
-                                            "android.accounts.ChooseAccountActivity");
+                                    ComponentName componentName = ComponentName.unflattenFromString(
+                                            Resources.getSystem().getString(
+                                                    R.string.config_chooseAccountActivity));
+                                    intent.setClassName(componentName.getPackageName(),
+                                            componentName.getClassName());
                                     intent.putExtra(KEY_ACCOUNTS, accounts);
                                     intent.putExtra(KEY_ACCOUNT_MANAGER_RESPONSE,
                                             new AccountManagerResponse(chooseResponse));
@@ -1934,7 +1940,10 @@
             String[] addAccountRequiredFeatures,
             Bundle addAccountOptions) {
         Intent intent = new Intent();
-        intent.setClassName("android", "android.accounts.ChooseTypeAndAccountActivity");
+        ComponentName componentName = ComponentName.unflattenFromString(
+                Resources.getSystem().getString(R.string.config_chooseTypeAndAccountActivity));
+        intent.setClassName(componentName.getPackageName(),
+                componentName.getClassName());
         intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST,
                 allowableAccounts);
         intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY,
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index ed4036d..d753e32 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -185,7 +185,7 @@
         TypedArray a =
                 context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Animator);
 
-        long duration = a.getInt(com.android.internal.R.styleable.Animator_duration, 0);
+        long duration = a.getInt(com.android.internal.R.styleable.Animator_duration, 300);
 
         long startDelay = a.getInt(com.android.internal.R.styleable.Animator_startOffset, 0);
 
diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java
index 6172aab..4026f7f 100644
--- a/core/java/android/animation/KeyframeSet.java
+++ b/core/java/android/animation/KeyframeSet.java
@@ -21,6 +21,7 @@
 import android.animation.Keyframe.IntKeyframe;
 import android.animation.Keyframe.FloatKeyframe;
 import android.animation.Keyframe.ObjectKeyframe;
+import android.util.Log;
 
 /**
  * This class holds a collection of Keyframe objects and is called by ValueAnimator to calculate
@@ -56,24 +57,36 @@
         } else {
             keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
             for (int i = 1; i < numKeyframes; ++i) {
-                keyframes[i] = (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
+                keyframes[i] =
+                        (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
             }
         }
         return new IntKeyframeSet(keyframes);
     }
 
     public static KeyframeSet ofFloat(float... values) {
+        boolean badValue = false;
         int numKeyframes = values.length;
         FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
         if (numKeyframes == 1) {
             keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
             keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
+            if (Float.isNaN(values[0])) {
+                badValue = true;
+            }
         } else {
             keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
             for (int i = 1; i < numKeyframes; ++i) {
-                keyframes[i] = (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
+                keyframes[i] =
+                        (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
+                if (Float.isNaN(values[i])) {
+                    badValue = true;
+                }
             }
         }
+        if (badValue) {
+            Log.w("Animator", "Bad value (NaN) in float animator");
+        }
         return new FloatKeyframeSet(keyframes);
     }
 
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index f7460c4..4a58072 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -1015,6 +1015,7 @@
         mRunning = false;
         mStarted = false;
         mStartListenersCalled = false;
+        mPlayingBackwards = false;
     }
 
     /**
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 37804e9..20114cc 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -261,6 +261,7 @@
                 context.getContentResolver()) : null;
         try {
             intent.setAllowFds(false);
+            intent.migrateExtraStreamToClipData();
             IIntentSender target =
                 ActivityManagerNative.getDefault().getIntentSender(
                     ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
@@ -285,6 +286,7 @@
                 context.getContentResolver()) : null;
         try {
             intent.setAllowFds(false);
+            intent.migrateExtraStreamToClipData();
             IIntentSender target =
                 ActivityManagerNative.getDefault().getIntentSender(
                     ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
index 18492ab..f44dc5c0 100644
--- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java
+++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
@@ -529,7 +529,8 @@
             case FORMAT_UINT32:
                 mValue[offset++] = (byte)(value & 0xFF);
                 mValue[offset++] = (byte)((value >> 8) & 0xFF);
-                mValue[offset] = (byte)((value >> 16) & 0xFF);
+                mValue[offset++] = (byte)((value >> 16) & 0xFF);
+                mValue[offset] = (byte)((value >> 24) & 0xFF);
                 break;
 
             default:
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index 91a1a94..6b69377 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -534,7 +534,7 @@
      *
      * <p>The connection may not be established right away, but will be
      * completed when the remote device is available. A
-     * {@link BluetoothGattCallback#onConnectionStateChange} callback will be
+     * {@link BluetoothGattServerCallback#onConnectionStateChange} callback will be
      * invoked when the connection state changes as a result of this function.
      *
      * <p>The autoConnect paramter determines whether to actively connect to
@@ -553,7 +553,7 @@
      * @return true, if the connection attempt was initiated successfully
      */
     public boolean connect(BluetoothDevice device, boolean autoConnect) {
-        if (DBG) Log.d(TAG, "connect: " + device.getAddress() + ", auto: " + autoConnect);
+        if (DBG) Log.d(TAG, "connect() - device: " + device.getAddress() + ", auto: " + autoConnect);
         if (mService == null || mServerIf == 0) return false;
 
         try {
diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
index 3ba4f26..43c2392 100644
--- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
+++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
@@ -373,4 +373,14 @@
     public void setDependencyMet(boolean met) {
         // not supported on this network
     }
+
+    @Override
+    public void addStackedLink(LinkProperties link) {
+        mLinkProperties.addStackedLink(link);
+    }
+
+    @Override
+    public void removeStackedLink(LinkProperties link) {
+        mLinkProperties.removeStackedLink(link);
+    }
 }
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 8aef405..4968268 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -1256,6 +1256,13 @@
      * interfaces that are cheaper and/or unnatural for a table-like
      * model.
      *
+     * <p class="note"><strong>WARNING:</strong> The framework does no permission checking
+     * on this entry into the content provider besides the basic ability for the application
+     * to get access to the provider at all.  For example, it has no idea whether the call
+     * being executed may read or write data in the provider, so can't enforce those
+     * individual permissions.  Any implementation of this method <strong>must</strong>
+     * do its own permission checks on incoming calls to make sure they are allowed.</p>
+     *
      * @param method method name to call.  Opaque to framework, but should not be {@code null}.
      * @param arg provider-defined String argument.  May be {@code null}.
      * @param extras provider-defined Bundle argument.  May be {@code null}.
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 4e89dec..5d7d677 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -68,7 +68,7 @@
                 try {
                     // Ensure the cursor window is filled.
                     cursor.getCount();
-                    registerContentObserver(cursor, mObserver);
+                    cursor.registerContentObserver(mObserver);
                 } catch (RuntimeException ex) {
                     cursor.close();
                     throw ex;
@@ -93,14 +93,6 @@
         }
     }
 
-    /**
-     * Registers an observer to get notifications from the content provider
-     * when the cursor needs to be refreshed.
-     */
-    void registerContentObserver(Cursor cursor, ContentObserver observer) {
-        cursor.registerContentObserver(mObserver);
-    }
-
     /* Runs on the UI thread */
     @Override
     public void deliverResult(Cursor cursor) {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 60e9f58..53c47d2 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1164,12 +1164,13 @@
      * additional optional contextual information about where the user was when they requested
      * the voice assist.
      * Output: nothing.
+     * @hide
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
 
     /**
-     * An optional field on {@link #ACTION_ASSIST} and {@link #ACTION_VOICE_ASSIST}
+     * An optional field on {@link #ACTION_ASSIST}
      * containing the name of the current foreground application package at the time
      * the assist was invoked.
      */
@@ -1177,7 +1178,7 @@
             = "android.intent.extra.ASSIST_PACKAGE";
 
     /**
-     * An optional field on {@link #ACTION_ASSIST} and {@link #ACTION_VOICE_ASSIST}
+     * An optional field on {@link #ACTION_ASSIST}
      * containing additional contextual information supplied by the current
      * foreground app at the time of the assist request.  This is a {@link Bundle} of
      * additional data.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c507245..0d463ee 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -691,6 +691,17 @@
     public static final int DELETE_ALL_USERS = 0x00000002;
 
     /**
+     * Flag parameter for {@link #deletePackage} to indicate that, if you are calling
+     * uninstall on a system that has been updated, then don't do the normal process
+     * of uninstalling the update and rolling back to the older system version (which
+     * needs to happen for all users); instead, just mark the app as uninstalled for
+     * the current user.
+     *
+     * @hide
+     */
+    public static final int DELETE_SYSTEM_APP = 0x00000004;
+
+    /**
      * Return code for when package deletion succeeds. This is passed to the
      * {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
      * succeeded in deleting the package.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index e1887bc..5eac903 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1941,6 +1941,28 @@
                     return false;
                 }
 
+            } else if (tagName.equals("library")) {
+                sa = res.obtainAttributes(attrs,
+                        com.android.internal.R.styleable.AndroidManifestLibrary);
+
+                // Note: don't allow this value to be a reference to a resource
+                // that may change.
+                String lname = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestLibrary_name);
+
+                sa.recycle();
+
+                if (lname != null) {
+                    if (owner.libraryNames == null) {
+                        owner.libraryNames = new ArrayList<String>();
+                    }
+                    if (!owner.libraryNames.contains(lname)) {
+                        owner.libraryNames.add(lname.intern());
+                    }
+                }
+
+                XmlUtils.skipCurrentTag(parser);
+
             } else if (tagName.equals("uses-library")) {
                 sa = res.obtainAttributes(attrs,
                         com.android.internal.R.styleable.AndroidManifestUsesLibrary);
@@ -3182,7 +3204,8 @@
         public final ArrayList<Boolean> requestedPermissionsRequired = new ArrayList<Boolean>();
 
         public ArrayList<String> protectedBroadcasts;
-        
+
+        public ArrayList<String> libraryNames = null;
         public ArrayList<String> usesLibraries = null;
         public ArrayList<String> usesOptionalLibraries = null;
         public String[] usesLibraryFiles = null;
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 805b05e..0152615 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1739,6 +1739,15 @@
     }
 
     /**
+     * Return true if given resource identifier includes a package.
+     *
+     * @hide
+     */
+    public static boolean resourceHasPackage(int resid) {
+        return (resid >>> 24) != 0;
+    }
+
+    /**
      * Return the full name for a given resource identifier.  This name is
      * a single string of the form "package:type/entry".
      * 
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 2d67875..1128230 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -54,7 +54,7 @@
         implements HandlerCaller.Callback {
     private static final String TAG = "InputMethodWrapper";
     private static final boolean DEBUG = false;
-    
+
     private static final int DO_DUMP = 1;
     private static final int DO_ATTACH_TOKEN = 10;
     private static final int DO_SET_INPUT_CONTEXT = 20;
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 99624cc..5a9cde1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -48,6 +48,7 @@
 import android.view.ViewTreeObserver;
 import android.view.Window;
 import android.view.WindowManager;
+import android.view.WindowManager.BadTokenException;
 import android.view.animation.AnimationUtils;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.EditorInfo;
@@ -421,7 +422,12 @@
             boolean wasVis = isInputViewShown();
             mShowInputFlags = 0;
             if (onShowInputRequested(flags, false)) {
-                showWindow(true);
+                try {
+                    showWindow(true);
+                } catch (BadTokenException e) {
+                    if (DEBUG) Log.v(TAG, "BadTokenException: IME is done.");
+                    mWindowVisible = false;
+                }
             }
             // If user uses hard keyboard, IME button should always be shown.
             boolean showing = onEvaluateInputViewShown();
diff --git a/core/java/android/net/BaseNetworkStateTracker.java b/core/java/android/net/BaseNetworkStateTracker.java
index 4b60f07..a554611 100644
--- a/core/java/android/net/BaseNetworkStateTracker.java
+++ b/core/java/android/net/BaseNetworkStateTracker.java
@@ -155,4 +155,14 @@
     public void setDependencyMet(boolean met) {
         // Base tracker doesn't handle dependencies
     }
+
+    @Override
+    public void addStackedLink(LinkProperties link) {
+        mLinkProperties.addStackedLink(link);
+    }
+
+    @Override
+    public void removeStackedLink(LinkProperties link) {
+        mLinkProperties.removeStackedLink(link);
+    }
 }
diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java
index fd22b10..4194c9d 100644
--- a/core/java/android/net/DhcpStateMachine.java
+++ b/core/java/android/net/DhcpStateMachine.java
@@ -77,7 +77,7 @@
         RENEW
     };
 
-    private String mInterfaceName;
+    private final String mInterfaceName;
     private boolean mRegisteredForPreDhcpNotification = false;
 
     private static final int BASE = Protocol.BASE_DHCP;
@@ -349,6 +349,7 @@
     private boolean runDhcp(DhcpAction dhcpAction) {
         boolean success = false;
         DhcpResults dhcpResults = new DhcpResults();
+        dhcpResults.linkProperties.mLogMe = true;
 
         if (dhcpAction == DhcpAction.START) {
             /* Stop any existing DHCP daemon before starting new */
@@ -358,7 +359,7 @@
         } else if (dhcpAction == DhcpAction.RENEW) {
             if (DBG) Log.d(TAG, "DHCP renewal on " + mInterfaceName);
             success = NetworkUtils.runDhcpRenew(mInterfaceName, dhcpResults);
-            dhcpResults.updateFromDhcpRequest(mDhcpResults);
+            if (success) dhcpResults.updateFromDhcpRequest(mDhcpResults);
         }
         if (success) {
             if (DBG) Log.d(TAG, "DHCP succeeded on " + mInterfaceName);
diff --git a/core/java/android/net/DummyDataStateTracker.java b/core/java/android/net/DummyDataStateTracker.java
index 39440c2..db8f0bcb 100644
--- a/core/java/android/net/DummyDataStateTracker.java
+++ b/core/java/android/net/DummyDataStateTracker.java
@@ -203,6 +203,16 @@
         // not supported on this network
     }
 
+    @Override
+    public void addStackedLink(LinkProperties link) {
+        mLinkProperties.addStackedLink(link);
+    }
+
+    @Override
+    public void removeStackedLink(LinkProperties link) {
+        mLinkProperties.removeStackedLink(link);
+    }
+
     static private void log(String s) {
         Slog.d(TAG, s);
     }
diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java
index 8947162..b744a47 100644
--- a/core/java/android/net/EthernetDataTracker.java
+++ b/core/java/android/net/EthernetDataTracker.java
@@ -407,4 +407,14 @@
     public void setDependencyMet(boolean met) {
         // not supported on this network
     }
+
+    @Override
+    public void addStackedLink(LinkProperties link) {
+        mLinkProperties.addStackedLink(link);
+    }
+
+    @Override
+    public void removeStackedLink(LinkProperties link) {
+        mLinkProperties.removeStackedLink(link);
+    }
 }
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 5d13a18..7044d39 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -20,12 +20,16 @@
 import android.os.Parcelable;
 import android.os.Parcel;
 import android.text.TextUtils;
+import android.util.Log;
 
 import java.net.InetAddress;
+import java.net.Inet4Address;
+
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Hashtable;
 
 /**
  * Describes the properties of a network link.
@@ -47,16 +51,27 @@
  * don't care which is used.  The gateways will be
  * selected based on the destination address and the
  * source address has no relavence.
+ *
+ * Links can also be stacked on top of each other.
+ * This can be used, for example, to represent a tunnel
+ * interface that runs on top of a physical interface.
+ *
  * @hide
  */
 public class LinkProperties implements Parcelable {
-
+    // The interface described by the network link.
     private String mIfaceName;
     private Collection<LinkAddress> mLinkAddresses = new ArrayList<LinkAddress>();
     private Collection<InetAddress> mDnses = new ArrayList<InetAddress>();
     private String mDomains;
     private Collection<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
     private ProxyProperties mHttpProxy;
+    public boolean mLogMe;
+
+    // Stores the properties of links that are "stacked" above this link.
+    // Indexed by interface name to allow modification and to prevent duplicates being added.
+    private Hashtable<String, LinkProperties> mStackedLinks =
+        new Hashtable<String, LinkProperties>();
 
     public static class CompareResult<T> {
         public Collection<T> removed = new ArrayList<T>();
@@ -75,6 +90,7 @@
 
     public LinkProperties() {
         clear();
+        mLogMe = false;
     }
 
     // copy constructor instead of clone
@@ -87,10 +103,21 @@
             for (RouteInfo r : source.getRoutes()) mRoutes.add(r);
             mHttpProxy = (source.getHttpProxy() == null)  ?
                     null : new ProxyProperties(source.getHttpProxy());
+            for (LinkProperties l: source.mStackedLinks.values()) {
+                addStackedLink(l);
+            }
         }
     }
 
     public void setInterfaceName(String iface) {
+        if (mLogMe) {
+            Log.d("LinkProperties", "setInterfaceName from " + mIfaceName +
+                    " to " + iface);
+            for (StackTraceElement e : Thread.currentThread().getStackTrace()) {
+                Log.d("LinkProperties", "  " + e.toString());
+            }
+        }
+
         mIfaceName = iface;
         ArrayList<RouteInfo> newRoutes = new ArrayList<RouteInfo>(mRoutes.size());
         for (RouteInfo route : mRoutes) {
@@ -154,10 +181,25 @@
         }
     }
 
+    /**
+     * Returns all the routes on this link.
+     */
     public Collection<RouteInfo> getRoutes() {
         return Collections.unmodifiableCollection(mRoutes);
     }
 
+    /**
+     * Returns all the routes on this link and all the links stacked above it.
+     */
+    public Collection<RouteInfo> getAllRoutes() {
+        Collection<RouteInfo> routes = new ArrayList();
+        routes.addAll(mRoutes);
+        for (LinkProperties stacked: mStackedLinks.values()) {
+            routes.addAll(stacked.getAllRoutes());
+        }
+        return routes;
+    }
+
     public void setHttpProxy(ProxyProperties proxy) {
         mHttpProxy = proxy;
     }
@@ -165,13 +207,61 @@
         return mHttpProxy;
     }
 
+    /**
+     * Adds a stacked link.
+     *
+     * If there is already a stacked link with the same interfacename as link,
+     * that link is replaced with link. Otherwise, link is added to the list
+     * of stacked links. If link is null, nothing changes.
+     *
+     * @param link The link to add.
+     */
+    public void addStackedLink(LinkProperties link) {
+        if (link != null && link.getInterfaceName() != null) {
+            mStackedLinks.put(link.getInterfaceName(), link);
+        }
+    }
+
+    /**
+     * Removes a stacked link.
+     *
+     * If there a stacked link with the same interfacename as link, it is
+     * removed. Otherwise, nothing changes.
+     *
+     * @param link The link to add.
+     */
+    public void removeStackedLink(LinkProperties link) {
+        if (link != null && link.getInterfaceName() != null) {
+            mStackedLinks.remove(link.getInterfaceName());
+        }
+    }
+
+    /**
+     * Returns all the links stacked on top of this link.
+     */
+    public Collection<LinkProperties> getStackedLinks() {
+        Collection<LinkProperties> stacked = new ArrayList<LinkProperties>();
+        for (LinkProperties link : mStackedLinks.values()) {
+          stacked.add(new LinkProperties(link));
+        }
+        return Collections.unmodifiableCollection(stacked);
+    }
+
     public void clear() {
+        if (mLogMe) {
+            Log.d("LinkProperties", "clear from " + mIfaceName);
+            for (StackTraceElement e : Thread.currentThread().getStackTrace()) {
+                Log.d("LinkProperties", "  " + e.toString());
+            }
+        }
+
         mIfaceName = null;
         mLinkAddresses.clear();
         mDnses.clear();
         mDomains = null;
         mRoutes.clear();
         mHttpProxy = null;
+        mStackedLinks.clear();
     }
 
     /**
@@ -201,7 +291,29 @@
         routes += "] ";
         String proxy = (mHttpProxy == null ? "" : "HttpProxy: " + mHttpProxy.toString() + " ");
 
-        return ifaceName + linkAddresses + routes + dns + domainName + proxy;
+        String stacked = "";
+        if (mStackedLinks.values().size() > 0) {
+            stacked += " Stacked: [";
+            for (LinkProperties link: mStackedLinks.values()) {
+                stacked += " [" + link.toString() + " ],";
+            }
+            stacked += "] ";
+        }
+        return ifaceName + linkAddresses + routes + dns + domainName + proxy + stacked;
+    }
+
+    /**
+     * Returns true if this link has an IPv4 address.
+     *
+     * @return {@code true} if there is an IPv4 address, {@code false} otherwise.
+     */
+    public boolean hasIPv4Address() {
+        for (LinkAddress address : mLinkAddresses) {
+          if (address.getAddress() instanceof Inet4Address) {
+            return true;
+          }
+        }
+        return false;
     }
 
     /**
@@ -268,6 +380,26 @@
                     getHttpProxy().equals(target.getHttpProxy());
     }
 
+    /**
+     * Compares this {@code LinkProperties} stacked links against the target
+     *
+     * @param target LinkProperties to compare.
+     * @return {@code true} if both are identical, {@code false} otherwise.
+     */
+    public boolean isIdenticalStackedLinks(LinkProperties target) {
+        if (!mStackedLinks.keys().equals(target.mStackedLinks.keys())) {
+            return false;
+        }
+        for (LinkProperties stacked : mStackedLinks.values()) {
+            // Hashtable values can never be null.
+            String iface = stacked.getInterfaceName();
+            if (!stacked.equals(target.mStackedLinks.get(iface))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     @Override
     /**
      * Compares this {@code LinkProperties} instance against the target
@@ -280,6 +412,10 @@
      * 1. Duplicated elements. eg, (A, B, B) and (A, A, B) are equal.
      * 2. Worst case performance is O(n^2).
      *
+     * This method does not check that stacked interfaces are equal, because
+     * stacked interfaces are not so much a property of the link as a
+     * description of connections between links.
+     *
      * @param obj the object to be tested for equality.
      * @return {@code true} if both objects are equal, {@code false} otherwise.
      */
@@ -294,7 +430,8 @@
                 isIdenticalAddresses(target) &&
                 isIdenticalDnses(target) &&
                 isIdenticalRoutes(target) &&
-                isIdenticalHttpProxy(target);
+                isIdenticalHttpProxy(target) &&
+                isIdenticalStackedLinks(target);
     }
 
     /**
@@ -376,10 +513,10 @@
          */
         CompareResult<RouteInfo> result = new CompareResult<RouteInfo>();
 
-        result.removed = new ArrayList<RouteInfo>(mRoutes);
+        result.removed = getAllRoutes();
         result.added.clear();
         if (target != null) {
-            for (RouteInfo r : target.getRoutes()) {
+            for (RouteInfo r : target.getAllRoutes()) {
                 if (! result.removed.remove(r)) {
                     result.added.add(r);
                 }
@@ -401,7 +538,8 @@
                 + mDnses.size() * 37
                 + ((null == mDomains) ? 0 : mDomains.hashCode())
                 + mRoutes.size() * 41
-                + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode()));
+                + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode())
+                + mStackedLinks.hashCode() * 47);
     }
 
     /**
@@ -431,6 +569,8 @@
         } else {
             dest.writeByte((byte)0);
         }
+        ArrayList<LinkProperties> stackedLinks = new ArrayList(mStackedLinks.values());
+        dest.writeList(stackedLinks);
     }
 
     /**
@@ -463,6 +603,11 @@
                 if (in.readByte() == 1) {
                     netProp.setHttpProxy((ProxyProperties)in.readParcelable(null));
                 }
+                ArrayList<LinkProperties> stackedLinks = new ArrayList<LinkProperties>();
+                in.readList(stackedLinks, LinkProperties.class.getClassLoader());
+                for (LinkProperties stackedLink: stackedLinks) {
+                    netProp.addStackedLink(stackedLink);
+                }
                 return netProp;
             }
 
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index b35d61c..faf739b 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -522,6 +522,16 @@
     }
 
     @Override
+    public void addStackedLink(LinkProperties link) {
+        mLinkProperties.addStackedLink(link);
+    }
+
+    @Override
+    public void removeStackedLink(LinkProperties link) {
+        mLinkProperties.removeStackedLink(link);
+    }
+
+    @Override
     public String toString() {
         final CharArrayWriter writer = new CharArrayWriter();
         final PrintWriter pw = new PrintWriter(writer);
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index 0a0c1e0..b22159c 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -197,4 +197,14 @@
      * An external dependency has been met/unmet
      */
     public void setDependencyMet(boolean met);
+
+    /**
+     * Informs the state tracker that another interface is stacked on top of it.
+     **/
+    public void addStackedLink(LinkProperties link);
+
+    /**
+     * Informs the state tracker that a stacked interface has been removed.
+     **/
+    public void removeStackedLink(LinkProperties link);
 }
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 2bec1c1..9666d9a 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import android.util.Log;
+
 import java.io.BufferedInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -25,6 +27,8 @@
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Comparator;
 import java.util.regex.Pattern;
 import java.util.zip.CRC32;
 import java.util.zip.CheckedInputStream;
@@ -34,6 +38,8 @@
  * @hide
  */
 public class FileUtils {
+    private static final String TAG = "FileUtils";
+
     public static final int S_IRWXU = 00700;
     public static final int S_IRUSR = 00400;
     public static final int S_IWUSR = 00200;
@@ -161,7 +167,8 @@
             } else if (max < 0) {  // "tail" mode: keep the last N
                 int len;
                 boolean rolled = false;
-                byte[] last = null, data = null;
+                byte[] last = null;
+                byte[] data = null;
                 do {
                     if (last != null) rolled = true;
                     byte[] tmp = last; last = data; data = tmp;
@@ -237,4 +244,40 @@
             }
         }
     }
+
+    /**
+     * Delete older files in a directory until only those matching the given
+     * constraints remain.
+     *
+     * @param minCount Always keep at least this many files.
+     * @param minAge Always keep files younger than this age.
+     */
+    public static void deleteOlderFiles(File dir, int minCount, long minAge) {
+        if (minCount < 0 || minAge < 0) {
+            throw new IllegalArgumentException("Constraints must be positive or 0");
+        }
+
+        final File[] files = dir.listFiles();
+        if (files == null) return;
+
+        // Sort with newest files first
+        Arrays.sort(files, new Comparator<File>() {
+            @Override
+            public int compare(File lhs, File rhs) {
+                return (int) (rhs.lastModified() - lhs.lastModified());
+            }
+        });
+
+        // Keep at least minCount files
+        for (int i = minCount; i < files.length; i++) {
+            final File file = files[i];
+
+            // Keep files newer than minAge
+            final long age = System.currentTimeMillis() - file.lastModified();
+            if (age > minAge) {
+                Log.d(TAG, "Deleting old file " + file);
+                file.delete();
+            }
+        }
+    }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 266d0d3..d251ca2 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4784,6 +4784,13 @@
        public static final String WIFI_ON = "wifi_on";
 
        /**
+        * Setting to allow scans to be enabled even wifi is turned off for connectivity.
+        * @hide
+        */
+       public static final String WIFI_SCAN_ALWAYS_AVAILABLE =
+                "wifi_scan_always_enabled";
+
+       /**
         * Used to save the Wifi_ON state prior to tethering.
         * This state will be checked to restore Wifi after
         * the user turns off tethering.
@@ -5345,6 +5352,7 @@
             WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
             WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
             WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
+            WIFI_SCAN_ALWAYS_AVAILABLE,
             WIFI_NUM_OPEN_NETWORKS_KEPT,
             EMERGENCY_TONE,
             CALL_AUTO_RETRY,
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 4379418..703dcff 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -810,9 +810,15 @@
                 return TextToSpeech.ERROR;
             }
 
+            // In test env, ParcelFileDescriptor instance may be EXACTLY the same
+            // one that is used by client. And it will be closed by a client, thus
+            // preventing us from writing anything to it.
+            final ParcelFileDescriptor sameFileDescriptor = ParcelFileDescriptor.adoptFd(
+                    fileDescriptor.detachFd());
+
             SpeechItem item = new SynthesisToFileOutputStreamSpeechItem(caller,
                     Binder.getCallingUid(), Binder.getCallingPid(), params, text,
-                    new ParcelFileDescriptor.AutoCloseOutputStream(fileDescriptor));
+                    new ParcelFileDescriptor.AutoCloseOutputStream(sameFileDescriptor));
             return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
         }
 
diff --git a/core/java/android/text/GraphicsOperations.java b/core/java/android/text/GraphicsOperations.java
index 60545e5..831ccc5 100644
--- a/core/java/android/text/GraphicsOperations.java
+++ b/core/java/android/text/GraphicsOperations.java
@@ -38,7 +38,7 @@
      * {@hide}
      */
     void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
-            float x, float y, Paint p);
+            float x, float y, int flags, Paint p);
 
    /**
      * Just like {@link Paint#measureText}.
@@ -55,12 +55,19 @@
      * @hide
      */
     float getTextRunAdvances(int start, int end, int contextStart, int contextEnd,
-            float[] advances, int advancesIndex, Paint paint);
+            int flags, float[] advances, int advancesIndex, Paint paint);
+
+    /**
+     * Just like {@link Paint#getTextRunAdvances}.
+     * @hide
+     */
+    float getTextRunAdvances(int start, int end, int contextStart, int contextEnd,
+            int flags, float[] advances, int advancesIndex, Paint paint, int reserved);
 
     /**
      * Just like {@link Paint#getTextRunCursor}.
      * @hide
      */
-    int getTextRunCursor(int contextStart, int contextEnd, int offset,
+    int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
             int cursorOpt, Paint p);
 }
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index 0c881a4..bd9310c1 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -159,15 +159,18 @@
         mPos = p + len;
 
         if (mEasy) {
-            return paint.getTextRunAdvances(mChars, p, len, p, len, mWidths, p);
+            int flags = mDir == Layout.DIR_LEFT_TO_RIGHT
+                ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL;
+            return paint.getTextRunAdvances(mChars, p, len, p, len, flags, mWidths, p);
         }
 
         float totalAdvance = 0;
         int level = mLevels[p];
         for (int q = p, i = p + 1, e = p + len;; ++i) {
             if (i == e || mLevels[i] != level) {
+                int flags = (level & 0x1) == 0 ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL;
                 totalAdvance +=
-                        paint.getTextRunAdvances(mChars, q, i - q, q, i - q, mWidths, q);
+                        paint.getTextRunAdvances(mChars, q, i - q, q, i - q, flags, mWidths, q);
                 if (i == e) {
                     break;
                 }
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index 9e43671..0f30d25 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -1130,20 +1130,20 @@
      * {@hide}
      */
     public void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
-            float x, float y, Paint p) {
+            float x, float y, int flags, Paint p) {
         checkRange("drawTextRun", start, end);
 
         int contextLen = contextEnd - contextStart;
         int len = end - start;
         if (contextEnd <= mGapStart) {
-            c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, p);
+            c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, flags, p);
         } else if (contextStart >= mGapStart) {
             c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
-                    contextLen, x, y, p);
+                    contextLen, x, y, flags, p);
         } else {
             char[] buf = TextUtils.obtain(contextLen);
             getChars(contextStart, contextEnd, buf, 0);
-            c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, p);
+            c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, flags, p);
             TextUtils.recycle(buf);
         }
     }
@@ -1200,7 +1200,7 @@
      * Don't call this yourself -- exists for Paint to use internally.
      * {@hide}
      */
-    public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd,
+    public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
             float[] advances, int advancesPos, Paint p) {
 
         float ret;
@@ -1210,15 +1210,44 @@
 
         if (end <= mGapStart) {
             ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
-                    advances, advancesPos);
+                    flags, advances, advancesPos);
         } else if (start >= mGapStart) {
             ret = p.getTextRunAdvances(mText, start + mGapLength, len,
-                    contextStart + mGapLength, contextLen, advances, advancesPos);
+                    contextStart + mGapLength, contextLen, flags, advances, advancesPos);
         } else {
             char[] buf = TextUtils.obtain(contextLen);
             getChars(contextStart, contextEnd, buf, 0);
             ret = p.getTextRunAdvances(buf, start - contextStart, len,
-                    0, contextLen, advances, advancesPos);
+                    0, contextLen, flags, advances, advancesPos);
+            TextUtils.recycle(buf);
+        }
+
+        return ret;
+    }
+
+    /**
+     * Don't call this yourself -- exists for Paint to use internally.
+     * {@hide}
+     */
+    public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
+            float[] advances, int advancesPos, Paint p, int reserved) {
+
+        float ret;
+
+        int contextLen = contextEnd - contextStart;
+        int len = end - start;
+
+        if (end <= mGapStart) {
+            ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
+                    flags, advances, advancesPos, reserved);
+        } else if (start >= mGapStart) {
+            ret = p.getTextRunAdvances(mText, start + mGapLength, len,
+                    contextStart + mGapLength, contextLen, flags, advances, advancesPos, reserved);
+        } else {
+            char[] buf = TextUtils.obtain(contextLen);
+            getChars(contextStart, contextEnd, buf, 0);
+            ret = p.getTextRunAdvances(buf, start - contextStart, len,
+                    0, contextLen, flags, advances, advancesPos, reserved);
             TextUtils.recycle(buf);
         }
 
@@ -1241,7 +1270,7 @@
      *
      * @param contextStart the start index of the context
      * @param contextEnd the (non-inclusive) end index of the context
-     * @param flags reserved
+     * @param flags either DIRECTION_RTL or DIRECTION_LTR
      * @param offset the cursor position to move from
      * @param cursorOpt how to move the cursor, one of CURSOR_AFTER,
      * CURSOR_AT_OR_AFTER, CURSOR_BEFORE,
@@ -1252,30 +1281,22 @@
      */
     @Deprecated
     public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
-                                int cursorOpt, Paint p) {
-        return getTextRunCursor(contextStart, contextEnd, offset, cursorOpt, p);
-    }
-
-    /**
-     * @hide
-     */
-    public int getTextRunCursor(int contextStart, int contextEnd, int offset,
-                                int cursorOpt, Paint p) {
+            int cursorOpt, Paint p) {
 
         int ret;
 
         int contextLen = contextEnd - contextStart;
         if (contextEnd <= mGapStart) {
             ret = p.getTextRunCursor(mText, contextStart, contextLen,
-                    offset, cursorOpt);
+                    flags, offset, cursorOpt);
         } else if (contextStart >= mGapStart) {
             ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
-                    offset + mGapLength, cursorOpt) - mGapLength;
+                    flags, offset + mGapLength, cursorOpt) - mGapLength;
         } else {
             char[] buf = TextUtils.obtain(contextLen);
             getChars(contextStart, contextEnd, buf, 0);
             ret = p.getTextRunCursor(buf, 0, contextLen,
-                    offset - contextStart, cursorOpt) + contextStart;
+                    flags, offset - contextStart, cursorOpt) + contextStart;
             TextUtils.recycle(buf);
         }
 
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index e34a0ef..1fecf81 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -664,13 +664,14 @@
             }
         }
 
+        int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
         int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
         if (mCharsValid) {
             return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart,
-                    offset, cursorOpt);
+                    flags, offset, cursorOpt);
         } else {
             return wp.getTextRunCursor(mText, mStart + spanStart,
-                    mStart + spanLimit, mStart + offset, cursorOpt) - mStart;
+                    mStart + spanLimit, flags, mStart + offset, cursorOpt) - mStart;
         }
     }
 
@@ -737,13 +738,15 @@
 
         int contextLen = contextEnd - contextStart;
         if (needWidth || (c != null && (wp.bgColor != 0 || wp.underlineColor != 0 || runIsRtl))) {
+            int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
             if (mCharsValid) {
                 ret = wp.getTextRunAdvances(mChars, start, runLen,
-                        contextStart, contextLen, null, 0);
+                        contextStart, contextLen, flags, null, 0);
             } else {
                 int delta = mStart;
-                ret = wp.getTextRunAdvances(mText, delta + start, delta + end,
-                        delta + contextStart, delta + contextEnd, null, 0);
+                ret = wp.getTextRunAdvances(mText, delta + start,
+                        delta + end, delta + contextStart, delta + contextEnd,
+                        flags, null, 0);
             }
         }
 
@@ -783,7 +786,8 @@
                 wp.setAntiAlias(previousAntiAlias);
             }
 
-            drawTextRun(c, wp, start, end, contextStart, contextEnd, x, y + wp.baselineShift);
+            drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
+                    x, y + wp.baselineShift);
         }
 
         return runIsRtl ? -ret : ret;
@@ -966,21 +970,23 @@
      * @param end the end of the run
      * @param contextStart the start of context for the run
      * @param contextEnd the end of the context for the run
+     * @param runIsRtl true if the run is right-to-left
      * @param x the x position of the left edge of the run
      * @param y the baseline of the run
      */
     private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
-            int contextStart, int contextEnd, float x, int y) {
+            int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
 
+        int flags = runIsRtl ? Canvas.DIRECTION_RTL : Canvas.DIRECTION_LTR;
         if (mCharsValid) {
             int count = end - start;
             int contextCount = contextEnd - contextStart;
             c.drawTextRun(mChars, start, count, contextStart, contextCount,
-                    x, y, wp);
+                    x, y, flags, wp);
         } else {
             int delta = mStart;
             c.drawTextRun(mText, delta + start, delta + end,
-                    delta + contextStart, delta + contextEnd, x, y, wp);
+                    delta + contextStart, delta + contextEnd, x, y, flags, wp);
         }
     }
 
diff --git a/core/java/android/text/bidi/BidiFormatter.java b/core/java/android/text/bidi/BidiFormatter.java
index 370cbf7..c5a77a6 100644
--- a/core/java/android/text/bidi/BidiFormatter.java
+++ b/core/java/android/text/bidi/BidiFormatter.java
@@ -172,9 +172,9 @@
      * A class for building a BidiFormatter with non-default options.
      */
     public static final class Builder {
-        private boolean isRtlContext;
-        private int flags;
-        private TextDirectionHeuristic textDirectionHeuristic;
+        private boolean mIsRtlContext;
+        private int mFlags;
+        private TextDirectionHeuristic mTextDirectionHeuristic;
 
         /**
          * Constructor.
@@ -208,9 +208,9 @@
          * @param isRtlContext Whether the context is RTL or not.
          */
         private void initialize(boolean isRtlContext) {
-            this.isRtlContext = isRtlContext;
-            textDirectionHeuristic = DEFAULT_TEXT_DIRECTION_HEURISTIC;
-            this.flags = DEFAULT_FLAGS;
+            mIsRtlContext = isRtlContext;
+            mTextDirectionHeuristic = DEFAULT_TEXT_DIRECTION_HEURISTIC;
+            mFlags = DEFAULT_FLAGS;
         }
 
         /**
@@ -219,9 +219,9 @@
          */
         public Builder stereoReset(boolean stereoReset) {
             if (stereoReset) {
-                flags |= FLAG_STEREO_RESET;
+                mFlags |= FLAG_STEREO_RESET;
             } else {
-                flags &= ~FLAG_STEREO_RESET;
+                mFlags &= ~FLAG_STEREO_RESET;
             }
             return this;
         }
@@ -234,7 +234,7 @@
          * @return the builder itself.
          */
         public Builder setTextDirectionHeuristic(TextDirectionHeuristic heuristic) {
-            this.textDirectionHeuristic = heuristic;
+            mTextDirectionHeuristic = heuristic;
             return this;
         }
 
@@ -246,11 +246,11 @@
          * @return A BidiFormatter with the specified options.
          */
         public BidiFormatter build() {
-            if (flags == DEFAULT_FLAGS &&
-                    textDirectionHeuristic == DEFAULT_TEXT_DIRECTION_HEURISTIC) {
-                return getDefaultInstanceFromContext(isRtlContext);
+            if (mFlags == DEFAULT_FLAGS &&
+                    mTextDirectionHeuristic == DEFAULT_TEXT_DIRECTION_HEURISTIC) {
+                return getDefaultInstanceFromContext(mIsRtlContext);
             }
-            return new BidiFormatter(isRtlContext, flags, textDirectionHeuristic);
+            return new BidiFormatter(mIsRtlContext, mFlags, mTextDirectionHeuristic);
         }
     }
 
@@ -268,9 +268,9 @@
             DEFAULT_FLAGS,
             DEFAULT_TEXT_DIRECTION_HEURISTIC);
 
-    private final boolean isRtlContext;
-    private final int flags;
-    private final TextDirectionHeuristic defaultTextDirectionHeuristic;
+    private final boolean mIsRtlContext;
+    private final int mFlags;
+    private final TextDirectionHeuristic mDefaultTextDirectionHeuristic;
 
     /**
      * Factory for creating an instance of BidiFormatter given the context directionality.
@@ -296,16 +296,16 @@
      * @param heuristic The default text direction heuristic.
      */
     private BidiFormatter(boolean isRtlContext, int flags, TextDirectionHeuristic heuristic) {
-        this.isRtlContext = isRtlContext;
-        this.flags = flags;
-        this.defaultTextDirectionHeuristic = heuristic;
+        mIsRtlContext = isRtlContext;
+        mFlags = flags;
+        mDefaultTextDirectionHeuristic = heuristic;
     }
 
     /**
      * @return Whether the context directionality is RTL
      */
     public boolean isRtlContext() {
-        return isRtlContext;
+        return mIsRtlContext;
     }
 
     /**
@@ -313,7 +313,7 @@
      * bidi-wrapped, not just after it.
      */
     public boolean getStereoReset() {
-        return (flags & FLAG_STEREO_RESET) != 0;
+        return (mFlags & FLAG_STEREO_RESET) != 0;
     }
 
     /**
@@ -384,7 +384,7 @@
      *     context; else, the empty string.
      */
     public String dirAttr(boolean isRtl) {
-        return (isRtl != isRtlContext) ? (isRtl ? DIR_RTL_STRING :  DIR_LTR_STRING) : EMPTY_STRING;
+        return (isRtl != mIsRtlContext) ? (isRtl ? DIR_RTL_STRING :  DIR_LTR_STRING) : EMPTY_STRING;
     }
 
     /**
@@ -401,7 +401,7 @@
      *     else, the empty string.
      */
     public String markAfter(String str) {
-        return markAfter(str, defaultTextDirectionHeuristic);
+        return markAfter(str, mDefaultTextDirectionHeuristic);
     }
 
     /**
@@ -417,10 +417,10 @@
     public String markAfter(String str, TextDirectionHeuristic heuristic) {
         final boolean isRtl = heuristic.isRtl(str, 0, str.length());
         // getExitDir() is called only if needed (short-circuit).
-        if (!isRtlContext && (isRtl || getExitDir(str) == DIR_RTL)) {
+        if (!mIsRtlContext && (isRtl || getExitDir(str) == DIR_RTL)) {
             return LRM_STRING;
         }
-        if (isRtlContext && (!isRtl || getExitDir(str) == DIR_LTR)) {
+        if (mIsRtlContext && (!isRtl || getExitDir(str) == DIR_LTR)) {
             return RLM_STRING;
         }
         return EMPTY_STRING;
@@ -440,7 +440,7 @@
      *     else, the empty string.
      */
     public String markBefore(String str) {
-        return markBefore(str, defaultTextDirectionHeuristic);
+        return markBefore(str, mDefaultTextDirectionHeuristic);
     }
 
     /**
@@ -456,10 +456,10 @@
     public String markBefore(String str, TextDirectionHeuristic heuristic) {
         final boolean isRtl = heuristic.isRtl(str, 0, str.length());
         // getEntryDir() is called only if needed (short-circuit).
-        if (!isRtlContext && (isRtl || getEntryDir(str) == DIR_RTL)) {
+        if (!mIsRtlContext && (isRtl || getEntryDir(str) == DIR_RTL)) {
             return LRM_STRING;
         }
-        if (isRtlContext && (!isRtl || getEntryDir(str) == DIR_LTR)) {
+        if (mIsRtlContext && (!isRtl || getEntryDir(str) == DIR_LTR)) {
             return RLM_STRING;
         }
         return EMPTY_STRING;
@@ -470,7 +470,7 @@
      * directionality, RLM for RTL context directionality).
      */
     public String mark() {
-        return isRtlContext ? RLM_STRING : LRM_STRING;
+        return mIsRtlContext ? RLM_STRING : LRM_STRING;
     }
 
     /**
@@ -478,7 +478,7 @@
      * returns "left".
      */
     public String startEdge() {
-        return isRtlContext  ? RIGHT : LEFT;
+        return mIsRtlContext ? RIGHT : LEFT;
     }
 
     /**
@@ -486,7 +486,7 @@
      * returns "right".
      */
     public String endEdge() {
-        return isRtlContext ? LEFT : RIGHT;
+        return mIsRtlContext ? LEFT : RIGHT;
     }
 
     /**
@@ -497,7 +497,7 @@
      *          false.
      */
     public boolean isRtl(String str) {
-        return defaultTextDirectionHeuristic.isRtl(str, 0, str.length());
+        return mDefaultTextDirectionHeuristic.isRtl(str, 0, str.length());
     }
 
     /**
@@ -536,7 +536,7 @@
             result.append(markBefore(origStr,
                     isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR));
         }
-        if (isRtl != isRtlContext) {
+        if (isRtl != mIsRtlContext) {
             result.append("<span ").append(dirAttr(isRtl)).append('>').append(str).append("</span>");
         } else {
             result.append(str);
@@ -570,7 +570,7 @@
      * @return Input string after applying the above processing.
      */
     public String spanWrap(String str, boolean isolate) {
-        return spanWrap(str, defaultTextDirectionHeuristic, isolate);
+        return spanWrap(str, mDefaultTextDirectionHeuristic, isolate);
     }
 
     /**
@@ -581,7 +581,7 @@
      * @return Input string after applying the above processing.
      */
     public String spanWrap(String str) {
-        return spanWrap(str, defaultTextDirectionHeuristic, true /* isolate */);
+        return spanWrap(str, mDefaultTextDirectionHeuristic, true /* isolate */);
     }
 
     /**
@@ -620,7 +620,7 @@
             result.append(markBefore(str,
                     isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR));
         }
-        if (isRtl != isRtlContext) {
+        if (isRtl != mIsRtlContext) {
             result.append(isRtl ? RLE : LRE);
             result.append(str);
             result.append(PDF);
@@ -656,7 +656,7 @@
      * @return Input string after applying the above processing.
      */
     public String unicodeWrap(String str, boolean isolate) {
-        return unicodeWrap(str, defaultTextDirectionHeuristic, isolate);
+        return unicodeWrap(str, mDefaultTextDirectionHeuristic, isolate);
     }
 
     /**
@@ -667,7 +667,7 @@
      * @return Input string after applying the above processing.
      */
     public String unicodeWrap(String str) {
-        return unicodeWrap(str, defaultTextDirectionHeuristic, true /* isolate */);
+        return unicodeWrap(str, mDefaultTextDirectionHeuristic, true /* isolate */);
     }
 
     /**
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 6c48e43..c369ebe 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -1162,14 +1162,14 @@
 
         int modifiers = setupModifiers(paint);
         try {
-            nDrawText(mRenderer, text, index, count, x, y, paint.mNativePaint);
+            nDrawText(mRenderer, text, index, count, x, y, paint.mBidiFlags, paint.mNativePaint);
         } finally {
             if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
         }
     }
     
     private static native void nDrawText(int renderer, char[] text, int index, int count,
-            float x, float y, int paint);
+            float x, float y, int bidiFlags, int paint);
 
     @Override
     public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
@@ -1177,14 +1177,16 @@
         try {
             if (text instanceof String || text instanceof SpannedString ||
                     text instanceof SpannableString) {
-                nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mNativePaint);
+                nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mBidiFlags,
+                        paint.mNativePaint);
             } else if (text instanceof GraphicsOperations) {
                 ((GraphicsOperations) text).drawText(this, start, end, x, y,
                                                          paint);
             } else {
                 char[] buf = TemporaryBuffer.obtain(end - start);
                 TextUtils.getChars(text, start, end, buf, 0);
-                nDrawText(mRenderer, buf, 0, end - start, x, y, paint.mNativePaint);
+                nDrawText(mRenderer, buf, 0, end - start, x, y,
+                        paint.mBidiFlags, paint.mNativePaint);
                 TemporaryBuffer.recycle(buf);
             }
         } finally {
@@ -1200,20 +1202,21 @@
 
         int modifiers = setupModifiers(paint);
         try {
-            nDrawText(mRenderer, text, start, end, x, y, paint.mNativePaint);
+            nDrawText(mRenderer, text, start, end, x, y, paint.mBidiFlags, paint.mNativePaint);
         } finally {
             if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
         }
     }
 
     private static native void nDrawText(int renderer, String text, int start, int end,
-            float x, float y, int paint);
+            float x, float y, int bidiFlags, int paint);
 
     @Override
     public void drawText(String text, float x, float y, Paint paint) {
         int modifiers = setupModifiers(paint);
         try {
-            nDrawText(mRenderer, text, 0, text.length(), x, y, paint.mNativePaint);
+            nDrawText(mRenderer, text, 0, text.length(), x, y, paint.mBidiFlags,
+                    paint.mNativePaint);
         } finally {
             if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
         }
@@ -1229,14 +1232,14 @@
         int modifiers = setupModifiers(paint);
         try {
             nDrawTextOnPath(mRenderer, text, index, count, path.mNativePath, hOffset, vOffset,
-                    paint.mNativePaint);
+                    paint.mBidiFlags, paint.mNativePaint);
         } finally {
             if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
         }
     }
 
     private static native void nDrawTextOnPath(int renderer, char[] text, int index, int count,
-            int path, float hOffset, float vOffset, int nativePaint);
+            int path, float hOffset, float vOffset, int bidiFlags, int nativePaint);
 
     @Override
     public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) {
@@ -1245,25 +1248,28 @@
         int modifiers = setupModifiers(paint);
         try {
             nDrawTextOnPath(mRenderer, text, 0, text.length(), path.mNativePath, hOffset, vOffset,
-                    paint.mNativePaint);
+                    paint.mBidiFlags, paint.mNativePaint);
         } finally {
             if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
         }
     }
 
     private static native void nDrawTextOnPath(int renderer, String text, int start, int end,
-            int path, float hOffset, float vOffset, int nativePaint);
+            int path, float hOffset, float vOffset, int bidiFlags, int nativePaint);
 
     @Override
     public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount,
-            float x, float y, Paint paint) {
+            float x, float y, int dir, Paint paint) {
         if ((index | count | text.length - index - count) < 0) {
             throw new IndexOutOfBoundsException();
         }
+        if (dir != DIRECTION_LTR && dir != DIRECTION_RTL) {
+            throw new IllegalArgumentException("Unknown direction: " + dir);
+        }
 
         int modifiers = setupModifiers(paint);
         try {
-            nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y,
+            nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, dir,
                     paint.mNativePaint);
         } finally {
             if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
@@ -1271,31 +1277,32 @@
     }
 
     private static native void nDrawTextRun(int renderer, char[] text, int index, int count,
-            int contextIndex, int contextCount, float x, float y, int nativePaint);
+            int contextIndex, int contextCount, float x, float y, int dir, int nativePaint);
 
     @Override
     public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd,
-            float x, float y, Paint paint) {
+            float x, float y, int dir, Paint paint) {
         if ((start | end | end - start | text.length() - end) < 0) {
             throw new IndexOutOfBoundsException();
         }
 
         int modifiers = setupModifiers(paint);
         try {
+            int flags = dir == 0 ? 0 : 1;
             if (text instanceof String || text instanceof SpannedString ||
                     text instanceof SpannableString) {
                 nDrawTextRun(mRenderer, text.toString(), start, end, contextStart,
-                        contextEnd, x, y, paint.mNativePaint);
+                        contextEnd, x, y, flags, paint.mNativePaint);
             } else if (text instanceof GraphicsOperations) {
                 ((GraphicsOperations) text).drawTextRun(this, start, end,
-                        contextStart, contextEnd, x, y, paint);
+                        contextStart, contextEnd, x, y, flags, paint);
             } else {
                 int contextLen = contextEnd - contextStart;
                 int len = end - start;
                 char[] buf = TemporaryBuffer.obtain(contextLen);
                 TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
                 nDrawTextRun(mRenderer, buf, start - contextStart, len, 0, contextLen,
-                        x, y, paint.mNativePaint);
+                        x, y, flags, paint.mNativePaint);
                 TemporaryBuffer.recycle(buf);
             }
         } finally {
@@ -1304,7 +1311,7 @@
     }
 
     private static native void nDrawTextRun(int renderer, String text, int start, int end,
-            int contextStart, int contextEnd, float x, float y, int nativePaint);
+            int contextStart, int contextEnd, float x, float y, int flags, int nativePaint);
 
     @Override
     public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset,
diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java
index 947cf44..7da2451 100644
--- a/core/java/android/view/GLES20RecordingCanvas.java
+++ b/core/java/android/view/GLES20RecordingCanvas.java
@@ -267,15 +267,15 @@
 
     @Override
     public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount,
-            float x, float y, Paint paint) {
-        super.drawTextRun(text, index, count, contextIndex, contextCount, x, y, paint);
+            float x, float y, int dir, Paint paint) {
+        super.drawTextRun(text, index, count, contextIndex, contextCount, x, y, dir, paint);
         recordShaderBitmap(paint);
     }
 
     @Override
     public void drawTextRun(CharSequence text, int start, int end, int contextStart,
-            int contextEnd, float x, float y, Paint paint) {
-        super.drawTextRun(text, start, end, contextStart, contextEnd, x, y, paint);
+            int contextEnd, float x, float y, int dir, Paint paint) {
+        super.drawTextRun(text, start, end, contextStart, contextEnd, x, y, dir, paint);
         recordShaderBitmap(paint);
     }
 
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 9955bc1..0492d29 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -39,7 +39,6 @@
     private native void nativeUnlockCanvasAndPost(int nativeObject, Canvas canvas);
 
     private static native void nativeRelease(int nativeObject);
-    private static native void nativeDestroy(int nativeObject);
     private static native boolean nativeIsValid(int nativeObject);
     private static native boolean nativeIsConsumerRunningBehind(int nativeObject);
     private static native int nativeCopyFrom(int nativeObject, int surfaceControlNativeObject);
@@ -106,7 +105,6 @@
      * @hide
      */
     public Surface() {
-        mCloseGuard.open("release");
     }
 
     /**
@@ -135,6 +133,7 @@
         mCloseGuard.open("release");
     }
 
+    /* called from android_view_Surface_createFromIGraphicBufferProducer() */
     private Surface(int nativeObject) {
         mNativeObject = nativeObject;
         mCloseGuard.open("release");
@@ -146,9 +145,7 @@
             if (mCloseGuard != null) {
                 mCloseGuard.warnIfOpen();
             }
-            if (mNativeObject != 0) {
-                nativeRelease(mNativeObject);
-            }
+            release();
         } finally {
             super.finalize();
         }
@@ -175,12 +172,7 @@
      * @hide
      */
     public void destroy() {
-        if (mNativeObject != 0) {
-            nativeDestroy(mNativeObject);
-            mNativeObject = 0;
-            mGenerationId++;
-        }
-        mCloseGuard.close();
+        release();
     }
 
     /**
@@ -287,6 +279,10 @@
                     "SurfaceControl native object is null. Are you using a released SurfaceControl?");
         }
         mNativeObject = nativeCopyFrom(mNativeObject, other.mNativeObject);
+        if (mNativeObject == 0) {
+            // nativeCopyFrom released our reference
+            mCloseGuard.close();
+        }
         mGenerationId++;
     }
 
@@ -308,11 +304,15 @@
                 nativeRelease(mNativeObject);
             }
             // transfer the reference from other to us
+            if (other.mNativeObject != 0 && mNativeObject == 0) {
+                mCloseGuard.open("release");
+            }
             mNativeObject = other.mNativeObject;
             mGenerationId++;
 
             other.mNativeObject = 0;
             other.mGenerationId++;
+            other.mCloseGuard.close();
         }
     }
 
@@ -326,7 +326,11 @@
             throw new IllegalArgumentException("source must not be null");
         }
         mName = source.readString();
-        mNativeObject = nativeReadFromParcel(mNativeObject, source);
+        int nativeObject = nativeReadFromParcel(mNativeObject, source);
+        if (nativeObject !=0 && mNativeObject == 0) {
+            mCloseGuard.open("release");
+        }
+        mNativeObject = nativeObject;
         mGenerationId++;
     }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2e60f51..d412d82 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5038,7 +5038,8 @@
             }
 
             if ((mAttachInfo.mAccessibilityFetchFlags
-                    & AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS) != 0) {
+                    & AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS) != 0
+                    && Resources.resourceHasPackage(mID)) {
                 try {
                     String viewId = getResources().getResourceName(mID);
                     info.setViewIdResourceName(viewId);
@@ -11553,7 +11554,10 @@
                 final int scrollY = mScrollY;
                 final int inside = (viewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
 
-                int left, top, right, bottom;
+                int left;
+                int top;
+                int right;
+                int bottom;
 
                 if (drawHorizontalScrollBar) {
                     int size = scrollBar.getSize(false);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a937882..8808af0 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1947,20 +1947,16 @@
             // Would not normally trigger another layout, so just let it pass through as usual
             return true;
         }
+        if (!mLayoutRequesters.contains(view)) {
+            mLayoutRequesters.add(view);
+        }
         if (!mHandlingLayoutInLayoutRequest) {
-            if (!mLayoutRequesters.contains(view)) {
-                mLayoutRequesters.add(view);
-            }
+            // Let the request proceed normally; it will be processed in a second layout pass
+            // if necessary
             return true;
         } else {
-            Log.w("View", "requestLayout() called by " + view + " during second layout pass: " +
-                    "posting to next frame");
-            view.post(new Runnable() {
-                @Override
-                public void run() {
-                    view.requestLayout();
-                }
-            });
+            // Don't let the request proceed during the second layout pass.
+            // It will post to the next frame instead.
             return false;
         }
     }
@@ -1988,59 +1984,50 @@
                 // If no layout-request flags are set on the requesting views, there is no problem.
                 // If some requests are still pending, then we need to clear those flags and do
                 // a full request/measure/layout pass to handle this situation.
-
-                // Check state of layout flags for all requesters
-                ArrayList<View> mValidLayoutRequesters = null;
-                for (int i = 0; i < numViewsRequestingLayout; ++i) {
-                    View view = mLayoutRequesters.get(i);
-                    if ((view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) == View.PFLAG_FORCE_LAYOUT) {
-                        while (view != null && view.mAttachInfo != null && view.mParent != null &&
-                                (view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) != 0) {
-                            if ((view.mViewFlags & View.VISIBILITY_MASK) != View.GONE) {
-                                // Only trigger new requests for non-GONE views
-                                Log.w(TAG, "requestLayout() improperly called during " +
-                                        "layout: running second layout pass for " + view);
-                                if (mValidLayoutRequesters == null) {
-                                    mValidLayoutRequesters = new ArrayList<View>();
-                                }
-                                mValidLayoutRequesters.add(view);
-                                break;
-                            }
-                            if (view.mParent instanceof View) {
-                                view = (View) view.mParent;
-                            } else {
-                                view = null;
-                            }
-                        }
-                    }
-                }
-                if (mValidLayoutRequesters != null) {
-                    // Clear flags throughout hierarchy, walking up from each flagged requester
-                    for (int i = 0; i < numViewsRequestingLayout; ++i) {
-                        View view = mLayoutRequesters.get(i);
-                        while (view != null &&
-                                (view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) != 0) {
-                            view.mPrivateFlags &= ~View.PFLAG_FORCE_LAYOUT;
-                            if (view.mParent instanceof View) {
-                                view = (View) view.mParent;
-                            } else {
-                                view = null;
-                            }
-                        }
-                    }
-                    // Process fresh layout requests, then measure and layout
+                ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
+                        false);
+                if (validLayoutRequesters != null) {
+                    // Set this flag to indicate that any further requests are happening during
+                    // the second pass, which may result in posting those requests to the next
+                    // frame instead
                     mHandlingLayoutInLayoutRequest = true;
-                    int numValidRequests = mValidLayoutRequesters.size();
+
+                    // Process fresh layout requests, then measure and layout
+                    int numValidRequests = validLayoutRequesters.size();
                     for (int i = 0; i < numValidRequests; ++i) {
-                        mValidLayoutRequesters.get(i).requestLayout();
+                        final View view = validLayoutRequesters.get(i);
+                        Log.w("View", "requestLayout() improperly called by " + view +
+                                " during layout: running second layout pass");
+                        view.requestLayout();
                     }
                     measureHierarchy(host, lp, mView.getContext().getResources(),
                             desiredWindowWidth, desiredWindowHeight);
                     mInLayout = true;
                     host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
+
                     mHandlingLayoutInLayoutRequest = false;
+
+                    // Check the valid requests again, this time without checking/clearing the
+                    // layout flags, since requests happening during the second pass get noop'd
+                    validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
+                    if (validLayoutRequesters != null) {
+                        final ArrayList<View> finalRequesters = validLayoutRequesters;
+                        // Post second-pass requests to the next frame
+                        getRunQueue().post(new Runnable() {
+                            @Override
+                            public void run() {
+                                int numValidRequests = finalRequesters.size();
+                                for (int i = 0; i < numValidRequests; ++i) {
+                                    final View view = finalRequesters.get(i);
+                                    Log.w("View", "requestLayout() improperly called by " + view +
+                                            " during second layout pass: posting in next frame");
+                                    view.requestLayout();
+                                }
+                            }
+                        });
+                    }
                 }
-                mLayoutRequesters.clear();
+
             }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
@@ -2048,6 +2035,68 @@
         mInLayout = false;
     }
 
+    /**
+     * This method is called during layout when there have been calls to requestLayout() during
+     * layout. It walks through the list of views that requested layout to determine which ones
+     * still need it, based on visibility in the hierarchy and whether they have already been
+     * handled (as is usually the case with ListView children).
+     *
+     * @param layoutRequesters The list of views that requested layout during layout
+     * @param secondLayoutRequests Whether the requests were issued during the second layout pass.
+     * If so, the FORCE_LAYOUT flag was not set on requesters.
+     * @return A list of the actual views that still need to be laid out.
+     */
+    private ArrayList<View> getValidLayoutRequesters(ArrayList<View> layoutRequesters,
+            boolean secondLayoutRequests) {
+
+        int numViewsRequestingLayout = layoutRequesters.size();
+        ArrayList<View> validLayoutRequesters = null;
+        for (int i = 0; i < numViewsRequestingLayout; ++i) {
+            View view = layoutRequesters.get(i);
+            if (view != null && view.mAttachInfo != null && view.mParent != null &&
+                    (secondLayoutRequests || (view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) ==
+                            View.PFLAG_FORCE_LAYOUT)) {
+                boolean gone = false;
+                View parent = view;
+                // Only trigger new requests for views in a non-GONE hierarchy
+                while (parent != null) {
+                    if ((parent.mViewFlags & View.VISIBILITY_MASK) == View.GONE) {
+                        gone = true;
+                        break;
+                    }
+                    if (parent.mParent instanceof View) {
+                        parent = (View) parent.mParent;
+                    } else {
+                        parent = null;
+                    }
+                }
+                if (!gone) {
+                    if (validLayoutRequesters == null) {
+                        validLayoutRequesters = new ArrayList<View>();
+                    }
+                    validLayoutRequesters.add(view);
+                }
+            }
+        }
+        if (!secondLayoutRequests) {
+            // If we're checking the layout flags, then we need to clean them up also
+            for (int i = 0; i < numViewsRequestingLayout; ++i) {
+                View view = layoutRequesters.get(i);
+                while (view != null &&
+                        (view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) != 0) {
+                    view.mPrivateFlags &= ~View.PFLAG_FORCE_LAYOUT;
+                    if (view.mParent instanceof View) {
+                        view = (View) view.mParent;
+                    } else {
+                        view = null;
+                    }
+                }
+            }
+        }
+        layoutRequesters.clear();
+        return validLayoutRequesters;
+    }
+
     public void requestTransparentRegion(View child) {
         // the test below should not fail unless someone is messing with us
         checkThread();
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 792188b..96ef0b4 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -272,7 +272,7 @@
         public static final int TYPE_APPLICATION_PANEL  = FIRST_SUB_WINDOW;
     
         /**
-         * Window type: window for showing media (e.g. video).  These windows
+         * Window type: window for showing media (such as video).  These windows
          * are displayed behind their attached window.
          */
         public static final int TYPE_APPLICATION_MEDIA  = FIRST_SUB_WINDOW+1;
@@ -584,14 +584,14 @@
         /** Window flag: this window can never receive touch events. */
         public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;
         
-        /** Window flag: Even when this window is focusable (its
-         * {@link #FLAG_NOT_FOCUSABLE is not set), allow any pointer events
+        /** Window flag: even when this window is focusable (its
+         * {@link #FLAG_NOT_FOCUSABLE} is not set), allow any pointer events
          * outside of the window to be sent to the windows behind it.  Otherwise
          * it will consume all pointer events itself, regardless of whether they
          * are inside of the window. */
         public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;
         
-        /** Window flag: When set, if the device is asleep when the touch
+        /** Window flag: when set, if the device is asleep when the touch
          * screen is pressed, you will receive this first touch event.  Usually
          * the first touch event is consumed by the system since the user can
          * not see what they are pressing on.
@@ -603,7 +603,7 @@
         public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;
         
         /** Window flag: place the window within the entire screen, ignoring
-         *  decorations around the border (a.k.a. the status bar).  The
+         *  decorations around the border (such as the status bar).  The
          *  window must correctly position its contents to take the screen
          *  decoration into account.  This flag is normally set for you
          *  by Window as described in {@link Window#setFlags}. */
@@ -613,7 +613,7 @@
         public static final int FLAG_LAYOUT_NO_LIMITS   = 0x00000200;
         
         /**
-         * Window flag: Hide all screen decorations (e.g. status bar) while
+         * Window flag: hide all screen decorations (such as the status bar) while
          * this window is displayed.  This allows the window to use the entire
          * display space for itself -- the status bar will be hidden when
          * an app window with this flag set is on the top layer.
@@ -631,8 +631,8 @@
          */
         public static final int FLAG_FULLSCREEN      = 0x00000400;
         
-        /** Window flag: Override {@link #FLAG_FULLSCREEN and force the
-         *  screen decorations (such as status bar) to be shown. */
+        /** Window flag: override {@link #FLAG_FULLSCREEN} and force the
+         *  screen decorations (such as the status bar) to be shown. */
         public static final int FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800;
         
         /** Window flag: turn on dithering when compositing this window to
@@ -641,7 +641,7 @@
         @Deprecated
         public static final int FLAG_DITHER             = 0x00001000;
         
-        /** Window flag: Treat the content of the window as secure, preventing
+        /** Window flag: treat the content of the window as secure, preventing
          * it from appearing in screenshots or from being viewed on non-secure
          * displays.
          *
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index adacef2..b084ac4 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8813,11 +8813,11 @@
         }
 
         public void drawTextRun(Canvas c, int start, int end,
-                int contextStart, int contextEnd, float x, float y, Paint p) {
+                int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
             int count = end - start;
             int contextCount = contextEnd - contextStart;
             c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
-                    contextCount, x, y, p);
+                    contextCount, x, y, flags, p);
         }
 
         public float measureText(int start, int end, Paint p) {
@@ -8829,20 +8829,30 @@
         }
 
         public float getTextRunAdvances(int start, int end, int contextStart,
-                int contextEnd, float[] advances, int advancesIndex,
+                int contextEnd, int flags, float[] advances, int advancesIndex,
                 Paint p) {
             int count = end - start;
             int contextCount = contextEnd - contextStart;
             return p.getTextRunAdvances(mChars, start + mStart, count,
-                    contextStart + mStart, contextCount, advances,
+                    contextStart + mStart, contextCount, flags, advances,
                     advancesIndex);
         }
 
-        public int getTextRunCursor(int contextStart, int contextEnd,
+        public float getTextRunAdvances(int start, int end, int contextStart,
+                int contextEnd, int flags, float[] advances, int advancesIndex,
+                Paint p, int reserved) {
+            int count = end - start;
+            int contextCount = contextEnd - contextStart;
+            return p.getTextRunAdvances(mChars, start + mStart, count,
+                    contextStart + mStart, contextCount, flags, advances,
+                    advancesIndex, reserved);
+        }
+
+        public int getTextRunCursor(int contextStart, int contextEnd, int flags,
                 int offset, int cursorOpt, Paint p) {
             int contextCount = contextEnd - contextStart;
             return p.getTextRunCursor(mChars, contextStart + mStart,
-                    contextCount, offset + mStart, cursorOpt);
+                    contextCount, flags, offset + mStart, cursorOpt);
         }
     }
 
diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java
index 7c2b1b5..91b109e 100644
--- a/core/java/com/android/internal/util/Protocol.java
+++ b/core/java/com/android/internal/util/Protocol.java
@@ -45,6 +45,7 @@
     public static final int BASE_WIFI_P2P_SERVICE                                   = 0x00023000;
     public static final int BASE_WIFI_MONITOR                                       = 0x00024000;
     public static final int BASE_WIFI_MANAGER                                       = 0x00025000;
+    public static final int BASE_WIFI_CONTROLLER                                    = 0x00026000;
     public static final int BASE_DHCP                                               = 0x00030000;
     public static final int BASE_DATA_CONNECTION                                    = 0x00040000;
     public static final int BASE_DATA_CONNECTION_AC                                 = 0x00041000;
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index e547f23..2cdd579 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -1549,6 +1549,24 @@
      *
      * @param what  is assigned to Message.what
      * @param arg1  is assigned to Message.arg1
+     * @return  A Message object from the global pool
+     */
+    public final Message obtainMessage(int what, int arg1) {
+        // use this obtain so we don't match the obtain(h, what, Object) method
+        return Message.obtain(mSmHandler, what, arg1, 0);
+    }
+
+    /**
+     * Get a message and set Message.target state machine handler,
+     * what, arg1 and arg2
+     *
+     * Note: The handler can be null if the state machine has quit,
+     * which means target will be null and may cause a AndroidRuntimeException
+     * in MessageQueue#enqueMessage if sent directly or if sent using
+     * StateMachine#sendMessage the message will just be ignored.
+     *
+     * @param what  is assigned to Message.what
+     * @param arg1  is assigned to Message.arg1
      * @param arg2  is assigned to Message.arg2
      * @return  A Message object from the global pool
      */
@@ -1606,6 +1624,32 @@
      *
      * Message is ignored if state machine has quit.
      */
+    public final void sendMessage(int what, int arg1) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessage(obtainMessage(what, arg1));
+    }
+
+    /**
+     * Enqueue a message to this state machine.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    public final void sendMessage(int what, int arg1, int arg2) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessage(obtainMessage(what, arg1, arg2));
+    }
+
+    /**
+     * Enqueue a message to this state machine.
+     *
+     * Message is ignored if state machine has quit.
+     */
     public final void sendMessage(int what, int arg1, int arg2, Object obj) {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
@@ -1658,6 +1702,32 @@
      *
      * Message is ignored if state machine has quit.
      */
+    public final void sendMessageDelayed(int what, int arg1, long delayMillis) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageDelayed(obtainMessage(what, arg1), delayMillis);
+    }
+
+    /**
+     * Enqueue a message to this state machine after a delay.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    public final void sendMessageDelayed(int what, int arg1, int arg2, long delayMillis) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageDelayed(obtainMessage(what, arg1, arg2), delayMillis);
+    }
+
+    /**
+     * Enqueue a message to this state machine after a delay.
+     *
+     * Message is ignored if state machine has quit.
+     */
     public final void sendMessageDelayed(int what, int arg1, int arg2, Object obj,
             long delayMillis) {
         // mSmHandler can be null if the state machine has quit.
@@ -1686,6 +1756,20 @@
      *
      * Message is ignored if state machine has quit.
      */
+    protected final void sendMessageAtFrontOfQueue(int what) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageAtFrontOfQueue(obtainMessage(what));
+    }
+
+    /**
+     * Enqueue a message to the front of the queue for this state machine.
+     * Protected, may only be called by instances of StateMachine.
+     *
+     * Message is ignored if state machine has quit.
+     */
     protected final void sendMessageAtFrontOfQueue(int what, Object obj) {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
@@ -1700,12 +1784,27 @@
      *
      * Message is ignored if state machine has quit.
      */
-    protected final void sendMessageAtFrontOfQueue(int what) {
+    protected final void sendMessageAtFrontOfQueue(int what, int arg1) {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
         if (smh == null) return;
 
-        smh.sendMessageAtFrontOfQueue(obtainMessage(what));
+        smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1));
+    }
+
+
+    /**
+     * Enqueue a message to the front of the queue for this state machine.
+     * Protected, may only be called by instances of StateMachine.
+     *
+     * Message is ignored if state machine has quit.
+     */
+    protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2) {
+        // mSmHandler can be null if the state machine has quit.
+        SmHandler smh = mSmHandler;
+        if (smh == null) return;
+
+        smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1, arg2));
     }
 
     /**
diff --git a/core/java/com/android/internal/view/IInputConnectionCallback.aidl b/core/java/com/android/internal/view/IInputConnectionCallback.aidl
deleted file mode 100644
index 5b5b3df..0000000
--- a/core/java/com/android/internal/view/IInputConnectionCallback.aidl
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.view;
-
-import android.graphics.Rect;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.inputmethod.TextBoxAttribute;
-import com.android.internal.view.IInputContext;
-import android.os.IBinder;
-
-/**
- * {@hide}
- */
-oneway interface IInputMethodCallback {
-    void finishedEvent(int seq, boolean handled);
-}
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index c7fcab8..5db860b 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -35,9 +35,9 @@
  */
 oneway interface IInputMethod {
     void attachToken(IBinder token);
-    
+
     void bindInput(in InputBinding binding);
-    
+
     void unbindInput();
 
     void startInput(in IInputContext inputContext, in EditorInfo attribute);
@@ -45,13 +45,13 @@
     void restartInput(in IInputContext inputContext, in EditorInfo attribute);
 
     void createSession(IInputMethodCallback callback);
-    
+
     void setSessionEnabled(IInputMethodSession session, boolean enabled);
-    
+
     void revokeSession(IInputMethodSession session);
-    
+
     void showSoftInput(int flags, in ResultReceiver resultReceiver);
-    
+
     void hideSoftInput(int flags, in ResultReceiver resultReceiver);
 
     void changeInputMethodSubtype(in InputMethodSubtype subtype);
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index 6640555..9a9f6c8 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -752,37 +752,37 @@
     }
 
 
-    static void drawText___CIIFFPaint(JNIEnv* env, jobject, SkCanvas* canvas,
+    static void drawText___CIIFFIPaint(JNIEnv* env, jobject, SkCanvas* canvas,
                                       jcharArray text, int index, int count,
-                                      jfloat x, jfloat y, SkPaint* paint) {
+                                      jfloat x, jfloat y, int flags, SkPaint* paint) {
         jchar* textArray = env->GetCharArrayElements(text, NULL);
-        drawTextWithGlyphs(canvas, textArray + index, 0, count, x, y, paint);
+        drawTextWithGlyphs(canvas, textArray + index, 0, count, x, y, flags, paint);
         env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
     }
 
-    static void drawText__StringIIFFPaint(JNIEnv* env, jobject,
+    static void drawText__StringIIFFIPaint(JNIEnv* env, jobject,
                                           SkCanvas* canvas, jstring text,
                                           int start, int end,
-                                          jfloat x, jfloat y, SkPaint* paint) {
+                                          jfloat x, jfloat y, int flags, SkPaint* paint) {
         const jchar* textArray = env->GetStringChars(text, NULL);
-        drawTextWithGlyphs(canvas, textArray, start, end, x, y, paint);
+        drawTextWithGlyphs(canvas, textArray, start, end, x, y, flags, paint);
         env->ReleaseStringChars(text, textArray);
     }
 
     static void drawTextWithGlyphs(SkCanvas* canvas, const jchar* textArray,
             int start, int end,
-            jfloat x, jfloat y, SkPaint* paint) {
+            jfloat x, jfloat y, int flags, SkPaint* paint) {
 
         jint count = end - start;
-        drawTextWithGlyphs(canvas, textArray + start, 0, count, count, x, y, paint);
+        drawTextWithGlyphs(canvas, textArray + start, 0, count, count, x, y, flags, paint);
     }
 
     static void drawTextWithGlyphs(SkCanvas* canvas, const jchar* textArray,
             int start, int count, int contextCount,
-            jfloat x, jfloat y, SkPaint* paint) {
+            jfloat x, jfloat y, int flags, SkPaint* paint) {
 
         sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-                textArray, start, count, contextCount);
+                textArray, start, count, contextCount, flags);
         if (value == NULL) {
             return;
         }
@@ -793,7 +793,7 @@
             x -= value->getTotalAdvance();
         }
         paint->setTextAlign(SkPaint::kLeft_Align);
-        doDrawGlyphsPos(canvas, value->getGlyphs(), value->getPos(), 0, value->getGlyphsCount(), x, y, paint);
+        doDrawGlyphsPos(canvas, value->getGlyphs(), value->getPos(), 0, value->getGlyphsCount(), x, y, flags, paint);
         doDrawTextDecorations(canvas, x, y, value->getTotalAdvance(), paint);
         paint->setTextAlign(align);
     }
@@ -835,8 +835,14 @@
     }
 }
 
+    static void doDrawGlyphs(SkCanvas* canvas, const jchar* glyphArray, int index, int count,
+            jfloat x, jfloat y, int flags, SkPaint* paint) {
+        // Beware: this needs Glyph encoding (already done on the Paint constructor)
+        canvas->drawText(glyphArray + index * 2, count * 2, x, y, *paint);
+    }
+
     static void doDrawGlyphsPos(SkCanvas* canvas, const jchar* glyphArray, const jfloat* posArray,
-            int index, int count, jfloat x, jfloat y, SkPaint* paint) {
+            int index, int count, jfloat x, jfloat y, int flags, SkPaint* paint) {
         SkPoint* posPtr = new SkPoint[count];
         for (int indx = 0; indx < count; indx++) {
             posPtr[indx].fX = SkFloatToScalar(x + posArray[indx * 2]);
@@ -846,27 +852,27 @@
         delete[] posPtr;
     }
 
-    static void drawTextRun___CIIIIFFPaint(
+    static void drawTextRun___CIIIIFFIPaint(
         JNIEnv* env, jobject, SkCanvas* canvas, jcharArray text, int index,
         int count, int contextIndex, int contextCount,
-        jfloat x, jfloat y, SkPaint* paint) {
+        jfloat x, jfloat y, int dirFlags, SkPaint* paint) {
 
         jchar* chars = env->GetCharArrayElements(text, NULL);
         drawTextWithGlyphs(canvas, chars + contextIndex, index - contextIndex,
-                count, contextCount, x, y, paint);
+                count, contextCount, x, y, dirFlags, paint);
         env->ReleaseCharArrayElements(text, chars, JNI_ABORT);
     }
 
-    static void drawTextRun__StringIIIIFFPaint(
+    static void drawTextRun__StringIIIIFFIPaint(
         JNIEnv* env, jobject obj, SkCanvas* canvas, jstring text, jint start,
         jint end, jint contextStart, jint contextEnd,
-        jfloat x, jfloat y, SkPaint* paint) {
+        jfloat x, jfloat y, jint dirFlags, SkPaint* paint) {
 
         jint count = end - start;
         jint contextCount = contextEnd - contextStart;
         const jchar* chars = env->GetStringChars(text, NULL);
         drawTextWithGlyphs(canvas, chars + contextStart, start - contextStart,
-                count, contextCount, x, y, paint);
+                count, contextCount, x, y, dirFlags, paint);
         env->ReleaseStringChars(text, chars);
     }
 
@@ -929,19 +935,21 @@
 
     static void drawTextOnPath___CIIPathFFPaint(JNIEnv* env, jobject,
             SkCanvas* canvas, jcharArray text, int index, int count,
-            SkPath* path, jfloat hOffset, jfloat vOffset, SkPaint* paint) {
+            SkPath* path, jfloat hOffset, jfloat vOffset, jint bidiFlags, SkPaint* paint) {
 
         jchar* textArray = env->GetCharArrayElements(text, NULL);
-        TextLayout::drawTextOnPath(paint, textArray + index, count, hOffset, vOffset, path, canvas);
+        TextLayout::drawTextOnPath(paint, textArray + index, count, bidiFlags, hOffset, vOffset,
+                                   path, canvas);
         env->ReleaseCharArrayElements(text, textArray, 0);
     }
 
     static void drawTextOnPath__StringPathFFPaint(JNIEnv* env, jobject,
             SkCanvas* canvas, jstring text, SkPath* path,
-            jfloat hOffset, jfloat vOffset, SkPaint* paint) {
+            jfloat hOffset, jfloat vOffset, jint bidiFlags, SkPaint* paint) {
         const jchar* text_ = env->GetStringChars(text, NULL);
         int count = env->GetStringLength(text);
-        TextLayout::drawTextOnPath(paint, text_, count, hOffset, vOffset, path, canvas);
+        TextLayout::drawTextOnPath(paint, text_, count, bidiFlags, hOffset, vOffset,
+                                   path, canvas);
         env->ReleaseStringChars(text, text_);
     }
 
@@ -1051,21 +1059,21 @@
         (void*)SkCanvasGlue::drawBitmapMesh},
     {"nativeDrawVertices", "(III[FI[FI[II[SIII)V",
         (void*)SkCanvasGlue::drawVertices},
-    {"native_drawText","(I[CIIFFI)V",
-        (void*) SkCanvasGlue::drawText___CIIFFPaint},
-    {"native_drawText","(ILjava/lang/String;IIFFI)V",
-        (void*) SkCanvasGlue::drawText__StringIIFFPaint},
-    {"native_drawTextRun","(I[CIIIIFFI)V",
-        (void*) SkCanvasGlue::drawTextRun___CIIIIFFPaint},
-    {"native_drawTextRun","(ILjava/lang/String;IIIIFFI)V",
-        (void*) SkCanvasGlue::drawTextRun__StringIIIIFFPaint},
+    {"native_drawText","(I[CIIFFII)V",
+        (void*) SkCanvasGlue::drawText___CIIFFIPaint},
+    {"native_drawText","(ILjava/lang/String;IIFFII)V",
+        (void*) SkCanvasGlue::drawText__StringIIFFIPaint},
+    {"native_drawTextRun","(I[CIIIIFFII)V",
+        (void*) SkCanvasGlue::drawTextRun___CIIIIFFIPaint},
+    {"native_drawTextRun","(ILjava/lang/String;IIIIFFII)V",
+        (void*) SkCanvasGlue::drawTextRun__StringIIIIFFIPaint},
     {"native_drawPosText","(I[CII[FI)V",
         (void*) SkCanvasGlue::drawPosText___CII_FPaint},
     {"native_drawPosText","(ILjava/lang/String;[FI)V",
         (void*) SkCanvasGlue::drawPosText__String_FPaint},
-    {"native_drawTextOnPath","(I[CIIIFFI)V",
+    {"native_drawTextOnPath","(I[CIIIFFII)V",
         (void*) SkCanvasGlue::drawTextOnPath___CIIPathFFPaint},
-    {"native_drawTextOnPath","(ILjava/lang/String;IFFI)V",
+    {"native_drawTextOnPath","(ILjava/lang/String;IFFII)V",
         (void*) SkCanvasGlue::drawTextOnPath__StringPathFFPaint},
     {"native_drawPicture", "(II)V", (void*) SkCanvasGlue::drawPicture},
 
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 07f55e0..29a36de 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -401,7 +401,7 @@
         jfloat result = 0;
 
         TextLayout::getTextRunAdvances(paint, textArray, index, count, textLength,
-                NULL /* dont need all advances */, &result);
+                paint->getFlags(), NULL /* dont need all advances */, &result);
 
         env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray), JNI_ABORT);
         return result;
@@ -426,7 +426,7 @@
         jfloat width = 0;
 
         TextLayout::getTextRunAdvances(paint, textArray, start, count, textLength,
-                NULL /* dont need all advances */, &width);
+                paint->getFlags(), NULL /* dont need all advances */, &width);
 
         env->ReleaseStringChars(text, textArray);
         return width;
@@ -446,7 +446,7 @@
         jfloat width = 0;
 
         TextLayout::getTextRunAdvances(paint, textArray, 0, textLength, textLength,
-                NULL /* dont need all advances */, &width);
+                paint->getFlags(), NULL /* dont need all advances */, &width);
 
         env->ReleaseStringChars(text, textArray);
         return width;
@@ -473,7 +473,7 @@
         jfloat* widthsArray = autoWidths.ptr();
 
         TextLayout::getTextRunAdvances(paint, text, 0, count, count,
-                widthsArray, NULL /* dont need totalAdvance */);
+                paint->getFlags(), widthsArray, NULL /* dont need totalAdvance */);
 
         return count;
     }
@@ -494,8 +494,48 @@
         return count;
     }
 
+    static int doTextGlyphs(JNIEnv* env, SkPaint* paint, const jchar* text, jint start, jint count,
+            jint contextCount, jint flags, jcharArray glyphs) {
+        NPE_CHECK_RETURN_ZERO(env, paint);
+        NPE_CHECK_RETURN_ZERO(env, text);
+
+        if ((start | count | contextCount) < 0 || contextCount < count || !glyphs) {
+            doThrowAIOOBE(env);
+            return 0;
+        }
+        if (count == 0) {
+            return 0;
+        }
+        size_t glypthsLength = env->GetArrayLength(glyphs);
+        if ((size_t)count > glypthsLength) {
+            doThrowAIOOBE(env);
+            return 0;
+        }
+
+        jchar* glyphsArray = env->GetCharArrayElements(glyphs, NULL);
+
+        sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
+                text, start, count, contextCount, flags);
+        const jchar* shapedGlyphs = value->getGlyphs();
+        size_t glyphsCount = value->getGlyphsCount();
+        memcpy(glyphsArray, shapedGlyphs, sizeof(jchar) * glyphsCount);
+
+        env->ReleaseCharArrayElements(glyphs, glyphsArray, JNI_ABORT);
+        return glyphsCount;
+    }
+
+    static int getTextGlyphs__StringIIIII_C(JNIEnv* env, jobject clazz, SkPaint* paint,
+            jstring text, jint start, jint end, jint contextStart, jint contextEnd, jint flags,
+            jcharArray glyphs) {
+        const jchar* textArray = env->GetStringChars(text, NULL);
+        int count = doTextGlyphs(env, paint, textArray + contextStart, start - contextStart,
+                end - start, contextEnd - contextStart, flags, glyphs);
+        env->ReleaseStringChars(text, textArray);
+        return count;
+    }
+
     static jfloat doTextRunAdvances(JNIEnv *env, SkPaint *paint, const jchar *text,
-                                    jint start, jint count, jint contextCount,
+                                    jint start, jint count, jint contextCount, jint flags,
                                     jfloatArray advances, jint advancesIndex) {
         NPE_CHECK_RETURN_ZERO(env, paint);
         NPE_CHECK_RETURN_ZERO(env, text);
@@ -517,7 +557,7 @@
         jfloat advancesArray[count];
         jfloat totalAdvance = 0;
 
-        TextLayout::getTextRunAdvances(paint, text, start, count, contextCount,
+        TextLayout::getTextRunAdvances(paint, text, start, count, contextCount, flags,
                                        advancesArray, &totalAdvance);
 
         if (advances != NULL) {
@@ -526,32 +566,70 @@
         return totalAdvance;
     }
 
-    static float getTextRunAdvances___CIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint,
+    static jfloat doTextRunAdvancesICU(JNIEnv *env, SkPaint *paint, const jchar *text,
+                                    jint start, jint count, jint contextCount, jint flags,
+                                    jfloatArray advances, jint advancesIndex) {
+        NPE_CHECK_RETURN_ZERO(env, paint);
+        NPE_CHECK_RETURN_ZERO(env, text);
+
+        if ((start | count | contextCount | advancesIndex) < 0 || contextCount < count) {
+            doThrowAIOOBE(env);
+            return 0;
+        }
+        if (count == 0) {
+            return 0;
+        }
+        if (advances) {
+            size_t advancesLength = env->GetArrayLength(advances);
+            if ((size_t)count > advancesLength) {
+                doThrowAIOOBE(env);
+                return 0;
+            }
+        }
+
+        jfloat advancesArray[count];
+        jfloat totalAdvance = 0;
+
+        TextLayout::getTextRunAdvancesICU(paint, text, start, count, contextCount, flags,
+                                       advancesArray, totalAdvance);
+
+        if (advances != NULL) {
+            env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray);
+        }
+        return totalAdvance;
+    }
+
+    static float getTextRunAdvances___CIIIII_FII(JNIEnv* env, jobject clazz, SkPaint* paint,
             jcharArray text, jint index, jint count, jint contextIndex, jint contextCount,
-            jfloatArray advances, jint advancesIndex) {
+            jint flags, jfloatArray advances, jint advancesIndex, jint reserved) {
         jchar* textArray = env->GetCharArrayElements(text, NULL);
-        jfloat result = doTextRunAdvances(env, paint, textArray + contextIndex,
-                index - contextIndex, count, contextCount, advances, advancesIndex);
+        jfloat result = (reserved == 0) ?
+                doTextRunAdvances(env, paint, textArray + contextIndex, index - contextIndex,
+                        count, contextCount, flags, advances, advancesIndex) :
+                doTextRunAdvancesICU(env, paint, textArray + contextIndex, index - contextIndex,
+                        count, contextCount, flags, advances, advancesIndex);
         env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
         return result;
     }
 
-    static float getTextRunAdvances__StringIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint,
-            jstring text, jint start, jint end, jint contextStart, jint contextEnd,
-            jfloatArray advances, jint advancesIndex) {
+    static float getTextRunAdvances__StringIIIII_FII(JNIEnv* env, jobject clazz, SkPaint* paint,
+            jstring text, jint start, jint end, jint contextStart, jint contextEnd, jint flags,
+            jfloatArray advances, jint advancesIndex, jint reserved) {
         const jchar* textArray = env->GetStringChars(text, NULL);
-        jfloat result = doTextRunAdvances(env, paint, textArray + contextStart,
-                start - contextStart, end - start, contextEnd - contextStart,
-                advances, advancesIndex);
+        jfloat result = (reserved == 0) ?
+                doTextRunAdvances(env, paint, textArray + contextStart, start - contextStart,
+                        end - start, contextEnd - contextStart, flags, advances, advancesIndex) :
+                doTextRunAdvancesICU(env, paint, textArray + contextStart, start - contextStart,
+                        end - start, contextEnd - contextStart, flags, advances, advancesIndex);
         env->ReleaseStringChars(text, textArray);
         return result;
     }
 
     static jint doTextRunCursor(JNIEnv *env, SkPaint* paint, const jchar *text, jint start,
-            jint count, jint offset, jint opt) {
+            jint count, jint flags, jint offset, jint opt) {
         jfloat scalarArray[count];
 
-        TextLayout::getTextRunAdvances(paint, text, start, count, start + count,
+        TextLayout::getTextRunAdvances(paint, text, start, count, start + count, flags,
                 scalarArray, NULL /* dont need totalAdvance */);
 
         jint pos = offset - start;
@@ -592,39 +670,39 @@
     }
 
     static jint getTextRunCursor___C(JNIEnv* env, jobject clazz, SkPaint* paint, jcharArray text,
-            jint contextStart, jint contextCount, jint offset, jint cursorOpt) {
+            jint contextStart, jint contextCount, jint flags, jint offset, jint cursorOpt) {
         jchar* textArray = env->GetCharArrayElements(text, NULL);
-        jint result = doTextRunCursor(env, paint, textArray, contextStart, contextCount,
+        jint result = doTextRunCursor(env, paint, textArray, contextStart, contextCount, flags,
                 offset, cursorOpt);
         env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
         return result;
     }
 
     static jint getTextRunCursor__String(JNIEnv* env, jobject clazz, SkPaint* paint, jstring text,
-            jint contextStart, jint contextEnd, jint offset, jint cursorOpt) {
+            jint contextStart, jint contextEnd, jint flags, jint offset, jint cursorOpt) {
         const jchar* textArray = env->GetStringChars(text, NULL);
         jint result = doTextRunCursor(env, paint, textArray, contextStart,
-                contextEnd - contextStart, offset, cursorOpt);
+                contextEnd - contextStart, flags, offset, cursorOpt);
         env->ReleaseStringChars(text, textArray);
         return result;
     }
 
     static void getTextPath(JNIEnv* env, SkPaint* paint, const jchar* text, jint count,
-                            jfloat x, jfloat y, SkPath *path) {
-        TextLayout::getTextPath(paint, text, count, x, y, path);
+                            jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
+        TextLayout::getTextPath(paint, text, count, bidiFlags, x, y, path);
     }
 
-    static void getTextPath___C(JNIEnv* env, jobject clazz, SkPaint* paint,
+    static void getTextPath___C(JNIEnv* env, jobject clazz, SkPaint* paint, jint bidiFlags,
             jcharArray text, int index, int count, jfloat x, jfloat y, SkPath* path) {
         const jchar* textArray = env->GetCharArrayElements(text, NULL);
-        getTextPath(env, paint, textArray + index, count, x, y, path);
+        getTextPath(env, paint, textArray + index, count, bidiFlags, x, y, path);
         env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray), JNI_ABORT);
     }
 
-    static void getTextPath__String(JNIEnv* env, jobject clazz, SkPaint* paint,
+    static void getTextPath__String(JNIEnv* env, jobject clazz, SkPaint* paint, jint bidiFlags,
             jstring text, int start, int end, jfloat x, jfloat y, SkPath* path) {
         const jchar* textArray = env->GetStringChars(text, NULL);
-        getTextPath(env, paint, textArray + start, end - start, x, y, path);
+        getTextPath(env, paint, textArray + start, end - start, bidiFlags, x, y, path);
         env->ReleaseStringChars(text, textArray);
     }
 
@@ -648,7 +726,7 @@
                          int count, float maxWidth, jfloatArray jmeasured,
                          SkPaint::TextBufferDirection tbd) {
         sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(&paint,
-                text, 0, count, count);
+                text, 0, count, count, paint.getFlags());
         if (value == NULL) {
             return 0;
         }
@@ -720,7 +798,7 @@
         SkIRect ir;
 
         sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(&paint,
-                text, 0, count, count);
+                text, 0, count, count, paint.getFlags());
         if (value == NULL) {
             return;
         }
@@ -808,15 +886,19 @@
     {"native_breakText","(Ljava/lang/String;ZF[F)I", (void*) SkPaintGlue::breakTextS},
     {"native_getTextWidths","(I[CII[F)I", (void*) SkPaintGlue::getTextWidths___CII_F},
     {"native_getTextWidths","(ILjava/lang/String;II[F)I", (void*) SkPaintGlue::getTextWidths__StringII_F},
-    {"native_getTextRunAdvances","(I[CIIII[FI)F",
-        (void*) SkPaintGlue::getTextRunAdvances___CIIII_FI},
-    {"native_getTextRunAdvances","(ILjava/lang/String;IIII[FI)F",
-        (void*) SkPaintGlue::getTextRunAdvances__StringIIII_FI},
-    {"native_getTextRunCursor", "(I[CIIII)I", (void*) SkPaintGlue::getTextRunCursor___C},
-    {"native_getTextRunCursor", "(ILjava/lang/String;IIII)I",
+    {"native_getTextRunAdvances","(I[CIIIII[FII)F",
+        (void*) SkPaintGlue::getTextRunAdvances___CIIIII_FII},
+    {"native_getTextRunAdvances","(ILjava/lang/String;IIIII[FII)F",
+        (void*) SkPaintGlue::getTextRunAdvances__StringIIIII_FII},
+
+
+    {"native_getTextGlyphs","(ILjava/lang/String;IIIII[C)I",
+        (void*) SkPaintGlue::getTextGlyphs__StringIIIII_C},
+    {"native_getTextRunCursor", "(I[CIIIII)I", (void*) SkPaintGlue::getTextRunCursor___C},
+    {"native_getTextRunCursor", "(ILjava/lang/String;IIIII)I",
         (void*) SkPaintGlue::getTextRunCursor__String},
-    {"native_getTextPath","(I[CIIFFI)V", (void*) SkPaintGlue::getTextPath___C},
-    {"native_getTextPath","(ILjava/lang/String;IIFFI)V", (void*) SkPaintGlue::getTextPath__String},
+    {"native_getTextPath","(II[CIIFFI)V", (void*) SkPaintGlue::getTextPath___C},
+    {"native_getTextPath","(IILjava/lang/String;IIFFI)V", (void*) SkPaintGlue::getTextPath__String},
     {"nativeGetStringBounds", "(ILjava/lang/String;IILandroid/graphics/Rect;)V",
                                         (void*) SkPaintGlue::getStringBounds },
     {"nativeGetCharArrayBounds", "(I[CIILandroid/graphics/Rect;)V",
diff --git a/core/jni/android/graphics/TextLayout.cpp b/core/jni/android/graphics/TextLayout.cpp
index b77236c..2beedad 100644
--- a/core/jni/android/graphics/TextLayout.cpp
+++ b/core/jni/android/graphics/TextLayout.cpp
@@ -28,13 +28,33 @@
 
 namespace android {
 
+// Returns true if we might need layout.  If bidiFlags force LTR, assume no layout, if
+// bidiFlags indicate there probably is RTL, assume we do, otherwise scan the text
+// looking for a character >= the first RTL character in unicode and assume we do if
+// we find one.
+bool TextLayout::needsLayout(const jchar* text, jint len, jint bidiFlags) {
+    if (bidiFlags == kBidi_Force_LTR) {
+        return false;
+    }
+    if ((bidiFlags == kBidi_RTL) || (bidiFlags == kBidi_Default_RTL) ||
+            bidiFlags == kBidi_Force_RTL) {
+        return true;
+    }
+    for (int i = 0; i < len; ++i) {
+        if (text[i] >= UNICODE_FIRST_RTL_CHAR) {
+            return true;
+        }
+    }
+    return false;
+}
+
 // Draws or gets the path of a paragraph of text on a single line, running bidi and shaping.
 // This will draw if canvas is not null, otherwise path must be non-null and it will create
 // a path representing the text that would have been drawn.
 void TextLayout::handleText(SkPaint *paint, const jchar* text, jsize len,
-                            jfloat x, jfloat y, SkPath *path) {
+                            jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
     sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            text, 0, len, len);
+            text, 0, len, len, bidiFlags);
     if (value == NULL) {
         return ;
     }
@@ -45,10 +65,10 @@
 }
 
 void TextLayout::getTextRunAdvances(SkPaint* paint, const jchar* chars, jint start,
-                                    jint count, jint contextCount,
+                                    jint count, jint contextCount, jint dirFlags,
                                     jfloat* resultAdvances, jfloat* resultTotalAdvance) {
     sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            chars, start, count, contextCount);
+            chars, start, count, contextCount, dirFlags);
     if (value == NULL) {
         return ;
     }
@@ -60,20 +80,29 @@
     }
 }
 
-void TextLayout::getTextPath(SkPaint *paint, const jchar *text, jsize len,
-                             jfloat x, jfloat y, SkPath *path) {
-    handleText(paint, text, len, x, y, path);
+void TextLayout::getTextRunAdvancesICU(SkPaint* paint, const jchar* chars, jint start,
+                                    jint count, jint contextCount, jint dirFlags,
+                                    jfloat* resultAdvances, jfloat& resultTotalAdvance) {
+    // Compute advances and return them
+    computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags,
+            resultAdvances, &resultTotalAdvance);
 }
 
+void TextLayout::getTextPath(SkPaint *paint, const jchar *text, jsize len,
+                             jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
+    handleText(paint, text, len, bidiFlags, x, y, path);
+}
+
+
 void TextLayout::drawTextOnPath(SkPaint* paint, const jchar* text, int count,
-                                jfloat hOffset, jfloat vOffset,
+                                int bidiFlags, jfloat hOffset, jfloat vOffset,
                                 SkPath* path, SkCanvas* canvas) {
 
     SkScalar h_ = SkFloatToScalar(hOffset);
     SkScalar v_ = SkFloatToScalar(vOffset);
 
     sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            text, 0, count, count);
+            text, 0, count, count, bidiFlags);
     if (value == NULL) {
         return;
     }
@@ -82,4 +111,73 @@
     canvas->drawTextOnPathHV(value->getGlyphs(), value->getGlyphsCount() * 2, *path, h_, v_, *paint);
 }
 
+void TextLayout::computeAdvancesWithICU(SkPaint* paint, const UChar* chars,
+        size_t start, size_t count, size_t contextCount, int dirFlags,
+        jfloat* outAdvances, jfloat* outTotalAdvance) {
+    SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount);
+    jchar* buffer = tempBuffer.get();
+    SkScalar* scalarArray = (SkScalar*)outAdvances;
+
+    // this is where we'd call harfbuzz
+    // for now we just use ushape.c
+    size_t widths;
+    const jchar* text;
+    if (dirFlags & 0x1) { // rtl, call arabic shaping in case
+        UErrorCode status = U_ZERO_ERROR;
+        // Use fixed length since we need to keep start and count valid
+        u_shapeArabic(chars, contextCount, buffer, contextCount,
+                U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
+                U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
+                U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
+        // we shouldn't fail unless there's an out of memory condition,
+        // in which case we're hosed anyway
+        for (int i = start, e = i + count; i < e; ++i) {
+            if (buffer[i] == UNICODE_NOT_A_CHAR) {
+                buffer[i] = UNICODE_ZWSP; // zero-width-space for skia
+            }
+        }
+        text = buffer + start;
+        widths = paint->getTextWidths(text, count << 1, scalarArray);
+    } else {
+        text = chars + start;
+        widths = paint->getTextWidths(text, count << 1, scalarArray);
+    }
+
+    jfloat totalAdvance = 0;
+    if (widths < count) {
+#if DEBUG_ADVANCES
+    ALOGD("ICU -- count=%d", widths);
+#endif
+        // Skia operates on code points, not code units, so surrogate pairs return only
+        // one value. Expand the result so we have one value per UTF-16 code unit.
+
+        // Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
+        // leaving the remaining widths zero.  Not nice.
+        for (size_t i = 0, p = 0; i < widths; ++i) {
+            totalAdvance += outAdvances[p++] = SkScalarToFloat(scalarArray[i]);
+            if (p < count &&
+                    text[p] >= UNICODE_FIRST_LOW_SURROGATE &&
+                    text[p] < UNICODE_FIRST_PRIVATE_USE &&
+                    text[p-1] >= UNICODE_FIRST_HIGH_SURROGATE &&
+                    text[p-1] < UNICODE_FIRST_LOW_SURROGATE) {
+                outAdvances[p++] = 0;
+            }
+#if DEBUG_ADVANCES
+            ALOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
+#endif
+        }
+    } else {
+#if DEBUG_ADVANCES
+    ALOGD("ICU -- count=%d", count);
+#endif
+        for (size_t i = 0; i < count; i++) {
+            totalAdvance += outAdvances[i] = SkScalarToFloat(scalarArray[i]);
+#if DEBUG_ADVANCES
+            ALOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
+#endif
+        }
+    }
+    *outTotalAdvance = totalAdvance;
+}
+
 }
diff --git a/core/jni/android/graphics/TextLayout.h b/core/jni/android/graphics/TextLayout.h
index fa388c8..a0f9402 100644
--- a/core/jni/android/graphics/TextLayout.h
+++ b/core/jni/android/graphics/TextLayout.h
@@ -42,6 +42,17 @@
 #define USE_TEXT_LAYOUT_CACHE 1
 
 enum {
+    kBidi_LTR = 0,
+    kBidi_RTL = 1,
+    kBidi_Default_LTR = 2,
+    kBidi_Default_RTL = 3,
+    kBidi_Force_LTR = 4,
+    kBidi_Force_RTL = 5,
+
+    kBidi_Mask = 0x7
+};
+
+enum {
     kDirection_LTR = 0,
     kDirection_RTL = 1,
 
@@ -52,18 +63,28 @@
 public:
 
     static void getTextRunAdvances(SkPaint* paint, const jchar* chars, jint start,
-                                   jint count, jint contextCount,
+                                   jint count, jint contextCount, jint dirFlags,
                                    jfloat* resultAdvances, jfloat* resultTotalAdvance);
 
+    static void getTextRunAdvancesICU(SkPaint* paint, const jchar* chars, jint start,
+                                   jint count, jint contextCount, jint dirFlags,
+                                   jfloat* resultAdvances, jfloat& resultTotalAdvance);
+
     static void getTextPath(SkPaint* paint, const jchar* text, jsize len,
-                            jfloat x, jfloat y, SkPath* path);
+                            jint bidiFlags, jfloat x, jfloat y, SkPath* path);
 
     static void drawTextOnPath(SkPaint* paint, const jchar* text, jsize len,
-                               jfloat hOffset, jfloat vOffset,
+                               int bidiFlags, jfloat hOffset, jfloat vOffset,
                                SkPath* path, SkCanvas* canvas);
 
 private:
+    static bool needsLayout(const jchar* text, jint len, jint bidiFlags);
+
     static void handleText(SkPaint* paint, const jchar* text, jsize len,
-                           jfloat x, jfloat y, SkPath* path);
+                           int bidiFlags, jfloat x, jfloat y, SkPath* path);
+
+    static void computeAdvancesWithICU(SkPaint* paint, const UChar* chars,
+            size_t start, size_t count, size_t contextCount, int dirFlags,
+            jfloat* outAdvances, jfloat* outTotalAdvance);
 };
 } // namespace android
diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
index 1a8612e..1ace23e 100644
--- a/core/jni/android/graphics/TextLayoutCache.cpp
+++ b/core/jni/android/graphics/TextLayoutCache.cpp
@@ -87,7 +87,7 @@
  * Caching
  */
 sp<TextLayoutValue> TextLayoutCache::getValue(const SkPaint* paint,
-            const jchar* text, jint start, jint count, jint contextCount) {
+            const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) {
     AutoMutex _l(mLock);
     nsecs_t startTime = 0;
     if (mDebugEnabled) {
@@ -95,7 +95,7 @@
     }
 
     // Create the key
-    TextLayoutCacheKey key(paint, text, start, count, contextCount);
+    TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags);
 
     // Get value from cache if possible
     sp<TextLayoutValue> value = mCache.get(key);
@@ -111,7 +111,7 @@
         // Compute advances and store them
         mShaper->computeValues(value.get(), paint,
                 reinterpret_cast<const UChar*>(key.getText()), start, count,
-                size_t(contextCount));
+                size_t(contextCount), int(dirFlags));
 
         if (mDebugEnabled) {
             value->setElapsedTime(systemTime(SYSTEM_TIME_MONOTONIC) - startTime);
@@ -218,13 +218,14 @@
  * TextLayoutCacheKey
  */
 TextLayoutCacheKey::TextLayoutCacheKey(): start(0), count(0), contextCount(0),
-        typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0),
+        dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0),
         hinting(SkPaint::kNo_Hinting), variant(SkPaint::kDefault_Variant), language()  {
 }
 
 TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text,
-        size_t start, size_t count, size_t contextCount) :
-            start(start), count(count), contextCount(contextCount) {
+        size_t start, size_t count, size_t contextCount, int dirFlags) :
+            start(start), count(count), contextCount(contextCount),
+            dirFlags(dirFlags) {
     textCopy.setTo(text, contextCount);
     typeface = paint->getTypeface();
     textSize = paint->getTextSize();
@@ -241,6 +242,7 @@
         start(other.start),
         count(other.count),
         contextCount(other.contextCount),
+        dirFlags(other.dirFlags),
         typeface(other.typeface),
         textSize(other.textSize),
         textSkewX(other.textSkewX),
@@ -279,6 +281,9 @@
     deltaInt = lhs.hinting - rhs.hinting;
     if (deltaInt != 0) return (deltaInt);
 
+    deltaInt = lhs.dirFlags - rhs.dirFlags;
+    if (deltaInt) return (deltaInt);
+
     deltaInt = lhs.variant - rhs.variant;
     if (deltaInt) return (deltaInt);
 
@@ -354,9 +359,9 @@
 }
 
 void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* paint, const UChar* chars,
-        size_t start, size_t count, size_t contextCount) {
+        size_t start, size_t count, size_t contextCount, int dirFlags) {
 
-    computeValues(paint, chars, start, count, contextCount,
+    computeValues(paint, chars, start, count, contextCount, dirFlags,
             &value->mAdvances, &value->mTotalAdvance, &value->mGlyphs, &value->mPos);
 #if DEBUG_ADVANCES
     ALOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count,
@@ -365,7 +370,7 @@
 }
 
 void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars,
-        size_t start, size_t count, size_t contextCount,
+        size_t start, size_t count, size_t contextCount, int dirFlags,
         Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
         Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
         *outTotalAdvance = 0;
@@ -373,94 +378,110 @@
             return;
         }
 
-        UBiDiLevel bidiReq = UBIDI_DEFAULT_LTR;
+        UBiDiLevel bidiReq = 0;
+        bool forceLTR = false;
+        bool forceRTL = false;
+
+        switch (dirFlags & kBidi_Mask) {
+            case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
+            case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
+            case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
+            case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
+            case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR
+            case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL
+        }
+
         bool useSingleRun = false;
-        bool isRTL = false;
-
-        UBiDi* bidi = ubidi_open();
-        if (bidi) {
-            UErrorCode status = U_ZERO_ERROR;
+        bool isRTL = forceRTL;
+        if (forceLTR || forceRTL) {
+            useSingleRun = true;
+        } else {
+            UBiDi* bidi = ubidi_open();
+            if (bidi) {
+                UErrorCode status = U_ZERO_ERROR;
 #if DEBUG_GLYPHS
-            ALOGD("******** ComputeValues -- start");
-            ALOGD("      -- string = '%s'", String8(chars + start, count).string());
-            ALOGD("      -- start = %d", start);
-            ALOGD("      -- count = %d", count);
-            ALOGD("      -- contextCount = %d", contextCount);
-            ALOGD("      -- bidiReq = %d", bidiReq);
+                ALOGD("******** ComputeValues -- start");
+                ALOGD("      -- string = '%s'", String8(chars + start, count).string());
+                ALOGD("      -- start = %d", start);
+                ALOGD("      -- count = %d", count);
+                ALOGD("      -- contextCount = %d", contextCount);
+                ALOGD("      -- bidiReq = %d", bidiReq);
 #endif
-            ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
-            if (U_SUCCESS(status)) {
-                int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
-                ssize_t rc = ubidi_countRuns(bidi, &status);
+                ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
+                if (U_SUCCESS(status)) {
+                    int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
+                    ssize_t rc = ubidi_countRuns(bidi, &status);
 #if DEBUG_GLYPHS
-                ALOGD("      -- paraDir = %d", paraDir);
-                ALOGD("      -- run-count = %d", int(rc));
+                    ALOGD("      -- dirFlags = %d", dirFlags);
+                    ALOGD("      -- paraDir = %d", paraDir);
+                    ALOGD("      -- run-count = %d", int(rc));
 #endif
-                if (U_SUCCESS(status) && rc == 1) {
-                    // Normal case: one run, status is ok
-                    isRTL = (paraDir == 1);
-                    useSingleRun = true;
-                } else if (!U_SUCCESS(status) || rc < 1) {
-                    ALOGW("Need to force to single run -- string = '%s',"
-                            " status = %d, rc = %d",
-                            String8(chars + start, count).string(), status, int(rc));
-                    isRTL = (paraDir == 1);
-                    useSingleRun = true;
-                } else {
-                    int32_t end = start + count;
-                    for (size_t i = 0; i < size_t(rc); ++i) {
-                        int32_t startRun = -1;
-                        int32_t lengthRun = -1;
-                        UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
+                    if (U_SUCCESS(status) && rc == 1) {
+                        // Normal case: one run, status is ok
+                        isRTL = (paraDir == 1);
+                        useSingleRun = true;
+                    } else if (!U_SUCCESS(status) || rc < 1) {
+                        ALOGW("Need to force to single run -- string = '%s',"
+                                " status = %d, rc = %d",
+                                String8(chars + start, count).string(), status, int(rc));
+                        isRTL = (paraDir == 1);
+                        useSingleRun = true;
+                    } else {
+                        int32_t end = start + count;
+                        for (size_t i = 0; i < size_t(rc); ++i) {
+                            int32_t startRun = -1;
+                            int32_t lengthRun = -1;
+                            UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
 
-                        if (startRun == -1 || lengthRun == -1) {
-                            // Something went wrong when getting the visual run, need to clear
-                            // already computed data before doing a single run pass
-                            ALOGW("Visual run is not valid");
-                            outGlyphs->clear();
-                            outAdvances->clear();
-                            outPos->clear();
-                            *outTotalAdvance = 0;
-                            isRTL = (paraDir == 1);
-                            useSingleRun = true;
-                            break;
-                        }
+                            if (startRun == -1 || lengthRun == -1) {
+                                // Something went wrong when getting the visual run, need to clear
+                                // already computed data before doing a single run pass
+                                ALOGW("Visual run is not valid");
+                                outGlyphs->clear();
+                                outAdvances->clear();
+                                outPos->clear();
+                                *outTotalAdvance = 0;
+                                isRTL = (paraDir == 1);
+                                useSingleRun = true;
+                                break;
+                            }
 
-                        if (startRun >= end) {
-                            continue;
-                        }
-                        int32_t endRun = startRun + lengthRun;
-                        if (endRun <= int32_t(start)) {
-                            continue;
-                        }
-                        if (startRun < int32_t(start)) {
-                            startRun = int32_t(start);
-                        }
-                        if (endRun > end) {
-                            endRun = end;
-                        }
+                            if (startRun >= end) {
+                                continue;
+                            }
+                            int32_t endRun = startRun + lengthRun;
+                            if (endRun <= int32_t(start)) {
+                                continue;
+                            }
+                            if (startRun < int32_t(start)) {
+                                startRun = int32_t(start);
+                            }
+                            if (endRun > end) {
+                                endRun = end;
+                            }
 
-                        lengthRun = endRun - startRun;
-                        isRTL = (runDir == UBIDI_RTL);
+                            lengthRun = endRun - startRun;
+                            isRTL = (runDir == UBIDI_RTL);
 #if DEBUG_GLYPHS
-                        ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d",
-                                i, startRun, lengthRun, isRTL);
+                            ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d",
+                                    i, startRun, lengthRun, isRTL);
 #endif
-                        computeRunValues(paint, chars, startRun, lengthRun, contextCount, isRTL,
-                                outAdvances, outTotalAdvance, outGlyphs, outPos);
+                            computeRunValues(paint, chars, startRun, lengthRun, contextCount, isRTL,
+                                    outAdvances, outTotalAdvance, outGlyphs, outPos);
 
+                        }
                     }
+                } else {
+                    ALOGW("Cannot set Para");
+                    useSingleRun = true;
+                    isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
                 }
+                ubidi_close(bidi);
             } else {
-                ALOGW("Cannot set Para");
+                ALOGW("Cannot ubidi_open()");
                 useSingleRun = true;
                 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
             }
-            ubidi_close(bidi);
-        } else {
-            ALOGW("Cannot ubidi_open()");
-            useSingleRun = true;
-            isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
         }
 
         // Default single run case
@@ -897,11 +918,11 @@
 }
 
 sp<TextLayoutValue> TextLayoutEngine::getValue(const SkPaint* paint, const jchar* text,
-        jint start, jint count, jint contextCount) {
+        jint start, jint count, jint contextCount, jint dirFlags) {
     sp<TextLayoutValue> value;
 #if USE_TEXT_LAYOUT_CACHE
     value = mTextLayoutCache->getValue(paint, text, start, count,
-            contextCount);
+            contextCount, dirFlags);
     if (value == NULL) {
         ALOGE("Cannot get TextLayoutCache value for text = '%s'",
                 String8(text + start, count).string());
@@ -909,7 +930,7 @@
 #else
     value = new TextLayoutValue(count);
     mShaper->computeValues(value.get(), paint,
-            reinterpret_cast<const UChar*>(text), start, count, contextCount);
+            reinterpret_cast<const UChar*>(text), start, count, contextCount, dirFlags);
 #endif
     return value;
 }
diff --git a/core/jni/android/graphics/TextLayoutCache.h b/core/jni/android/graphics/TextLayoutCache.h
index 6858c0e..f9b9900 100644
--- a/core/jni/android/graphics/TextLayoutCache.h
+++ b/core/jni/android/graphics/TextLayoutCache.h
@@ -70,7 +70,7 @@
     TextLayoutCacheKey();
 
     TextLayoutCacheKey(const SkPaint* paint, const UChar* text, size_t start, size_t count,
-            size_t contextCount);
+            size_t contextCount, int dirFlags);
 
     TextLayoutCacheKey(const TextLayoutCacheKey& other);
 
@@ -97,6 +97,7 @@
     size_t start;
     size_t count;
     size_t contextCount;
+    int dirFlags;
     SkTypeface* typeface;
     SkScalar textSize;
     SkScalar textSkewX;
@@ -180,7 +181,7 @@
     virtual ~TextLayoutShaper();
 
     void computeValues(TextLayoutValue* value, const SkPaint* paint, const UChar* chars,
-            size_t start, size_t count, size_t contextCount);
+            size_t start, size_t count, size_t contextCount, int dirFlags);
 
     void purgeCaches();
 
@@ -214,7 +215,7 @@
     size_t shapeFontRun(const SkPaint* paint);
 
     void computeValues(const SkPaint* paint, const UChar* chars,
-            size_t start, size_t count, size_t contextCount,
+            size_t start, size_t count, size_t contextCount, int dirFlags,
             Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
             Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos);
 
@@ -251,7 +252,7 @@
     void operator()(TextLayoutCacheKey& text, sp<TextLayoutValue>& desc);
 
     sp<TextLayoutValue> getValue(const SkPaint* paint, const jchar* text, jint start,
-            jint count, jint contextCount);
+            jint count, jint contextCount, jint dirFlags);
 
     /**
      * Clear the cache
@@ -303,7 +304,7 @@
      * the call. Be careful of this when doing optimization.
      **/
     sp<TextLayoutValue> getValue(const SkPaint* paint, const jchar* text, jint start,
-            jint count, jint contextCount);
+            jint count, jint contextCount, jint dirFlags);
 
     void purgeCaches();
 
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 6fbaaf2..686e4e3 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -544,13 +544,17 @@
     sp<Camera> camera = get_native_camera(env, thiz, NULL);
     if (camera == 0) return;
 
+    sp<IGraphicBufferProducer> gbp;
     sp<Surface> surface;
     if (jSurface) {
         surface = android_view_Surface_getSurface(env, jSurface);
+        if (surface != NULL) {
+            gbp = surface->getIGraphicBufferProducer();
+        }
     }
 
-    if (camera->setPreviewDisplay(surface) != NO_ERROR) {
-        jniThrowException(env, "java/io/IOException", "setPreviewDisplay failed");
+    if (camera->setPreviewTexture(gbp) != NO_ERROR) {
+        jniThrowException(env, "java/io/IOException", "setPreviewTexture failed");
     }
 }
 
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index f5f22b2..faae11e 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -136,6 +136,10 @@
         result = ::dhcp_do_request(nameStr, ipaddr, gateway, &prefixLength,
                 dns, server, &lease, vendorInfo, domains);
     }
+    if (result != 0) {
+        ALOGD("dhcp_do_request failed");
+    }
+
     env->ReleaseStringUTFChars(ifname, nameStr);
     if (result == 0) {
         env->CallVoidMethod(dhcpResults, dhcpResultsFieldIds.clear);
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index ba62f6d..10c92ba 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -568,9 +568,9 @@
 // ----------------------------------------------------------------------------
 
 static void renderText(OpenGLRenderer* renderer, const jchar* text, int count,
-        jfloat x, jfloat y, SkPaint* paint) {
+        jfloat x, jfloat y, int flags, SkPaint* paint) {
     sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            text, 0, count, count);
+            text, 0, count, count, flags);
     if (value == NULL) {
         return;
     }
@@ -584,9 +584,9 @@
 }
 
 static void renderTextOnPath(OpenGLRenderer* renderer, const jchar* text, int count,
-        SkPath* path, jfloat hOffset, jfloat vOffset, SkPaint* paint) {
+        SkPath* path, jfloat hOffset, jfloat vOffset, int flags, SkPaint* paint) {
     sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            text, 0, count, count);
+            text, 0, count, count, flags);
     if (value == NULL) {
         return;
     }
@@ -599,9 +599,9 @@
 
 static void renderTextRun(OpenGLRenderer* renderer, const jchar* text,
         jint start, jint count, jint contextCount, jfloat x, jfloat y,
-        SkPaint* paint) {
+        int flags, SkPaint* paint) {
     sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            text, start, count, contextCount);
+            text, start, count, contextCount, flags);
     if (value == NULL) {
         return;
     }
@@ -616,62 +616,64 @@
 
 static void android_view_GLES20Canvas_drawTextArray(JNIEnv* env, jobject clazz,
         OpenGLRenderer* renderer, jcharArray text, jint index, jint count,
-        jfloat x, jfloat y, SkPaint* paint) {
+        jfloat x, jfloat y, jint flags, SkPaint* paint) {
     jchar* textArray = env->GetCharArrayElements(text, NULL);
-    renderText(renderer, textArray + index, count, x, y, paint);
+    renderText(renderer, textArray + index, count, x, y, flags, paint);
     env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
 }
 
 static void android_view_GLES20Canvas_drawText(JNIEnv* env, jobject clazz,
         OpenGLRenderer* renderer, jstring text, jint start, jint end,
-        jfloat x, jfloat y, SkPaint* paint) {
+        jfloat x, jfloat y, jint flags, SkPaint* paint) {
     const jchar* textArray = env->GetStringChars(text, NULL);
-    renderText(renderer, textArray + start, end - start, x, y, paint);
+    renderText(renderer, textArray + start, end - start, x, y, flags, paint);
     env->ReleaseStringChars(text, textArray);
 }
 
 static void android_view_GLES20Canvas_drawTextArrayOnPath(JNIEnv* env, jobject clazz,
         OpenGLRenderer* renderer, jcharArray text, jint index, jint count,
-        SkPath* path, jfloat hOffset, jfloat vOffset, SkPaint* paint) {
+        SkPath* path, jfloat hOffset, jfloat vOffset, jint flags, SkPaint* paint) {
     jchar* textArray = env->GetCharArrayElements(text, NULL);
     renderTextOnPath(renderer, textArray + index, count, path,
-            hOffset, vOffset, paint);
+            hOffset, vOffset, flags, paint);
     env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
 }
 
 static void android_view_GLES20Canvas_drawTextOnPath(JNIEnv* env, jobject clazz,
         OpenGLRenderer* renderer, jstring text, jint start, jint end,
-        SkPath* path, jfloat hOffset, jfloat vOffset, SkPaint* paint) {
+        SkPath* path, jfloat hOffset, jfloat vOffset, jint flags, SkPaint* paint) {
     const jchar* textArray = env->GetStringChars(text, NULL);
     renderTextOnPath(renderer, textArray + start, end - start, path,
-            hOffset, vOffset, paint);
+            hOffset, vOffset, flags, paint);
     env->ReleaseStringChars(text, textArray);
 }
 
 static void android_view_GLES20Canvas_drawTextRunArray(JNIEnv* env, jobject clazz,
         OpenGLRenderer* renderer, jcharArray text, jint index, jint count,
-        jint contextIndex, jint contextCount, jfloat x, jfloat y, SkPaint* paint) {
+        jint contextIndex, jint contextCount, jfloat x, jfloat y, jint dirFlags,
+        SkPaint* paint) {
     jchar* textArray = env->GetCharArrayElements(text, NULL);
     renderTextRun(renderer, textArray + contextIndex, index - contextIndex,
-            count, contextCount, x, y, paint);
+            count, contextCount, x, y, dirFlags, paint);
     env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
  }
 
 static void android_view_GLES20Canvas_drawTextRun(JNIEnv* env, jobject clazz,
         OpenGLRenderer* renderer, jstring text, jint start, jint end,
-        jint contextStart, int contextEnd, jfloat x, jfloat y, SkPaint* paint) {
+        jint contextStart, int contextEnd, jfloat x, jfloat y, jint dirFlags,
+        SkPaint* paint) {
     const jchar* textArray = env->GetStringChars(text, NULL);
     jint count = end - start;
     jint contextCount = contextEnd - contextStart;
     renderTextRun(renderer, textArray + contextStart, start - contextStart,
-            count, contextCount, x, y, paint);
+            count, contextCount, x, y, dirFlags, paint);
     env->ReleaseStringChars(text, textArray);
 }
 
 static void renderPosText(OpenGLRenderer* renderer, const jchar* text, int count,
-        const jfloat* positions, SkPaint* paint) {
+        const jfloat* positions, jint dirFlags, SkPaint* paint) {
     sp<TextLayoutValue> value = TextLayoutEngine::getInstance().getValue(paint,
-            text, 0, count, count);
+            text, 0, count, count, dirFlags);
     if (value == NULL) {
         return;
     }
@@ -689,7 +691,7 @@
     jchar* textArray = env->GetCharArrayElements(text, NULL);
     jfloat* positions = env->GetFloatArrayElements(pos, NULL);
 
-    renderPosText(renderer, textArray + index, count, positions, paint);
+    renderPosText(renderer, textArray + index, count, positions, kBidi_LTR, paint);
 
     env->ReleaseFloatArrayElements(pos, positions, JNI_ABORT);
     env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
@@ -701,7 +703,7 @@
     const jchar* textArray = env->GetStringChars(text, NULL);
     jfloat* positions = env->GetFloatArrayElements(pos, NULL);
 
-    renderPosText(renderer, textArray + start, end - start, positions, paint);
+    renderPosText(renderer, textArray + start, end - start, positions, kBidi_LTR, paint);
 
     env->ReleaseFloatArrayElements(pos, positions, JNI_ABORT);
     env->ReleaseStringChars(text, textArray);
@@ -1005,16 +1007,16 @@
     { "nSetupPaintFilter",  "(III)V",          (void*) android_view_GLES20Canvas_setupPaintFilter },
     { "nResetPaintFilter",  "(I)V",            (void*) android_view_GLES20Canvas_resetPaintFilter },
 
-    { "nDrawText",          "(I[CIIFFI)V",    (void*) android_view_GLES20Canvas_drawTextArray },
-    { "nDrawText",          "(ILjava/lang/String;IIFFI)V",
+    { "nDrawText",          "(I[CIIFFII)V",    (void*) android_view_GLES20Canvas_drawTextArray },
+    { "nDrawText",          "(ILjava/lang/String;IIFFII)V",
             (void*) android_view_GLES20Canvas_drawText },
 
-    { "nDrawTextOnPath",    "(I[CIIIFFI)V",   (void*) android_view_GLES20Canvas_drawTextArrayOnPath },
-    { "nDrawTextOnPath",    "(ILjava/lang/String;IIIFFI)V",
+    { "nDrawTextOnPath",    "(I[CIIIFFII)V",   (void*) android_view_GLES20Canvas_drawTextArrayOnPath },
+    { "nDrawTextOnPath",    "(ILjava/lang/String;IIIFFII)V",
             (void*) android_view_GLES20Canvas_drawTextOnPath },
 
-    { "nDrawTextRun",       "(I[CIIIIFFI)V",  (void*) android_view_GLES20Canvas_drawTextRunArray },
-    { "nDrawTextRun",       "(ILjava/lang/String;IIIIFFI)V",
+    { "nDrawTextRun",       "(I[CIIIIFFII)V",  (void*) android_view_GLES20Canvas_drawTextRunArray },
+    { "nDrawTextRun",       "(ILjava/lang/String;IIIIFFII)V",
             (void*) android_view_GLES20Canvas_drawTextRun },
 
     { "nDrawPosText",       "(I[CII[FI)V",     (void*) android_view_GLES20Canvas_drawPosTextArray },
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 0104f4b..4671282 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -27,6 +27,8 @@
 #include <android_runtime/android_view_Surface.h>
 #include <android_runtime/android_graphics_SurfaceTexture.h>
 
+#include <binder/Parcel.h>
+
 #include <gui/Surface.h>
 #include <gui/SurfaceControl.h>
 #include <gui/GLConsumer.h>
@@ -149,11 +151,6 @@
     sur->decStrong(&sRefBaseOwner);
 }
 
-static void nativeDestroy(JNIEnv* env, jclass clazz, jint nativeObject) {
-    sp<Surface> sur(reinterpret_cast<Surface *>(nativeObject));
-    sur->decStrong(&sRefBaseOwner);
-}
-
 static jboolean nativeIsValid(JNIEnv* env, jclass clazz, jint nativeObject) {
     sp<Surface> sur(reinterpret_cast<Surface *>(nativeObject));
     return isSurfaceValid(sur) ? JNI_TRUE : JNI_FALSE;
@@ -330,14 +327,32 @@
         doThrowNPE(env);
         return 0;
     }
+
     sp<Surface> self(reinterpret_cast<Surface *>(nativeObject));
-    if (self != NULL) {
-        self->decStrong(&sRefBaseOwner);
+    sp<IBinder> binder(parcel->readStrongBinder());
+
+    // update the Surface only if the underlying IGraphicBufferProducer
+    // has changed.
+    if (self != NULL
+            && (self->getIGraphicBufferProducer()->asBinder() == binder)) {
+        // same IGraphicBufferProducer, return ourselves
+        return int(self.get());
     }
-    sp<Surface> sur(Surface::readFromParcel(*parcel));
-    if (sur != NULL) {
+
+    sp<Surface> sur;
+    sp<IGraphicBufferProducer> gbp(interface_cast<IGraphicBufferProducer>(binder));
+    if (gbp != NULL) {
+        // we have a new IGraphicBufferProducer, create a new Surface for it
+        sur = new Surface(gbp);
+        // and keep a reference before passing to java
         sur->incStrong(&sRefBaseOwner);
     }
+
+    if (self != NULL) {
+        // and loose the java reference to ourselves
+        self->decStrong(&sRefBaseOwner);
+    }
+
     return int(sur.get());
 }
 
@@ -349,7 +364,7 @@
         return;
     }
     sp<Surface> self(reinterpret_cast<Surface *>(nativeObject));
-    Surface::writeToParcel(self, parcel);
+    parcel->writeStrongBinder( self != 0 ? self->getIGraphicBufferProducer()->asBinder() : NULL);
 }
 
 // ----------------------------------------------------------------------------
@@ -359,8 +374,6 @@
             (void*)nativeCreateFromSurfaceTexture },
     {"nativeRelease", "(I)V",
             (void*)nativeRelease },
-    {"nativeDestroy", "(I)V",
-            (void*)nativeDestroy },
     {"nativeIsValid", "(I)Z",
             (void*)nativeIsValid },
     {"nativeIsConsumerRunningBehind", "(I)Z",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8a53cc3..5a1c0f8 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -169,6 +169,7 @@
     <protected-broadcast android:name="android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE" />
     <protected-broadcast android:name="android.intent.action.AIRPLANE_MODE" />
     <protected-broadcast android:name="android.intent.action.ADVANCED_SETTINGS" />
+    <protected-broadcast android:name="android.intent.action.BUGREPORT_FINISHED" />
 
     <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" />
     <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_END" />
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 95e0b5b..2fc7f4a 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -329,7 +329,7 @@
     <string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"Ermöglicht der App, die Benutzeroberfläche zur Bestätigung der vollständigen Sicherung zu starten. Kann nicht von jeder App verwendet werden."</string>
     <string name="permlab_internalSystemWindow" msgid="2148563628140193231">"Nicht autorisierte Fenster anzeigen"</string>
     <string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"Ermöglicht der App die Erstellung von Fenstern, die von der Benutzeroberfläche des internen Systems verwendet werden. Nicht für normale Apps vorgesehen."</string>
-    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"Über andere Apps zeichnen"</string>
+    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"Über anderen Apps einblenden"</string>
     <string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Ermöglicht der App, über andere Apps oder Teile der Benutzeroberfläche zu zeichnen. Dies kann sich auf die Oberfläche in jeder App auswirken oder die erwartete Darstellung in anderen Apps verändern."</string>
     <string name="permlab_setAnimationScale" msgid="2805103241153907174">"Allgemeine Animationsgeschwindigkeit einstellen"</string>
     <string name="permdesc_setAnimationScale" msgid="7690063428924343571">"Ermöglicht der App, die allgemeine Animationsgeschwindigkeit (langsamere oder schnellere Animationen) jederzeit anzupassen."</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 985b088..1fc59b6 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -182,7 +182,7 @@
     <string name="permgroupdesc_bluetoothNetwork" msgid="5625288577164282391">"Acceder a dispositivos y redes a través de Bluetooth"</string>
     <string name="permgrouplab_audioSettings" msgid="8329261670151871235">"Configuración de audio"</string>
     <string name="permgroupdesc_audioSettings" msgid="2641515403347568130">"Cambiar la configuración de audio"</string>
-    <string name="permgrouplab_affectsBattery" msgid="6209246653424798033">"Afecta la batería."</string>
+    <string name="permgrouplab_affectsBattery" msgid="6209246653424798033">"Afecta la batería"</string>
     <string name="permgroupdesc_affectsBattery" msgid="6441275320638916947">"Uso de las características que se pueden agotar rápidamente la batería"</string>
     <string name="permgrouplab_calendar" msgid="5863508437783683902">"Calendario"</string>
     <string name="permgroupdesc_calendar" msgid="5777534316982184416">"Acceso directo a calendario y eventos"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 5a701a0..86ead94 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -329,7 +329,7 @@
     <string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"Permite que a aplicação inicie a IU de confirmação de cópia de segurança completa. Não deve ser utilizado por qualquer aplicação."</string>
     <string name="permlab_internalSystemWindow" msgid="2148563628140193231">"apresentar janelas não autorizadas"</string>
     <string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"Permite que a aplicação crie janelas que se destinam a ser utilizadas ​​pela interface de utilizador do sistema interno. Nunca é necessário para aplicações normais."</string>
-    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"desenhar sobre outras aplicações"</string>
+    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"mostrar sobre outras aplicações"</string>
     <string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Permite que a aplicação se sobreponha a outras aplicações ou partes da interface de utilizador. Poderá interferir na utilização da interface de qualquer aplicação ou alterar o que pensa estar a ver noutras aplicações."</string>
     <string name="permlab_setAnimationScale" msgid="2805103241153907174">"modificar velocidade global da animação"</string>
     <string name="permdesc_setAnimationScale" msgid="7690063428924343571">"Permite que a aplicação altere a velocidade global da animação (animações mais rápidas ou mais lentas) em qualquer altura."</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 9eb11b2..4650d55 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -329,7 +329,7 @@
     <string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"Permite que o aplicativo lance a interface de usuário de confirmação de backup completo. Não deve ser usado por qualquer aplicativo."</string>
     <string name="permlab_internalSystemWindow" msgid="2148563628140193231">"exibir janelas não autorizadas"</string>
     <string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"Permite que o aplicativo crie janelas destinadas ao uso ​​pela interface interna do sistema. Não deve ser usado em aplicativos normais."</string>
-    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"induzir outros aplicativos"</string>
+    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"sobrepor outros aplicativos"</string>
     <string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Permite que o aplicativo se sobreponha visualmente a outros aplicativos ou a partes da interface do usuário. Podem interferir com o uso da interface de qualquer aplicativo ou alterar o que você acha que está vendo em outros aplicativos."</string>
     <string name="permlab_setAnimationScale" msgid="2805103241153907174">"modificar velocidade de animação global"</string>
     <string name="permdesc_setAnimationScale" msgid="7690063428924343571">"Permite que o aplicativo altere a velocidade de animação global (animação mais rápida ou mais lenta) a qualquer momento."</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index eb7185f..88c56bc 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1200,8 +1200,8 @@
     <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formataţi"</string>
     <string name="adb_active_notification_title" msgid="6729044778949189918">"Depanarea USB este conectată"</string>
     <string name="adb_active_notification_message" msgid="1016654627626476142">"Atingeţi pentru a dezactiva depanarea USB."</string>
-    <string name="select_input_method" msgid="4653387336791222978">"Alegeţi metoda de introducere"</string>
-    <string name="configure_input_methods" msgid="9091652157722495116">"Configurare metode introducere"</string>
+    <string name="select_input_method" msgid="4653387336791222978">"Alegeți metoda de introducere de text"</string>
+    <string name="configure_input_methods" msgid="9091652157722495116">"Configurați metode introducere"</string>
     <string name="use_physical_keyboard" msgid="6203112478095117625">"Tastatură fizică"</string>
     <string name="hardware" msgid="7517821086888990278">"Hardware"</string>
     <string name="select_keyboard_layout_notification_title" msgid="1407367017263030773">"Selectaţi aspectul tastaturii"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index b8b1c29..9475472 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -329,7 +329,7 @@
     <string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"Приложение сможет отображать окно подтверждения полного резервного копирования. Это разрешение не предназначено для всех приложений."</string>
     <string name="permlab_internalSystemWindow" msgid="2148563628140193231">"показывать неавторизованные окна"</string>
     <string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"Приложение сможет создавать окна для интерфейса внутренней системы. Это разрешение не используется обычными приложениями."</string>
-    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"Показ сообщений поверх других окон"</string>
+    <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"Показ элементов интерфейса поверх других окон"</string>
     <string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Разрешает приложению отображать элементы своего интерфейса поверх окон других программ. Это может мешать вашему взаимодействию с другими приложениями и вести к недоразумениям."</string>
     <string name="permlab_setAnimationScale" msgid="2805103241153907174">"изменять глобальную скорость анимации"</string>
     <string name="permdesc_setAnimationScale" msgid="7690063428924343571">"Приложение сможет в любой момент изменить общую скорость анимации."</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index adfc22a..370c7d6 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -751,7 +751,7 @@
     <string name="relationTypeSister" msgid="1735983554479076481">"Syster"</string>
     <string name="relationTypeSpouse" msgid="394136939428698117">"Make/maka"</string>
     <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Anpassad"</string>
-    <string name="sipAddressTypeHome" msgid="6093598181069359295">"Startsida"</string>
+    <string name="sipAddressTypeHome" msgid="6093598181069359295">"Hem"</string>
     <string name="sipAddressTypeWork" msgid="6920725730797099047">"Arbete"</string>
     <string name="sipAddressTypeOther" msgid="4408436162950119849">"Övrigt"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3037685796058495017">"Ange PIN-kod"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 554c1aa..b952bd4 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1347,8 +1347,8 @@
     <string name="keyboardview_keycode_shift" msgid="2270748814315147690">"Shift"</string>
     <string name="keyboardview_keycode_enter" msgid="2985864015076059467">"Enter"</string>
     <string name="activitychooserview_choose_application" msgid="2125168057199941199">"选择应用"</string>
-    <string name="shareactionprovider_share_with" msgid="806688056141131819">"共享对象"</string>
-    <string name="shareactionprovider_share_with_application" msgid="5627411384638389738">"与“<xliff:g id="APPLICATION_NAME">%s</xliff:g>”共享"</string>
+    <string name="shareactionprovider_share_with" msgid="806688056141131819">"分享方式"</string>
+    <string name="shareactionprovider_share_with_application" msgid="5627411384638389738">"使用<xliff:g id="APPLICATION_NAME">%s</xliff:g>分享"</string>
     <string name="content_description_sliding_handle" msgid="415975056159262248">"滑动手柄。触摸并按住。"</string>
     <string name="description_direction_up" msgid="7169032478259485180">"向上滑动以<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string>
     <string name="description_direction_down" msgid="5087739728639014595">"向下滑动以<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string>
@@ -1399,7 +1399,7 @@
     <string name="sha1_fingerprint" msgid="7930330235269404581">"SHA-1 指纹:"</string>
     <string name="activity_chooser_view_see_all" msgid="4292569383976636200">"查看全部"</string>
     <string name="activity_chooser_view_dialog_title_default" msgid="4710013864974040615">"选择活动"</string>
-    <string name="share_action_provider_share_with" msgid="5247684435979149216">"分享对象"</string>
+    <string name="share_action_provider_share_with" msgid="5247684435979149216">"分享方式"</string>
     <string name="status_bar_device_locked" msgid="3092703448690669768">"设备已锁定。"</string>
     <string name="list_delimeter" msgid="3975117572185494152">"、 "</string>
     <string name="sending" msgid="3245653681008218030">"正在发送..."</string>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d899e9d..f1d8c03 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1065,6 +1065,21 @@
         <attr name="maxSdkVersion" format="integer" />
     </declare-styleable>
     
+    <!-- The <code>library</code> tag declares that this apk is providing itself
+         as a shared library for other applications to use.  It can only be used
+         with apks that are built in to the system image.  Other apks can link to
+         it with the {@link #AndroidManifestUsesLibrary uses-library} tag.
+
+         <p>This appears as a child tag of the
+         {@link #AndroidManifestApplication application} tag. -->
+    <declare-styleable name="AndroidManifestLibrary" parent="AndroidManifest">
+        <!-- Required public name of the library, which other components and
+        packages will use when referring to this library.  This is a string using
+        Java-style scoping to ensure it is unique.  The name should typically
+        be the same as the apk's package name. -->
+        <attr name="name" />
+    </declare-styleable>
+
     <!-- The <code>uses-libraries</code> specifies a shared library that this
          package requires to be linked against.  Specifying this flag tells the
          system to include this library's code in your class loader.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ccdddd8..6a8407f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1031,4 +1031,14 @@
 
     <!-- Flag indicating if the speed up audio on mt call code should be executed -->
     <bool name="config_speed_up_audio_on_mt_calls">false</bool>
+
+    <!-- Class name of the framework account picker activity.
+         Can be customized for other product types -->
+    <string name="config_chooseAccountActivity"
+            >android/android.accounts.ChooseAccountActivity</string>
+    <!-- Class name of the account type and account picker activity.
+         Can be customized for other product types -->
+    <string name="config_chooseTypeAndAccountActivity"
+            >android/android.accounts.ChooseTypeAndAccountActivity</string>
+
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 140ff70..d57d56a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -863,6 +863,9 @@
   <java-symbol type="string" name="media_route_status_available" />
   <java-symbol type="string" name="media_route_status_not_available" />
   <java-symbol type="string" name="owner_name" />
+  <java-symbol type="string" name="config_chooseAccountActivity" />
+  <java-symbol type="string" name="config_chooseTypeAndAccountActivity" />
+
 
   <java-symbol type="plurals" name="abbrev_in_num_days" />
   <java-symbol type="plurals" name="abbrev_in_num_hours" />
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
index fffaa00..274ac6b 100644
--- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java
+++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
@@ -22,6 +22,7 @@
 import junit.framework.TestCase;
 
 import java.net.InetAddress;
+import java.util.ArrayList;
 
 public class LinkPropertiesTest extends TestCase {
     private static String ADDRV4 = "75.208.6.1";
@@ -256,4 +257,30 @@
         assertEquals(3, lp.compareRoutes(lp2).added.size());
         assertEquals(3, lp.compareRoutes(lp2).removed.size());
     }
+
+    @SmallTest
+    public void testStackedInterfaces() {
+        LinkProperties rmnet0 = new LinkProperties();
+        rmnet0.setInterfaceName("rmnet0");
+
+        LinkProperties clat4 = new LinkProperties();
+        clat4.setInterfaceName("clat4");
+
+        assertEquals(0, rmnet0.getStackedLinks().size());
+        rmnet0.addStackedLink(clat4);
+        assertEquals(1, rmnet0.getStackedLinks().size());
+        rmnet0.addStackedLink(clat4);
+        assertEquals(1, rmnet0.getStackedLinks().size());
+        assertEquals(0, clat4.getStackedLinks().size());
+
+        // Modify an item in the returned collection to see what happens.
+        for (LinkProperties link : rmnet0.getStackedLinks()) {
+            if (link.getInterfaceName().equals("clat4")) {
+               link.setInterfaceName("newname");
+            }
+        }
+        for (LinkProperties link : rmnet0.getStackedLinks()) {
+            assertFalse("newname".equals(link.getInterfaceName()));
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index 4d0b892..0f2b803 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -16,61 +16,65 @@
 
 package android.os;
 
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.WEEK_IN_MILLIS;
+
 import android.content.Context;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 
+import com.google.android.collect.Sets;
+
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.FileWriter;
+import java.util.Arrays;
+import java.util.HashSet;
 
+import libcore.io.IoUtils;
+
+@MediumTest
 public class FileUtilsTest extends AndroidTestCase {
     private static final String TEST_DATA =
             "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
 
+    private File mDir;
     private File mTestFile;
     private File mCopyFile;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        File testDir = getContext().getDir("testing", Context.MODE_PRIVATE);
-        mTestFile = new File(testDir, "test.file");
-        mCopyFile = new File(testDir, "copy.file");
-        FileWriter writer = new FileWriter(mTestFile);
-        try {
-            writer.write(TEST_DATA, 0, TEST_DATA.length());
-        } finally {
-            writer.close();
-        }
+        mDir = getContext().getDir("testing", Context.MODE_PRIVATE);
+        mTestFile = new File(mDir, "test.file");
+        mCopyFile = new File(mDir, "copy.file");
     }
 
     @Override
     protected void tearDown() throws Exception {
-        if (mTestFile.exists()) mTestFile.delete();
-        if (mCopyFile.exists()) mCopyFile.delete();
+        IoUtils.deleteContents(mDir);
     }
 
     // TODO: test setPermissions(), getPermissions()
 
-    @MediumTest
     public void testCopyFile() throws Exception {
+        stageFile(mTestFile, TEST_DATA);
         assertFalse(mCopyFile.exists());
         FileUtils.copyFile(mTestFile, mCopyFile);
         assertTrue(mCopyFile.exists());
         assertEquals(TEST_DATA, FileUtils.readTextFile(mCopyFile, 0, null));
     }
 
-    @MediumTest
     public void testCopyToFile() throws Exception {
         final String s = "Foo Bar";
         assertFalse(mCopyFile.exists());
-        FileUtils.copyToFile(new ByteArrayInputStream(s.getBytes()), mCopyFile);        assertTrue(mCopyFile.exists());
+        FileUtils.copyToFile(new ByteArrayInputStream(s.getBytes()), mCopyFile);
+        assertTrue(mCopyFile.exists());
         assertEquals(s, FileUtils.readTextFile(mCopyFile, 0, null));
     }
 
-    @MediumTest
     public void testIsFilenameSafe() throws Exception {
         assertTrue(FileUtils.isFilenameSafe(new File("foobar")));
         assertTrue(FileUtils.isFilenameSafe(new File("a_b-c=d.e/0,1+23")));
@@ -78,8 +82,9 @@
         assertFalse(FileUtils.isFilenameSafe(new File("foo\nbar")));
     }
 
-    @MediumTest
     public void testReadTextFile() throws Exception {
+        stageFile(mTestFile, TEST_DATA);
+
         assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, 0, null));
 
         assertEquals("ABCDE", FileUtils.readTextFile(mTestFile, 5, null));
@@ -97,8 +102,8 @@
         assertEquals(TEST_DATA, FileUtils.readTextFile(mTestFile, -100, "<>"));
     }
 
-    @MediumTest
     public void testReadTextFileWithZeroLengthFile() throws Exception {
+        stageFile(mTestFile, TEST_DATA);
         new FileOutputStream(mTestFile).close();  // Zero out the file
         assertEquals("", FileUtils.readTextFile(mTestFile, 0, null));
         assertEquals("", FileUtils.readTextFile(mTestFile, 1, "<>"));
@@ -106,4 +111,81 @@
         assertEquals("", FileUtils.readTextFile(mTestFile, -1, "<>"));
         assertEquals("", FileUtils.readTextFile(mTestFile, -10, "<>"));
     }
+
+    public void testDeleteOlderEmptyDir() throws Exception {
+        FileUtils.deleteOlderFiles(mDir, 10, WEEK_IN_MILLIS);
+        assertDirContents();
+    }
+
+    public void testDeleteOlderTypical() throws Exception {
+        touch("file1", HOUR_IN_MILLIS);
+        touch("file2", 1 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        touch("file3", 2 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        touch("file4", 3 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        touch("file5", 4 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        FileUtils.deleteOlderFiles(mDir, 3, DAY_IN_MILLIS);
+        assertDirContents("file1", "file2", "file3");
+    }
+
+    public void testDeleteOlderInFuture() throws Exception {
+        touch("file1", -HOUR_IN_MILLIS);
+        touch("file2", HOUR_IN_MILLIS);
+        touch("file3", WEEK_IN_MILLIS);
+        FileUtils.deleteOlderFiles(mDir, 0, DAY_IN_MILLIS);
+        assertDirContents("file1", "file2");
+
+        touch("file1", -HOUR_IN_MILLIS);
+        touch("file2", HOUR_IN_MILLIS);
+        touch("file3", WEEK_IN_MILLIS);
+        FileUtils.deleteOlderFiles(mDir, 0, DAY_IN_MILLIS);
+        assertDirContents("file1", "file2");
+    }
+
+    public void testDeleteOlderOnlyAge() throws Exception {
+        touch("file1", HOUR_IN_MILLIS);
+        touch("file2", 1 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        touch("file3", 2 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        touch("file4", 3 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        touch("file5", 4 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        FileUtils.deleteOlderFiles(mDir, 0, DAY_IN_MILLIS);
+        assertDirContents("file1");
+    }
+
+    public void testDeleteOlderOnlyCount() throws Exception {
+        touch("file1", HOUR_IN_MILLIS);
+        touch("file2", 1 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        touch("file3", 2 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        touch("file4", 3 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        touch("file5", 4 * DAY_IN_MILLIS + HOUR_IN_MILLIS);
+        FileUtils.deleteOlderFiles(mDir, 2, 0);
+        assertDirContents("file1", "file2");
+    }
+
+    private void touch(String name, long age) throws Exception {
+        final File file = new File(mDir, name);
+        file.createNewFile();
+        file.setLastModified(System.currentTimeMillis() - age);
+    }
+
+    private void stageFile(File file, String data) throws Exception {
+        FileWriter writer = new FileWriter(file);
+        try {
+            writer.write(data, 0, data.length());
+        } finally {
+            writer.close();
+        }
+    }
+
+    private void assertDirContents(String... expected) {
+        final HashSet<String> expectedSet = Sets.newHashSet(expected);
+        String[] actual = mDir.list();
+        if (actual == null) actual = new String[0];
+
+        assertEquals(
+                "Expected " + Arrays.toString(expected) + " but actual " + Arrays.toString(actual),
+                expected.length, actual.length);
+        for (String actualFile : actual) {
+            assertTrue("Unexpected actual file " + actualFile, expectedSet.contains(actualFile));
+        }
+    }
 }
diff --git a/docs/downloads/training/BitmapFun.zip b/docs/downloads/training/BitmapFun.zip
index 61b537d..48bea78 100644
--- a/docs/downloads/training/BitmapFun.zip
+++ b/docs/downloads/training/BitmapFun.zip
Binary files differ
diff --git a/docs/downloads/training/InteractiveChart.zip b/docs/downloads/training/InteractiveChart.zip
new file mode 100644
index 0000000..95248ad
--- /dev/null
+++ b/docs/downloads/training/InteractiveChart.zip
Binary files differ
diff --git a/docs/html/index.jd b/docs/html/index.jd
index f2df7be..ec0469c 100644
--- a/docs/html/index.jd
+++ b/docs/html/index.jd
@@ -19,12 +19,11 @@
                     <div class="content-right col-5">
                     <h1>Google I/O 2013</h1>
                     <p>Android will be at Google I/O on May 15-17, 2013, with sessions covering a variety of topics
-                    such as design, performance, and how to extend your app with the latest Android features.
-                    Registration opens on March 13, 2013 at 7:00 AM PDT (GMT-7).</p>
+                    such as design, performance, and how to extend your app with the latest Android features.</p>
                     <p>For more information about event details and planned sessions,
                     stay tuned to <a
                     href="http://google.com/+GoogleDevelopers">+Google Developers</a>.</p>
-                    <p><a href="https://developers.google.com/events/io/register" class="button">Register here</a></p>
+                    <p><a href="https://developers.google.com/events/io/" class="button">Learn more</a></p>
                     </div>
                 </li>
                 <li class="item carousel-home">
diff --git a/docs/html/training/displaying-bitmaps/cache-bitmap.jd b/docs/html/training/displaying-bitmaps/cache-bitmap.jd
index 417ec5b..b1608c3 100644
--- a/docs/html/training/displaying-bitmaps/cache-bitmap.jd
+++ b/docs/html/training/displaying-bitmaps/cache-bitmap.jd
@@ -3,10 +3,6 @@
 parent.link=index.html
 
 trainingnavtop=true
-next.title=Displaying Bitmaps in Your UI
-next.link=display-bitmap.html
-previous.title=Processing Bitmaps Off the UI Thread
-previous.link=process-bitmap.html
 
 @jd:body
 
diff --git a/docs/html/training/displaying-bitmaps/display-bitmap.jd b/docs/html/training/displaying-bitmaps/display-bitmap.jd
index 4572c42..ed1836c 100644
--- a/docs/html/training/displaying-bitmaps/display-bitmap.jd
+++ b/docs/html/training/displaying-bitmaps/display-bitmap.jd
@@ -3,8 +3,6 @@
 parent.link=index.html
 
 trainingnavtop=true
-previous.title=Caching Bitmaps
-previous.link=cache-bitmap.html
 
 @jd:body
 
diff --git a/docs/html/training/displaying-bitmaps/index.jd b/docs/html/training/displaying-bitmaps/index.jd
index b91172b..a828291 100644
--- a/docs/html/training/displaying-bitmaps/index.jd
+++ b/docs/html/training/displaying-bitmaps/index.jd
@@ -26,7 +26,7 @@
 </div>
 </div>
 
-<p>This class covers some common techniques for processing and loading {@link
+<p>Learn how to use common techniques to process and load {@link
 android.graphics.Bitmap} objects in a way that keeps your user interface (UI) components responsive
 and avoids exceeding your application memory limit. If you're not careful, bitmaps can quickly
 consume your available memory budget leading to an application crash due to the dreaded
@@ -70,6 +70,9 @@
     <dd>This lesson walks you through using a memory and disk bitmap cache to improve the
     responsiveness and fluidity of your UI when loading multiple bitmaps.</dd>
 
+  <dt><b><a href="manage-memory.html">Managing Bitmap Memory</a></b></dt>
+    <dd>This lesson explains how to manage bitmap memory to maximize your app's performance.</dd>
+
   <dt><b><a href="display-bitmap.html">Displaying Bitmaps in Your UI</a></b></dt>
     <dd>This lesson brings everything together, showing you how to load multiple bitmaps into
     components like {@link android.support.v4.view.ViewPager} and {@link android.widget.GridView}
diff --git a/docs/html/training/displaying-bitmaps/load-bitmap.jd b/docs/html/training/displaying-bitmaps/load-bitmap.jd
index 283f272..633ffd2 100644
--- a/docs/html/training/displaying-bitmaps/load-bitmap.jd
+++ b/docs/html/training/displaying-bitmaps/load-bitmap.jd
@@ -3,8 +3,6 @@
 parent.link=index.html
 
 trainingnavtop=true
-next.title=Processing Bitmaps Off the UI Thread
-next.link=process-bitmap.html
 
 @jd:body
 
@@ -167,4 +165,4 @@
 <p>You can follow a similar process to decode bitmaps from other sources, by substituting the
 appropriate {@link
 android.graphics.BitmapFactory#decodeByteArray(byte[],int,int,android.graphics.BitmapFactory.Options)
-BitmapFactory.decode*} method as needed.</p>
\ No newline at end of file
+BitmapFactory.decode*} method as needed.</p>
diff --git a/docs/html/training/displaying-bitmaps/manage-memory.jd b/docs/html/training/displaying-bitmaps/manage-memory.jd
new file mode 100644
index 0000000..60ac2e6
--- /dev/null
+++ b/docs/html/training/displaying-bitmaps/manage-memory.jd
@@ -0,0 +1,297 @@
+page.title=Managing Bitmap Memory
+parent.title=Displaying Bitmaps Efficiently
+parent.link=index.html
+
+trainingnavtop=true
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+  <li><a href="#recycle">Manage Memory on Android 2.3.3 and Lower</a></li>
+  <li><a href="#inBitmap">Manage Memory on Android 3.0 and Higher</a></li>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+  <li><a href="http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html">Memory Analysis for Android Applications</a> blog post</li>
+  <li><a href="http://www.google.com/events/io/2011/sessions/memory-management-for-android-apps.html">Memory management for Android Apps</a> Google I/O presentation</li>
+  <li><a href="{@docRoot}design/patterns/swipe-views.html">Android Design: Swipe Views</a></li>
+  <li><a href="{@docRoot}design/building-blocks/grid-lists.html">Android Design: Grid Lists</a></li>
+</ul>
+
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a>
+  <p class="filename">BitmapFun.zip</p>
+</div>
+
+</div>
+</div>
+
+<p>In addition to the steps described in <a href="cache-bitmap.html">Caching Bitmaps</a>,
+there are  specific things you can do to facilitate garbage collection
+and bitmap reuse. The recommended strategy depends on which version(s)
+of Android you are targeting. The {@code BitmapFun} sample app included with
+this class shows you how to design your app to work efficiently across
+different versions of Android.</p>
+
+<p>To set the stage for this lesson, here is how Android's management of
+bitmap memory has evolved:</p>
+<ul> 
+  <li>
+On Android Android 2.2 (API level 8) and lower, when garbage 
+collection occurs, your app's threads get stopped. This causes a lag that
+can degrade performance. 
+<strong>Android 2.3 adds concurrent garbage collection, which means that
+the memory is reclaimed soon after a bitmap is no longer referenced.</strong>
+</li>
+
+  <li>On Android 2.3.3 (API level 10) and lower, the backing pixel data for a
+bitmap is stored in native memory. It is separate from the bitmap itself,
+which is stored in the Dalvik heap. The pixel data in native memory is
+not released in a predictable manner, potentially causing an application
+to briefly exceed its memory limits and crash.
+<strong>As of Android 3.0 (API Level 11), the pixel data is stored on the
+Dalvik heap along with the associated bitmap.</strong></li>
+
+</ul>
+
+<p>The following sections describe how to optimize bitmap memory
+management for different Android versions.</p>
+
+<h2 id="recycle">Manage Memory on Android 2.3.3 and Lower</h2>
+
+<p>On Android 2.3.3 (API level 10) and lower, using 
+{@link android.graphics.Bitmap#recycle recycle()}
+is recommended. If you're displaying large amounts of bitmap data in your app,
+you're likely to run into
+{@link java.lang.OutOfMemoryError} errors. The
+{@link android.graphics.Bitmap#recycle recycle()} method allows an app
+to reclaim memory as soon as possible.</p>
+
+<p class="note"><strong>Caution:</strong> You should use
+{@link android.graphics.Bitmap#recycle recycle()} only when you are sure that the
+bitmap is no longer being used. If you call {@link android.graphics.Bitmap#recycle recycle()}
+and later attempt to draw the bitmap, you will get the error:
+{@code &quot;Canvas: trying to use a recycled bitmap&quot;}.</p>
+
+<p>The following code snippet gives an example of calling
+{@link android.graphics.Bitmap#recycle recycle()}. It uses reference counting
+(in the variables {@code mDisplayRefCount} and {@code mCacheRefCount}) to track 
+whether a bitmap is currently being displayed or in the cache. The
+code recycles the bitmap when these conditions are met:</p>
+
+<ul>
+<li>The reference count for both {@code mDisplayRefCount} and 
+{@code mCacheRefCount} is 0.</li>
+<li>The bitmap is not {@code null}, and it hasn't been recycled yet.</li>
+</ul>
+
+<pre>private int mCacheRefCount = 0;
+private int mDisplayRefCount = 0;
+...
+// Notify the drawable that the displayed state has changed.
+// Keep a count to determine when the drawable is no longer displayed.
+public void setIsDisplayed(boolean isDisplayed) {
+    synchronized (this) {
+        if (isDisplayed) {
+            mDisplayRefCount++;
+            mHasBeenDisplayed = true;
+        } else {
+            mDisplayRefCount--;
+        }
+    }
+    // Check to see if recycle() can be called.
+    checkState();
+}
+
+// Notify the drawable that the cache state has changed.
+// Keep a count to determine when the drawable is no longer being cached.
+public void setIsCached(boolean isCached) {
+    synchronized (this) {
+        if (isCached) {
+            mCacheRefCount++;
+        } else {
+            mCacheRefCount--;
+        }
+    }
+    // Check to see if recycle() can be called.
+    checkState();
+}
+
+private synchronized void checkState() {
+    // If the drawable cache and display ref counts = 0, and this drawable
+    // has been displayed, then recycle.
+    if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed
+            && hasValidBitmap()) {
+        getBitmap().recycle();
+    }
+}
+
+private synchronized boolean hasValidBitmap() {
+    Bitmap bitmap = getBitmap();
+    return bitmap != null && !bitmap.isRecycled();
+}</pre>
+
+<h2 id="inBitmap">Manage Memory on Android 3.0 and Higher</h2>
+
+<p>Android 3.0 (API Level 11) introduces the
+{@link android.graphics.BitmapFactory.Options#inBitmap BitmapFactory.Options.inBitmap}
+field. If this option is set, decode methods that take the 
+{@link android.graphics.BitmapFactory.Options Options} object
+will attempt to reuse an existing bitmap when loading content. This means
+that the bitmap's memory is reused, resulting in improved performance, and
+removing both memory allocation and de-allocation. There are some caveats in using
+{@link android.graphics.BitmapFactory.Options#inBitmap}:</p>
+<ul>
+  <li>The reused bitmap must be of the same size as the source content (to make
+sure that the same amount of memory is used), and in JPEG or PNG format
+(whether as a resource or as a stream).</li>
+
+
+<li>The {@link android.graphics.Bitmap.Config configuration} of the reused bitmap
+overrides the setting of
+{@link android.graphics.BitmapFactory.Options#inPreferredConfig}, if set. </li>
+
+  <li>You should always use the returned bitmap of the decode method,
+because you can't assume that reusing the bitmap worked (for example, if there is
+a size mismatch).</li>
+
+<h3>Save a bitmap for later use</h3>
+
+<p>The following snippet demonstrates how an existing bitmap is stored for possible
+later use in the sample app. When an app is running on Android 3.0 or higher and 
+a bitmap is evicted from the {@link android.util.LruCache},
+a soft reference to the bitmap is placed
+in a {@link java.util.HashSet}, for possible reuse later with
+{@link android.graphics.BitmapFactory.Options#inBitmap}:
+
+<pre>HashSet&lt;SoftReference&lt;Bitmap&gt;&gt; mReusableBitmaps;
+private LruCache&lt;String, BitmapDrawable&gt; mMemoryCache;
+
+// If you're running on Honeycomb or newer, create
+// a HashSet of references to reusable bitmaps.
+if (Utils.hasHoneycomb()) {
+    mReusableBitmaps = new HashSet&lt;SoftReference&lt;Bitmap&gt;&gt;();
+}
+
+mMemoryCache = new LruCache&lt;String, BitmapDrawable&gt;(mCacheParams.memCacheSize) {
+
+    // Notify the removed entry that is no longer being cached.
+    &#64;Override
+    protected void entryRemoved(boolean evicted, String key,
+            BitmapDrawable oldValue, BitmapDrawable newValue) {
+        if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
+            // The removed entry is a recycling drawable, so notify it
+            // that it has been removed from the memory cache.
+            ((RecyclingBitmapDrawable) oldValue).setIsCached(false);
+        } else {
+            // The removed entry is a standard BitmapDrawable.
+            if (Utils.hasHoneycomb()) {
+                // We're running on Honeycomb or later, so add the bitmap
+                // to a SoftReference set for possible use with inBitmap later.
+                mReusableBitmaps.add
+                        (new SoftReference&lt;Bitmap&gt;(oldValue.getBitmap()));
+            }
+        }
+    }
+....
+}</pre>
+
+
+<h3>Use an existing bitmap</h3>
+<p>In the running app, decoder methods check to see if there is an existing
+bitmap they can use. For example:</p>
+
+<pre>public static Bitmap decodeSampledBitmapFromFile(String filename,
+        int reqWidth, int reqHeight, ImageCache cache) {
+
+    final BitmapFactory.Options options = new BitmapFactory.Options();
+    ...
+    BitmapFactory.decodeFile(filename, options);
+    ...
+
+    // If we're running on Honeycomb or newer, try to use inBitmap.
+    if (Utils.hasHoneycomb()) {
+        addInBitmapOptions(options, cache);
+    }
+    ...
+    return BitmapFactory.decodeFile(filename, options);
+}</pre
+
+<p>The next snippet shows the {@code addInBitmapOptions()} method that is called in the
+above snippet. It looks for an existing bitmap to set as the value for
+{@link android.graphics.BitmapFactory.Options#inBitmap}. Note that this
+method only sets a value for {@link android.graphics.BitmapFactory.Options#inBitmap}
+if it finds a suitable match (your code should never assume that a match will be found):</p>
+
+<pre>private static void addInBitmapOptions(BitmapFactory.Options options,
+        ImageCache cache) {
+    // inBitmap only works with mutable bitmaps, so force the decoder to
+    // return mutable bitmaps.
+    options.inMutable = true;
+
+    if (cache != null) {
+        // Try to find a bitmap to use for inBitmap.
+        Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
+
+        if (inBitmap != null) {
+            // If a suitable bitmap has been found, set it as the value of
+            // inBitmap.
+            options.inBitmap = inBitmap;
+        }
+    }
+}
+
+// This method iterates through the reusable bitmaps, looking for one 
+// to use for inBitmap:
+protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
+        Bitmap bitmap = null;
+
+    if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
+        final Iterator&lt;SoftReference&lt;Bitmap&gt;&gt; iterator
+                = mReusableBitmaps.iterator();
+        Bitmap item;
+
+        while (iterator.hasNext()) {
+            item = iterator.next().get();
+
+            if (null != item && item.isMutable()) {
+                // Check to see it the item can be used for inBitmap.
+                if (canUseForInBitmap(item, options)) {
+                    bitmap = item;
+
+                    // Remove from reusable set so it can't be used again.
+                    iterator.remove();
+                    break;
+                }
+            } else {
+                // Remove from the set if the reference has been cleared.
+                iterator.remove();
+            }
+        }
+    }
+    return bitmap;
+}</pre>
+
+<p>Finally, this method determines whether a candidate bitmap
+satisfies the size criteria to be used for
+{@link android.graphics.BitmapFactory.Options#inBitmap}:</p>
+
+<pre>private static boolean canUseForInBitmap(
+        Bitmap candidate, BitmapFactory.Options targetOptions) {
+    int width = targetOptions.outWidth / targetOptions.inSampleSize;
+    int height = targetOptions.outHeight / targetOptions.inSampleSize;
+
+    // Returns true if "candidate" can be used for inBitmap re-use with
+    // "targetOptions".
+    return candidate.getWidth() == width && candidate.getHeight() == height;
+}</pre>
+
+</body>
+</html>
diff --git a/docs/html/training/displaying-bitmaps/process-bitmap.jd b/docs/html/training/displaying-bitmaps/process-bitmap.jd
index d4fcff3..272b8bc 100644
--- a/docs/html/training/displaying-bitmaps/process-bitmap.jd
+++ b/docs/html/training/displaying-bitmaps/process-bitmap.jd
@@ -3,10 +3,6 @@
 parent.link=index.html
 
 trainingnavtop=true
-next.title=Caching Bitmaps
-next.link=cache-bitmap.html
-previous.title=Loading Large Bitmaps Efficiently
-previous.link=load-bitmap.html
 
 @jd:body
 
diff --git a/docs/html/training/gestures/detector.jd b/docs/html/training/gestures/detector.jd
index 06d0e98..65ddb1b 100644
--- a/docs/html/training/gestures/detector.jd
+++ b/docs/html/training/gestures/detector.jd
@@ -25,12 +25,18 @@
    <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
 
 </div>
 </div>
diff --git a/docs/html/training/gestures/index.jd b/docs/html/training/gestures/index.jd
index 0191450..16ca7b0 100644
--- a/docs/html/training/gestures/index.jd
+++ b/docs/html/training/gestures/index.jd
@@ -20,12 +20,18 @@
     <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
 
 </div>
 </div>
diff --git a/docs/html/training/gestures/movement.jd b/docs/html/training/gestures/movement.jd
index f2c49d7..fdc1ea4 100644
--- a/docs/html/training/gestures/movement.jd
+++ b/docs/html/training/gestures/movement.jd
@@ -24,12 +24,18 @@
     <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
 
 </div>
 </div>
diff --git a/docs/html/training/gestures/multi.jd b/docs/html/training/gestures/multi.jd
index d4c5b1d..6a0df11 100644
--- a/docs/html/training/gestures/multi.jd
+++ b/docs/html/training/gestures/multi.jd
@@ -25,12 +25,18 @@
    <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
 
 </div>
 </div>
diff --git a/docs/html/training/gestures/scale.jd b/docs/html/training/gestures/scale.jd
index 17e4085..f2e4eb8 100644
--- a/docs/html/training/gestures/scale.jd
+++ b/docs/html/training/gestures/scale.jd
@@ -15,6 +15,7 @@
 <h2>This lesson teaches you to</h2>
 <ol>
   <li><a href="#drag">Drag an Object</a></li>
+  <li><a href="#pan">Drag to Pan</a></li>
   <li><a href="#scale">Use Touch to Perform Scaling</a></li>
 </ol>
 
@@ -25,20 +26,25 @@
     <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
 
 </div>
 </div>
+
 <p>This lesson describes how to use touch gestures to drag and scale on-screen
 objects, using {@link android.view.View#onTouchEvent onTouchEvent()} to intercept
-touch events. Here is the original <a
-href="http://code.google.com/p/android-touchexample/">source code</a>
-for the examples used in this lesson.
+touch events. 
 </p>
 
 <h2 id="drag">Drag an Object</h2>
@@ -128,17 +134,15 @@
         final float x = MotionEventCompat.getX(ev, pointerIndex);
         final float y = MotionEventCompat.getY(ev, pointerIndex);
             
-        // Only move if the ScaleGestureDetector isn't processing a gesture.
-        if (!mScaleDetector.isInProgress()) {
-            // Calculate the distance moved
-            final float dx = x - mLastTouchX;
-            final float dy = y - mLastTouchY;
+        // Calculate the distance moved
+        final float dx = x - mLastTouchX;
+        final float dy = y - mLastTouchY;
 
-            mPosX += dx;
-            mPosY += dy;
+        mPosX += dx;
+        mPosY += dy;
 
-            invalidate();
-        }
+        invalidate();
+
         // Remember this touch position for the next move event
         mLastTouchX = x;
         mLastTouchY = y;
@@ -175,6 +179,88 @@
     return true;
 }</pre>
 
+<h2 id="pan">Drag to Pan</h2>
+
+<p>The previous section showed an example of dragging an object around the screen. Another 
+common scenario is <em>panning</em>, which is when a user's dragging motion causes scrolling 
+in both the x and y axes. The above snippet directly intercepted the {@link android.view.MotionEvent} 
+actions to implement dragging. The snippet in this section takes advantage of the platform's 
+built-in support for common gestures. It overrides 
+{@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} in 
+{@link android.view.GestureDetector.SimpleOnGestureListener}.</p>
+
+<p>To provide a little more context, {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} 
+is called when a user is dragging his finger to pan the content. 
+{@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} is only called when 
+a finger is down; as soon as the finger is lifted from the screen, the gesture either ends, 
+or a fling gesture is started (if the finger was moving with some speed just before it was lifted). 
+For more discussion of scrolling vs. flinging, see <a href="scroll.html">Animating a Scroll Gesture</a>.</p>
+
+<p>Here is the snippet for {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()}:
+
+
+<pre>// The current viewport. This rectangle represents the currently visible 
+// chart domain and range. 
+private RectF mCurrentViewport = 
+        new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);
+
+// The current destination rectangle (in pixel coordinates) into which the 
+// chart data should be drawn.
+private Rect mContentRect;
+
+private final GestureDetector.SimpleOnGestureListener mGestureListener
+            = new GestureDetector.SimpleOnGestureListener() {
+...
+
+&#64;Override
+public boolean onScroll(MotionEvent e1, MotionEvent e2, 
+            float distanceX, float distanceY) {
+    // Scrolling uses math based on the viewport (as opposed to math using pixels).
+    
+    // Pixel offset is the offset in screen pixels, while viewport offset is the
+    // offset within the current viewport. 
+    float viewportOffsetX = distanceX * mCurrentViewport.width() 
+            / mContentRect.width();
+    float viewportOffsetY = -distanceY * mCurrentViewport.height() 
+            / mContentRect.height();
+    ...
+    // Updates the viewport, refreshes the display. 
+    setViewportBottomLeft(
+            mCurrentViewport.left + viewportOffsetX,
+            mCurrentViewport.bottom + viewportOffsetY);
+    ...
+    return true;
+}</pre>
+
+<p>The implementation of {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} 
+scrolls the viewport in response to the touch gesture:</p>
+
+<pre>
+/**
+ * Sets the current viewport (defined by mCurrentViewport) to the given
+ * X and Y positions. Note that the Y value represents the topmost pixel position, 
+ * and thus the bottom of the mCurrentViewport rectangle.
+ */
+private void setViewportBottomLeft(float x, float y) {
+    /*
+     * Constrains within the scroll range. The scroll range is simply the viewport 
+     * extremes (AXIS_X_MAX, etc.) minus the viewport size. For example, if the 
+     * extremes were 0 and 10, and the viewport size was 2, the scroll range would 
+     * be 0 to 8.
+     */
+
+    float curWidth = mCurrentViewport.width();
+    float curHeight = mCurrentViewport.height();
+    x = Math.max(AXIS_X_MIN, Math.min(x, AXIS_X_MAX - curWidth));
+    y = Math.max(AXIS_Y_MIN + curHeight, Math.min(y, AXIS_Y_MAX));
+
+    mCurrentViewport.set(x, y - curHeight, x + curWidth, y);
+
+    // Invalidates the View to update the display.
+    ViewCompat.postInvalidateOnAnimation(this);
+}
+</pre>
+
 <h2 id="scale">Use Touch to Perform Scaling</h2>
 
 <p>As discussed in <a href="detector.html">Detecting Common Gestures</a>,
@@ -191,10 +277,10 @@
 {@link android.view.ScaleGestureDetector.SimpleOnScaleGestureListener} 
 as a helper class that you can extend if you don’t care about all of the reported events.</p>
 
-<p>Here is a snippet that gives you the basic idea of how to perform scaling.
-Here is the original <a
-href="http://code.google.com/p/android-touchexample/">source code</a>
-for the examples.</p>
+
+<h3>Basic scaling example</h3>
+
+<p>Here is a snippet that illustrates the basic ingredients involved in scaling.</p>
 
 <pre>private ScaleGestureDetector mScaleDetector;
 private float mScaleFactor = 1.f;
@@ -238,3 +324,88 @@
         return true;
     }
 }</pre>
+
+
+
+
+<h3>More complex scaling example</h3>
+<p>Here is a more complex example from the {@code InteractiveChart} sample provided with this class. 
+The {@code InteractiveChart} sample supports both scrolling (panning) and scaling with multiple fingers,
+using the {@link android.view.ScaleGestureDetector} "span" 
+({@link android.view.ScaleGestureDetector#getCurrentSpanX getCurrentSpanX/Y}) and 
+"focus" ({@link android.view.ScaleGestureDetector#getFocusX getFocusX/Y}) features:</p>
+
+<pre>&#64;Override
+private RectF mCurrentViewport = 
+        new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);
+private Rect mContentRect;
+private ScaleGestureDetector mScaleGestureDetector;
+...
+public boolean onTouchEvent(MotionEvent event) {
+    boolean retVal = mScaleGestureDetector.onTouchEvent(event);
+    retVal = mGestureDetector.onTouchEvent(event) || retVal;
+    return retVal || super.onTouchEvent(event);
+}
+
+/**
+ * The scale listener, used for handling multi-finger scale gestures.
+ */
+private final ScaleGestureDetector.OnScaleGestureListener mScaleGestureListener
+        = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
+    /**
+     * This is the active focal point in terms of the viewport. Could be a local
+     * variable but kept here to minimize per-frame allocations.
+     */
+    private PointF viewportFocus = new PointF();
+    private float lastSpanX;
+    private float lastSpanY;
+
+    // Detects that new pointers are going down.
+    &#64;Override
+    public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
+        lastSpanX = ScaleGestureDetectorCompat.
+                getCurrentSpanX(scaleGestureDetector);
+        lastSpanY = ScaleGestureDetectorCompat.
+                getCurrentSpanY(scaleGestureDetector);
+        return true;
+    }
+
+    &#64;Override
+    public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
+
+        float spanX = ScaleGestureDetectorCompat.
+                getCurrentSpanX(scaleGestureDetector);
+        float spanY = ScaleGestureDetectorCompat.
+                getCurrentSpanY(scaleGestureDetector);
+
+        float newWidth = lastSpanX / spanX * mCurrentViewport.width();
+        float newHeight = lastSpanY / spanY * mCurrentViewport.height();
+
+        float focusX = scaleGestureDetector.getFocusX();
+        float focusY = scaleGestureDetector.getFocusY();
+        // Makes sure that the chart point is within the chart region.
+        // See the sample for the implementation of hitTest().
+        hitTest(scaleGestureDetector.getFocusX(),
+                scaleGestureDetector.getFocusY(),
+                viewportFocus);
+
+        mCurrentViewport.set(
+                viewportFocus.x
+                        - newWidth * (focusX - mContentRect.left)
+                        / mContentRect.width(),
+                viewportFocus.y
+                        - newHeight * (mContentRect.bottom - focusY)
+                        / mContentRect.height(),
+                0,
+                0);
+        mCurrentViewport.right = mCurrentViewport.left + newWidth;
+        mCurrentViewport.bottom = mCurrentViewport.top + newHeight;     
+        ...
+        // Invalidates the View to update the display.
+        ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);
+
+        lastSpanX = spanX;
+        lastSpanY = spanY;
+        return true;
+    }
+};</pre>
diff --git a/docs/html/training/gestures/scroll.jd b/docs/html/training/gestures/scroll.jd
index 8576948b..bd1537a 100644
--- a/docs/html/training/gestures/scroll.jd
+++ b/docs/html/training/gestures/scroll.jd
@@ -14,6 +14,7 @@
 <!-- table of contents -->
 <h2>This lesson teaches you to</h2>
 <ol>
+  <li><a href="#term">Understand Scrolling Terminology</a></li>
   <li><a href="#scroll">Implement Touch-Based Scrolling</a></li>
 </ol>
 
@@ -24,12 +25,18 @@
     <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
 
 </div>
 </div>
@@ -45,7 +52,26 @@
 
 <p>You can use scrollers ({@link android.widget.Scroller} or {@link
 android.widget.OverScroller}) to collect the data you need to produce a
-scrolling animation in response to a touch event.</p>
+scrolling animation in response to a touch event. They are similar, but
+{@link android.widget.OverScroller}
+includes methods for indicating to users that they've reached the content edges 
+after a pan or fling gesture. The {@code InteractiveChart} sample 
+uses the the {@link android.widget.EdgeEffect} class 
+(actually the {@link android.support.v4.widget.EdgeEffectCompat} class)
+to display a "glow" effect when users reach the content edges.</p>
+
+<p class="note"><strong>Note:</strong> We recommend that you
+use {@link android.widget.OverScroller} rather than {@link
+android.widget.Scroller} for scrolling animations.
+{@link android.widget.OverScroller} provides the best backward
+compatibility with older devices.
+<br />
+Also note that you generally only need to use scrollers
+when implementing scrolling yourself. {@link android.widget.ScrollView} and
+{@link android.widget.HorizontalScrollView} do all of this for you if you nest your 
+layout within them.
+</p>
+
 
 <p>A scroller is used  to animate scrolling over time, using platform-standard
 scrolling physics (friction, velocity, etc.). The scroller itself doesn't
@@ -54,101 +80,280 @@
 responsibility to get and apply new coordinates at a rate that will make the
 scrolling animation look smooth.</p>
 
-<p class="note"><strong>Note:</strong> You generally only need to use scrollers
-when implementing scrolling yourself. {@link android.widget.ScrollView} and
-{@link android.widget.HorizontalScrollView} do all this for you do all of this for you if you nest your layout within them.</p>
-
-<h2 id = "scroll">Implement Touch-Based Scrolling</h2>
 
 
-<p>This snippet illustrates the basics of using a scroller. It uses a 
-{@link android.view.GestureDetector}, and overrides the  
-{@link android.view.GestureDetector.SimpleOnGestureListener} methods 
-{@link android.view.GestureDetector.OnGestureListener#onDown onDown()} and 
-{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}. It also 
-overrides {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} 
-to return {@code false} since you don't need to animate a scroll.</p>
+<h2 id="term">Understand Scrolling Terminology</h2>
 
+<p>"Scrolling" is a word that can take on different meanings in Android, depending on the context.</p>
 
-<p>It's common to use scrollers in conjunction with a fling gesture, but they
+<p><strong>Scrolling</strong> is the general process of moving the viewport (that is, the 'window' 
+of content you're looking at). When scrolling is in both the x and y axes, it's called 
+<em>panning</em>. The sample application provided with this class, {@code InteractiveChart}, illustrates 
+two different types of scrolling, dragging and flinging:</p>
+<ul>
+    <li><strong>Dragging</strong> is the type of scrolling that occurs when a user drags her 
+finger across the touch screen. Simple dragging is often implemented by overriding 
+{@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} in 
+{@link android.view.GestureDetector.OnGestureListener}. For more discussion of dragging, see 
+<a href="dragging.jd">Dragging and Scaling</a>.</li>
+
+    <li><strong>Flinging</strong> is the type of scrolling that occurs when a user 
+drags and lifts her finger quickly. After the user lifts her finger, you generally 
+want to keep scrolling (moving the viewport), but decelerate until the viewport stops moving. 
+Flinging can be implemented by overriding 
+{@link android.view.GestureDetector.OnGestureListener#onFling onFling()} 
+in {@link android.view.GestureDetector.OnGestureListener}, and by using 
+a scroller object. This is the use 
+case that is the topic of this lesson.</li>
+</ul>
+
+<p>It's common to use scroller objects 
+in conjunction with a fling gesture, but they
 can be used in pretty much any context where you want the UI to display
-scrolling in response to a touch event. For example, you could override {@link
-android.view.View#onTouchEvent onTouchEvent()} to process touch events directly,
-and produce a scrolling effect in response to those touch events.</p>
+scrolling in response to a touch event. For example, you could override  
+{@link android.view.View#onTouchEvent onTouchEvent()} to process touch 
+events directly, and produce a scrolling effect or a "snapping to page" animation 
+in response to those touch events.</p>
 
-<pre>
-private OverScroller mScroller = new OverScroller(context);
 
-private GestureDetector.SimpleOnGestureListener mGestureListener
+<h2 id="#scroll">Implement Touch-Based Scrolling</h2> 
+
+<p>This section describes how to use a scroller.
+The snippet shown below comes from the {@code InteractiveChart} sample 
+provided with this class.
+It uses a 
+{@link android.view.GestureDetector}, and overrides the  
+{@link android.view.GestureDetector.SimpleOnGestureListener} method 
+{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}.
+It uses {@link android.widget.OverScroller} to track the fling gesture.
+If the user reaches the content edges 
+after the fling gesture, the app displays a "glow" effect.
+</p>
+
+<p class="note"><strong>Note:</strong> The {@code InteractiveChart} sample app displays a 
+chart that you can zoom, pan, scroll, and so on. In the following snippet, 
+{@code mContentRect} represents the rectangle coordinates within the view that the chart 
+will be drawn into. At any given time, a subset of the total chart domain and range are drawn 
+into this rectangular area. 
+{@code mCurrentViewport} represents the portion of the chart that is currently 
+visible in the screen. Because pixel offsets are generally treated as integers, 
+{@code mContentRect} is of the type {@link android.graphics.Rect}. Because the 
+graph domain and range are decimal/float values, {@code mCurrentViewport} is of 
+the type {@link android.graphics.RectF}.</p>
+
+<p>The first part of the snippet shows the implementation of 
+{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}:</p>
+
+<pre>// The current viewport. This rectangle represents the currently visible 
+// chart domain and range. The viewport is the part of the app that the
+// user manipulates via touch gestures.
+private RectF mCurrentViewport = 
+        new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);
+
+// The current destination rectangle (in pixel coordinates) into which the 
+// chart data should be drawn.
+private Rect mContentRect;
+
+private OverScroller mScroller;
+private RectF mScrollerStartViewport;
+...
+private final GestureDetector.SimpleOnGestureListener mGestureListener
         = new GestureDetector.SimpleOnGestureListener() {
     &#64;Override
     public boolean onDown(MotionEvent e) {
-        // Abort any active scroll animations and invalidate.
+        // Initiates the decay phase of any active edge effects.
+        releaseEdgeEffects();
+        mScrollerStartViewport.set(mCurrentViewport);
+        // Aborts any active scroll animations and invalidates.
         mScroller.forceFinished(true);
-        // There is also a compatibility version: 
-        // ViewCompat.postInvalidateOnAnimation
-        postInvalidateOnAnimation();
+        ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);
         return true;
     }
-
-    &#64;Override
-    public boolean onScroll(MotionEvent e1, MotionEvent e2, 
-            float distanceX, float distanceY) {
-        // You don't use a scroller in onScroll because you don't need to animate
-        // a scroll. The scroll occurs instantly in response to touch feedback.
-        return false;
-    }
-
+    ...
     &#64;Override
     public boolean onFling(MotionEvent e1, MotionEvent e2, 
             float velocityX, float velocityY) {
-        // Before flinging, abort the current animation.
-        mScroller.forceFinished(true);
-        // Begin the scroll animation
-        mScroller.fling(
-                // Current scroll position
-                startX,
-                startY,
-                // Velocities, negated for natural touch response
-                (int) -velocityX,
-                (int) -velocityY,
-                // Minimum and maximum scroll positions. The minimum scroll 
-                // position is generally zero and the maximum scroll position 
-                // is generally the content size less the screen size. So if the 
-                // content width is 1000 pixels and the screen width is 200  
-                // pixels, the maximum scroll offset should be 800 pixels.
-                minX, maxX,
-                minY, maxY,
-                // The maximum overscroll bounds. This is useful when using
-                // the EdgeEffect class to draw overscroll "glow" overlays.
-                mContentRect.width() / 2,
-                mContentRect.height() / 2);
-        // Invalidate to trigger computeScroll()
-        postInvalidateOnAnimation();
+        fling((int) -velocityX, (int) -velocityY);
         return true;
     }
 };
 
+private void fling(int velocityX, int velocityY) {
+    // Initiates the decay phase of any active edge effects.
+    releaseEdgeEffects();
+    // Flings use math in pixels (as opposed to math based on the viewport).
+    Point surfaceSize = computeScrollSurfaceSize();
+    mScrollerStartViewport.set(mCurrentViewport);
+    int startX = (int) (surfaceSize.x * (mScrollerStartViewport.left - 
+            AXIS_X_MIN) / (
+            AXIS_X_MAX - AXIS_X_MIN));
+    int startY = (int) (surfaceSize.y * (AXIS_Y_MAX - 
+            mScrollerStartViewport.bottom) / (
+            AXIS_Y_MAX - AXIS_Y_MIN));
+    // Before flinging, aborts the current animation.
+    mScroller.forceFinished(true);
+    // Begins the animation
+    mScroller.fling(
+            // Current scroll position
+            startX,
+            startY,
+            velocityX,
+            velocityY,
+            /*
+             * Minimum and maximum scroll positions. The minimum scroll 
+             * position is generally zero and the maximum scroll position 
+             * is generally the content size less the screen size. So if the 
+             * content width is 1000 pixels and the screen width is 200  
+             * pixels, the maximum scroll offset should be 800 pixels.
+             */
+            0, surfaceSize.x - mContentRect.width(),
+            0, surfaceSize.y - mContentRect.height(),
+            // The edges of the content. This comes into play when using
+            // the EdgeEffect class to draw "glow" overlays.
+            mContentRect.width() / 2,
+            mContentRect.height() / 2);
+    // Invalidates to trigger computeScroll()
+    ViewCompat.postInvalidateOnAnimation(this);
+}</pre>
+
+<p>When {@link android.view.GestureDetector.OnGestureListener#onFling onFling()} calls 
+{@link android.support.v4.view.ViewCompat#postInvalidateOnAnimation postInvalidateOnAnimation()}, 
+it triggers 
+{@link android.view.View#computeScroll computeScroll()} to update the values for x and y. 
+This is typically be done when a view child is animating a scroll using a scroller object, as in this example. </p>
+
+<p>Most views pass the scroller object's x and y position directly to 
+{@link android.view.View#scrollTo scrollTo()}. 
+The following implementation of {@link android.view.View#computeScroll computeScroll()} 
+takes a different approach&mdash;it calls 
+{@link android.widget.OverScroller#computeScrollOffset computeScrollOffset()} to get the current 
+location of x and y. When the criteria for displaying an overscroll "glow" edge effect are met 
+(the display is zoomed in, x or y is out of bounds, and the app isn't already showing an overscroll), 
+the code sets up the overscroll glow effect and calls 
+{@link android.support.v4.view.ViewCompat#postInvalidateOnAnimation postInvalidateOnAnimation()} 
+to trigger an invalidate on the view:</p>
+
+<pre>// Edge effect / overscroll tracking objects.
+private EdgeEffectCompat mEdgeEffectTop;
+private EdgeEffectCompat mEdgeEffectBottom;
+private EdgeEffectCompat mEdgeEffectLeft;
+private EdgeEffectCompat mEdgeEffectRight;
+
+private boolean mEdgeEffectTopActive;
+private boolean mEdgeEffectBottomActive;
+private boolean mEdgeEffectLeftActive;
+private boolean mEdgeEffectRightActive;
+
 &#64;Override
 public void computeScroll() {
     super.computeScroll();
 
-    // Compute the current scroll offsets. If this returns true, then the 
-    // scroll has not yet finished.
+    boolean needsInvalidate = false;
+
+    // The scroller isn't finished, meaning a fling or programmatic pan 
+    // operation is currently active.
     if (mScroller.computeScrollOffset()) {
+        Point surfaceSize = computeScrollSurfaceSize();
         int currX = mScroller.getCurrX();
         int currY = mScroller.getCurrY();
 
-        // Actually render the scrolled viewport, or actually scroll the 
-        // view using View.scrollTo.
+        boolean canScrollX = (mCurrentViewport.left > AXIS_X_MIN
+                || mCurrentViewport.right < AXIS_X_MAX);
+        boolean canScrollY = (mCurrentViewport.top > AXIS_Y_MIN
+                || mCurrentViewport.bottom < AXIS_Y_MAX);
 
-        // If currX or currY are outside the bounds, render the overscroll 
-        // glow using EdgeEffect.
+        /*          
+         * If you are zoomed in and currX or currY is
+         * outside of bounds and you're not already
+         * showing overscroll, then render the overscroll
+         * glow edge effect.
+         */
+        if (canScrollX
+                && currX < 0
+                && mEdgeEffectLeft.isFinished()
+                && !mEdgeEffectLeftActive) {
+            mEdgeEffectLeft.onAbsorb((int) 
+                    OverScrollerCompat.getCurrVelocity(mScroller));
+            mEdgeEffectLeftActive = true;
+            needsInvalidate = true;
+        } else if (canScrollX
+                && currX > (surfaceSize.x - mContentRect.width())
+                && mEdgeEffectRight.isFinished()
+                && !mEdgeEffectRightActive) {
+            mEdgeEffectRight.onAbsorb((int) 
+                    OverScrollerCompat.getCurrVelocity(mScroller));
+            mEdgeEffectRightActive = true;
+            needsInvalidate = true;
+        }
 
-    } else {
-        // The scroll has finished.
-    }
+        if (canScrollY
+                && currY < 0
+                && mEdgeEffectTop.isFinished()
+                && !mEdgeEffectTopActive) {
+            mEdgeEffectTop.onAbsorb((int) 
+                    OverScrollerCompat.getCurrVelocity(mScroller));
+            mEdgeEffectTopActive = true;
+            needsInvalidate = true;
+        } else if (canScrollY
+                && currY > (surfaceSize.y - mContentRect.height())
+                && mEdgeEffectBottom.isFinished()
+                && !mEdgeEffectBottomActive) {
+            mEdgeEffectBottom.onAbsorb((int) 
+                    OverScrollerCompat.getCurrVelocity(mScroller));
+            mEdgeEffectBottomActive = true;
+            needsInvalidate = true;
+        }
+        ...
+    }</pre>
+
+<p>Here is the section of the code that performs the actual zoom:</p>
+
+<pre>// Custom object that is functionally similar to Scroller
+Zoomer mZoomer;
+private PointF mZoomFocalPoint = new PointF();
+...
+
+// If a zoom is in progress (either programmatically or via double
+// touch), performs the zoom.
+if (mZoomer.computeZoom()) {
+    float newWidth = (1f - mZoomer.getCurrZoom()) * 
+            mScrollerStartViewport.width();
+    float newHeight = (1f - mZoomer.getCurrZoom()) * 
+            mScrollerStartViewport.height();
+    float pointWithinViewportX = (mZoomFocalPoint.x - 
+            mScrollerStartViewport.left)
+            / mScrollerStartViewport.width();
+    float pointWithinViewportY = (mZoomFocalPoint.y - 
+            mScrollerStartViewport.top)
+            / mScrollerStartViewport.height();
+    mCurrentViewport.set(
+            mZoomFocalPoint.x - newWidth * pointWithinViewportX,
+            mZoomFocalPoint.y - newHeight * pointWithinViewportY,
+            mZoomFocalPoint.x + newWidth * (1 - pointWithinViewportX),
+            mZoomFocalPoint.y + newHeight * (1 - pointWithinViewportY));
+    constrainViewport();
+    needsInvalidate = true;
+}
+if (needsInvalidate) {
+    ViewCompat.postInvalidateOnAnimation(this);
+}
+</pre>
+
+<p>This is the {@code computeScrollSurfaceSize()} method that's called in the above snippet. It 
+computes the current scrollable surface size, in pixels. For example, if the entire chart area is visible, 
+this is simply the current size of {@code mContentRect}. If the chart is zoomed in 200% in both directions, 
+the returned size will be twice as large horizontally and vertically.</p>
+
+<pre>private Point computeScrollSurfaceSize() {
+    return new Point(
+            (int) (mContentRect.width() * (AXIS_X_MAX - AXIS_X_MIN)
+                    / mCurrentViewport.width()),
+            (int) (mContentRect.height() * (AXIS_Y_MAX - AXIS_Y_MIN)
+                    / mCurrentViewport.height()));
 }</pre>
 
-<p>For another example of scroller usage, see the <a href="http://github.com/android/platform_frameworks_support/blob/master/v4/java/android/support/v4/view/ViewPager.java">source code</a> for the 
-{@link android.support.v4.view.ViewPager} class.</p>
+<p>For another example of scroller usage, see the 
+<a href="http://github.com/android/platform_frameworks_support/blob/master/v4/java/android/support/v4/view/ViewPager.java">source code</a> for the 
+{@link android.support.v4.view.ViewPager} class. It scrolls in response to flings, 
+and uses scrolling to implement the "snapping to page" animation.</p>
+
diff --git a/docs/html/training/gestures/viewgroup.jd b/docs/html/training/gestures/viewgroup.jd
index 257a5d8..5b32300 100644
--- a/docs/html/training/gestures/viewgroup.jd
+++ b/docs/html/training/gestures/viewgroup.jd
@@ -26,12 +26,19 @@
     <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
+
 
 </div>
 </div>
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs
index 79980be..985fc44 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -287,6 +287,10 @@
             Caching Bitmaps
           </a>
           </li>
+          <li><a href="<?cs var:toroot ?>training/displaying-bitmaps/manage-memory.html">
+            Managing Bitmap Memory
+          </a>
+          </li>
           <li><a href="<?cs var:toroot ?>training/displaying-bitmaps/display-bitmap.html">
             Displaying Bitmaps in Your UI
           </a></li>
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 9bb90f1..cc7f23f 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -62,6 +62,18 @@
     @SuppressWarnings({"UnusedDeclaration"})
     private int mSurfaceFormat;
 
+    /**
+     * Flag for drawTextRun indicating left-to-right run direction.
+     * @hide
+     */
+    public static final int DIRECTION_LTR = 0;
+    
+    /**
+     * Flag for drawTextRun indicating right-to-left run direction.
+     * @hide
+     */
+    public static final int DIRECTION_RTL = 1;
+
     // Maximum bitmap size as defined in Skia's native code
     // (see SkCanvas.cpp, SkDraw.cpp)
     private static final int MAXMIMUM_BITMAP_SIZE = 32766;
@@ -683,8 +695,16 @@
     }
 
     public enum EdgeType {
+
+        /**
+         * Black-and-White: Treat edges by just rounding to nearest pixel boundary
+         */
         BW(0),  //!< treat edges by just rounding to nearest pixel boundary
-        AA(1);  //!< treat edges by rounding-out, since they may be antialiased
+
+        /**
+         * Antialiased: Treat edges by rounding-out, since they may be antialiased
+         */
+        AA(1);
         
         EdgeType(int nativeInt) {
             this.nativeInt = nativeInt;
@@ -703,7 +723,9 @@
      * therefore you can skip making the draw calls).
      *
      * @param rect  the rect to compare with the current clip
-     * @param type  specifies how to treat the edges (BW or antialiased)
+     * @param type  {@link Canvas.EdgeType#AA} if the path should be considered antialiased,
+     *              since that means it may affect a larger area (more pixels) than
+     *              non-antialiased ({@link Canvas.EdgeType#BW}).
      * @return      true if the rect (transformed by the canvas' matrix)
      *              does not intersect with the canvas' clip
      */
@@ -720,10 +742,9 @@
      * (i.e. the bounds of the path intersects, but the path does not).
      *
      * @param path        The path to compare with the current clip
-     * @param type        true if the path should be considered antialiased,
-     *                    since that means it may
-     *                    affect a larger area (more pixels) than
-     *                    non-antialiased.
+     * @param type        {@link Canvas.EdgeType#AA} if the path should be considered antialiased,
+     *                    since that means it may affect a larger area (more pixels) than
+     *                    non-antialiased ({@link Canvas.EdgeType#BW}).
      * @return            true if the path (transformed by the canvas' matrix)
      *                    does not intersect with the canvas' clip
      */
@@ -745,9 +766,9 @@
      *                    current clip
      * @param bottom      The bottom of the rectangle to compare with the
      *                    current clip
-     * @param type        true if the rect should be considered antialiased,
-     *                    since that means it may affect a larger area (more
-     *                    pixels) than non-antialiased.
+     * @param type        {@link Canvas.EdgeType#AA} if the path should be considered antialiased,
+     *                    since that means it may affect a larger area (more pixels) than
+     *                    non-antialiased ({@link Canvas.EdgeType#BW}).
      * @return            true if the rect (transformed by the canvas' matrix)
      *                    does not intersect with the canvas' clip
      */
@@ -1333,7 +1354,8 @@
             (text.length - index - count)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        native_drawText(mNativeCanvas, text, index, count, x, y, paint.mNativePaint);
+        native_drawText(mNativeCanvas, text, index, count, x, y, paint.mBidiFlags,
+                paint.mNativePaint);
     }
 
     /**
@@ -1346,7 +1368,8 @@
      * @param paint The paint used for the text (e.g. color, size, style)
      */
     public void drawText(String text, float x, float y, Paint paint) {
-        native_drawText(mNativeCanvas, text, 0, text.length(), x, y, paint.mNativePaint);
+        native_drawText(mNativeCanvas, text, 0, text.length(), x, y, paint.mBidiFlags,
+                paint.mNativePaint);
     }
 
     /**
@@ -1364,7 +1387,8 @@
         if ((start | end | (end - start) | (text.length() - end)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        native_drawText(mNativeCanvas, text, start, end, x, y, paint.mNativePaint);
+        native_drawText(mNativeCanvas, text, start, end, x, y, paint.mBidiFlags,
+                paint.mNativePaint);
     }
 
     /**
@@ -1383,14 +1407,16 @@
     public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
         if (text instanceof String || text instanceof SpannedString ||
             text instanceof SpannableString) {
-            native_drawText(mNativeCanvas, text.toString(), start, end, x, y, paint.mNativePaint);
+            native_drawText(mNativeCanvas, text.toString(), start, end, x, y,
+                            paint.mBidiFlags, paint.mNativePaint);
         } else if (text instanceof GraphicsOperations) {
             ((GraphicsOperations) text).drawText(this, start, end, x, y,
                                                      paint);
         } else {
             char[] buf = TemporaryBuffer.obtain(end - start);
             TextUtils.getChars(text, start, end, buf, 0);
-            native_drawText(mNativeCanvas, buf, 0, end - start, x, y, paint.mNativePaint);
+            native_drawText(mNativeCanvas, buf, 0, end - start, x, y,
+                    paint.mBidiFlags, paint.mNativePaint);
             TemporaryBuffer.recycle(buf);
         }
     }
@@ -1411,11 +1437,13 @@
      *         + count.
      * @param x the x position at which to draw the text
      * @param y the y position at which to draw the text
+     * @param dir the run direction, either {@link #DIRECTION_LTR} or
+     *         {@link #DIRECTION_RTL}.
      * @param paint the paint
      * @hide
      */
     public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount,
-            float x, float y, Paint paint) {
+            float x, float y, int dir, Paint paint) {
 
         if (text == null) {
             throw new NullPointerException("text is null");
@@ -1426,9 +1454,12 @@
         if ((index | count | text.length - index - count) < 0) {
             throw new IndexOutOfBoundsException();
         }
+        if (dir != DIRECTION_LTR && dir != DIRECTION_RTL) {
+            throw new IllegalArgumentException("unknown dir: " + dir);
+        }
 
         native_drawTextRun(mNativeCanvas, text, index, count,
-                contextIndex, contextCount, x, y, paint.mNativePaint);
+                contextIndex, contextCount, x, y, dir, paint.mNativePaint);
     }
 
     /**
@@ -1444,11 +1475,12 @@
      *            position can be used for shaping context.
      * @param x the x position at which to draw the text
      * @param y the y position at which to draw the text
+     * @param dir the run direction, either 0 for LTR or 1 for RTL.
      * @param paint the paint
      * @hide
      */
     public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd,
-            float x, float y, Paint paint) {
+            float x, float y, int dir, Paint paint) {
 
         if (text == null) {
             throw new NullPointerException("text is null");
@@ -1460,20 +1492,22 @@
             throw new IndexOutOfBoundsException();
         }
 
+        int flags = dir == 0 ? 0 : 1;
+
         if (text instanceof String || text instanceof SpannedString ||
                 text instanceof SpannableString) {
             native_drawTextRun(mNativeCanvas, text.toString(), start, end,
-                    contextStart, contextEnd, x, y, paint.mNativePaint);
+                    contextStart, contextEnd, x, y, flags, paint.mNativePaint);
         } else if (text instanceof GraphicsOperations) {
             ((GraphicsOperations) text).drawTextRun(this, start, end,
-                    contextStart, contextEnd, x, y, paint);
+                    contextStart, contextEnd, x, y, flags, paint);
         } else {
             int contextLen = contextEnd - contextStart;
             int len = end - start;
             char[] buf = TemporaryBuffer.obtain(contextLen);
             TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
             native_drawTextRun(mNativeCanvas, buf, start - contextStart, len,
-                    0, contextLen, x, y, paint.mNativePaint);
+                    0, contextLen, x, y, flags, paint.mNativePaint);
             TemporaryBuffer.recycle(buf);
         }
     }
@@ -1539,7 +1573,8 @@
             throw new ArrayIndexOutOfBoundsException();
         }
         native_drawTextOnPath(mNativeCanvas, text, index, count,
-                              path.ni(), hOffset, vOffset, paint.mNativePaint);
+                              path.ni(), hOffset, vOffset,
+                              paint.mBidiFlags, paint.mNativePaint);
     }
 
     /**
@@ -1558,7 +1593,7 @@
     public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) {
         if (text.length() > 0) {
             native_drawTextOnPath(mNativeCanvas, text, path.ni(), hOffset, vOffset,
-                    paint.mNativePaint);
+                    paint.mBidiFlags, paint.mNativePaint);
         }
     }
 
@@ -1720,18 +1755,18 @@
     
     private static native void native_drawText(int nativeCanvas, char[] text,
                                                int index, int count, float x,
-                                               float y, int paint);
+                                               float y, int flags, int paint);
     private static native void native_drawText(int nativeCanvas, String text,
                                                int start, int end, float x,
-                                               float y, int paint);
+                                               float y, int flags, int paint);
 
     private static native void native_drawTextRun(int nativeCanvas, String text,
             int start, int end, int contextStart, int contextEnd,
-            float x, float y, int paint);
+            float x, float y, int flags, int paint);
 
     private static native void native_drawTextRun(int nativeCanvas, char[] text,
             int start, int count, int contextStart, int contextCount,
-            float x, float y, int paint);
+            float x, float y, int flags, int paint);
 
     private static native void native_drawPosText(int nativeCanvas,
                                                   char[] text, int index,
@@ -1744,13 +1779,13 @@
                                                      char[] text, int index,
                                                      int count, int path,
                                                      float hOffset,
-                                                     float vOffset,
+                                                     float vOffset, int bidiFlags,
                                                      int paint);
     private static native void native_drawTextOnPath(int nativeCanvas,
                                                      String text, int path,
                                                      float hOffset, 
                                                      float vOffset, 
-                                                     int paint);
+                                                     int flags, int paint);
     private static native void native_drawPicture(int nativeCanvas,
                                                   int nativePicture);
     private static native void finalizer(int nativeCanvas);
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 8da20f2..216dc6c 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -69,6 +69,11 @@
      */
     public int shadowColor;
 
+    /**
+     * @hide
+     */
+    public  int         mBidiFlags = BIDI_DEFAULT_LTR;
+    
     static final Style[] sStyleArray = {
         Style.FILL, Style.STROKE, Style.FILL_AND_STROKE
     };
@@ -115,6 +120,74 @@
     public static final int HINTING_ON = 0x1;
 
     /**
+     * Bidi flag to set LTR paragraph direction.
+     * 
+     * @hide
+     */
+    public static final int BIDI_LTR = 0x0;
+
+    /**
+     * Bidi flag to set RTL paragraph direction.
+     * 
+     * @hide
+     */
+    public static final int BIDI_RTL = 0x1;
+
+    /**
+     * Bidi flag to detect paragraph direction via heuristics, defaulting to
+     * LTR.
+     * 
+     * @hide
+     */
+    public static final int BIDI_DEFAULT_LTR = 0x2;
+
+    /**
+     * Bidi flag to detect paragraph direction via heuristics, defaulting to
+     * RTL.
+     * 
+     * @hide
+     */
+    public static final int BIDI_DEFAULT_RTL = 0x3;
+
+    /**
+     * Bidi flag to override direction to all LTR (ignore bidi).
+     * 
+     * @hide
+     */
+    public static final int BIDI_FORCE_LTR = 0x4;
+
+    /**
+     * Bidi flag to override direction to all RTL (ignore bidi).
+     * 
+     * @hide
+     */
+    public static final int BIDI_FORCE_RTL = 0x5;
+
+    /**
+     * Maximum Bidi flag value.
+     * @hide
+     */
+    private static final int BIDI_MAX_FLAG_VALUE = BIDI_FORCE_RTL;
+
+    /**
+     * Mask for bidi flags.
+     * @hide
+     */
+    private static final int BIDI_FLAG_MASK = 0x7;
+
+    /**
+     * Flag for getTextRunAdvances indicating left-to-right run direction.
+     * @hide
+     */
+    public static final int DIRECTION_LTR = 0;
+
+    /**
+     * Flag for getTextRunAdvances indicating right-to-left run direction.
+     * @hide
+     */
+    public static final int DIRECTION_RTL = 1;
+
+    /**
      * Option for getTextRunCursor to compute the valid cursor after
      * offset or the limit of the context, whichever is less.
      * @hide
@@ -322,6 +395,7 @@
         shadowRadius = 0;
         shadowColor = 0;
 
+        mBidiFlags = BIDI_DEFAULT_LTR;
         setTextLocale(Locale.getDefault());
     }
     
@@ -361,6 +435,7 @@
         shadowRadius = paint.shadowRadius;
         shadowColor = paint.shadowColor;
 
+        mBidiFlags = paint.mBidiFlags;
         mLocale = paint.mLocale;
     }
 
@@ -377,6 +452,29 @@
     }
 
     /**
+     * Return the bidi flags on the paint.
+     * 
+     * @return the bidi flags on the paint
+     * @hide
+     */
+    public int getBidiFlags() {
+        return mBidiFlags;
+    }
+
+    /**
+     * Set the bidi flags on the paint.
+     * @hide
+     */
+    public void setBidiFlags(int flags) {
+        // only flag value is the 3-bit BIDI control setting
+        flags &= BIDI_FLAG_MASK;
+        if (flags > BIDI_MAX_FLAG_VALUE) {
+            throw new IllegalArgumentException("unknown bidi flag: " + flags);
+        }
+        mBidiFlags = flags;
+    }
+
+    /**
      * Return the paint's flags. Use the Flag enum to test flag values.
      * 
      * @return the paint's flags (see enums ending in _Flag for bit masks)
@@ -1570,19 +1668,76 @@
     }
 
     /**
+     * Return the glyph Ids for the characters in the string.
+     *
+     * @param text   The text to measure
+     * @param start  The index of the first char to to measure
+     * @param end    The end of the text slice to measure
+     * @param contextStart the index of the first character to use for shaping context,
+     * must be <= start
+     * @param contextEnd the index past the last character to use for shaping context,
+     * must be >= end
+     * @param flags the flags to control the advances, either {@link #DIRECTION_LTR}
+     * or {@link #DIRECTION_RTL}
+     * @param glyphs array to receive the glyph Ids of the characters.
+     *               Must be at least a large as the text.
+     * @return       the number of glyphs in the returned array
+     *
+     * @hide
+     *
+     * Used only for BiDi / RTL Tests
+     */
+    public int getTextGlyphs(String text, int start, int end, int contextStart, int contextEnd,
+            int flags, char[] glyphs) {
+        if (text == null) {
+            throw new IllegalArgumentException("text cannot be null");
+        }
+        if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
+            throw new IllegalArgumentException("unknown flags value: " + flags);
+        }
+        if ((start | end | contextStart | contextEnd | (end - start)
+                | (start - contextStart) | (contextEnd - end) | (text.length() - end)
+                | (text.length() - contextEnd)) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (end - start > glyphs.length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        return native_getTextGlyphs(mNativePaint, text, start, end, contextStart, contextEnd,
+                flags, glyphs);
+    }
+
+    /**
      * Convenience overload that takes a char array instead of a
      * String.
      *
-     * @see #getTextRunAdvances(String, int, int, int, int, float[], int)
+     * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int)
      * @hide
      */
     public float getTextRunAdvances(char[] chars, int index, int count,
-            int contextIndex, int contextCount, float[] advances,
+            int contextIndex, int contextCount, int flags, float[] advances,
             int advancesIndex) {
+        return getTextRunAdvances(chars, index, count, contextIndex, contextCount, flags,
+                advances, advancesIndex, 0 /* use Harfbuzz*/);
+    }
+
+    /**
+     * Convenience overload that takes a char array instead of a
+     * String.
+     *
+     * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int, int)
+     * @hide
+     */
+    public float getTextRunAdvances(char[] chars, int index, int count,
+            int contextIndex, int contextCount, int flags, float[] advances,
+            int advancesIndex, int reserved) {
 
         if (chars == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
+        if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
+            throw new IllegalArgumentException("unknown flags value: " + flags);
+        }
         if ((index | count | contextIndex | contextCount | advancesIndex
                 | (index - contextIndex) | (contextCount - count)
                 | ((contextIndex + contextCount) - (index + count))
@@ -1597,13 +1752,13 @@
         }
         if (!mHasCompatScaling) {
             return native_getTextRunAdvances(mNativePaint, chars, index, count,
-                    contextIndex, contextCount, advances, advancesIndex);
+                    contextIndex, contextCount, flags, advances, advancesIndex, reserved);
         }
 
         final float oldSize = getTextSize();
         setTextSize(oldSize * mCompatScaling);
         float res = native_getTextRunAdvances(mNativePaint, chars, index, count,
-                contextIndex, contextCount, advances, advancesIndex);
+                contextIndex, contextCount, flags, advances, advancesIndex, reserved);
         setTextSize(oldSize);
 
         if (advances != null) {
@@ -1618,12 +1773,26 @@
      * Convenience overload that takes a CharSequence instead of a
      * String.
      *
-     * @see #getTextRunAdvances(String, int, int, int, int, float[], int)
+     * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int)
      * @hide
      */
     public float getTextRunAdvances(CharSequence text, int start, int end,
-            int contextStart, int contextEnd, float[] advances,
+            int contextStart, int contextEnd, int flags, float[] advances,
             int advancesIndex) {
+        return getTextRunAdvances(text, start, end, contextStart, contextEnd, flags,
+                advances, advancesIndex, 0 /* use Harfbuzz */);
+    }
+
+    /**
+     * Convenience overload that takes a CharSequence instead of a
+     * String.
+     *
+     * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int)
+     * @hide
+     */
+    public float getTextRunAdvances(CharSequence text, int start, int end,
+            int contextStart, int contextEnd, int flags, float[] advances,
+            int advancesIndex, int reserved) {
 
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
@@ -1638,16 +1807,16 @@
 
         if (text instanceof String) {
             return getTextRunAdvances((String) text, start, end,
-                    contextStart, contextEnd, advances, advancesIndex);
+                    contextStart, contextEnd, flags, advances, advancesIndex, reserved);
         }
         if (text instanceof SpannedString ||
             text instanceof SpannableString) {
             return getTextRunAdvances(text.toString(), start, end,
-                    contextStart, contextEnd, advances, advancesIndex);
+                    contextStart, contextEnd, flags, advances, advancesIndex, reserved);
         }
         if (text instanceof GraphicsOperations) {
             return ((GraphicsOperations) text).getTextRunAdvances(start, end,
-                    contextStart, contextEnd, advances, advancesIndex, this);
+                    contextStart, contextEnd, flags, advances, advancesIndex, this);
         }
         if (text.length() == 0 || end == start) {
             return 0f;
@@ -1658,7 +1827,7 @@
         char[] buf = TemporaryBuffer.obtain(contextLen);
         TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
         float result = getTextRunAdvances(buf, start - contextStart, len,
-                0, contextLen, advances, advancesIndex);
+                0, contextLen, flags, advances, advancesIndex, reserved);
         TemporaryBuffer.recycle(buf);
         return result;
     }
@@ -1695,6 +1864,8 @@
      * must be <= start
      * @param contextEnd the index past the last character to use for shaping context,
      * must be >= end
+     * @param flags the flags to control the advances, either {@link #DIRECTION_LTR}
+     * or {@link #DIRECTION_RTL}
      * @param advances array to receive the advances, must have room for all advances,
      * can be null if only total advance is needed
      * @param advancesIndex the position in advances at which to put the
@@ -1704,11 +1875,63 @@
      * @hide
      */
     public float getTextRunAdvances(String text, int start, int end, int contextStart,
-            int contextEnd, float[] advances, int advancesIndex) {
+            int contextEnd, int flags, float[] advances, int advancesIndex) {
+        return getTextRunAdvances(text, start, end, contextStart, contextEnd, flags,
+                advances, advancesIndex, 0 /* use Harfbuzz*/);
+    }
+
+    /**
+     * Returns the total advance width for the characters in the run
+     * between start and end, and if advances is not null, the advance
+     * assigned to each of these characters (java chars).
+     *
+     * <p>The trailing surrogate in a valid surrogate pair is assigned
+     * an advance of 0.  Thus the number of returned advances is
+     * always equal to count, not to the number of unicode codepoints
+     * represented by the run.
+     *
+     * <p>In the case of conjuncts or combining marks, the total
+     * advance is assigned to the first logical character, and the
+     * following characters are assigned an advance of 0.
+     *
+     * <p>This generates the sum of the advances of glyphs for
+     * characters in a reordered cluster as the width of the first
+     * logical character in the cluster, and 0 for the widths of all
+     * other characters in the cluster.  In effect, such clusters are
+     * treated like conjuncts.
+     *
+     * <p>The shaping bounds limit the amount of context available
+     * outside start and end that can be used for shaping analysis.
+     * These bounds typically reflect changes in bidi level or font
+     * metrics across which shaping does not occur.
+     *
+     * @param text the text to measure. Cannot be null.
+     * @param start the index of the first character to measure
+     * @param end the index past the last character to measure
+     * @param contextStart the index of the first character to use for shaping context,
+     * must be <= start
+     * @param contextEnd the index past the last character to use for shaping context,
+     * must be >= end
+     * @param flags the flags to control the advances, either {@link #DIRECTION_LTR}
+     * or {@link #DIRECTION_RTL}
+     * @param advances array to receive the advances, must have room for all advances,
+     * can be null if only total advance is needed
+     * @param advancesIndex the position in advances at which to put the
+     * advance corresponding to the character at start
+     * @param reserved int reserved value
+     * @return the total advance
+     *
+     * @hide
+     */
+    public float getTextRunAdvances(String text, int start, int end, int contextStart,
+            int contextEnd, int flags, float[] advances, int advancesIndex, int reserved) {
 
         if (text == null) {
             throw new IllegalArgumentException("text cannot be null");
         }
+        if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
+            throw new IllegalArgumentException("unknown flags value: " + flags);
+        }
         if ((start | end | contextStart | contextEnd | advancesIndex | (end - start)
                 | (start - contextStart) | (contextEnd - end)
                 | (text.length() - contextEnd)
@@ -1723,13 +1946,13 @@
 
         if (!mHasCompatScaling) {
             return native_getTextRunAdvances(mNativePaint, text, start, end,
-                    contextStart, contextEnd, advances, advancesIndex);
+                    contextStart, contextEnd, flags, advances, advancesIndex, reserved);
         }
 
         final float oldSize = getTextSize();
         setTextSize(oldSize * mCompatScaling);
         float totalAdvance = native_getTextRunAdvances(mNativePaint, text, start, end,
-                contextStart, contextEnd, advances, advancesIndex);
+                contextStart, contextEnd, flags, advances, advancesIndex, reserved);
         setTextSize(oldSize);
 
         if (advances != null) {
@@ -1758,6 +1981,7 @@
      * @param text the text
      * @param contextStart the start of the context
      * @param contextLength the length of the context
+     * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
      * @param offset the cursor position to move from
      * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
      * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
@@ -1766,7 +1990,7 @@
      * @hide
      */
     public int getTextRunCursor(char[] text, int contextStart, int contextLength,
-            int offset, int cursorOpt) {
+            int flags, int offset, int cursorOpt) {
         int contextEnd = contextStart + contextLength;
         if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
                 | (offset - contextStart) | (contextEnd - offset)
@@ -1776,7 +2000,7 @@
         }
 
         return native_getTextRunCursor(mNativePaint, text,
-                contextStart, contextLength, offset, cursorOpt);
+                contextStart, contextLength, flags, offset, cursorOpt);
     }
 
     /**
@@ -1797,6 +2021,7 @@
      * @param text the text
      * @param contextStart the start of the context
      * @param contextEnd the end of the context
+     * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
      * @param offset the cursor position to move from
      * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
      * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
@@ -1805,22 +2030,22 @@
      * @hide
      */
     public int getTextRunCursor(CharSequence text, int contextStart,
-           int contextEnd, int offset, int cursorOpt) {
+           int contextEnd, int flags, int offset, int cursorOpt) {
 
         if (text instanceof String || text instanceof SpannedString ||
                 text instanceof SpannableString) {
             return getTextRunCursor(text.toString(), contextStart, contextEnd,
-                    offset, cursorOpt);
+                    flags, offset, cursorOpt);
         }
         if (text instanceof GraphicsOperations) {
             return ((GraphicsOperations) text).getTextRunCursor(
-                    contextStart, contextEnd, offset, cursorOpt, this);
+                    contextStart, contextEnd, flags, offset, cursorOpt, this);
         }
 
         int contextLen = contextEnd - contextStart;
         char[] buf = TemporaryBuffer.obtain(contextLen);
         TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
-        int result = getTextRunCursor(buf, 0, contextLen, offset - contextStart, cursorOpt);
+        int result = getTextRunCursor(buf, 0, contextLen, flags, offset - contextStart, cursorOpt);
         TemporaryBuffer.recycle(buf);
         return result;
     }
@@ -1843,6 +2068,7 @@
      * @param text the text
      * @param contextStart the start of the context
      * @param contextEnd the end of the context
+     * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR}
      * @param offset the cursor position to move from
      * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER},
      * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE},
@@ -1851,7 +2077,7 @@
      * @hide
      */
     public int getTextRunCursor(String text, int contextStart, int contextEnd,
-            int offset, int cursorOpt) {
+            int flags, int offset, int cursorOpt) {
         if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
                 | (offset - contextStart) | (contextEnd - offset)
                 | (text.length() - contextEnd) | cursorOpt) < 0)
@@ -1860,7 +2086,7 @@
         }
 
         return native_getTextRunCursor(mNativePaint, text,
-                contextStart, contextEnd, offset, cursorOpt);
+                contextStart, contextEnd, flags, offset, cursorOpt);
     }
 
     /**
@@ -1881,7 +2107,7 @@
         if ((index | count) < 0 || index + count > text.length) {
             throw new ArrayIndexOutOfBoundsException();
         }
-        native_getTextPath(mNativePaint, text, index, count, x, y,
+        native_getTextPath(mNativePaint, mBidiFlags, text, index, count, x, y, 
                 path.ni());
     }
 
@@ -1903,7 +2129,7 @@
         if ((start | end | (end - start) | (text.length() - end)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        native_getTextPath(mNativePaint, text, start, end, x, y,
+        native_getTextPath(mNativePaint, mBidiFlags, text, start, end, x, y, 
                 path.ni());
     }
     
@@ -1995,22 +2221,26 @@
     private static native int native_getTextWidths(int native_object,
                             String text, int start, int end, float[] widths);
 
+    private static native int native_getTextGlyphs(int native_object,
+            String text, int start, int end, int contextStart, int contextEnd,
+            int flags, char[] glyphs);
+
     private static native float native_getTextRunAdvances(int native_object,
             char[] text, int index, int count, int contextIndex, int contextCount,
-            float[] advances, int advancesIndex);
+            int flags, float[] advances, int advancesIndex, int reserved);
     private static native float native_getTextRunAdvances(int native_object,
             String text, int start, int end, int contextStart, int contextEnd,
-            float[] advances, int advancesIndex);
+            int flags, float[] advances, int advancesIndex, int reserved);
 
     private native int native_getTextRunCursor(int native_object, char[] text,
-            int contextStart, int contextLength, int offset, int cursorOpt);
+            int contextStart, int contextLength, int flags, int offset, int cursorOpt);
     private native int native_getTextRunCursor(int native_object, String text,
-            int contextStart, int contextEnd, int offset, int cursorOpt);
+            int contextStart, int contextEnd, int flags, int offset, int cursorOpt);
 
-    private static native void native_getTextPath(int native_object, char[] text,
-            int index, int count, float x, float y, int path);
-    private static native void native_getTextPath(int native_object, String text,
-            int start, int end, float x, float y, int path);
+    private static native void native_getTextPath(int native_object, int bidiFlags,
+                char[] text, int index, int count, float x, float y, int path);
+    private static native void native_getTextPath(int native_object, int bidiFlags,
+                String text, int start, int end, float x, float y, int path);
     private static native void nativeGetStringBounds(int nativePaint,
                                 String text, int start, int end, Rect bounds);
     private static native void nativeGetCharArrayBounds(int nativePaint,
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 14b476f..9988bb8 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -1213,6 +1213,7 @@
             break;
         }
         mLocalBounds.set(x, mY + metrics.fTop, x + length, mY + metrics.fBottom);
+        memset(&mPrecacheTransform.data[0], 0xff, 16 * sizeof(float));
     }
 
     /*
@@ -1223,9 +1224,11 @@
     virtual void onDrawOpDeferred(OpenGLRenderer& renderer) {
         SkPaint* paint = getPaint(renderer);
         FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(paint);
-        const bool pureTranslate = state.mMatrix.isPureTranslate();
-        const mat4 transform = renderer.findBestFontTransform(state.mMatrix);
-        fontRenderer.precache(paint, mText, mCount, transform);
+        const mat4& transform = renderer.findBestFontTransform(state.mMatrix);
+        if (mPrecacheTransform != transform) {
+            fontRenderer.precache(paint, mText, mCount, transform);
+            mPrecacheTransform = transform;
+        }
     }
 
     virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, int level,
@@ -1254,6 +1257,7 @@
     float mY;
     const float* mPositions;
     float mLength;
+    mat4 mPrecacheTransform;
 };
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index 7b7357e..75e280c 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -93,6 +93,14 @@
         return *this;
     }
 
+    friend bool operator==(const Matrix4& a, const Matrix4& b) {
+        return !memcmp(&a.data[0], &b.data[0], 16 * sizeof(float));
+    }
+
+    friend bool operator!=(const Matrix4& a, const Matrix4& b) {
+        return !(a == b);
+    }
+
     void loadIdentity();
 
     void load(const float* v);
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index afdc2c9..fb687cd 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -55,6 +55,8 @@
         PathCache::drawPath(t->path, t->paint, *bitmap, left, top, offset, width, height);
         t->setResult(bitmap);
     } else {
+        texture->width = 0;
+        texture->height = 0;
         t->setResult(NULL);
     }
 }
@@ -136,8 +138,7 @@
                 addTexture(entry, bitmap, texture);
                 texture->clearTask();
             } else {
-                ALOGW("Path too large to be rendered into a texture (%dx%d)",
-                        texture->width, texture->height);
+                ALOGW("Path too large to be rendered into a texture");
                 texture->clearTask();
                 texture = NULL;
                 mCache.remove(entry);
diff --git a/libs/hwui/ShapeCache.h b/libs/hwui/ShapeCache.h
index 58fea08..92314b0 100644
--- a/libs/hwui/ShapeCache.h
+++ b/libs/hwui/ShapeCache.h
@@ -717,7 +717,9 @@
             ALOGD("Shape %s deleted, size = %d", mName, size);
         }
 
-        glDeleteTextures(1, &texture->id);
+        if (texture->id) {
+            glDeleteTextures(1, &texture->id);
+        }
         delete texture;
     }
 }
diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp
index c932087..02c1aa1 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -15,10 +15,12 @@
  */
 
 #define LOG_TAG "OpenGLRenderer"
+#define ATRACE_TAG ATRACE_TAG_VIEW
 
 #include <cutils/compiler.h>
 
 #include <utils/JenkinsHash.h>
+#include <utils/Trace.h>
 
 #include <SkGlyph.h>
 #include <SkUtils.h>
@@ -261,11 +263,8 @@
 }
 
 CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit, bool precaching) {
-    CachedGlyphInfo* cachedGlyph = NULL;
-    ssize_t index = mCachedGlyphs.indexOfKey(textUnit);
-    if (index >= 0) {
-        cachedGlyph = mCachedGlyphs.valueAt(index);
-
+    CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueFor(textUnit);
+    if (cachedGlyph) {
         // Is the glyph still in texture cache?
         if (!cachedGlyph->mIsValid) {
             const SkGlyph& skiaGlyph = GET_METRICS(paint, textUnit,
@@ -346,11 +345,13 @@
 }
 
 void Font::precache(SkPaint* paint, const char* text, int numGlyphs) {
+    ATRACE_NAME("precacheText");
+
     if (numGlyphs == 0 || text == NULL) {
         return;
     }
-    int glyphsCount = 0;
 
+    int glyphsCount = 0;
     while (glyphsCount < numGlyphs) {
         glyph_t glyph = GET_GLYPH(text);
 
@@ -360,7 +361,6 @@
         }
 
         CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph, true);
-
         glyphsCount++;
     }
 }
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 312c252..efa8089 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -120,14 +120,14 @@
     oneway void dispatchMediaKeyEvent(in KeyEvent keyEvent);
     void dispatchMediaKeyEventUnderWakelock(in KeyEvent keyEvent);
 
-    oneway void registerMediaButtonIntent(in PendingIntent pi, in ComponentName c);
+           void registerMediaButtonIntent(in PendingIntent pi, in ComponentName c);
     oneway void unregisterMediaButtonIntent(in PendingIntent pi,  in ComponentName c);
 
     oneway void registerMediaButtonEventReceiverForCalls(in ComponentName c);
     oneway void unregisterMediaButtonEventReceiverForCalls();
 
-    int registerRemoteControlClient(in PendingIntent mediaIntent,
-           in IRemoteControlClient rcClient, in String callingPackageName);
+           int registerRemoteControlClient(in PendingIntent mediaIntent,
+               in IRemoteControlClient rcClient, in String callingPackageName);
     oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent,
            in IRemoteControlClient rcClient);
 
diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java
index dfd0e94..c3cc1e6 100644
--- a/media/java/android/media/MediaMuxer.java
+++ b/media/java/android/media/MediaMuxer.java
@@ -276,7 +276,7 @@
      */
     public void release() {
         if (mState == MUXER_STATE_STARTED) {
-            throw new IllegalStateException("Can't release when muxer is started");
+            stop();
         }
         if (mNativeObject != 0) {
             nativeRelease(mNativeObject);
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index 4ebbbde..9888591 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -327,7 +327,7 @@
         }
 
         ALOGI("prepare: surface=%p", native_surface.get());
-        if (process_media_recorder_call(env, mr->setPreviewSurface(native_surface), "java/lang/RuntimeException", "setPreviewSurface failed.")) {
+        if (process_media_recorder_call(env, mr->setPreviewSurface(native_surface->getIGraphicBufferProducer()), "java/lang/RuntimeException", "setPreviewSurface failed.")) {
             return;
         }
     }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaRecorderStressTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaRecorderStressTestRunner.java
index 5c74552..fa60ca6 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaRecorderStressTestRunner.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaRecorderStressTestRunner.java
@@ -43,7 +43,7 @@
     public static int mBitRate = profile.videoBitRate;
     public static boolean mRemoveVideo = true;
     public static int mDuration = 60 * 1000; // 60 seconds
-    public static int mTimeLapseDuration = 180 * 1000; // 3 minutes
+    public static int mTimeLapseDuration = 15 * 60 * 1000; // 15 minutes
     public static double mCaptureRate = 0.5; // 2 sec timelapse interval
 
     @Override
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java
index 6eb9891..199f179 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaRecorderStressTest.java
@@ -50,16 +50,10 @@
     private Camera mCamera;
 
     private static final int CAMERA_ID = 0;
-    private static final int NUMBER_OF_CAMERA_STRESS_LOOPS = 100;
-    private static final int NUMBER_OF_RECORDER_STRESS_LOOPS = 100;
-    private static final int NUMBER_OF_RECORDERANDPLAY_STRESS_LOOPS = 50;
-    private static final int NUMBER_OF_SWTICHING_LOOPS_BW_CAMERA_AND_RECORDER = 200;
-    private static final int NUMBER_OF_TIME_LAPSE_LOOPS = 25;
-    private static final int TIME_LAPSE_PLAYBACK_WAIT_TIME = 5* 1000; // 5 seconds
+    private static final int NUMBER_OF_TIME_LAPSE_LOOPS = 1;
+    private static final int TIME_LAPSE_PLAYBACK_WAIT_TIME = 30 * 1000; // 30 seconds
     private static final int USE_TEST_RUNNER_PROFILE = -1;
     private static final long WAIT_TIMEOUT = 10 * 1000; // 10 seconds
-    private static final long WAIT_TIME_CAMERA_TEST = 3 * 1000; // 3 seconds
-    private static final long WAIT_TIME_RECORDER_TEST = 6 * 1000; // 6 seconds
     private static final String OUTPUT_FILE_EXT = ".3gp";
     private static final String MEDIA_STRESS_OUTPUT = "mediaStressOutput.txt";
 
@@ -150,158 +144,6 @@
         }
     }
 
-    //Test case for stressing the camera preview.
-    @LargeTest
-    public void testStressCamera() throws Exception {
-        SurfaceHolder mSurfaceHolder;
-        mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
-        Log.v(TAG, "Camera start preview stress test");
-        mOutput.write("Total number of loops:" + NUMBER_OF_CAMERA_STRESS_LOOPS + "\n");
-        try {
-            Log.v(TAG, "Start preview");
-            mOutput.write("No of loop: ");
-
-            for (int i = 0; i< NUMBER_OF_CAMERA_STRESS_LOOPS; i++) {
-                runOnLooper(new Runnable() {
-                    @Override
-                    public void run() {
-                        mCamera = Camera.open(CAMERA_ID);
-                    }
-                });
-                mCamera.setErrorCallback(mCameraErrorCallback);
-                mCamera.setPreviewDisplay(mSurfaceHolder);
-                mCamera.startPreview();
-                Thread.sleep(WAIT_TIME_CAMERA_TEST);
-                mCamera.stopPreview();
-                mCamera.release();
-                if (i == 0) {
-                    mOutput.write(i + 1);
-                } else {
-                    mOutput.write(String.format(", %d", (i + 1)));
-                }
-            }
-        } catch (Exception e) {
-            Log.e(TAG, e.toString());
-            fail("Camera startup preview stress test");
-        }
-    }
-
-    //Test case for stressing the camera preview.
-    @LargeTest
-    public void testStressRecorder() throws Exception {
-        SurfaceHolder mSurfaceHolder;
-        mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
-        Log.v(TAG, "H263 video record: reset after prepare Stress test");
-        mOutput.write("Total number of loops:" + NUMBER_OF_RECORDER_STRESS_LOOPS + "\n");
-        try {
-            mOutput.write("No of loop: ");
-            Log.v(TAG, "Start preview");
-            for (int i = 0; i < NUMBER_OF_RECORDER_STRESS_LOOPS; i++) {
-                runOnLooper(new Runnable() {
-                    @Override
-                    public void run() {
-                        mRecorder = new MediaRecorder();
-                    }
-                });
-                Log.v(TAG, "counter = " + i);
-                String fileName = String.format("%s/temp%d%s",
-                        Environment.getExternalStorageDirectory(),
-                        i, OUTPUT_FILE_EXT);
-
-                Log.v(TAG, fileName);
-                mRecorder.setOnErrorListener(mRecorderErrorCallback);
-                mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
-                mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
-                mRecorder.setOutputFile(fileName);
-                mRecorder.setVideoFrameRate(MediaRecorderStressTestRunner.mFrameRate);
-                mRecorder.setVideoSize(176,144);
-                Log.v(TAG, "setEncoder");
-                mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H263);
-                mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
-                Log.v(TAG, "setPreview");
-                mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
-                Log.v(TAG, "prepare");
-                mRecorder.prepare();
-                Log.v(TAG, "before release");
-                Thread.sleep(WAIT_TIME_RECORDER_TEST);
-                mRecorder.reset();
-                mRecorder.release();
-                if (i == 0) {
-                    mOutput.write(i + 1);
-                } else {
-                    mOutput.write(String.format(", %d", (i + 1)));
-                }
-            }
-        } catch (Exception e) {
-            Log.e(TAG, e.toString());
-            fail("H263 video recording stress test");
-        }
-    }
-
-    //Stress test case for switching camera and video recorder preview.
-    @LargeTest
-    public void testStressCameraSwitchRecorder() throws Exception {
-        SurfaceHolder mSurfaceHolder;
-        mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
-        Log.v(TAG, "Camera and video recorder preview switching");
-        mOutput.write("Total number of loops: " +
-                NUMBER_OF_SWTICHING_LOOPS_BW_CAMERA_AND_RECORDER + "\n");
-        try {
-            Log.v(TAG, "Start preview");
-            mOutput.write("No of loop: ");
-            for (int i = 0; i < NUMBER_OF_SWTICHING_LOOPS_BW_CAMERA_AND_RECORDER; i++) {
-                runOnLooper(new Runnable() {
-                    @Override
-                    public void run() {
-                        mCamera = Camera.open(CAMERA_ID);
-                    }
-                });
-                mCamera.setErrorCallback(mCameraErrorCallback);
-                mCamera.setPreviewDisplay(mSurfaceHolder);
-                mCamera.startPreview();
-                Thread.sleep(WAIT_TIME_CAMERA_TEST);
-                mCamera.stopPreview();
-                mCamera.release();
-                mCamera = null;
-                Log.v(TAG, "release camera");
-                String fileName = String.format("%s/temp%d%s",
-                        Environment.getExternalStorageDirectory(),
-                        i, OUTPUT_FILE_EXT);
-                Log.v(TAG, fileName);
-                runOnLooper(new Runnable() {
-                    @Override
-                    public void run() {
-                        mRecorder = new MediaRecorder();
-                    }
-                });
-                mRecorder.setOnErrorListener(mRecorderErrorCallback);
-                mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
-                mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
-                mRecorder.setOutputFile(fileName);
-                mRecorder.setVideoFrameRate(MediaRecorderStressTestRunner.mFrameRate);
-                mRecorder.setVideoSize(176,144);
-                Log.v(TAG, "Media recorder setEncoder");
-                mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H263);
-                Log.v(TAG, "mediaRecorder setPreview");
-                mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
-                Log.v(TAG, "prepare");
-                mRecorder.prepare();
-                Log.v(TAG, "before release");
-                Thread.sleep(WAIT_TIME_CAMERA_TEST);
-                mRecorder.release();
-                Log.v(TAG, "release video recorder");
-                if (i == 0) {
-                    mOutput.write(i + 1);
-                } else {
-                    mOutput.write(String.format(", %d", (i + 1)));
-                }
-            }
-        } catch (Exception e) {
-            Log.e(TAG, e.toString());
-            fail("Camera and recorder switch mode");
-        }
-    }
-
     public void validateRecordedVideo(String recordedFile) {
         try {
             MediaPlayer mp = new MediaPlayer();
diff --git a/packages/Shell/Android.mk b/packages/Shell/Android.mk
index f993ab5..fc4c0f5 100644
--- a/packages/Shell/Android.mk
+++ b/packages/Shell/Android.mk
@@ -5,6 +5,8 @@
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+
 LOCAL_PACKAGE_NAME := Shell
 LOCAL_CERTIFICATE := platform
 
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b42db45..ffb4c20 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -68,7 +68,32 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.MANAGE_USERS" />
     <uses-permission android:name="android.permission.BLUETOOTH_STACK" />
-    
-    <application android:hasCode="false" android:label="@string/app_label">
+    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+
+    <application android:label="@string/app_label">
+        <provider
+            android:name="android.support.v4.content.FileProvider"
+            android:authorities="com.android.shell"
+            android:grantUriPermissions="true"
+            android:exported="false">
+            <meta-data
+                android:name="android.support.FILE_PROVIDER_PATHS"
+                android:resource="@xml/file_provider_paths" />
+        </provider>
+
+        <activity
+            android:name=".BugreportWarningActivity"
+            android:theme="@*android:style/Theme.Holo.Dialog.Alert"
+            android:finishOnCloseSystemDialogs="true"
+            android:excludeFromRecents="true"
+            android:exported="false" />
+
+        <receiver
+            android:name=".BugreportReceiver"
+            android:permission="android.permission.DUMP">
+            <intent-filter>
+                <action android:name="android.intent.action.BUGREPORT_FINISHED" />
+            </intent-filter>
+        </receiver>
     </application>
 </manifest>
diff --git a/packages/Shell/res/layout/confirm_repeat.xml b/packages/Shell/res/layout/confirm_repeat.xml
new file mode 100644
index 0000000..dc250d6
--- /dev/null
+++ b/packages/Shell/res/layout/confirm_repeat.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingStart="16dip"
+    android:paddingEnd="16dip"
+    android:paddingTop="8dip"
+    android:paddingBottom="16dip"
+    android:orientation="vertical">
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/bugreport_confirm"
+        android:paddingBottom="16dip"
+        style="?android:attr/textAppearanceMedium" />
+    <CheckBox
+        android:id="@android:id/checkbox"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/bugreport_confirm_repeat" />
+</LinearLayout>
diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml
index 50610d5..e5606c7 100644
--- a/packages/Shell/res/values/strings.xml
+++ b/packages/Shell/res/values/strings.xml
@@ -16,4 +16,14 @@
 
 <resources>
     <string name="app_label">Shell</string>
+
+    <!-- Title of notification indicating a bugreport has been successfully captured. [CHAR LIMIT=50] -->
+    <string name="bugreport_finished_title">Bug report captured</string>
+    <!-- Text of notification indicating that touching will share the captured bugreport. [CHAR LIMIT=100] -->
+    <string name="bugreport_finished_text">Touch to share your bug report</string>
+
+    <!-- Body of dialog informing user about contents of a bugreport. [CHAR LIMIT=NONE] -->
+    <string name="bugreport_confirm">Bug reports contain data from the system\'s various log files, including personal and private information.  Only share bug reports with apps and people you trust.</string>
+    <!-- Checkbox that indicates this dialog should be shown again when the next bugreport is taken. [CHAR LIMIT=50] -->
+    <string name="bugreport_confirm_repeat">Show this message next time</string>
 </resources>
diff --git a/packages/Shell/res/xml/file_provider_paths.xml b/packages/Shell/res/xml/file_provider_paths.xml
new file mode 100644
index 0000000..225c757
--- /dev/null
+++ b/packages/Shell/res/xml/file_provider_paths.xml
@@ -0,0 +1,3 @@
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+    <files-path name="bugreports" path="bugreports/" />
+</paths>
diff --git a/packages/Shell/src/com/android/shell/BugreportPrefs.java b/packages/Shell/src/com/android/shell/BugreportPrefs.java
new file mode 100644
index 0000000..3748e89
--- /dev/null
+++ b/packages/Shell/src/com/android/shell/BugreportPrefs.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.shell;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+/**
+ * Preferences related to bug reports.
+ */
+public class BugreportPrefs {
+    private static final String PREFS_BUGREPORT = "bugreports";
+
+    private static final String KEY_WARNING_STATE = "warning-state";
+
+    public static final int STATE_UNKNOWN = 0;
+    public static final int STATE_SHOW = 1;
+    public static final int STATE_HIDE = 2;
+
+    public static int getWarningState(Context context, int def) {
+        final SharedPreferences prefs = context.getSharedPreferences(
+                PREFS_BUGREPORT, Context.MODE_PRIVATE);
+        return prefs.getInt(KEY_WARNING_STATE, def);
+    }
+
+    public static void setWarningState(Context context, int value) {
+        final SharedPreferences prefs = context.getSharedPreferences(
+                PREFS_BUGREPORT, Context.MODE_PRIVATE);
+        prefs.edit().putInt(KEY_WARNING_STATE, value).apply();
+    }
+}
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
new file mode 100644
index 0000000..de04909
--- /dev/null
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.shell;
+
+import static com.android.shell.BugreportPrefs.STATE_SHOW;
+import static com.android.shell.BugreportPrefs.getWarningState;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.FileUtils;
+import android.os.SystemProperties;
+import android.support.v4.content.FileProvider;
+import android.text.format.DateUtils;
+import android.util.Patterns;
+
+import com.google.android.collect.Lists;
+
+import java.io.File;
+import java.util.ArrayList;
+
+/**
+ * Receiver that handles finished bugreports, usually by attaching them to an
+ * {@link Intent#ACTION_SEND}.
+ */
+public class BugreportReceiver extends BroadcastReceiver {
+    private static final String TAG = "Shell";
+
+    private static final String AUTHORITY = "com.android.shell";
+
+    private static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
+    private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
+
+    /**
+     * Always keep the newest 8 bugreport files; 4 reports and 4 screenshots are
+     * roughly 17MB of disk space.
+     */
+    private static final int MIN_KEEP_COUNT = 8;
+
+    /**
+     * Always keep bugreports taken in the last week.
+     */
+    private static final long MIN_KEEP_AGE = DateUtils.WEEK_IN_MILLIS;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
+        final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
+
+        // Files are kept on private storage, so turn into Uris that we can
+        // grant temporary permissions for.
+        final Uri bugreportUri = FileProvider.getUriForFile(context, AUTHORITY, bugreportFile);
+        final Uri screenshotUri = FileProvider.getUriForFile(context, AUTHORITY, screenshotFile);
+
+        Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri);
+        Intent notifIntent;
+
+        // Send through warning dialog by default
+        if (getWarningState(context, STATE_SHOW) == STATE_SHOW) {
+            notifIntent = buildWarningIntent(context, sendIntent);
+        } else {
+            notifIntent = sendIntent;
+        }
+        notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        final Notification.Builder builder = new Notification.Builder(context);
+        builder.setSmallIcon(com.android.internal.R.drawable.stat_sys_adb);
+        builder.setContentTitle(context.getString(R.string.bugreport_finished_title));
+        builder.setContentText(context.getString(R.string.bugreport_finished_text));
+        builder.setContentIntent(PendingIntent.getActivity(
+                context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT));
+        builder.setAutoCancel(true);
+        NotificationManager.from(context).notify(TAG, 0, builder.build());
+
+        // Clean up older bugreports in background
+        final PendingResult result = goAsync();
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                FileUtils.deleteOlderFiles(
+                        bugreportFile.getParentFile(), MIN_KEEP_COUNT, MIN_KEEP_AGE);
+                result.finish();
+                return null;
+            }
+        }.execute();
+    }
+
+    private static Intent buildWarningIntent(Context context, Intent sendIntent) {
+        final Intent intent = new Intent(context, BugreportWarningActivity.class);
+        intent.putExtra(Intent.EXTRA_INTENT, sendIntent);
+        return intent;
+    }
+
+    /**
+     * Build {@link Intent} that can be used to share the given bugreport.
+     */
+    private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) {
+        final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
+        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        intent.setType("application/vnd.android.bugreport");
+
+        intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment());
+        intent.putExtra(Intent.EXTRA_TEXT, SystemProperties.get("ro.build.description"));
+
+        final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri, screenshotUri);
+        intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
+
+        final Account sendToAccount = findSendToAccount(context);
+        if (sendToAccount != null) {
+            intent.putExtra(Intent.EXTRA_EMAIL, new String[] { sendToAccount.name });
+        }
+
+        return intent;
+    }
+
+    /**
+     * Find the best matching {@link Account} based on build properties.
+     */
+    private static Account findSendToAccount(Context context) {
+        final AccountManager am = (AccountManager) context.getSystemService(
+                Context.ACCOUNT_SERVICE);
+
+        String preferredDomain = SystemProperties.get("sendbug.preferred.domain");
+        if (!preferredDomain.startsWith("@")) {
+            preferredDomain = "@" + preferredDomain;
+        }
+
+        final Account[] accounts = am.getAccounts();
+        Account foundAccount = null;
+        for (Account account : accounts) {
+            if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
+                if (!preferredDomain.isEmpty()) {
+                    // if we have a preferred domain and it matches, return; otherwise keep
+                    // looking
+                    if (account.name.endsWith(preferredDomain)) {
+                        return account;
+                    } else {
+                        foundAccount = account;
+                    }
+                    // if we don't have a preferred domain, just return since it looks like
+                    // an email address
+                } else {
+                    return account;
+                }
+            }
+        }
+        return foundAccount;
+    }
+
+    private static File getFileExtra(Intent intent, String key) {
+        final String path = intent.getStringExtra(key);
+        if (path != null) {
+            return new File(path);
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/packages/Shell/src/com/android/shell/BugreportWarningActivity.java b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
new file mode 100644
index 0000000..a1d879a
--- /dev/null
+++ b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.shell;
+
+import static com.android.shell.BugreportPrefs.STATE_HIDE;
+import static com.android.shell.BugreportPrefs.STATE_SHOW;
+import static com.android.shell.BugreportPrefs.STATE_UNKNOWN;
+import static com.android.shell.BugreportPrefs.getWarningState;
+import static com.android.shell.BugreportPrefs.setWarningState;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.widget.CheckBox;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+/**
+ * Dialog that warns about contents of a bugreport.
+ */
+public class BugreportWarningActivity extends AlertActivity
+        implements DialogInterface.OnClickListener {
+
+    private Intent mSendIntent;
+    private CheckBox mConfirmRepeat;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        mSendIntent = getIntent().getParcelableExtra(Intent.EXTRA_INTENT);
+
+        // We need to touch the extras to unpack them so they get migrated to
+        // ClipData correctly.
+        mSendIntent.hasExtra(Intent.EXTRA_STREAM);
+
+        final AlertController.AlertParams ap = mAlertParams;
+        ap.mView = LayoutInflater.from(this).inflate(R.layout.confirm_repeat, null);
+        ap.mPositiveButtonText = getString(android.R.string.ok);
+        ap.mNegativeButtonText = getString(android.R.string.cancel);
+        ap.mPositiveButtonListener = this;
+        ap.mNegativeButtonListener = this;
+
+        mConfirmRepeat = (CheckBox) ap.mView.findViewById(android.R.id.checkbox);
+        mConfirmRepeat.setChecked(getWarningState(this, STATE_UNKNOWN) == STATE_SHOW);
+
+        setupAlert();
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        if (which == AlertDialog.BUTTON_POSITIVE) {
+            // Remember confirm state, and launch target
+            setWarningState(this, mConfirmRepeat.isChecked() ? STATE_SHOW : STATE_HIDE);
+            startActivity(mSendIntent);
+        }
+
+        finish();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index f3eecf2..627235f 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -627,10 +627,26 @@
             }
             
             mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
+            if (mEglContext == EGL_NO_CONTEXT) {
+                throw new RuntimeException("createContext failed " +
+                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
+            }
+
+            int attribs[] = {
+                EGL_WIDTH, 1,
+                EGL_HEIGHT, 1,
+                EGL_NONE
+            };
+            EGLSurface tmpSurface = mEgl.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs);
+            mEgl.eglMakeCurrent(mEglDisplay, tmpSurface, tmpSurface, mEglContext);
 
             int[] maxSize = new int[1];
             Rect frame = surfaceHolder.getSurfaceFrame();
             glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxSize, 0);
+
+            mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+            mEgl.eglDestroySurface(mEglDisplay, tmpSurface);
+
             if(frame.width() > maxSize[0] || frame.height() > maxSize[0]) {
                 mEgl.eglDestroyContext(mEglDisplay, mEglContext);
                 mEgl.eglTerminate(mEglDisplay);
@@ -639,9 +655,8 @@
                     maxSize[0] + "x" + maxSize[0]);
                 return false;
             }
-    
+
             mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null);
-    
             if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
                 int error = mEgl.eglGetError();
                 if (error == EGL_BAD_NATIVE_WINDOW || error == EGL_BAD_ALLOC) {
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/EmergencyButton.java b/policy/src/com/android/internal/policy/impl/keyguard/EmergencyButton.java
index cd7324c..c68bab5 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/EmergencyButton.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/EmergencyButton.java
@@ -20,6 +20,7 @@
 import android.content.Intent;
 import android.os.PowerManager;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.telephony.TelephonyManager;
 import android.util.AttributeSet;
 import android.view.View;
@@ -104,7 +105,8 @@
             Intent intent = new Intent(ACTION_EMERGENCY_DIAL);
             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                     | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            getContext().startActivity(intent);
+            getContext().startActivityAsUser(intent,
+                    new UserHandle(mLockPatternUtils.getCurrentUser()));
         }
     }
 
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
index 06f06b5..78d7caa 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
@@ -310,9 +310,7 @@
         mKeyguardSelectorView = (KeyguardSelectorView) findViewById(R.id.keyguard_selector_view);
         mViewStateManager.setSecurityViewContainer(mSecurityViewContainer);
 
-        if (!(mContext instanceof Activity)) {
-            setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_BACK);
-        }
+        setBackButtonEnabled(false);
 
         addDefaultWidgets();
 
@@ -329,6 +327,13 @@
         updateSecurityViews();
     }
 
+    private void setBackButtonEnabled(boolean enabled) {
+        if (mContext instanceof Activity) return;  // always enabled in activity mode
+        setSystemUiVisibility(enabled ?
+                getSystemUiVisibility() & ~View.STATUS_BAR_DISABLE_BACK :
+                getSystemUiVisibility() | View.STATUS_BAR_DISABLE_BACK);
+    }
+
     private boolean shouldEnableAddWidget() {
         return numWidgets() < MAX_WIDGETS && mUserSetupCompleted;
     }
@@ -907,6 +912,10 @@
             // Discard current runnable if we're switching back to the selector view
             setOnDismissAction(null);
         }
+        if (securityMode == SecurityMode.Account && !mLockPatternUtils.isPermanentlyLocked()) {
+            // we're showing account as a backup, provide a way to get back to primary
+            setBackButtonEnabled(true);
+        }
         mCurrentSecuritySelection = securityMode;
     }
 
@@ -1579,6 +1588,12 @@
     }
 
     public boolean handleBackKey() {
+        if (mCurrentSecuritySelection == SecurityMode.Account) {
+            // go back to primary screen and re-disable back
+            setBackButtonEnabled(false);
+            showPrimarySecurityScreen(false /*turningOff*/);
+            return true;
+        }
         if (mCurrentSecuritySelection != SecurityMode.None) {
             mCallback.dismiss(false);
             return true;
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
index 8562f0c..30c95fb 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
@@ -200,6 +200,9 @@
                     stretch, stretch, type, flags, PixelFormat.TRANSLUCENT);
             lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
             lp.windowAnimations = com.android.internal.R.style.Animation_LockScreen;
+            lp.screenOrientation = enableScreenRotation ?
+                    ActivityInfo.SCREEN_ORIENTATION_USER : ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+
             if (ActivityManager.isHighEndGfx()) {
                 lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
                 lp.privateFlags |=
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index c83a919..0b3df35 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -1430,11 +1430,11 @@
 
     private boolean modifyRouteToAddress(LinkProperties lp, InetAddress addr, boolean doAdd,
             boolean toDefaultTable) {
-        String iface = lp.getInterfaceName();
-        RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getRoutes(), addr);
+        RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), addr);
         if (bestRoute == null) {
-            bestRoute = RouteInfo.makeHostRoute(addr, iface);
+            bestRoute = RouteInfo.makeHostRoute(addr, lp.getInterfaceName());
         } else {
+            String iface = bestRoute.getInterface();
             if (bestRoute.getGateway().equals(addr)) {
                 // if there is no better route, add the implied hostroute for our gateway
                 bestRoute = RouteInfo.makeHostRoute(addr, iface);
@@ -1449,9 +1449,8 @@
 
     private boolean modifyRoute(LinkProperties lp, RouteInfo r, int cycleCount, boolean doAdd,
             boolean toDefaultTable) {
-        String ifaceName = lp.getInterfaceName();
-        if ((ifaceName == null) || (lp == null) || (r == null)) {
-            if (DBG) log("modifyRoute got unexpected null: " + ifaceName + ", " + lp + ", " + r);
+        if ((lp == null) || (r == null)) {
+            if (DBG) log("modifyRoute got unexpected null: " + lp + ", " + r);
             return false;
         }
 
@@ -1460,8 +1459,14 @@
             return false;
         }
 
+        String ifaceName = r.getInterface();
+        if(ifaceName == null) {
+            loge("Error modifying route - no interface name");
+            return false;
+        }
+
         if (r.isHostRoute() == false) {
-            RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getRoutes(), r.getGateway());
+            RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), r.getGateway());
             if (bestRoute != null) {
                 if (bestRoute.getGateway().equals(r.getGateway())) {
                     // if there is no better route, add the implied hostroute for our gateway
@@ -2300,7 +2305,7 @@
             routeDiff = curLp.compareRoutes(newLp);
             dnsDiff = curLp.compareDnses(newLp);
         } else if (newLp != null) {
-            routeDiff.added = newLp.getRoutes();
+            routeDiff.added = newLp.getAllRoutes();
             dnsDiff.added = newLp.getDnses();
         }
 
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 01f7544..f6c9d82 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -1199,7 +1199,7 @@
             mCurId = info.getId();
             mCurToken = new Binder();
             try {
-                if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
+                if (true || DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
                 mIWindowManager.addWindowToken(mCurToken,
                         WindowManager.LayoutParams.TYPE_INPUT_METHOD);
             } catch (RemoteException e) {
@@ -1243,10 +1243,6 @@
                     unbindCurrentMethodLocked(false, false);
                     return;
                 }
-                // Remove commands relating to the previous service. Otherwise WindowManagerService
-                // will reject the command because the token attached to these messages is invalid.
-                mCaller.removeMessages(MSG_SHOW_SOFT_INPUT);
-                mCaller.removeMessages(MSG_HIDE_SOFT_INPUT);
                 if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
                 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
                         MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
@@ -1717,6 +1713,7 @@
 
         boolean res = false;
         if (mCurMethod != null) {
+            if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);
             executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
                     MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
                     resultReceiver));
@@ -1788,13 +1785,11 @@
     boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
         if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
                 && (mShowExplicitlyRequested || mShowForced)) {
-            if (DEBUG) Slog.v(TAG,
-                    "Not hiding: explicit show not cancelled by non-explicit hide");
+            if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
             return false;
         }
         if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
-            if (DEBUG) Slog.v(TAG,
-                    "Not hiding: forced show not cancelled by not-always hide");
+            if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
             return false;
         }
         boolean res;
@@ -2305,8 +2300,9 @@
             case MSG_SHOW_SOFT_INPUT:
                 args = (SomeArgs)msg.obj;
                 try {
-                    ((IInputMethod)args.arg1).showSoftInput(msg.arg1,
-                            (ResultReceiver)args.arg2);
+                    if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
+                            + msg.arg1 + ", " + args.arg2 + ")");
+                    ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2);
                 } catch (RemoteException e) {
                 }
                 args.recycle();
@@ -2314,8 +2310,9 @@
             case MSG_HIDE_SOFT_INPUT:
                 args = (SomeArgs)msg.obj;
                 try {
-                    ((IInputMethod)args.arg1).hideSoftInput(0,
-                            (ResultReceiver)args.arg2);
+                    if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
+                            + args.arg2 + ")");
+                    ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2);
                 } catch (RemoteException e) {
                 }
                 args.recycle();
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 51f001f..2d12a77 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -170,7 +170,7 @@
 public class PackageManagerService extends IPackageManager.Stub {
     static final String TAG = "PackageManager";
     static final boolean DEBUG_SETTINGS = false;
-    static final boolean DEBUG_PREFERRED = true;
+    static final boolean DEBUG_PREFERRED = false;
     static final boolean DEBUG_UPGRADE = false;
     private static final boolean DEBUG_INSTALL = false;
     private static final boolean DEBUG_REMOVE = false;
@@ -339,9 +339,20 @@
     final SparseArray<HashSet<String>> mSystemPermissions =
             new SparseArray<HashSet<String>>();
 
+    static final class SharedLibraryEntry {
+        final String path;
+        final String apk;
+
+        SharedLibraryEntry(String _path, String _apk) {
+            path = _path;
+            apk = _apk;
+        }
+    }
+
     // These are the built-in shared libraries that were read from the
     // etc/permissions.xml file.
-    final HashMap<String, String> mSharedLibraries = new HashMap<String, String>();
+    final HashMap<String, SharedLibraryEntry> mSharedLibraries
+            = new HashMap<String, SharedLibraryEntry>();
 
     // Temporary for building the final shared libraries for an .apk.
     String[] mTmpSharedLibraries = null;
@@ -390,8 +401,7 @@
     final SparseArray<PackageVerificationState> mPendingVerification
             = new SparseArray<PackageVerificationState>();
 
-    final ArrayList<PackageParser.Package> mDeferredDexOpt =
-            new ArrayList<PackageParser.Package>();
+    HashSet<PackageParser.Package> mDeferredDexOpt = null;
 
     /** Token for keys in mPendingVerification. */
     private int mPendingVerificationToken = 0;
@@ -514,10 +524,9 @@
         void doHandleMessage(Message msg) {
             switch (msg.what) {
                 case INIT_COPY: {
-                    if (DEBUG_INSTALL) Slog.i(TAG, "init_copy");
                     HandlerParams params = (HandlerParams) msg.obj;
                     int idx = mPendingInstalls.size();
-                    if (DEBUG_INSTALL) Slog.i(TAG, "idx=" + idx);
+                    if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);
                     // If a bind was already initiated we dont really
                     // need to do anything. The pending install
                     // will be processed later on.
@@ -1071,9 +1080,12 @@
              * Also ensure all external libraries have had dexopt run on them.
              */
             if (mSharedLibraries.size() > 0) {
-                Iterator<String> libs = mSharedLibraries.values().iterator();
+                Iterator<SharedLibraryEntry> libs = mSharedLibraries.values().iterator();
                 while (libs.hasNext()) {
-                    String lib = libs.next();
+                    String lib = libs.next().path;
+                    if (lib == null) {
+                        continue;
+                    }
                     try {
                         if (dalvik.system.DexFile.isDexOptNeeded(lib)) {
                             libFiles.add(lib);
@@ -1277,6 +1289,10 @@
                 mDrmAppInstallObserver = null;
             }
 
+            // Now that we know all of the shared libraries, update all clients to have
+            // the correct library paths.
+            updateAllSharedLibrariesLPw();
+
             EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
                     SystemClock.uptimeMillis());
             Slog.i(TAG, "Time to scan packages: "
@@ -1517,7 +1533,7 @@
                                 + parser.getPositionDescription());
                     } else {
                         //Log.i(TAG, "Got library " + lname + " in " + lfile);
-                        mSharedLibraries.put(lname, lfile);
+                        mSharedLibraries.put(lname, new SharedLibraryEntry(lfile, null));
                     }
                     XmlUtils.skipCurrentTag(parser);
                     continue;
@@ -3249,6 +3265,7 @@
             int parseFlags, int scanMode, long currentTime, UserHandle user) {
         mLastScanError = PackageManager.INSTALL_SUCCEEDED;
         String scanPath = scanFile.getPath();
+        if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanPath);
         parseFlags |= mDefParseFlags;
         PackageParser pp = new PackageParser(scanPath);
         pp.setSeparateProcesses(mSeparateProcesses);
@@ -3278,6 +3295,7 @@
             // package.  Must look for it either under the original or real
             // package name depending on our state.
             updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
+            if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg);
         }
         // First check if this is a system package that may involve an update
         if (updatedPkg != null && (parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
@@ -3285,6 +3303,7 @@
                 // The path has changed from what was last scanned...  check the
                 // version of the new path against what we have stored to determine
                 // what to do.
+                if (DEBUG_INSTALL) Slog.d(TAG, "Path changing from " + ps.codePath);
                 if (pkg.mVersionCode < ps.versionCode) {
                     // The system package has been updated and the code path does not match
                     // Ignore entry. Skip it.
@@ -3298,6 +3317,7 @@
                         updatedPkg.codePath = scanFile;
                         updatedPkg.codePathString = scanFile.toString();                        
                     }
+                    updatedPkg.pkg = pkg;
                     mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
                     return null;
                 } else {
@@ -3353,6 +3373,7 @@
              */
             if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures)
                     != PackageManager.SIGNATURE_MATCH) {
+                if (DEBUG_INSTALL) Slog.d(TAG, "Signature mismatch!");
                 deletePackageLI(pkg.packageName, null, true, 0, null, false);
                 ps = null;
             } else {
@@ -3484,28 +3505,28 @@
     }
 
     public void performBootDexOpt() {
-        ArrayList<PackageParser.Package> pkgs = null;
+        HashSet<PackageParser.Package> pkgs = null;
         synchronized (mPackages) {
-            if (mDeferredDexOpt.size() > 0) {
-                pkgs = new ArrayList<PackageParser.Package>(mDeferredDexOpt);
-                mDeferredDexOpt.clear();
-            }
+            pkgs = mDeferredDexOpt;
+            mDeferredDexOpt = null;
         }
         if (pkgs != null) {
-            for (int i=0; i<pkgs.size(); i++) {
+            int i = 0;
+            for (PackageParser.Package pkg : pkgs) {
                 if (!isFirstBoot()) {
+                    i++;
                     try {
                         ActivityManagerNative.getDefault().showBootMessage(
                                 mContext.getResources().getString(
                                         com.android.internal.R.string.android_upgrading_apk,
-                                        i+1, pkgs.size()), true);
+                                        i, pkgs.size()), true);
                     } catch (RemoteException e) {
                     }
                 }
-                PackageParser.Package p = pkgs.get(i);
+                PackageParser.Package p = pkg;
                 synchronized (mInstallLock) {
                     if (!p.mDidDexOpt) {
-                        performDexOptLI(p, false, false);
+                        performDexOptLI(p, false, false, true);
                     }
                 }
             }
@@ -3527,7 +3548,27 @@
             }
         }
         synchronized (mInstallLock) {
-            return performDexOptLI(p, false, false) == DEX_OPT_PERFORMED;
+            return performDexOptLI(p, false, false, true) == DEX_OPT_PERFORMED;
+        }
+    }
+
+    private void performDexOptLibsLI(ArrayList<String> libs, boolean forceDex, boolean defer,
+            HashSet<String> done) {
+        for (int i=0; i<libs.size(); i++) {
+            PackageParser.Package libPkg;
+            String libName;
+            synchronized (mPackages) {
+                libName = libs.get(i);
+                SharedLibraryEntry lib = mSharedLibraries.get(libName);
+                if (lib != null && lib.apk != null) {
+                    libPkg = mPackages.get(lib.apk);
+                } else {
+                    libPkg = null;
+                }
+            }
+            if (libPkg != null && !done.contains(libName)) {
+                performDexOptLI(libPkg, forceDex, defer, done);
+            }
         }
     }
 
@@ -3536,14 +3577,27 @@
     static final int DEX_OPT_DEFERRED = 2;
     static final int DEX_OPT_FAILED = -1;
 
-    private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer) {
+    private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer,
+            HashSet<String> done) {
         boolean performed = false;
+        if (done != null) {
+            done.add(pkg.packageName);
+            if (pkg.usesLibraries != null) {
+                performDexOptLibsLI(pkg.usesLibraries, forceDex, defer, done);
+            }
+            if (pkg.usesOptionalLibraries != null) {
+                performDexOptLibsLI(pkg.usesOptionalLibraries, forceDex, defer, done);
+            }
+        }
         if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
             String path = pkg.mScanPath;
             int ret = 0;
             try {
                 if (forceDex || dalvik.system.DexFile.isDexOptNeeded(path)) {
                     if (!forceDex && defer) {
+                        if (mDeferredDexOpt == null) {
+                            mDeferredDexOpt = new HashSet<PackageParser.Package>();
+                        }
                         mDeferredDexOpt.add(pkg);
                         return DEX_OPT_DEFERRED;
                     } else {
@@ -3576,6 +3630,19 @@
         return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
     }
 
+    private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer,
+            boolean inclDependencies) {
+        HashSet<String> done;
+        boolean performed = false;
+        if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
+            done = new HashSet<String>();
+            done.add(pkg.packageName);
+        } else {
+            done = null;
+        }
+        return performDexOptLI(pkg, forceDex, defer, done);
+    }
+
     private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, PackageParser.Package newPkg) {
         if ((oldPkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) {
             Slog.w(TAG, "Unable to update from " + oldPkg.name
@@ -3646,6 +3713,113 @@
         return res;
     }
 
+    private int addSharedLibraryLPw(final SharedLibraryEntry file, int num,
+            PackageParser.Package changingLib) {
+        if (file.path != null) {
+            mTmpSharedLibraries[num] = file.path;
+            return num+1;
+        }
+        PackageParser.Package p = mPackages.get(file.apk);
+        if (changingLib != null && changingLib.packageName.equals(file.apk)) {
+            // If we are doing this while in the middle of updating a library apk,
+            // then we need to make sure to use that new apk for determining the
+            // dependencies here.  (We haven't yet finished committing the new apk
+            // to the package manager state.)
+            if (p == null || p.packageName.equals(changingLib.packageName)) {
+                p = changingLib;
+            }
+        }
+        if (p != null) {
+            String path = p.mPath;
+            for (int i=0; i<num; i++) {
+                if (mTmpSharedLibraries[i].equals(path)) {
+                    return num;
+                }
+            }
+            mTmpSharedLibraries[num] = p.mPath;
+            return num+1;
+        }
+        return num;
+    }
+
+    private boolean updateSharedLibrariesLPw(PackageParser.Package pkg,
+            PackageParser.Package changingLib) {
+        if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) {
+            if (mTmpSharedLibraries == null ||
+                    mTmpSharedLibraries.length < mSharedLibraries.size()) {
+                mTmpSharedLibraries = new String[mSharedLibraries.size()];
+            }
+            int num = 0;
+            int N = pkg.usesLibraries != null ? pkg.usesLibraries.size() : 0;
+            for (int i=0; i<N; i++) {
+                final SharedLibraryEntry file = mSharedLibraries.get(pkg.usesLibraries.get(i));
+                if (file == null) {
+                    Slog.e(TAG, "Package " + pkg.packageName
+                            + " requires unavailable shared library "
+                            + pkg.usesLibraries.get(i) + "; failing!");
+                    mLastScanError = PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
+                    return false;
+                }
+                num = addSharedLibraryLPw(file, num, changingLib);
+            }
+            N = pkg.usesOptionalLibraries != null ? pkg.usesOptionalLibraries.size() : 0;
+            for (int i=0; i<N; i++) {
+                final SharedLibraryEntry file = mSharedLibraries.get(pkg.usesOptionalLibraries.get(i));
+                if (file == null) {
+                    Slog.w(TAG, "Package " + pkg.packageName
+                            + " desires unavailable shared library "
+                            + pkg.usesOptionalLibraries.get(i) + "; ignoring!");
+                } else {
+                    num = addSharedLibraryLPw(file, num, changingLib);
+                }
+            }
+            if (num > 0) {
+                pkg.usesLibraryFiles = new String[num];
+                System.arraycopy(mTmpSharedLibraries, 0,
+                        pkg.usesLibraryFiles, 0, num);
+            } else {
+                pkg.usesLibraryFiles = null;
+            }
+        }
+        return true;
+    }
+
+    private static boolean hasString(List<String> list, List<String> which) {
+        if (list == null) {
+            return false;
+        }
+        for (int i=list.size()-1; i>=0; i--) {
+            for (int j=which.size()-1; j>=0; j--) {
+                if (which.get(j).equals(list.get(i))) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private void updateAllSharedLibrariesLPw() {
+        for (PackageParser.Package pkg : mPackages.values()) {
+            updateSharedLibrariesLPw(pkg, null);
+        }
+    }
+
+    private ArrayList<PackageParser.Package> updateAllSharedLibrariesLPw(
+            PackageParser.Package changingPkg) {
+        ArrayList<PackageParser.Package> res = null;
+        for (PackageParser.Package pkg : mPackages.values()) {
+            if (hasString(pkg.usesLibraries, changingPkg.libraryNames)
+                    || hasString(pkg.usesOptionalLibraries, changingPkg.libraryNames)) {
+                if (res == null) {
+                    res = new ArrayList<PackageParser.Package>();
+                }
+                res.add(pkg);
+                updateSharedLibrariesLPw(pkg, changingPkg);
+            }
+        }
+        return res;
+    }
+
     private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
             int parseFlags, int scanMode, long currentTime, UserHandle user) {
         File scanFile = new File(pkg.mScanPath);
@@ -3725,42 +3899,14 @@
 
         // writer
         synchronized (mPackages) {
-            // Check all shared libraries and map to their actual file path.
-            if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) {
-                if (mTmpSharedLibraries == null ||
-                        mTmpSharedLibraries.length < mSharedLibraries.size()) {
-                    mTmpSharedLibraries = new String[mSharedLibraries.size()];
-                }
-                int num = 0;
-                int N = pkg.usesLibraries != null ? pkg.usesLibraries.size() : 0;
-                for (int i=0; i<N; i++) {
-                    final String file = mSharedLibraries.get(pkg.usesLibraries.get(i));
-                    if (file == null) {
-                        Slog.e(TAG, "Package " + pkg.packageName
-                                + " requires unavailable shared library "
-                                + pkg.usesLibraries.get(i) + "; failing!");
-                        mLastScanError = PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
-                        return null;
-                    }
-                    mTmpSharedLibraries[num] = file;
-                    num++;
-                }
-                N = pkg.usesOptionalLibraries != null ? pkg.usesOptionalLibraries.size() : 0;
-                for (int i=0; i<N; i++) {
-                    final String file = mSharedLibraries.get(pkg.usesOptionalLibraries.get(i));
-                    if (file == null) {
-                        Slog.w(TAG, "Package " + pkg.packageName
-                                + " desires unavailable shared library "
-                                + pkg.usesOptionalLibraries.get(i) + "; ignoring!");
-                    } else {
-                        mTmpSharedLibraries[num] = file;
-                        num++;
-                    }
-                }
-                if (num > 0) {
-                    pkg.usesLibraryFiles = new String[num];
-                    System.arraycopy(mTmpSharedLibraries, 0,
-                            pkg.usesLibraryFiles, 0, num);
+            if ((parseFlags&PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+                // Check all shared libraries and map to their actual file path.
+                // We only do this here for apps not on a system dir, because those
+                // are the only ones that can fail an install due to this.  We
+                // will take care of the system apps by updating all of their
+                // library paths after the scan is done.
+                if (!updateSharedLibrariesLPw(pkg, null)) {
+                    return null;
                 }
             }
 
@@ -4166,7 +4312,7 @@
         pkg.mScanPath = path;
 
         if ((scanMode&SCAN_NO_DEX) == 0) {
-            if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0)
+            if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
                     == DEX_OPT_FAILED) {
                 mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
                 return null;
@@ -4178,6 +4324,80 @@
             pkg.applicationInfo.flags |= ApplicationInfo.FLAG_FACTORY_TEST;
         }
 
+        ArrayList<PackageParser.Package> clientLibPkgs = null;
+
+        // writer
+        synchronized (mPackages) {
+            if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
+                // Only system apps can add new shared libraries.
+                if (pkg.libraryNames != null) {
+                    for (int i=0; i<pkg.libraryNames.size(); i++) {
+                        String name = pkg.libraryNames.get(i);
+                        boolean allowed = false;
+                        if (isUpdatedSystemApp(pkg)) {
+                            // New library entries can only be added through the
+                            // system image.  This is important to get rid of a lot
+                            // of nasty edge cases: for example if we allowed a non-
+                            // system update of the app to add a library, then uninstalling
+                            // the update would make the library go away, and assumptions
+                            // we made such as through app install filtering would now
+                            // have allowed apps on the device which aren't compatible
+                            // with it.  Better to just have the restriction here, be
+                            // conservative, and create many fewer cases that can negatively
+                            // impact the user experience.
+                            final PackageSetting sysPs = mSettings
+                                    .getDisabledSystemPkgLPr(pkg.packageName);
+                            if (sysPs.pkg != null && sysPs.pkg.libraryNames != null) {
+                                for (int j=0; j<sysPs.pkg.libraryNames.size(); j++) {
+                                    if (name.equals(sysPs.pkg.libraryNames.get(j))) {
+                                        allowed = true;
+                                        allowed = true;
+                                        break;
+                                    }
+                                }
+                            }
+                        } else {
+                            allowed = true;
+                        }
+                        if (allowed) {
+                            if (!mSharedLibraries.containsKey(name)) {
+                                mSharedLibraries.put(name, new SharedLibraryEntry(null,
+                                        pkg.packageName));
+                            } else if (!name.equals(pkg.packageName)) {
+                                Slog.w(TAG, "Package " + pkg.packageName + " library "
+                                        + name + " already exists; skipping");
+                            }
+                        } else {
+                            Slog.w(TAG, "Package " + pkg.packageName + " declares lib "
+                                    + name + " that is not declared on system image; skipping");
+                        }
+                    }
+                    if ((scanMode&SCAN_BOOTING) == 0) {
+                        // If we are not booting, we need to update any applications
+                        // that are clients of our shared library.  If we are booting,
+                        // this will all be done once the scan is complete.
+                        clientLibPkgs = updateAllSharedLibrariesLPw(pkg);
+                    }
+                }
+            }
+        }
+
+        // We also need to dexopt any apps that are dependent on this library.  Note that
+        // if these fail, we should abort the install since installing the library will
+        // result in some apps being broken.
+        if (clientLibPkgs != null) {
+            if ((scanMode&SCAN_NO_DEX) == 0) {
+                for (int i=0; i<clientLibPkgs.size(); i++) {
+                    PackageParser.Package clientPkg = clientLibPkgs.get(i);
+                    if (performDexOptLI(clientPkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
+                            == DEX_OPT_FAILED) {
+                        mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
+                        return null;
+                    }
+                }
+            }
+        }
+
         // Request the ActivityManager to kill the process(only for existing packages)
         // so that we do not end up in a confused state while the user is still using the older
         // version of the application while the new one gets installed.
@@ -4186,6 +4406,15 @@
                         pkg.applicationInfo.uid);
         }
 
+        // Also need to kill any apps that are dependent on the library.
+        if (clientLibPkgs != null) {
+            for (int i=0; i<clientLibPkgs.size(); i++) {
+                PackageParser.Package clientPkg = clientLibPkgs.get(i);
+                killApplication(clientPkg.applicationInfo.packageName,
+                        clientPkg.applicationInfo.uid);
+            }
+        }
+
         // writer
         synchronized (mPackages) {
             // We don't expect installation to fail beyond this point,
@@ -4591,7 +4820,7 @@
                     }
                 }
             }
-            if (chatty) {
+            if (DEBUG_REMOVE && chatty) {
                 if (r == null) {
                     r = new StringBuilder(256);
                 } else {
@@ -4627,7 +4856,7 @@
         for (i=0; i<N; i++) {
             PackageParser.Activity a = pkg.receivers.get(i);
             mReceivers.removeActivity(a, "receiver");
-            if (chatty) {
+            if (DEBUG_REMOVE && chatty) {
                 if (r == null) {
                     r = new StringBuilder(256);
                 } else {
@@ -4645,7 +4874,7 @@
         for (i=0; i<N; i++) {
             PackageParser.Activity a = pkg.activities.get(i);
             mActivities.removeActivity(a, "activity");
-            if (chatty) {
+            if (DEBUG_REMOVE && chatty) {
                 if (r == null) {
                     r = new StringBuilder(256);
                 } else {
@@ -4668,7 +4897,7 @@
             }
             if (bp != null && bp.perm == p) {
                 bp.perm = null;
-                if (chatty) {
+                if (DEBUG_REMOVE && chatty) {
                     if (r == null) {
                         r = new StringBuilder(256);
                     } else {
@@ -4687,7 +4916,7 @@
         for (i=0; i<N; i++) {
             PackageParser.Instrumentation a = pkg.instrumentation.get(i);
             mInstrumentation.remove(a.getComponentName());
-            if (chatty) {
+            if (DEBUG_REMOVE && chatty) {
                 if (r == null) {
                     r = new StringBuilder(256);
                 } else {
@@ -4699,6 +4928,31 @@
         if (r != null) {
             if (DEBUG_REMOVE) Log.d(TAG, "  Instrumentation: " + r);
         }
+
+        r = null;
+        if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
+            // Only system apps can hold shared libraries.
+            if (pkg.libraryNames != null) {
+                for (i=0; i<pkg.libraryNames.size(); i++) {
+                    String name = pkg.libraryNames.get(i);
+                    SharedLibraryEntry cur = mSharedLibraries.get(name);
+                    if (cur != null && cur.apk != null && cur.apk.equals(pkg.packageName)) {
+                        mSharedLibraries.remove(name);
+                        if (DEBUG_REMOVE && chatty) {
+                            if (r == null) {
+                                r = new StringBuilder(256);
+                            } else {
+                                r.append(' ');
+                            }
+                            r.append(name);
+                        }
+                    }
+                }
+            }
+        }
+        if (r != null) {
+            if (DEBUG_REMOVE) Log.d(TAG, "  Libraries: " + r);
+        }
     }
 
     private static final boolean isPackageFilename(String name) {
@@ -4860,7 +5114,23 @@
                                 if (origGp.grantedPermissions.contains(perm)) {
                                     allowed = true;
                                 } else {
+                                    // The system apk may have been updated with an older
+                                    // version of the one on the data partition, but which
+                                    // granted a new system permission that it didn't have
+                                    // before.  In this case we do want to allow the app to
+                                    // now get the new permission, because it is allowed by
+                                    // the system image.
                                     allowed = false;
+                                    if (sysPs.pkg != null) {
+                                        for (int j=0;
+                                                j<sysPs.pkg.requestedPermissions.size(); j++) {
+                                            if (perm.equals(
+                                                    sysPs.pkg.requestedPermissions.get(j))) {
+                                                allowed = true;
+                                                break;
+                                            }
+                                        }
+                                    }
                                 }
                             } else {
                                 allowed = true;
@@ -5557,6 +5827,7 @@
                 }
                 if ((event&REMOVE_EVENTS) != 0) {
                     if (ps != null) {
+                        if (DEBUG_REMOVE) Slog.d(TAG, "Package disappeared: " + ps);
                         removePackageLI(ps, true);
                         removedPackage = ps.name;
                         removedAppId = ps.appId;
@@ -5565,6 +5836,7 @@
 
                 if ((event&ADD_EVENTS) != 0) {
                     if (p == null) {
+                        if (DEBUG_INSTALL) Slog.d(TAG, "New file appeared: " + fullPath);
                         p = scanPackageLI(fullPath,
                                 (mIsRom ? PackageParser.PARSE_IS_SYSTEM
                                         | PackageParser.PARSE_IS_SYSTEM_DIR: 0) |
@@ -6126,7 +6398,7 @@
         final boolean startCopy() {
             boolean res;
             try {
-                if (DEBUG_INSTALL) Slog.i(TAG, "startCopy");
+                if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
 
                 if (++mRetries > MAX_RETRIES) {
                     Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
@@ -6170,6 +6442,13 @@
         }
 
         @Override
+        public String toString() {
+            return "MeasureParams{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + mStats.packageName + "}";
+        }
+
+        @Override
         void handleStartCopy() throws RemoteException {
             synchronized (mInstallLock) {
                 mSuccess = getPackageSizeInfoLI(mStats.packageName, mStats.userHandle, mStats);
@@ -6258,6 +6537,13 @@
             this.encryptionParams = encryptionParams;
         }
 
+        @Override
+        public String toString() {
+            return "InstallParams{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + mPackageURI + "}";
+        }
+
         public ManifestDigest getManifestDigest() {
             if (verificationParams == null) {
                 return null;
@@ -6671,6 +6957,13 @@
             }
         }
 
+        @Override
+        public String toString() {
+            return "MoveParams{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + packageName + "}";
+        }
+
         public void handleStartCopy() throws RemoteException {
             mRet = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
             // Check for storage space on target medium
@@ -7582,6 +7875,7 @@
         // Remember this for later, in case we need to rollback this install
         String pkgName = pkg.packageName;
 
+        if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + pkg);
         boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists();
         synchronized(mPackages) {
             if (mSettings.mRenamedPackages.containsKey(pkgName)) {
@@ -7638,6 +7932,7 @@
         // First find the old package info and check signatures
         synchronized(mPackages) {
             oldPackage = mPackages.get(pkgName);
+            if (DEBUG_INSTALL) Slog.d(TAG, "replacePackageLI: new=" + pkg + ", old=" + oldPackage);
             if (compareSignatures(oldPackage.mSignatures, pkg.mSignatures)
                     != PackageManager.SIGNATURE_MATCH) {
                 Slog.w(TAG, "New package has a different signature: " + pkgName);
@@ -7663,6 +7958,8 @@
         boolean deletedPkg = true;
         boolean updatedSettings = false;
 
+        if (DEBUG_INSTALL) Slog.d(TAG, "replaceNonSystemPackageLI: new=" + pkg + ", old="
+                + deletedPackage);
         long origUpdateTime;
         if (pkg.mExtras != null) {
             origUpdateTime = ((PackageSetting)pkg.mExtras).lastUpdateTime;
@@ -7700,6 +7997,7 @@
             // scanPackageLocked, unless those directories existed before we even tried to
             // install.
             if(updatedSettings) {
+                if (DEBUG_INSTALL) Slog.d(TAG, "Install failed, rolling pack: " + pkgName);
                 deletePackageLI(
                         pkgName, null, true,
                         PackageManager.DELETE_KEEP_DATA,
@@ -7708,6 +8006,7 @@
             // Since we failed to install the new package we need to restore the old
             // package that we deleted.
             if(deletedPkg) {
+                if (DEBUG_INSTALL) Slog.d(TAG, "Install failed, reinstalling: " + deletedPackage);
                 File restoreFile = new File(deletedPackage.mPath);
                 // Parse old package
                 boolean oldOnSd = isExternal(deletedPackage);
@@ -7737,6 +8036,8 @@
     private void replaceSystemPackageLI(PackageParser.Package deletedPackage,
             PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user,
             String installerPackageName, PackageInstalledInfo res) {
+        if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg
+                + ", old=" + deletedPackage);
         PackageParser.Package newPackage = null;
         boolean updatedSettings = false;
         parseFlags |= PackageManager.INSTALL_REPLACE_EXISTING |
@@ -7859,7 +8160,7 @@
             return;
         }
 
-        Log.d(TAG, "New package installed in " + newPackage.mPath);
+        if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + newPackage.mPath);
 
         synchronized (mPackages) {
             updatePermissionsLPw(newPackage.packageName, newPackage,
@@ -7889,6 +8190,7 @@
         // Result object to be returned
         res.returnCode = PackageManager.INSTALL_SUCCEEDED;
 
+        if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile);
         // Retrieve PackageSettings and parse package
         int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
                 | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
@@ -7950,14 +8252,18 @@
                     pkg.setPackageName(oldName);
                     pkgName = pkg.packageName;
                     replace = true;
+                    if (DEBUG_INSTALL) Slog.d(TAG, "Replacing existing renamed package: oldName="
+                            + oldName + " pkgName=" + pkgName);
                 } else if (mPackages.containsKey(pkgName)) {
                     // This package, under its official name, already exists
                     // on the device; we should replace it.
                     replace = true;
+                    if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing pacakge: " + pkgName);
                 }
             }
             PackageSetting ps = mSettings.mPackages.get(pkgName);
             if (ps != null) {
+                if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps);
                 oldCodePath = mSettings.mPackages.get(pkgName).codePathString;
                 if (ps.pkg != null && ps.pkg.applicationInfo != null) {
                     systemApp = (ps.pkg.applicationInfo.flags &
@@ -8104,6 +8410,7 @@
             return;
         }
 
+        if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageAsUser: pkg=" + packageName + " user=" + userId);
         // Queue up an async operation since the package deletion may take a little while.
         mHandler.post(new Runnable() {
             public void run() {
@@ -8151,6 +8458,7 @@
         boolean removedForAllUsers = false;
         boolean systemUpdate = false;
         synchronized (mInstallLock) {
+            if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageX: pkg=" + packageName + " user=" + userId);
             res = deletePackageLI(packageName,
                     (flags & PackageManager.DELETE_ALL_USERS) != 0
                             ? UserHandle.ALL : new UserHandle(userId),
@@ -8159,6 +8467,8 @@
             if (res && !systemUpdate && mPackages.get(packageName) == null) {
                 removedForAllUsers = true;
             }
+            if (DEBUG_REMOVE) Slog.d(TAG, "delete res: systemUpdate=" + systemUpdate
+                    + " removedForAllUsers=" + removedForAllUsers);
         }
 
         if (res) {
@@ -8234,6 +8544,7 @@
     private void removePackageDataLI(PackageSetting ps, PackageRemovedInfo outInfo,
             int flags, boolean writeSettings) {
         String packageName = ps.name;
+        if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + ps);
         removePackageLI(ps, (flags&REMOVE_CHATTY) != 0);
         // Retrieve object to delete permissions for shared user later on
         final PackageSetting deletedPs;
@@ -8289,11 +8600,13 @@
         synchronized (mPackages) {
             disabledPs = mSettings.getDisabledSystemPkgLPr(newPs.name);
         }
+        if (DEBUG_REMOVE) Slog.d(TAG, "deleteSystemPackageLI: newPs=" + newPs
+                + " disabledPs=" + disabledPs);
         if (disabledPs == null) {
             Slog.w(TAG, "Attempt to delete unknown system package "+ newPs.name);
             return false;
-        } else {
-            Log.i(TAG, "Deleting system pkg from data partition");
+        } else if (DEBUG_REMOVE) {
+            Slog.d(TAG, "Deleting system pkg from data partition");
         }
         // Delete the updated package
         outInfo.isRemovedPackageSystemUpdate = true;
@@ -8317,6 +8630,7 @@
             NativeLibraryHelper.removeNativeBinariesLI(newPs.nativeLibraryPathString);
         }
         // Install the system package
+        if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
         PackageParser.Package newPkg = scanPackageLI(disabledPs.codePath,
                 PackageParser.PARSE_MUST_BE_APK | PackageParser.PARSE_IS_SYSTEM,
                 SCAN_MONITOR | SCAN_NO_PATHS, 0, null);
@@ -8366,6 +8680,7 @@
             Slog.w(TAG, "Attempt to delete null packageName.");
             return false;
         }
+        if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user);
         PackageSetting ps;
         boolean dataOnly = false;
         int removeUser = -1;
@@ -8376,11 +8691,14 @@
                 Slog.w(TAG, "Package named '" + packageName + "' doesn't exist.");
                 return false;
             }
-            if (user != null
+            if ((!isSystemApp(ps) || (flags&PackageManager.DELETE_SYSTEM_APP) != 0) && user != null
                     && user.getIdentifier() != UserHandle.USER_ALL) {
                 // The caller is asking that the package only be deleted for a single
                 // user.  To do this, we just mark its uninstalled state and delete
-                // its data.
+                // its data.  If this is a system app, we only allow this to happen if
+                // they have set the special DELETE_SYSTEM_APP which requests different
+                // semantics than normal for uninstalling system apps.
+                if (DEBUG_REMOVE) Slog.d(TAG, "Only deleting for single user");
                 ps.setUserState(user.getIdentifier(),
                         COMPONENT_ENABLED_STATE_DEFAULT,
                         false, //installed
@@ -8392,12 +8710,14 @@
                         // Other user still have this package installed, so all
                         // we need to do is clear this user's data and save that
                         // it is uninstalled.
+                        if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users");
                         removeUser = user.getIdentifier();
                         appId = ps.appId;
                         mSettings.writePackageRestrictionsLPr(removeUser);
                     } else {
                         // We need to set it back to 'installed' so the uninstall
                         // broadcasts will be sent correctly.
+                        if (DEBUG_REMOVE) Slog.d(TAG, "Not installed by other users, full delete");
                         ps.setInstalled(true, user.getIdentifier());
                     }
                 } else {
@@ -8405,6 +8725,7 @@
                     // other users still have this package installed, so all
                     // we need to do is clear this user's data and save that
                     // it is uninstalled.
+                    if (DEBUG_REMOVE) Slog.d(TAG, "Deleting system app");
                     removeUser = user.getIdentifier();
                     appId = ps.appId;
                     mSettings.writePackageRestrictionsLPr(removeUser);
@@ -8415,6 +8736,7 @@
         if (removeUser >= 0) {
             // From above, we determined that we are deleting this only
             // for a single user.  Continue the work here.
+            if (DEBUG_REMOVE) Slog.d(TAG, "Updating install state for user: " + removeUser);
             if (outInfo != null) {
                 outInfo.removedPackage = packageName;
                 outInfo.removedAppId = appId;
@@ -8427,17 +8749,18 @@
 
         if (dataOnly) {
             // Delete application data first
+            if (DEBUG_REMOVE) Slog.d(TAG, "Removing package data only");
             removePackageDataLI(ps, outInfo, flags, writeSettings);
             return true;
         }
         boolean ret = false;
         if (isSystemApp(ps)) {
-            Log.i(TAG, "Removing system package:" + ps.name);
+            if (DEBUG_REMOVE) Slog.d(TAG, "Removing system package:" + ps.name);
             // When an updated system application is deleted we delete the existing resources as well and
             // fall back to existing code in system partition
             ret = deleteSystemPackageLI(ps, flags, outInfo, writeSettings);
         } else {
-            Log.i(TAG, "Removing non-system package:" + ps.name);
+            if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package:" + ps.name);
             // Kill application pre-emptively especially for apps on sd.
             killApplication(packageName, ps.appId);
             ret = deleteInstalledPackageLI(ps, deleteCodeAndResources, flags, outInfo,
@@ -9408,7 +9731,15 @@
                     pw.print("  ");
                     pw.print(name);
                     pw.print(" -> ");
-                    pw.println(mSharedLibraries.get(name));
+                    SharedLibraryEntry ent = mSharedLibraries.get(name);
+                    if (ent.path != null) {
+                        pw.print("(jar) ");
+                        pw.print(ent.path);
+                    } else {
+                        pw.print("(apk) ");
+                        pw.print(ent.apk);
+                    }
+                    pw.println();
                 }
             }
 
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index 13f514b..e645078 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -32,7 +32,6 @@
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.XmlUtils;
-import com.android.server.IntentResolver;
 import com.android.server.pm.PackageManagerService.DumpState;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -2657,6 +2656,162 @@
         ApplicationInfo.FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE",
     };
 
+    void dumpPackageLPr(PrintWriter pw, String prefix, PackageSetting ps, SimpleDateFormat sdf,
+            Date date, List<UserInfo> users) {
+        pw.print(prefix); pw.print("Package [");
+            pw.print(ps.realName != null ? ps.realName : ps.name);
+            pw.print("] (");
+            pw.print(Integer.toHexString(System.identityHashCode(ps)));
+            pw.println("):");
+
+        if (ps.realName != null) {
+            pw.print(prefix); pw.print("  compat name=");
+            pw.println(ps.name);
+        }
+
+        pw.print(prefix); pw.print("  userId="); pw.print(ps.appId);
+                pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids));
+        if (ps.sharedUser != null) {
+            pw.print(prefix); pw.print("  sharedUser="); pw.println(ps.sharedUser);
+        }
+        pw.print(prefix); pw.print("  pkg="); pw.println(ps.pkg);
+        pw.print(prefix); pw.print("  codePath="); pw.println(ps.codePathString);
+        pw.print(prefix); pw.print("  resourcePath="); pw.println(ps.resourcePathString);
+        pw.print(prefix); pw.print("  nativeLibraryPath="); pw.println(ps.nativeLibraryPathString);
+        pw.print(prefix); pw.print("  versionCode="); pw.print(ps.versionCode);
+        if (ps.pkg != null) {
+            pw.print(" targetSdk="); pw.print(ps.pkg.applicationInfo.targetSdkVersion);
+        }
+        pw.println();
+        if (ps.pkg != null) {
+            pw.print(prefix); pw.print("  versionName="); pw.println(ps.pkg.mVersionName);
+            pw.print(prefix); pw.print("  applicationInfo=");
+                pw.println(ps.pkg.applicationInfo.toString());
+            pw.print(prefix); pw.print("  flags="); printFlags(pw, ps.pkg.applicationInfo.flags,
+                    FLAG_DUMP_SPEC); pw.println();
+            pw.print(prefix); pw.print("  dataDir="); pw.println(ps.pkg.applicationInfo.dataDir);
+            if (ps.pkg.mOperationPending) {
+                pw.print(prefix); pw.println("  mOperationPending=true");
+            }
+            pw.print(prefix); pw.print("  supportsScreens=[");
+            boolean first = true;
+            if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) {
+                if (!first)
+                    pw.print(", ");
+                first = false;
+                pw.print("small");
+            }
+            if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) {
+                if (!first)
+                    pw.print(", ");
+                first = false;
+                pw.print("medium");
+            }
+            if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
+                if (!first)
+                    pw.print(", ");
+                first = false;
+                pw.print("large");
+            }
+            if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
+                if (!first)
+                    pw.print(", ");
+                first = false;
+                pw.print("xlarge");
+            }
+            if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
+                if (!first)
+                    pw.print(", ");
+                first = false;
+                pw.print("resizeable");
+            }
+            if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
+                if (!first)
+                    pw.print(", ");
+                first = false;
+                pw.print("anyDensity");
+            }
+            pw.println("]");
+            if (ps.pkg.libraryNames != null && ps.pkg.libraryNames.size() > 0) {
+                pw.print(prefix); pw.println("  libraries:");
+                for (int i=0; i<ps.pkg.libraryNames.size(); i++) {
+                    pw.print(prefix); pw.print("    "); pw.println(ps.pkg.libraryNames.get(i));
+                }
+            }
+            if (ps.pkg.usesLibraries != null && ps.pkg.usesLibraries.size() > 0) {
+                pw.print(prefix); pw.println("  usesLibraries:");
+                for (int i=0; i<ps.pkg.usesLibraries.size(); i++) {
+                    pw.print(prefix); pw.print("    "); pw.println(ps.pkg.usesLibraries.get(i));
+                }
+            }
+            if (ps.pkg.usesOptionalLibraries != null
+                    && ps.pkg.usesOptionalLibraries.size() > 0) {
+                pw.print(prefix); pw.println("  usesOptionalLibraries:");
+                for (int i=0; i<ps.pkg.usesOptionalLibraries.size(); i++) {
+                    pw.print(prefix); pw.print("    ");
+                        pw.println(ps.pkg.usesOptionalLibraries.get(i));
+                }
+            }
+            if (ps.pkg.usesLibraryFiles != null
+                    && ps.pkg.usesLibraryFiles.length > 0) {
+                pw.print(prefix); pw.println("  usesLibraryFiles:");
+                for (int i=0; i<ps.pkg.usesLibraryFiles.length; i++) {
+                    pw.print(prefix); pw.print("    "); pw.println(ps.pkg.usesLibraryFiles[i]);
+                }
+            }
+        }
+        pw.print(prefix); pw.print("  timeStamp=");
+            date.setTime(ps.timeStamp);
+            pw.println(sdf.format(date));
+        pw.print(prefix); pw.print("  firstInstallTime=");
+            date.setTime(ps.firstInstallTime);
+            pw.println(sdf.format(date));
+        pw.print(prefix); pw.print("  lastUpdateTime=");
+            date.setTime(ps.lastUpdateTime);
+            pw.println(sdf.format(date));
+        if (ps.installerPackageName != null) {
+            pw.print(prefix); pw.print("  installerPackageName=");
+                    pw.println(ps.installerPackageName);
+        }
+        pw.print(prefix); pw.print("  signatures="); pw.println(ps.signatures);
+        pw.print(prefix); pw.print("  permissionsFixed="); pw.print(ps.permissionsFixed);
+                pw.print(" haveGids="); pw.print(ps.haveGids);
+                pw.print(" installStatus="); pw.println(ps.installStatus);
+        pw.print(prefix); pw.print("  pkgFlags="); printFlags(pw, ps.pkgFlags, FLAG_DUMP_SPEC);
+                pw.println();
+        for (UserInfo user : users) {
+            pw.print(prefix); pw.print("  User "); pw.print(user.id); pw.print(": ");
+            pw.print(" installed=");
+            pw.print(ps.getInstalled(user.id));
+            pw.print(" stopped=");
+            pw.print(ps.getStopped(user.id));
+            pw.print(" notLaunched=");
+            pw.print(ps.getNotLaunched(user.id));
+            pw.print(" enabled=");
+            pw.println(ps.getEnabled(user.id));
+            HashSet<String> cmp = ps.getDisabledComponents(user.id);
+            if (cmp != null && cmp.size() > 0) {
+                pw.print(prefix); pw.println("    disabledComponents:");
+                for (String s : cmp) {
+                    pw.print(prefix); pw.print("    "); pw.println(s);
+                }
+            }
+            cmp = ps.getEnabledComponents(user.id);
+            if (cmp != null && cmp.size() > 0) {
+                pw.print(prefix); pw.println("    enabledComponents:");
+                for (String s : cmp) {
+                    pw.print(prefix); pw.print("    "); pw.println(s);
+                }
+            }
+        }
+        if (ps.grantedPermissions.size() > 0) {
+            pw.print(prefix); pw.println("  grantedPermissions:");
+            for (String s : ps.grantedPermissions) {
+                pw.print(prefix); pw.print("    "); pw.println(s);
+            }
+        }
+    }
+
     void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState) {
         final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         final Date date = new Date();
@@ -2678,123 +2833,7 @@
                 pw.println("Packages:");
                 printedSomething = true;
             }
-            pw.print("  Package [");
-                pw.print(ps.realName != null ? ps.realName : ps.name);
-                pw.print("] (");
-                pw.print(Integer.toHexString(System.identityHashCode(ps)));
-                pw.println("):");
-
-            if (ps.realName != null) {
-                pw.print("    compat name=");
-                pw.println(ps.name);
-            }
-
-            pw.print("    userId="); pw.print(ps.appId);
-            pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids));
-            pw.print("    sharedUser="); pw.println(ps.sharedUser);
-            pw.print("    pkg="); pw.println(ps.pkg);
-            pw.print("    codePath="); pw.println(ps.codePathString);
-            pw.print("    resourcePath="); pw.println(ps.resourcePathString);
-            pw.print("    nativeLibraryPath="); pw.println(ps.nativeLibraryPathString);
-            pw.print("    versionCode="); pw.println(ps.versionCode);
-            if (ps.pkg != null) {
-                pw.print("    applicationInfo="); pw.println(ps.pkg.applicationInfo.toString());
-                pw.print("    flags="); printFlags(pw, ps.pkg.applicationInfo.flags, FLAG_DUMP_SPEC); pw.println();
-                pw.print("    versionName="); pw.println(ps.pkg.mVersionName);
-                pw.print("    dataDir="); pw.println(ps.pkg.applicationInfo.dataDir);
-                pw.print("    targetSdk="); pw.println(ps.pkg.applicationInfo.targetSdkVersion);
-                if (ps.pkg.mOperationPending) {
-                    pw.println("    mOperationPending=true");
-                }
-                pw.print("    supportsScreens=[");
-                boolean first = true;
-                if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) {
-                    if (!first)
-                        pw.print(", ");
-                    first = false;
-                    pw.print("small");
-                }
-                if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) {
-                    if (!first)
-                        pw.print(", ");
-                    first = false;
-                    pw.print("medium");
-                }
-                if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
-                    if (!first)
-                        pw.print(", ");
-                    first = false;
-                    pw.print("large");
-                }
-                if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
-                    if (!first)
-                        pw.print(", ");
-                    first = false;
-                    pw.print("xlarge");
-                }
-                if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
-                    if (!first)
-                        pw.print(", ");
-                    first = false;
-                    pw.print("resizeable");
-                }
-                if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
-                    if (!first)
-                        pw.print(", ");
-                    first = false;
-                    pw.print("anyDensity");
-                }
-                pw.println("]");
-            }
-            pw.print("    timeStamp=");
-                date.setTime(ps.timeStamp);
-                pw.println(sdf.format(date));
-            pw.print("    firstInstallTime=");
-                date.setTime(ps.firstInstallTime);
-                pw.println(sdf.format(date));
-            pw.print("    lastUpdateTime=");
-                date.setTime(ps.lastUpdateTime);
-                pw.println(sdf.format(date));
-            if (ps.installerPackageName != null) {
-                pw.print("    installerPackageName="); pw.println(ps.installerPackageName);
-            }
-            pw.print("    signatures="); pw.println(ps.signatures);
-            pw.print("    permissionsFixed="); pw.print(ps.permissionsFixed);
-                    pw.print(" haveGids="); pw.print(ps.haveGids);
-                    pw.print(" installStatus="); pw.println(ps.installStatus);
-            pw.print("    pkgFlags="); printFlags(pw, ps.pkgFlags, FLAG_DUMP_SPEC);
-                    pw.println();
-            for (UserInfo user : users) {
-                pw.print("    User "); pw.print(user.id); pw.print(": ");
-                pw.print(" installed=");
-                pw.print(ps.getInstalled(user.id));
-                pw.print(" stopped=");
-                pw.print(ps.getStopped(user.id));
-                pw.print(" notLaunched=");
-                pw.print(ps.getNotLaunched(user.id));
-                pw.print(" enabled=");
-                pw.println(ps.getEnabled(user.id));
-                HashSet<String> cmp = ps.getDisabledComponents(user.id);
-                if (cmp != null && cmp.size() > 0) {
-                    pw.println("      disabledComponents:");
-                    for (String s : cmp) {
-                        pw.print("      "); pw.println(s);
-                    }
-                }
-                cmp = ps.getEnabledComponents(user.id);
-                if (cmp != null && cmp.size() > 0) {
-                    pw.println("      enabledComponents:");
-                    for (String s : cmp) {
-                        pw.print("      "); pw.println(s);
-                    }
-                }
-            }
-            if (ps.grantedPermissions.size() > 0) {
-                pw.println("    grantedPermissions:");
-                for (String s : ps.grantedPermissions) {
-                    pw.print("      "); pw.println(s);
-                }
-            }
+            dumpPackageLPr(pw, "  ", ps, sdf, date, users);
         }
 
         printedSomething = false;
@@ -2830,27 +2869,7 @@
                     pw.println("Hidden system packages:");
                     printedSomething = true;
                 }
-                pw.print("  Package [");
-                pw.print(ps.realName != null ? ps.realName : ps.name);
-                pw.print("] (");
-                pw.print(Integer.toHexString(System.identityHashCode(ps)));
-                pw.println("):");
-                if (ps.realName != null) {
-                    pw.print("    compat name=");
-                    pw.println(ps.name);
-                }
-                if (ps.pkg != null && ps.pkg.applicationInfo != null) {
-                    pw.print("    applicationInfo=");
-                    pw.println(ps.pkg.applicationInfo.toString());
-                }
-                pw.print("    userId=");
-                pw.println(ps.appId);
-                pw.print("    sharedUser=");
-                pw.println(ps.sharedUser);
-                pw.print("    codePath=");
-                pw.println(ps.codePathString);
-                pw.print("    resourcePath=");
-                pw.println(ps.resourcePathString);
+                dumpPackageLPr(pw, "  ", ps, sdf, date, users);
             }
         }
     }
diff --git a/services/java/com/android/server/power/ElectronBeam.java b/services/java/com/android/server/power/ElectronBeam.java
index 457e92d..4a74149 100644
--- a/services/java/com/android/server/power/ElectronBeam.java
+++ b/services/java/com/android/server/power/ElectronBeam.java
@@ -82,7 +82,7 @@
     private int mDisplayHeight;     // real height, not rotated
     private SurfaceSession mSurfaceSession;
     private SurfaceControl mSurfaceControl;
-    private final Surface mSurface = new Surface();
+    private Surface mSurface;
     private NaturalSurfaceLayout mSurfaceLayout;
     private EGLDisplay mEglDisplay;
     private EGLConfig mEglConfig;
@@ -519,6 +519,7 @@
 
             mSurfaceControl.setLayerStack(mDisplayLayerStack);
             mSurfaceControl.setSize(mDisplayWidth, mDisplayHeight);
+            mSurface = new Surface();
             mSurface.copyFrom(mSurfaceControl);
             
             mSurfaceLayout = new NaturalSurfaceLayout(mDisplayManager, mSurfaceControl);
diff --git a/services/java/com/android/server/wifi/README.txt b/services/java/com/android/server/wifi/README.txt
new file mode 100644
index 0000000..c03bff5
--- /dev/null
+++ b/services/java/com/android/server/wifi/README.txt
@@ -0,0 +1,12 @@
+WifiService: Implements the IWifiManager 3rd party API. The API and the device state information (screen on/off, battery state, sleep policy) go as input into the WifiController which tracks high level states as to whether STA or AP mode is operational and controls the WifiStateMachine to handle bringup and shut down.
+
+WifiController: Acts as a controller to the WifiStateMachine based on various inputs (API and device state). Runs on the same thread created in WifiService.
+
+WifiSettingsStore: Tracks the various settings (wifi toggle, airplane toggle, tethering toggle, scan mode toggle) and provides API to figure if wifi should be turned on or off.
+
+WifiTrafficPoller: Polls traffic on wifi and notifies apps listening on it.
+
+WifiNotificationController: Controls whether the open network notification is displayed or not based on the scan results.
+
+WifiStateMachine: Tracks the various states on STA and AP connectivity and handles bring up and shut down.
+
diff --git a/services/java/com/android/server/wifi/WifiController.java b/services/java/com/android/server/wifi/WifiController.java
new file mode 100644
index 0000000..4d7c434
--- /dev/null
+++ b/services/java/com/android/server/wifi/WifiController.java
@@ -0,0 +1,626 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import static android.net.wifi.WifiManager.WIFI_MODE_FULL;
+import static android.net.wifi.WifiManager.WIFI_MODE_FULL_HIGH_PERF;
+import static android.net.wifi.WifiManager.WIFI_MODE_SCAN_ONLY;
+import android.net.wifi.WifiStateMachine;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.server.wifi.WifiService.LockList;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+class WifiController extends StateMachine {
+    private static final String TAG = "WifiController";
+    private static final boolean DBG = false;
+    private Context mContext;
+    private boolean mScreenOff;
+    private boolean mDeviceIdle;
+    private int mPluggedType;
+    private int mStayAwakeConditions;
+    private long mIdleMillis;
+    private int mSleepPolicy;
+
+    private AlarmManager mAlarmManager;
+    private PendingIntent mIdleIntent;
+    private static final int IDLE_REQUEST = 0;
+
+    /**
+     * See {@link Settings.Global#WIFI_IDLE_MS}. This is the default value if a
+     * Settings.Global value is not present. This timeout value is chosen as
+     * the approximate point at which the battery drain caused by Wi-Fi
+     * being enabled but not active exceeds the battery drain caused by
+     * re-establishing a connection to the mobile data network.
+     */
+    private static final long DEFAULT_IDLE_MS = 15 * 60 * 1000; /* 15 minutes */
+
+    NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
+
+    private static final String ACTION_DEVICE_IDLE =
+            "com.android.server.WifiManager.action.DEVICE_IDLE";
+
+    /* References to values tracked in WifiService */
+    final WifiStateMachine mWifiStateMachine;
+    final WifiSettingsStore mSettingsStore;
+    final LockList mLocks;
+
+    /**
+     * Temporary for computing UIDS that are responsible for starting WIFI.
+     * Protected by mWifiStateTracker lock.
+     */
+    private final WorkSource mTmpWorkSource = new WorkSource();
+
+    private static final int BASE = Protocol.BASE_WIFI_CONTROLLER;
+
+    static final int CMD_EMERGENCY_MODE_CHANGED     = BASE + 1;
+    static final int CMD_SCREEN_ON                  = BASE + 2;
+    static final int CMD_SCREEN_OFF                 = BASE + 3;
+    static final int CMD_BATTERY_CHANGED            = BASE + 4;
+    static final int CMD_DEVICE_IDLE                = BASE + 5;
+    static final int CMD_LOCKS_CHANGED              = BASE + 6;
+    static final int CMD_SCAN_ALWAYS_MODE_CHANGED   = BASE + 7;
+    static final int CMD_WIFI_TOGGLED               = BASE + 8;
+    static final int CMD_AIRPLANE_TOGGLED           = BASE + 9;
+    static final int CMD_SET_AP                     = BASE + 10;
+
+    private DefaultState mDefaultState = new DefaultState();
+    private StaEnabledState mStaEnabledState = new StaEnabledState();
+    private ApStaDisabledState mApStaDisabledState = new ApStaDisabledState();
+    private StaDisabledWithScanState mStaDisabledWithScanState = new StaDisabledWithScanState();
+    private ApEnabledState mApEnabledState = new ApEnabledState();
+    private DeviceActiveState mDeviceActiveState = new DeviceActiveState();
+    private DeviceInactiveState mDeviceInactiveState = new DeviceInactiveState();
+    private ScanOnlyLockHeldState mScanOnlyLockHeldState = new ScanOnlyLockHeldState();
+    private FullLockHeldState mFullLockHeldState = new FullLockHeldState();
+    private FullHighPerfLockHeldState mFullHighPerfLockHeldState = new FullHighPerfLockHeldState();
+    private NoLockHeldState mNoLockHeldState = new NoLockHeldState();
+    private EcmState mEcmState = new EcmState();
+
+    WifiController(Context context, WifiService service, Looper looper) {
+        super(TAG, looper);
+        mContext = context;
+        mWifiStateMachine = service.mWifiStateMachine;
+        mSettingsStore = service.mSettingsStore;
+        mLocks = service.mLocks;
+
+        mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+        Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null);
+        mIdleIntent = PendingIntent.getBroadcast(mContext, IDLE_REQUEST, idleIntent, 0);
+
+        addState(mDefaultState);
+            addState(mApStaDisabledState, mDefaultState);
+            addState(mStaEnabledState, mDefaultState);
+                addState(mDeviceActiveState, mStaEnabledState);
+                addState(mDeviceInactiveState, mStaEnabledState);
+                    addState(mScanOnlyLockHeldState, mDeviceInactiveState);
+                    addState(mFullLockHeldState, mDeviceInactiveState);
+                    addState(mFullHighPerfLockHeldState, mDeviceInactiveState);
+                    addState(mNoLockHeldState, mDeviceInactiveState);
+            addState(mStaDisabledWithScanState, mDefaultState);
+            addState(mApEnabledState, mDefaultState);
+            addState(mEcmState, mDefaultState);
+        setInitialState(mApStaDisabledState);
+        setLogRecSize(25);
+        setLogOnlyTransitions(true);
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_DEVICE_IDLE);
+        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        String action = intent.getAction();
+                        if (action.equals(ACTION_DEVICE_IDLE)) {
+                            sendMessage(CMD_DEVICE_IDLE);
+                        } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+                            mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
+                                    WifiManager.EXTRA_NETWORK_INFO);
+                        }
+                    }
+                },
+                new IntentFilter(filter));
+
+        initializeAndRegisterForSettingsChange(looper);
+    }
+
+    private void initializeAndRegisterForSettingsChange(Looper looper) {
+        Handler handler = new Handler(looper);
+        readStayAwakeConditions();
+        registerForStayAwakeModeChange(handler);
+        readWifiIdleTime();
+        registerForWifiIdleTimeChange(handler);
+        readStayAwakeConditions();
+        registerForWifiSleepPolicyChange(handler);
+    }
+
+    private void readStayAwakeConditions() {
+        mStayAwakeConditions = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
+    }
+
+    private void readWifiIdleTime() {
+        mIdleMillis = Settings.Global.getLong(mContext.getContentResolver(),
+                Settings.Global.WIFI_IDLE_MS, DEFAULT_IDLE_MS);
+    }
+
+    private void readWifiSleepPolicy() {
+        mSleepPolicy = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SLEEP_POLICY,
+                Settings.Global.WIFI_SLEEP_POLICY_NEVER);
+    }
+
+    /**
+     * Observes settings changes to scan always mode.
+     */
+    private void registerForStayAwakeModeChange(Handler handler) {
+        ContentObserver contentObserver = new ContentObserver(handler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                readStayAwakeConditions();
+            }
+        };
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.STAY_ON_WHILE_PLUGGED_IN),
+                false, contentObserver);
+    }
+
+    /**
+     * Observes settings changes to scan always mode.
+     */
+    private void registerForWifiIdleTimeChange(Handler handler) {
+        ContentObserver contentObserver = new ContentObserver(handler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                readWifiIdleTime();
+            }
+        };
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.WIFI_IDLE_MS),
+                false, contentObserver);
+    }
+
+    /**
+     * Observes changes to wifi sleep policy
+     */
+    private void registerForWifiSleepPolicyChange(Handler handler) {
+        ContentObserver contentObserver = new ContentObserver(handler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                readWifiSleepPolicy();
+            }
+        };
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.WIFI_SLEEP_POLICY),
+                false, contentObserver);
+    }
+
+    /**
+     * Determines whether the Wi-Fi chipset should stay awake or be put to
+     * sleep. Looks at the setting for the sleep policy and the current
+     * conditions.
+     *
+     * @see #shouldDeviceStayAwake(int)
+     */
+    private boolean shouldWifiStayAwake(int pluggedType) {
+        if (mSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER) {
+            // Never sleep
+            return true;
+        } else if ((mSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED) &&
+                (pluggedType != 0)) {
+            // Never sleep while plugged, and we're plugged
+            return true;
+        } else {
+            // Default
+            return shouldDeviceStayAwake(pluggedType);
+        }
+    }
+
+    /**
+     * Determine whether the bit value corresponding to {@code pluggedType} is set in
+     * the bit string mStayAwakeConditions. This determines whether the device should
+     * stay awake based on the current plugged type.
+     *
+     * @param pluggedType the type of plug (USB, AC, or none) for which the check is
+     * being made
+     * @return {@code true} if {@code pluggedType} indicates that the device is
+     * supposed to stay awake, {@code false} otherwise.
+     */
+    private boolean shouldDeviceStayAwake(int pluggedType) {
+        return (mStayAwakeConditions & pluggedType) != 0;
+    }
+
+    private void updateBatteryWorkSource() {
+        mTmpWorkSource.clear();
+        if (mDeviceIdle) {
+            mLocks.updateWorkSource(mTmpWorkSource);
+        }
+        mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource);
+    }
+
+    class DefaultState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_SCREEN_ON:
+                    mAlarmManager.cancel(mIdleIntent);
+                    mScreenOff = false;
+                    mDeviceIdle = false;
+                    updateBatteryWorkSource();
+                    break;
+                case CMD_SCREEN_OFF:
+                    mScreenOff = true;
+                    /*
+                    * Set a timer to put Wi-Fi to sleep, but only if the screen is off
+                    * AND the "stay on while plugged in" setting doesn't match the
+                    * current power conditions (i.e, not plugged in, plugged in to USB,
+                    * or plugged in to AC).
+                    */
+                    if (!shouldWifiStayAwake(mPluggedType)) {
+                        //Delayed shutdown if wifi is connected
+                        if (mNetworkInfo.getDetailedState() ==
+                                NetworkInfo.DetailedState.CONNECTED) {
+                            if (DBG) Slog.d(TAG, "set idle timer: " + mIdleMillis + " ms");
+                            mAlarmManager.set(AlarmManager.RTC_WAKEUP,
+                                    System.currentTimeMillis() + mIdleMillis, mIdleIntent);
+                        } else {
+                            sendMessage(CMD_DEVICE_IDLE);
+                        }
+                    }
+                    break;
+                case CMD_DEVICE_IDLE:
+                    mDeviceIdle = true;
+                    updateBatteryWorkSource();
+                    break;
+                case CMD_BATTERY_CHANGED:
+                    /*
+                    * Set a timer to put Wi-Fi to sleep, but only if the screen is off
+                    * AND we are transitioning from a state in which the device was supposed
+                    * to stay awake to a state in which it is not supposed to stay awake.
+                    * If "stay awake" state is not changing, we do nothing, to avoid resetting
+                    * the already-set timer.
+                    */
+                    int pluggedType = msg.arg1;
+                    if (DBG) Slog.d(TAG, "battery changed pluggedType: " + pluggedType);
+                    if (mScreenOff && shouldWifiStayAwake(mPluggedType) &&
+                            !shouldWifiStayAwake(pluggedType)) {
+                        long triggerTime = System.currentTimeMillis() + mIdleMillis;
+                        if (DBG) Slog.d(TAG, "set idle timer for " + mIdleMillis + "ms");
+                        mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent);
+                    }
+
+                    mPluggedType = pluggedType;
+                    break;
+                case CMD_SET_AP:
+                case CMD_SCAN_ALWAYS_MODE_CHANGED:
+                case CMD_LOCKS_CHANGED:
+                case CMD_WIFI_TOGGLED:
+                case CMD_AIRPLANE_TOGGLED:
+                case CMD_EMERGENCY_MODE_CHANGED:
+                    break;
+                default:
+                    throw new RuntimeException("WifiController.handleMessage " + msg.what);
+            }
+            return HANDLED;
+        }
+
+    }
+
+    class ApStaDisabledState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setSupplicantRunning(false);
+        }
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_WIFI_TOGGLED:
+                case CMD_AIRPLANE_TOGGLED:
+                    if (mSettingsStore.isWifiToggleEnabled()) {
+                        if (mDeviceIdle == false) {
+                            transitionTo(mDeviceActiveState);
+                        } else {
+                            checkLocksAndTransitionWhenDeviceIdle();
+                        }
+                    }
+                    break;
+                case CMD_SCAN_ALWAYS_MODE_CHANGED:
+                    if (mSettingsStore.isScanAlwaysAvailable()) {
+                        transitionTo(mStaDisabledWithScanState);
+                    }
+                    break;
+                case CMD_SET_AP:
+                    if (msg.arg1 == 1) {
+                        mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj,
+                                true);
+                        transitionTo(mApEnabledState);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+
+    }
+
+    class StaEnabledState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setSupplicantRunning(true);
+        }
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_WIFI_TOGGLED:
+                    if (! mSettingsStore.isWifiToggleEnabled()) {
+                        if (mSettingsStore.isScanAlwaysAvailable()) {
+                            transitionTo(mStaDisabledWithScanState);
+                        } else {
+                            transitionTo(mApStaDisabledState);
+                        }
+                    }
+                    break;
+                case CMD_AIRPLANE_TOGGLED:
+                    /* When wi-fi is turned off due to airplane,
+                    * disable entirely (including scan)
+                    */
+                    if (! mSettingsStore.isWifiToggleEnabled()) {
+                        transitionTo(mApStaDisabledState);
+                    }
+                    break;
+                case CMD_EMERGENCY_MODE_CHANGED:
+                    if (msg.arg1 == 1) {
+                        transitionTo(mEcmState);
+                        break;
+                    }
+                default:
+                    return NOT_HANDLED;
+
+            }
+            return HANDLED;
+        }
+    }
+
+    class StaDisabledWithScanState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setSupplicantRunning(true);
+            mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_WITH_WIFI_OFF_MODE);
+            mWifiStateMachine.setDriverStart(true);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_WIFI_TOGGLED:
+                    if (mSettingsStore.isWifiToggleEnabled()) {
+                        if (mDeviceIdle == false) {
+                            transitionTo(mDeviceActiveState);
+                        } else {
+                            checkLocksAndTransitionWhenDeviceIdle();
+                        }
+                    }
+                    break;
+                case CMD_AIRPLANE_TOGGLED:
+                    if (mSettingsStore.isAirplaneModeOn() &&
+                            ! mSettingsStore.isWifiToggleEnabled()) {
+                        transitionTo(mApStaDisabledState);
+                    }
+                case CMD_SCAN_ALWAYS_MODE_CHANGED:
+                    if (! mSettingsStore.isScanAlwaysAvailable()) {
+                        transitionTo(mApStaDisabledState);
+                    }
+                    break;
+                case CMD_SET_AP:
+                    // Before starting tethering, turn off supplicant for scan mode
+                    if (msg.arg1 == 1) {
+                        deferMessage(msg);
+                        transitionTo(mApStaDisabledState);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+    }
+
+    class ApEnabledState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_AIRPLANE_TOGGLED:
+                    if (mSettingsStore.isAirplaneModeOn()) {
+                        mWifiStateMachine.setHostApRunning(null, false);
+                        transitionTo(mApStaDisabledState);
+                    }
+                    break;
+                case CMD_SET_AP:
+                    if (msg.arg1 == 0) {
+                        mWifiStateMachine.setHostApRunning(null, false);
+                        transitionTo(mApStaDisabledState);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+    }
+
+    class EcmState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setSupplicantRunning(false);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (msg.what == CMD_EMERGENCY_MODE_CHANGED && msg.arg1 == 0) {
+                if (mSettingsStore.isWifiToggleEnabled()) {
+                    if (mDeviceIdle == false) {
+                        transitionTo(mDeviceActiveState);
+                    } else {
+                        checkLocksAndTransitionWhenDeviceIdle();
+                    }
+                } else if (mSettingsStore.isScanAlwaysAvailable()) {
+                    transitionTo(mStaDisabledWithScanState);
+                } else {
+                    transitionTo(mApStaDisabledState);
+                }
+                return HANDLED;
+            } else {
+                return NOT_HANDLED;
+            }
+        }
+    }
+
+    /* Parent: StaEnabledState */
+    class DeviceActiveState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+            mWifiStateMachine.setDriverStart(true);
+            mWifiStateMachine.setHighPerfModeEnabled(false);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (msg.what == CMD_DEVICE_IDLE) {
+                checkLocksAndTransitionWhenDeviceIdle();
+                // We let default state handle the rest of work
+            }
+            return NOT_HANDLED;
+        }
+    }
+
+    /* Parent: StaEnabledState */
+    class DeviceInactiveState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_LOCKS_CHANGED:
+                    checkLocksAndTransitionWhenDeviceIdle();
+                    updateBatteryWorkSource();
+                    return HANDLED;
+                case CMD_SCREEN_ON:
+                    transitionTo(mDeviceActiveState);
+                    // More work in default state
+                    return NOT_HANDLED;
+                default:
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a scan only lock. */
+    class ScanOnlyLockHeldState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_MODE);
+            mWifiStateMachine.setDriverStart(true);
+        }
+    }
+
+    /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a full lock. */
+    class FullLockHeldState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+            mWifiStateMachine.setDriverStart(true);
+            mWifiStateMachine.setHighPerfModeEnabled(false);
+        }
+    }
+
+    /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a high perf lock. */
+    class FullHighPerfLockHeldState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+            mWifiStateMachine.setDriverStart(true);
+            mWifiStateMachine.setHighPerfModeEnabled(true);
+        }
+    }
+
+    /* Parent: DeviceInactiveState. Device is inactive and no app is holding a wifi lock. */
+    class NoLockHeldState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setDriverStart(false);
+        }
+    }
+
+    private void checkLocksAndTransitionWhenDeviceIdle() {
+        if (mLocks.hasLocks()) {
+            switch (mLocks.getStrongestLockMode()) {
+                case WIFI_MODE_FULL:
+                    transitionTo(mFullLockHeldState);
+                    break;
+                case WIFI_MODE_FULL_HIGH_PERF:
+                    transitionTo(mFullHighPerfLockHeldState);
+                    break;
+                case WIFI_MODE_SCAN_ONLY:
+                    transitionTo(mScanOnlyLockHeldState);
+                    break;
+                default:
+                    loge("Illegal lock " + mLocks.getStrongestLockMode());
+            }
+        } else {
+            if (mSettingsStore.isScanAlwaysAvailable()) {
+                transitionTo(mScanOnlyLockHeldState);
+            } else {
+                transitionTo(mNoLockHeldState);
+            }
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        super.dump(fd, pw, args);
+
+        pw.println("mScreenOff " + mScreenOff);
+        pw.println("mDeviceIdle " + mDeviceIdle);
+        pw.println("mPluggedType " + mPluggedType);
+        pw.println("mIdleMillis " + mIdleMillis);
+        pw.println("mSleepPolicy " + mSleepPolicy);
+    }
+}
diff --git a/services/java/com/android/server/wifi/WifiService.java b/services/java/com/android/server/wifi/WifiService.java
index d675822..bc6bdaf 100644
--- a/services/java/com/android/server/wifi/WifiService.java
+++ b/services/java/com/android/server/wifi/WifiService.java
@@ -17,15 +17,14 @@
 package com.android.server.wifi;
 
 import android.app.ActivityManager;
-import android.app.AlarmManager;
 import android.app.AppOpsManager;
-import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.database.ContentObserver;
 import android.net.wifi.IWifiManager;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiInfo;
@@ -33,12 +32,9 @@
 import android.net.wifi.WifiStateMachine;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiWatchdogStateMachine;
-import android.net.ConnectivityManager;
 import android.net.DhcpInfo;
 import android.net.DhcpResults;
 import android.net.LinkAddress;
-import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
 import android.os.Binder;
@@ -63,39 +59,35 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import com.android.internal.R;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.util.AsyncChannel;
 import com.android.server.am.BatteryStatsService;
-import com.android.internal.R;
-
+import static com.android.server.wifi.WifiController.CMD_AIRPLANE_TOGGLED;
+import static com.android.server.wifi.WifiController.CMD_BATTERY_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_EMERGENCY_MODE_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_LOCKS_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_SCAN_ALWAYS_MODE_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_SCREEN_OFF;
+import static com.android.server.wifi.WifiController.CMD_SCREEN_ON;
+import static com.android.server.wifi.WifiController.CMD_SET_AP;
+import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
 /**
  * WifiService handles remote WiFi operation requests by implementing
  * the IWifiManager interface.
  *
  * @hide
  */
-//TODO: Clean up multiple locks and implement WifiService
-// as a SM to track soft AP/client/adhoc bring up based
-// on device idle state, airplane mode and boot.
-
 public final class WifiService extends IWifiManager.Stub {
     private static final String TAG = "WifiService";
     private static final boolean DBG = false;
 
-    private final WifiStateMachine mWifiStateMachine;
+    final WifiStateMachine mWifiStateMachine;
 
     private final Context mContext;
 
-    private AlarmManager mAlarmManager;
-    private PendingIntent mIdleIntent;
-    private static final int IDLE_REQUEST = 0;
-    private boolean mScreenOff;
-    private boolean mDeviceIdle;
-    private boolean mEmergencyCallbackMode = false;
-    private int mPluggedType;
-
-    private final LockList mLocks = new LockList();
+    final LockList mLocks = new LockList();
     // some wifi lock statistics
     private int mFullHighPerfLocksAcquired;
     private int mFullHighPerfLocksReleased;
@@ -119,19 +111,7 @@
     /* Polls traffic stats and notifies clients */
     private WifiTrafficPoller mTrafficPoller;
     /* Tracks the persisted states for wi-fi & airplane mode */
-    private WifiSettingsStore mSettingsStore;
-
-    /**
-     * See {@link Settings.Global#WIFI_IDLE_MS}. This is the default value if a
-     * Settings.Global value is not present. This timeout value is chosen as
-     * the approximate point at which the battery drain caused by Wi-Fi
-     * being enabled but not active exceeds the battery drain caused by
-     * re-establishing a connection to the mobile data network.
-     */
-    private static final long DEFAULT_IDLE_MS = 15 * 60 * 1000; /* 15 minutes */
-
-    private static final String ACTION_DEVICE_IDLE =
-            "com.android.server.WifiManager.action.DEVICE_IDLE";
+    final WifiSettingsStore mSettingsStore;
 
     /* The work source (UID) that triggered the current WIFI scan, synchronized
      * on this */
@@ -139,8 +119,6 @@
 
     private boolean mIsReceiverRegistered = false;
 
-    NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
-
     /**
      * Asynchronous channel to WifiStateMachine
      */
@@ -195,7 +173,7 @@
                     break;
                 }
                 default: {
-                    Slog.d(TAG, "WifiServicehandler.handleMessage ignoring msg=" + msg);
+                    Slog.d(TAG, "ClientHandler.handleMessage ignoring msg=" + msg);
                     break;
                 }
             }
@@ -243,11 +221,6 @@
     }
     WifiStateMachineHandler mWifiStateMachineHandler;
 
-    /**
-     * Temporary for computing UIDS that are responsible for starting WIFI.
-     * Protected by mWifiStateTracker lock.
-     */
-    private final WorkSource mTmpWorkSource = new WorkSource();
     private WifiWatchdogStateMachine mWifiWatchdogStateMachine;
 
     public WifiService(Context context) {
@@ -260,20 +233,24 @@
         mBatteryStats = BatteryStatsService.getService();
         mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
 
-        mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
-        Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null);
-        mIdleIntent = PendingIntent.getBroadcast(mContext, IDLE_REQUEST, idleIntent, 0);
-
         mNotificationController = new WifiNotificationController(mContext, mWifiStateMachine);
         mTrafficPoller = new WifiTrafficPoller(mContext, mInterfaceName);
         mSettingsStore = new WifiSettingsStore(mContext);
 
+        HandlerThread wifiThread = new HandlerThread("WifiService");
+        wifiThread.start();
+        mClientHandler = new ClientHandler(wifiThread.getLooper());
+        mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper());
+        mWifiController = new WifiController(mContext, this, wifiThread.getLooper());
+        mWifiController.start();
+
+        registerForScanModeChange();
         mContext.registerReceiver(
                 new BroadcastReceiver() {
                     @Override
                     public void onReceive(Context context, Intent intent) {
                         if (mSettingsStore.handleAirplaneModeToggled()) {
-                            updateWifiState();
+                            mWifiController.sendMessage(CMD_AIRPLANE_TOGGLED);
                         }
                     }
                 },
@@ -289,13 +266,10 @@
                         }
                     }
                 }, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
-
-        HandlerThread wifiThread = new HandlerThread("WifiService");
-        wifiThread.start();
-        mClientHandler = new ClientHandler(wifiThread.getLooper());
-        mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper());
     }
 
+    private WifiController mWifiController;
+
     /** Tell battery stats about a new WIFI scan */
     private void noteScanStart() {
         WorkSource scanWorkSource = null;
@@ -342,7 +316,7 @@
      */
     public void checkAndStartWifi() {
         /* Check if wi-fi needs to be enabled */
-        boolean wifiEnabled = mSettingsStore.shouldWifiBeEnabled();
+        boolean wifiEnabled = mSettingsStore.isWifiToggleEnabled();
         Slog.i(TAG, "WifiService starting up with Wi-Fi " +
                 (wifiEnabled ? "enabled" : "disabled"));
 
@@ -430,11 +404,7 @@
             Binder.restoreCallingIdentity(ident);
         }
 
-        if (enable) {
-            reportStartWorkSource();
-        }
-
-        mWifiStateMachine.setWifiEnabled(enable);
+        mWifiController.sendMessage(CMD_WIFI_TOGGLED);
 
         if (enable) {
             if (!mIsReceiverRegistered) {
@@ -470,7 +440,7 @@
      */
     public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
         enforceChangePermission();
-        mWifiStateMachine.setWifiApEnabled(wifiConfig, enabled);
+        mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget();
     }
 
     /**
@@ -507,6 +477,26 @@
     }
 
     /**
+     * @param enable {@code true} to enable, {@code false} to disable.
+     * @return {@code true} if the enable/disable operation was
+     *         started or is already in the queue.
+     */
+    public boolean isScanningAlwaysAvailable() {
+        // TODO: implement
+        return true;
+    }
+
+    /**
+     * @param enable {@code true} to enable, {@code false} to disable.
+     * @return {@code true} if the enable/disable operation was
+     *         started or is already in the queue.
+     */
+    public void setScanningAlwaysAvailable(boolean enable) {
+        // TODO: implement
+    }
+
+
+    /**
      * see {@link android.net.wifi.WifiManager#disconnect()}
      */
     public void disconnect() {
@@ -777,7 +767,7 @@
          * of WifiLock & device idle status unless wifi enabled status is toggled
          */
 
-        mWifiStateMachine.setDriverStart(true, mEmergencyCallbackMode);
+        mWifiStateMachine.setDriverStart(true);
         mWifiStateMachine.reconnectCommand();
     }
 
@@ -796,7 +786,7 @@
          * TODO: if a stop is issued, wifi is brought up only by startWifi
          * unless wifi enabled status is toggled
          */
-        mWifiStateMachine.setDriverStart(false, mEmergencyCallbackMode);
+        mWifiStateMachine.setDriverStart(false);
     }
 
     /**
@@ -848,175 +838,39 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-
-            long idleMillis =
-                Settings.Global.getLong(mContext.getContentResolver(),
-                                        Settings.Global.WIFI_IDLE_MS, DEFAULT_IDLE_MS);
-            int stayAwakeConditions =
-                Settings.Global.getInt(mContext.getContentResolver(),
-                                       Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
             if (action.equals(Intent.ACTION_SCREEN_ON)) {
-                if (DBG) {
-                    Slog.d(TAG, "ACTION_SCREEN_ON");
-                }
-                mAlarmManager.cancel(mIdleIntent);
-                mScreenOff = false;
-                setDeviceIdleAndUpdateWifi(false);
+                mWifiController.sendMessage(CMD_SCREEN_ON);
             } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
-                if (DBG) {
-                    Slog.d(TAG, "ACTION_SCREEN_OFF");
-                }
-                mScreenOff = true;
-                /*
-                 * Set a timer to put Wi-Fi to sleep, but only if the screen is off
-                 * AND the "stay on while plugged in" setting doesn't match the
-                 * current power conditions (i.e, not plugged in, plugged in to USB,
-                 * or plugged in to AC).
-                 */
-                if (!shouldWifiStayAwake(stayAwakeConditions, mPluggedType)) {
-                    //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 {
-                        setDeviceIdleAndUpdateWifi(true);
-                    }
-                }
-            } else if (action.equals(ACTION_DEVICE_IDLE)) {
-                setDeviceIdleAndUpdateWifi(true);
+                mWifiController.sendMessage(CMD_SCREEN_OFF);
             } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
-                /*
-                 * Set a timer to put Wi-Fi to sleep, but only if the screen is off
-                 * AND we are transitioning from a state in which the device was supposed
-                 * to stay awake to a state in which it is not supposed to stay awake.
-                 * If "stay awake" state is not changing, we do nothing, to avoid resetting
-                 * the already-set timer.
-                 */
                 int pluggedType = intent.getIntExtra("plugged", 0);
-                if (DBG) {
-                    Slog.d(TAG, "ACTION_BATTERY_CHANGED pluggedType: " + pluggedType);
-                }
-                if (mScreenOff && shouldWifiStayAwake(stayAwakeConditions, mPluggedType) &&
-                        !shouldWifiStayAwake(stayAwakeConditions, pluggedType)) {
-                    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);
-                }
-
-                mPluggedType = pluggedType;
+                mWifiController.sendMessage(CMD_BATTERY_CHANGED, pluggedType, 0, null);
             } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
                 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
                         BluetoothAdapter.STATE_DISCONNECTED);
                 mWifiStateMachine.sendBluetoothAdapterStateChange(state);
             } else if (action.equals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
-                mEmergencyCallbackMode = intent.getBooleanExtra("phoneinECMState", false);
-                updateWifiState();
+                boolean emergencyMode = intent.getBooleanExtra("phoneinECMState", false);
+                mWifiController.sendMessage(CMD_EMERGENCY_MODE_CHANGED, emergencyMode ? 1 : 0, 0);
             }
         }
-
-        /**
-         * Determines whether the Wi-Fi chipset should stay awake or be put to
-         * sleep. Looks at the setting for the sleep policy and the current
-         * conditions.
-         *
-         * @see #shouldDeviceStayAwake(int, int)
-         */
-        private boolean shouldWifiStayAwake(int stayAwakeConditions, int pluggedType) {
-            //Never sleep as long as the user has not changed the settings
-            int wifiSleepPolicy = Settings.Global.getInt(mContext.getContentResolver(),
-                    Settings.Global.WIFI_SLEEP_POLICY,
-                    Settings.Global.WIFI_SLEEP_POLICY_NEVER);
-
-            if (wifiSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER) {
-                // Never sleep
-                return true;
-            } else if ((wifiSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED) &&
-                    (pluggedType != 0)) {
-                // Never sleep while plugged, and we're plugged
-                return true;
-            } else {
-                // Default
-                return shouldDeviceStayAwake(stayAwakeConditions, pluggedType);
-            }
-        }
-
-        /**
-         * Determine whether the bit value corresponding to {@code pluggedType} is set in
-         * the bit string {@code stayAwakeConditions}. Because a {@code pluggedType} value
-         * of {@code 0} isn't really a plugged type, but rather an indication that the
-         * device isn't plugged in at all, there is no bit value corresponding to a
-         * {@code pluggedType} value of {@code 0}. That is why we shift by
-         * {@code pluggedType - 1} instead of by {@code pluggedType}.
-         * @param stayAwakeConditions a bit string specifying which "plugged types" should
-         * keep the device (and hence Wi-Fi) awake.
-         * @param pluggedType the type of plug (USB, AC, or none) for which the check is
-         * being made
-         * @return {@code true} if {@code pluggedType} indicates that the device is
-         * supposed to stay awake, {@code false} otherwise.
-         */
-        private boolean shouldDeviceStayAwake(int stayAwakeConditions, int pluggedType) {
-            return (stayAwakeConditions & pluggedType) != 0;
-        }
     };
 
-    private void setDeviceIdleAndUpdateWifi(boolean deviceIdle) {
-        mDeviceIdle = deviceIdle;
-        reportStartWorkSource();
-        updateWifiState();
-    }
-
-    private synchronized void reportStartWorkSource() {
-        mTmpWorkSource.clear();
-        if (mDeviceIdle) {
-            for (int i=0; i<mLocks.mList.size(); i++) {
-                mTmpWorkSource.add(mLocks.mList.get(i).mWorkSource);
+    /**
+     * Observes settings changes to scan always mode.
+     */
+    private void registerForScanModeChange() {
+        ContentObserver contentObserver = new ContentObserver(null) {
+            @Override
+            public void onChange(boolean selfChange) {
+                mSettingsStore.handleWifiScanAlwaysAvailableToggled();
+                mWifiController.sendMessage(CMD_SCAN_ALWAYS_MODE_CHANGED);
             }
-        }
-        mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource);
-    }
+        };
 
-    private void updateWifiState() {
-        boolean lockHeld = mLocks.hasLocks();
-        int strongestLockMode = WifiManager.WIFI_MODE_FULL;
-        boolean wifiShouldBeStarted;
-
-        if (mEmergencyCallbackMode) {
-            wifiShouldBeStarted = false;
-        } else {
-            wifiShouldBeStarted = !mDeviceIdle || lockHeld;
-        }
-
-        if (lockHeld) {
-            strongestLockMode = mLocks.getStrongestLockMode();
-        }
-        /* If device is not idle, lockmode cannot be scan only */
-        if (!mDeviceIdle && strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY) {
-            strongestLockMode = WifiManager.WIFI_MODE_FULL;
-        }
-
-        /* Disable tethering when airplane mode is enabled */
-        if (mSettingsStore.isAirplaneModeOn()) {
-            mWifiStateMachine.setWifiApEnabled(null, false);
-        }
-
-        if (mSettingsStore.shouldWifiBeEnabled()) {
-            if (wifiShouldBeStarted) {
-                reportStartWorkSource();
-                mWifiStateMachine.setWifiEnabled(true);
-                mWifiStateMachine.setScanOnlyMode(
-                        strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY);
-                mWifiStateMachine.setDriverStart(true, mEmergencyCallbackMode);
-                mWifiStateMachine.setHighPerfModeEnabled(strongestLockMode
-                        == WifiManager.WIFI_MODE_FULL_HIGH_PERF);
-            } else {
-                mWifiStateMachine.setDriverStart(false, mEmergencyCallbackMode);
-            }
-        } else {
-            mWifiStateMachine.setWifiEnabled(false);
-        }
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE),
+                false, contentObserver);
     }
 
     private void registerForBroadcasts() {
@@ -1024,7 +878,7 @@
         intentFilter.addAction(Intent.ACTION_SCREEN_ON);
         intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
         intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
-        intentFilter.addAction(ACTION_DEVICE_IDLE);
+        intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
         intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
         intentFilter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
         mContext.registerReceiver(mReceiver, intentFilter);
@@ -1043,12 +897,9 @@
         pw.println("Stay-awake conditions: " +
                 Settings.Global.getInt(mContext.getContentResolver(),
                                        Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0));
-        pw.println("mScreenOff " + mScreenOff);
-        pw.println("mDeviceIdle " + mDeviceIdle);
-        pw.println("mPluggedType " + mPluggedType);
-        pw.println("mEmergencyCallbackMode " + mEmergencyCallbackMode);
         pw.println("mMulticastEnabled " + mMulticastEnabled);
         pw.println("mMulticastDisabled " + mMulticastDisabled);
+        mWifiController.dump(fd, pw, args);
         mSettingsStore.dump(fd, pw, args);
         mNotificationController.dump(fd, pw, args);
         mTrafficPoller.dump(fd, pw, args);
@@ -1099,18 +950,18 @@
         }
     }
 
-    private class LockList {
+    class LockList {
         private List<WifiLock> mList;
 
         private LockList() {
             mList = new ArrayList<WifiLock>();
         }
 
-        private synchronized boolean hasLocks() {
+        synchronized boolean hasLocks() {
             return !mList.isEmpty();
         }
 
-        private synchronized int getStrongestLockMode() {
+        synchronized int getStrongestLockMode() {
             if (mList.isEmpty()) {
                 return WifiManager.WIFI_MODE_FULL;
             }
@@ -1126,6 +977,12 @@
             return WifiManager.WIFI_MODE_SCAN_ONLY;
         }
 
+        synchronized void updateWorkSource(WorkSource ws) {
+            for (int i = 0; i < mLocks.mList.size(); i++) {
+                ws.add(mLocks.mList.get(i).mWorkSource);
+            }
+        }
+
         private void addLock(WifiLock lock) {
             if (findLockByBinder(lock.mBinder) < 0) {
                 mList.add(lock);
@@ -1145,9 +1002,10 @@
 
         private int findLockByBinder(IBinder binder) {
             int size = mList.size();
-            for (int i = size - 1; i >= 0; i--)
+            for (int i = size - 1; i >= 0; i--) {
                 if (mList.get(i).mBinder == binder)
                     return i;
+            }
             return -1;
         }
 
@@ -1231,12 +1089,7 @@
                 ++mScanLocksAcquired;
                 break;
             }
-
-            // Be aggressive about adding new locks into the accounted state...
-            // we want to over-report rather than under-report.
-            reportStartWorkSource();
-
-            updateWifiState();
+            mWifiController.sendMessage(CMD_LOCKS_CHANGED);
             return true;
         } catch (RemoteException e) {
             return false;
@@ -1303,11 +1156,8 @@
                         ++mScanLocksReleased;
                         break;
                 }
+                mWifiController.sendMessage(CMD_LOCKS_CHANGED);
             }
-
-            // TODO - should this only happen if you hadLock?
-            updateWifiState();
-
         } catch (RemoteException e) {
         } finally {
             Binder.restoreCallingIdentity(ident);
diff --git a/services/java/com/android/server/wifi/WifiSettingsStore.java b/services/java/com/android/server/wifi/WifiSettingsStore.java
index d7c8752..3ff8061 100644
--- a/services/java/com/android/server/wifi/WifiSettingsStore.java
+++ b/services/java/com/android/server/wifi/WifiSettingsStore.java
@@ -37,7 +37,11 @@
     private int mPersistWifiState = WIFI_DISABLED;
     /* Tracks current airplane mode state */
     private boolean mAirplaneModeOn = false;
-    /* Tracks whether wifi is enabled from WifiStateMachine's perspective */
+
+    /* Tracks the setting of scan being available even when wi-fi is turned off
+     */
+    private boolean mScanAlwaysAvailable;
+
     private final Context mContext;
 
     /* Tracks if we have checked the saved wi-fi state after boot */
@@ -47,9 +51,10 @@
         mContext = context;
         mAirplaneModeOn = getPersistedAirplaneModeOn();
         mPersistWifiState = getPersistedWifiState();
+        mScanAlwaysAvailable = getPersistedScanAlwaysAvailable();
     }
 
-    synchronized boolean shouldWifiBeEnabled() {
+    synchronized boolean isWifiToggleEnabled() {
         if (!mCheckSavedStateAtBoot) {
             mCheckSavedStateAtBoot = true;
             if (testAndClearWifiSavedState()) return true;
@@ -70,6 +75,10 @@
        return mAirplaneModeOn;
     }
 
+    synchronized boolean isScanAlwaysAvailable() {
+        return mScanAlwaysAvailable;
+    }
+
     synchronized boolean handleWifiToggled(boolean wifiEnabled) {
         // Can Wi-Fi be toggled in airplane mode ?
         if (mAirplaneModeOn && !isAirplaneToggleable()) {
@@ -114,6 +123,10 @@
         return true;
     }
 
+    synchronized void handleWifiScanAlwaysAvailableToggled() {
+        mScanAlwaysAvailable = getPersistedScanAlwaysAvailable();
+    }
+
     void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("mPersistWifiState " + mPersistWifiState);
         pw.println("mAirplaneModeOn " + mAirplaneModeOn);
@@ -175,4 +188,10 @@
         return Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
     }
+
+    private boolean getPersistedScanAlwaysAvailable() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE,
+                0) == 1;
+    }
 }
diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
index fbdd333..dffb617 100644
--- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
+++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 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.
@@ -32,6 +32,7 @@
 import android.test.InstrumentationTestRunner;
 import android.util.Log;
 
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -48,16 +49,22 @@
 public class AppLaunch extends InstrumentationTestCase {
 
     private static final int JOIN_TIMEOUT = 10000;
-    private static final String TAG = "AppLaunch";
+    private static final String TAG = AppLaunch.class.getSimpleName();
     private static final String KEY_APPS = "apps";
+    private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations";
+    private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 7500; //7.5s to allow app to idle
+    private static final int POST_LAUNCH_IDLE_TIMEOUT = 750; //750ms idle for non initial launches
+    private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 2000; //2s between launching apps
 
     private Map<String, Intent> mNameToIntent;
     private Map<String, String> mNameToProcess;
     private Map<String, String> mNameToResultKey;
-
+    private Map<String, Long> mNameToLaunchTime;
     private IActivityManager mAm;
+    private int mLaunchIterations = 10;
+    private Bundle mResult = new Bundle();
 
-    public void testMeasureStartUpTime() throws RemoteException {
+    public void testMeasureStartUpTime() throws RemoteException, NameNotFoundException {
         InstrumentationTestRunner instrumentation =
                 (InstrumentationTestRunner)getInstrumentation();
         Bundle args = instrumentation.getArguments();
@@ -66,25 +73,59 @@
         createMappings();
         parseArgs(args);
 
-        Bundle results = new Bundle();
+        // do initial app launch, without force stopping
         for (String app : mNameToResultKey.keySet()) {
-            try {
-                startApp(app, results);
-                sleep(750);
-                closeApp(app);
-                sleep(2000);
-            } catch (NameNotFoundException e) {
-                Log.i(TAG, "Application " + app + " not found");
+            long launchTime = startApp(app, false);
+            if (launchTime <=0 ) {
+                mNameToLaunchTime.put(app, -1L);
+                // simply pass the app if launch isn't successful
+                // error should have already been logged by startApp
+                continue;
             }
-
+            sleep(INITIAL_LAUNCH_IDLE_TIMEOUT);
+            closeApp(app, false);
+            sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT);
         }
-        instrumentation.sendStatus(0, results);
+        // do the real app launch now
+        for (int i = 0; i < mLaunchIterations; i++) {
+            for (String app : mNameToResultKey.keySet()) {
+                long totalLaunchTime = mNameToLaunchTime.get(app);
+                long launchTime = 0;
+                if (totalLaunchTime < 0) {
+                    // skip if the app has previous failures
+                    continue;
+                }
+                launchTime = startApp(app, true);
+                if (launchTime <= 0) {
+                    // if it fails once, skip the rest of the launches
+                    mNameToLaunchTime.put(app, -1L);
+                    continue;
+                }
+                totalLaunchTime += launchTime;
+                mNameToLaunchTime.put(app, totalLaunchTime);
+                sleep(POST_LAUNCH_IDLE_TIMEOUT);
+                closeApp(app, true);
+                sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT);
+            }
+        }
+        for (String app : mNameToResultKey.keySet()) {
+            long totalLaunchTime = mNameToLaunchTime.get(app);
+            if (totalLaunchTime != -1) {
+                mResult.putDouble(mNameToResultKey.get(app),
+                        ((double) totalLaunchTime) / mLaunchIterations);
+            }
+        }
+        instrumentation.sendStatus(0, mResult);
     }
 
     private void parseArgs(Bundle args) {
         mNameToResultKey = new LinkedHashMap<String, String>();
+        mNameToLaunchTime = new HashMap<String, Long>();
+        String launchIterations = args.getString(KEY_LAUNCH_ITERATIONS);
+        if (launchIterations != null) {
+            mLaunchIterations = Integer.parseInt(launchIterations);
+        }
         String appList = args.getString(KEY_APPS);
-
         if (appList == null)
             return;
 
@@ -97,6 +138,7 @@
             }
 
             mNameToResultKey.put(parts[0], parts[1]);
+            mNameToLaunchTime.put(parts[0], 0L);
         }
     }
 
@@ -118,23 +160,26 @@
                         | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
                 startIntent.setClassName(ri.activityInfo.packageName,
                         ri.activityInfo.name);
-                mNameToIntent.put(ri.loadLabel(pm).toString(), startIntent);
-                mNameToProcess.put(ri.loadLabel(pm).toString(),
-                        ri.activityInfo.processName);
+                String appName = ri.loadLabel(pm).toString();
+                if (appName != null) {
+                    mNameToIntent.put(appName, startIntent);
+                    mNameToProcess.put(appName, ri.activityInfo.processName);
+                }
             }
         }
     }
 
-    private void startApp(String appName, Bundle results)
+    private long startApp(String appName, boolean forceStopBeforeLaunch)
             throws NameNotFoundException, RemoteException {
         Log.i(TAG, "Starting " + appName);
 
         Intent startIntent = mNameToIntent.get(appName);
         if (startIntent == null) {
             Log.w(TAG, "App does not exist: " + appName);
-            return;
+            mResult.putString(mNameToResultKey.get(appName), "App does not exist");
+            return -1;
         }
-        AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent);
+        AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent, forceStopBeforeLaunch);
         Thread t = new Thread(runnable);
         t.start();
         try {
@@ -143,27 +188,38 @@
             // ignore
         }
         WaitResult result = runnable.getResult();
-        if(t.isAlive() || (result != null && result.result != ActivityManager.START_SUCCESS)) {
+        // report error if any of the following is true:
+        // * launch thread is alive
+        // * result is not null, but:
+        //   * result is not START_SUCESS
+        //   * or in case of no force stop, result is not TASK_TO_FRONT either
+        if (t.isAlive() || (result != null
+                && ((result.result != ActivityManager.START_SUCCESS)
+                        && (!forceStopBeforeLaunch
+                                && result.result != ActivityManager.START_TASK_TO_FRONT)))) {
             Log.w(TAG, "Assuming app " + appName + " crashed.");
-            reportError(appName, mNameToProcess.get(appName), results);
-            return;
+            reportError(appName, mNameToProcess.get(appName));
+            return -1;
         }
-        results.putString(mNameToResultKey.get(appName), String.valueOf(result.thisTime));
+        return result.thisTime;
     }
 
-    private void closeApp(String appName) {
+    private void closeApp(String appName, boolean forceStopApp) {
         Intent homeIntent = new Intent(Intent.ACTION_MAIN);
         homeIntent.addCategory(Intent.CATEGORY_HOME);
         homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
         getInstrumentation().getContext().startActivity(homeIntent);
-        Intent startIntent = mNameToIntent.get(appName);
-        if (startIntent != null) {
-            String packageName = startIntent.getComponent().getPackageName();
-            try {
-                mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Error closing app", e);
+        sleep(POST_LAUNCH_IDLE_TIMEOUT);
+        if (forceStopApp) {
+            Intent startIntent = mNameToIntent.get(appName);
+            if (startIntent != null) {
+                String packageName = startIntent.getComponent().getPackageName();
+                try {
+                    mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Error closing app", e);
+                }
             }
         }
     }
@@ -176,7 +232,7 @@
         }
     }
 
-    private void reportError(String appName, String processName, Bundle results) {
+    private void reportError(String appName, String processName) {
         ActivityManager am = (ActivityManager) getInstrumentation()
                 .getContext().getSystemService(Context.ACTIVITY_SERVICE);
         List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState();
@@ -186,12 +242,12 @@
                     continue;
 
                 Log.w(TAG, appName + " crashed: " + crash.shortMsg);
-                results.putString(mNameToResultKey.get(appName), crash.shortMsg);
+                mResult.putString(mNameToResultKey.get(appName), crash.shortMsg);
                 return;
             }
         }
 
-        results.putString(mNameToResultKey.get(appName),
+        mResult.putString(mNameToResultKey.get(appName),
                 "Crashed for unknown reason");
         Log.w(TAG, appName
                 + " not found in process list, most likely it is crashed");
@@ -200,8 +256,11 @@
     private class AppLaunchRunnable implements Runnable {
         private Intent mLaunchIntent;
         private IActivityManager.WaitResult mResult;
-        public AppLaunchRunnable(Intent intent) {
+        private boolean mForceStopBeforeLaunch;
+
+        public AppLaunchRunnable(Intent intent, boolean forceStopBeforeLaunch) {
             mLaunchIntent = intent;
+            mForceStopBeforeLaunch = forceStopBeforeLaunch;
         }
 
         public IActivityManager.WaitResult getResult() {
@@ -211,7 +270,9 @@
         public void run() {
             try {
                 String packageName = mLaunchIntent.getComponent().getPackageName();
-                mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
+                if (mForceStopBeforeLaunch) {
+                    mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT);
+                }
                 String mimeType = mLaunchIntent.getType();
                 if (mimeType == null && mLaunchIntent.getData() != null
                         && "content".equals(mLaunchIntent.getData().getScheme())) {
diff --git a/tests/BiDiTests/res/layout/canvas.xml b/tests/BiDiTests/res/layout/canvas.xml
new file mode 100644
index 0000000..0319a83
--- /dev/null
+++ b/tests/BiDiTests/res/layout/canvas.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/canvas"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent">
+
+    <LinearLayout android:orientation="vertical"
+                  android:layout_width="match_parent"
+                  android:layout_height="match_parent">
+
+        <SeekBar android:id="@+id/seekbar"
+                 android:layout_height="wrap_content"
+                 android:layout_width="match_parent"
+                />
+
+        <view class="com.android.bidi.BiDiTestView"
+              android:id="@+id/testview"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:background="#FF0000"
+                />
+
+    </LinearLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
index b88a885..209597e 100644
--- a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
@@ -101,6 +101,7 @@
 
         addItem(result, "Basic", BiDiTestBasic.class, R.id.basic);
 
+        addItem(result, "Canvas", BiDiTestCanvas.class, R.id.canvas);
         addItem(result, "Canvas2", BiDiTestCanvas2.class, R.id.canvas2);
 
         addItem(result, "TextView LTR", BiDiTestTextViewLtr.class, R.id.textview_ltr);
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestCanvas.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestCanvas.java
new file mode 100644
index 0000000..33ed731
--- /dev/null
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestCanvas.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bidi;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.SeekBar;
+
+import static com.android.bidi.BiDiTestConstants.FONT_MAX_SIZE;
+import static com.android.bidi.BiDiTestConstants.FONT_MIN_SIZE;
+
+public class BiDiTestCanvas extends Fragment {
+
+    static final int INIT_TEXT_SIZE = (FONT_MAX_SIZE - FONT_MIN_SIZE) / 2;
+
+    private BiDiTestView testView;
+    private SeekBar textSizeSeekBar;
+    private View currentView;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        currentView = inflater.inflate(R.layout.canvas, container, false);
+        return currentView;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        testView = (BiDiTestView) currentView.findViewById(R.id.testview);
+        testView.setCurrentTextSize(INIT_TEXT_SIZE);
+
+        textSizeSeekBar = (SeekBar) currentView.findViewById(R.id.seekbar);
+        textSizeSeekBar.setProgress(INIT_TEXT_SIZE);
+        textSizeSeekBar.setMax(FONT_MAX_SIZE - FONT_MIN_SIZE);
+
+        textSizeSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                testView.setCurrentTextSize(FONT_MIN_SIZE + progress);
+            }
+
+            public void onStartTrackingTouch(SeekBar seekBar) {
+            }
+
+            public void onStopTrackingTouch(SeekBar seekBar) {
+            }
+        });
+    }
+}
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java
new file mode 100644
index 0000000..0b1974a
--- /dev/null
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bidi;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.text.TextPaint;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+public class BiDiTestView extends View {
+
+    private static final String TAG = "BiDiTestView";
+
+    private static final int BORDER_PADDING = 4;
+    private static final int TEXT_PADDING = 16;
+    private static final int TEXT_SIZE = 16;
+    private static final int ORIGIN = 80;
+
+    private static final float DEFAULT_ITALIC_SKEW_X = -0.25f;
+
+    private Rect rect = new Rect();
+
+    private String NORMAL_TEXT;
+    private String NORMAL_LONG_TEXT;
+    private String NORMAL_LONG_TEXT_2;
+    private String NORMAL_LONG_TEXT_3;
+    private String ITALIC_TEXT;
+    private String BOLD_TEXT;
+    private String BOLD_ITALIC_TEXT;
+    private String ARABIC_TEXT;
+    private String CHINESE_TEXT;
+    private String MIXED_TEXT_1;
+    private String HEBREW_TEXT;
+    private String RTL_TEXT;
+    private String THAI_TEXT;
+
+    private int currentTextSize;
+
+    public BiDiTestView(Context context) {
+        super(context);
+        init(context);
+    }
+
+    public BiDiTestView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context);
+    }
+
+    public BiDiTestView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init(context);
+    }
+
+    private void init(Context context) {
+        NORMAL_TEXT = context.getString(R.string.normal_text);
+        NORMAL_LONG_TEXT = context.getString(R.string.normal_long_text);
+        NORMAL_LONG_TEXT_2 = context.getString(R.string.normal_long_text_2);
+        NORMAL_LONG_TEXT_3 = context.getString(R.string.normal_long_text_3);
+        ITALIC_TEXT = context.getString(R.string.italic_text);
+        BOLD_TEXT = context.getString(R.string.bold_text);
+        BOLD_ITALIC_TEXT = context.getString(R.string.bold_italic_text);
+        ARABIC_TEXT = context.getString(R.string.arabic_text);
+        CHINESE_TEXT = context.getString(R.string.chinese_text);
+        MIXED_TEXT_1 = context.getString(R.string.mixed_text_1);
+        HEBREW_TEXT = context.getString(R.string.hebrew_text);
+        RTL_TEXT = context.getString(R.string.rtl);
+        THAI_TEXT = context.getString(R.string.pointer_location);
+    }
+
+    public void setCurrentTextSize(int size) {
+        currentTextSize = size;
+        invalidate();
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        drawInsideRect(canvas, new Paint(), Color.BLACK);
+
+        int deltaX = 0;
+
+        deltaX  = testString(canvas, NORMAL_TEXT, ORIGIN, ORIGIN,
+                false, false,  Paint.DIRECTION_LTR, currentTextSize);
+
+        deltaX += testString(canvas, ITALIC_TEXT, ORIGIN + deltaX, ORIGIN,
+                true, false,  Paint.DIRECTION_LTR, currentTextSize);
+
+        deltaX += testString(canvas, BOLD_TEXT, ORIGIN + deltaX, ORIGIN,
+                false, true,  Paint.DIRECTION_LTR, currentTextSize);
+
+        deltaX += testString(canvas, BOLD_ITALIC_TEXT, ORIGIN + deltaX, ORIGIN,
+                true, true,  Paint.DIRECTION_LTR, currentTextSize);
+
+        // Test with a long string
+        deltaX = testString(canvas, NORMAL_LONG_TEXT, ORIGIN, ORIGIN + 2 * currentTextSize,
+                false, false,  Paint.DIRECTION_LTR, currentTextSize);
+
+        // Test with a long string
+        deltaX = testString(canvas, NORMAL_LONG_TEXT_2, ORIGIN, ORIGIN + 4 * currentTextSize,
+                false, false,  Paint.DIRECTION_LTR, currentTextSize);
+
+        // Test with a long string
+        deltaX = testString(canvas, NORMAL_LONG_TEXT_3, ORIGIN, ORIGIN + 6 * currentTextSize,
+                false, false,  Paint.DIRECTION_LTR, currentTextSize);
+
+        // Test Arabic ligature
+        deltaX = testString(canvas, ARABIC_TEXT, ORIGIN, ORIGIN + 8 * currentTextSize,
+                false, false,  Paint.DIRECTION_RTL, currentTextSize);
+
+        // Test Chinese
+        deltaX = testString(canvas, CHINESE_TEXT, ORIGIN, ORIGIN + 10 * currentTextSize,
+                false, false,  Paint.DIRECTION_LTR, currentTextSize);
+
+        // Test Mixed (English and Arabic)
+        deltaX = testString(canvas, MIXED_TEXT_1, ORIGIN, ORIGIN + 12 * currentTextSize,
+                false, false,  Paint.DIRECTION_LTR, currentTextSize);
+
+        // Test Hebrew
+        deltaX = testString(canvas, RTL_TEXT, ORIGIN, ORIGIN + 14 * currentTextSize,
+                false, false,  Paint.DIRECTION_RTL, currentTextSize);
+
+        // Test Thai
+        deltaX = testString(canvas, THAI_TEXT, ORIGIN, ORIGIN + 16 * currentTextSize,
+                false, false,  Paint.DIRECTION_LTR, currentTextSize);
+    }
+
+    private int testString(Canvas canvas, String text, int x, int y,
+            boolean isItalic, boolean isBold, int dir, int textSize) {
+
+        TextPaint paint = new TextPaint();
+        paint.setAntiAlias(true);
+
+        // Set paint properties
+        boolean oldFakeBold = paint.isFakeBoldText();
+        paint.setFakeBoldText(isBold);
+
+        float oldTextSkewX = paint.getTextSkewX();
+        if (isItalic) {
+            paint.setTextSkewX(DEFAULT_ITALIC_SKEW_X);
+        }
+
+        paint.setTextSize(textSize);
+        paint.setColor(Color.WHITE);
+        canvas.drawText(text, x, y, paint);
+
+        int length = text.length();
+        float[] advances = new float[length];
+        float textWidthHB = paint.getTextRunAdvances(text, 0, length, 0, length, dir, advances, 0);
+        setPaintDir(paint, dir);
+        float textWidthICU = paint.getTextRunAdvances(text, 0, length, 0, length, dir, advances, 0,
+                1 /* use ICU */);
+
+        logAdvances(text, textWidthHB, textWidthICU, advances);
+        drawMetricsAroundText(canvas, x, y, textWidthHB, textWidthICU, textSize, Color.RED, Color.GREEN);
+
+        // Restore old paint properties
+        paint.setFakeBoldText(oldFakeBold);
+        paint.setTextSkewX(oldTextSkewX);
+
+        return (int) Math.ceil(textWidthHB) + TEXT_PADDING;
+    }
+
+    private void setPaintDir(Paint paint, int dir) {
+        Log.v(TAG, "Setting Paint dir=" + dir);
+        paint.setBidiFlags(dir);
+    }
+
+    private void drawInsideRect(Canvas canvas, Paint paint, int color) {
+        paint.setColor(color);
+        int width = getWidth();
+        int height = getHeight();
+        rect.set(BORDER_PADDING, BORDER_PADDING, width - BORDER_PADDING, height - BORDER_PADDING);
+        canvas.drawRect(rect, paint);
+    }
+
+    private void drawMetricsAroundText(Canvas canvas, int x, int y, float textWidthHB,
+            float textWidthICU, int textSize, int color, int colorICU) {
+        Paint paint = new Paint();
+        paint.setColor(color);
+        canvas.drawLine(x, y - textSize, x, y + 8, paint);
+        canvas.drawLine(x, y + 8, x + textWidthHB, y + 8, paint);
+        canvas.drawLine(x + textWidthHB, y - textSize, x + textWidthHB, y + 8, paint);
+        paint.setColor(colorICU);
+        canvas.drawLine(x + textWidthICU, y - textSize, x + textWidthICU, y + 8, paint);
+    }
+
+    private void logAdvances(String text, float textWidth, float textWidthICU, float[] advances) {
+        Log.v(TAG, "Advances for text: " + text + " total= " + textWidth + " - totalICU= " + textWidthICU);
+//        int length = advances.length;
+//        for(int n=0; n<length; n++){
+//            Log.v(TAG, "adv[" + n + "]=" + advances[n]);
+//        }
+    }
+}
diff --git a/tests/SharedLibrary/client/Android.mk b/tests/SharedLibrary/client/Android.mk
new file mode 100644
index 0000000..60ef92a
--- /dev/null
+++ b/tests/SharedLibrary/client/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_APK_LIBRARIES := SharedLibrary
+
+LOCAL_PACKAGE_NAME := SharedLibraryClient
+
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/SharedLibrary/client/AndroidManifest.xml b/tests/SharedLibrary/client/AndroidManifest.xml
new file mode 100644
index 0000000..c6a43c0
--- /dev/null
+++ b/tests/SharedLibrary/client/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.android.test.lib_client">
+    <application android:label="@string/app_title">
+        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="com.google.android.test.shared_library" />
+        <activity android:name="ActivityMain">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/SharedLibrary/client/res/values/strings.xml b/tests/SharedLibrary/client/res/values/strings.xml
new file mode 100644
index 0000000..3757a25
--- /dev/null
+++ b/tests/SharedLibrary/client/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<resources>
+    <string name="app_title">SharedLibrary client</string>
+</resources>
diff --git a/tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java b/tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java
new file mode 100644
index 0000000..d6121a5
--- /dev/null
+++ b/tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.test.lib_client;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+import com.google.android.test.shared_library.SharedLibraryMain;
+
+public class ActivityMain extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        TextView content = new TextView(this);
+        content.setText("Library version: " + SharedLibraryMain.getVersion(this) + "!");
+
+        SharedLibraryMain.ensureVersion(this, SharedLibraryMain.VERSION_BASE);
+        setContentView(content);
+    }
+}
diff --git a/tests/SharedLibrary/lib/Android.mk b/tests/SharedLibrary/lib/Android.mk
new file mode 100644
index 0000000..c19e23a
--- /dev/null
+++ b/tests/SharedLibrary/lib/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := SharedLibrary
+
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_PACKAGE)
diff --git a/tests/SharedLibrary/lib/AndroidManifest.xml b/tests/SharedLibrary/lib/AndroidManifest.xml
new file mode 100644
index 0000000..31fac20
--- /dev/null
+++ b/tests/SharedLibrary/lib/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.android.test.shared_library"
+    android:versionCode="2">
+    <application android:label="SharedLibrary">
+        <library android:name="com.google.android.test.shared_library" />
+        <activity android:name="ActivityMain">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/SharedLibrary/lib/res/values/strings.xml b/tests/SharedLibrary/lib/res/values/strings.xml
new file mode 100644
index 0000000..bbfb0b4
--- /dev/null
+++ b/tests/SharedLibrary/lib/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="upgrade_title">Upgrade required</string>
+    <string name="upgrade_body"><xliff:g id="app">%1$s</xliff:g> requires a newer version
+            of <xliff:g id="lib">%2$s</xliff:g> to run.</string>
+    <string name="upgrade_button">Upgrade</string>
+</resources>
diff --git a/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/ActivityMain.java b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/ActivityMain.java
new file mode 100644
index 0000000..895aced
--- /dev/null
+++ b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/ActivityMain.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.test.shared_library;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class ActivityMain extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        TextView content = new TextView(this);
+        content.setText("Dummy main entry for this apk; not really needed...");
+        setContentView(content);
+    }
+}
diff --git a/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/SharedLibraryMain.java b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/SharedLibraryMain.java
new file mode 100644
index 0000000..c1cd925
--- /dev/null
+++ b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/SharedLibraryMain.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.test.shared_library;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+
+public class SharedLibraryMain {
+    private static String LIBRARY_PACKAGE = "com.google.android.test.shared_library";
+
+    /**
+     * Base version of the library.
+     */
+    public static int VERSION_BASE = 1;
+
+    /**
+     * The second version of the library.
+     */
+    public static int VERSION_SECOND = 2;
+
+    public static int getVersion(Context context) {
+        PackageInfo pi = null;
+        try {
+            pi = context.getPackageManager().getPackageInfo(LIBRARY_PACKAGE, 0);
+            return pi.versionCode;
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new IllegalStateException("Can't find my package!", e);
+        }
+    }
+
+    public static void ensureVersion(Activity activity, int minVersion) {
+        if (getVersion(activity) >= minVersion) {
+            return;
+        }
+
+        // The current version of the library does not meet the required version.  Show
+        // a dialog to inform the user and have them update to the current version.
+        // Note that updating the library will be necessity mean killing the current
+        // application (so it can be re-started with the new version, so there is no
+        // reason to return a result here.
+        final Context context;
+        try {
+            context = activity.createPackageContext(LIBRARY_PACKAGE, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new IllegalStateException("Can't find my package!", e);
+        }
+
+        // Display the dialog.  Note that we don't need to deal with activity lifecycle
+        // stuff because if the activity gets recreated, it will first call through to
+        // ensureVersion(), causing us to either re-display the dialog if needed or let
+        // it now proceed.
+        final Resources res = context.getResources();
+        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+        builder.setTitle(res.getText(R.string.upgrade_title));
+        builder.setMessage(res.getString(R.string.upgrade_body,
+                activity.getApplicationInfo().loadLabel(activity.getPackageManager()),
+                context.getApplicationInfo().loadLabel(context.getPackageManager())));
+        builder.setPositiveButton(res.getText(R.string.upgrade_button),
+                new Dialog.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        // Launch play store.
+                    }
+                });
+        builder.show();
+    }
+}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index bef5824..e0684fb 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -71,6 +71,10 @@
 
     DhcpInfo getDhcpInfo();
 
+    boolean isScanningAlwaysAvailable();
+
+    void setScanningAlwaysAvailable(boolean enable);
+
     boolean acquireWifiLock(IBinder lock, int lockType, String tag, in WorkSource ws);
 
     void updateWifiLockWorkSource(IBinder lock, in WorkSource ws);
diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java
index eb2f74c..2385c24 100644
--- a/wifi/java/android/net/wifi/WifiConfigStore.java
+++ b/wifi/java/android/net/wifi/WifiConfigStore.java
@@ -395,6 +395,23 @@
         return ret;
     }
 
+    void disableAllNetworks() {
+        boolean networkDisabled = false;
+        for(WifiConfiguration config : mConfiguredNetworks.values()) {
+            if(config != null && config.status != Status.DISABLED) {
+                if(mWifiNative.disableNetwork(config.networkId)) {
+                    networkDisabled = true;
+                    config.status = Status.DISABLED;
+                } else {
+                    loge("Disable network failed on " + config.networkId);
+                }
+            }
+        }
+
+        if (networkDisabled) {
+            sendConfiguredNetworksChangedBroadcast();
+        }
+    }
     /**
      * Disable a network. Note that there is no saveConfig operation.
      * @param netId network to be disabled
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index ed5d22c..8cdfe03 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -134,7 +134,12 @@
     private boolean mEnableBackgroundScan = false;
     private int mRssiPollToken = 0;
     private int mReconnectCount = 0;
-    private boolean mIsScanMode = false;
+    /* 3 operational states for STA operation: CONNECT_MODE, SCAN_ONLY_MODE, SCAN_ONLY_WIFI_OFF_MODE
+    * In CONNECT_MODE, the STA can scan and connect to an access point
+    * In SCAN_ONLY_MODE, the STA can only scan for access points
+    * In SCAN_ONLY_WIFI_OFF_MODE, the STA can only scan for access points with wifi toggle being off
+    */
+    private int mOperationalMode = CONNECT_MODE;
     private boolean mScanResultIsPending = false;
     /* Tracks if state machine has received any screen state change broadcast yet.
      * We can miss one of these at boot.
@@ -285,8 +290,8 @@
     /* Supplicant commands after driver start*/
     /* Initiate a scan */
     static final int CMD_START_SCAN                       = BASE + 71;
-    /* Set scan mode. CONNECT_MODE or SCAN_ONLY_MODE */
-    static final int CMD_SET_SCAN_MODE                    = BASE + 72;
+    /* Set operational mode. CONNECT, SCAN ONLY, SCAN_ONLY with Wi-Fi off mode */
+    static final int CMD_SET_OPERATIONAL_MODE             = BASE + 72;
     /* Disconnect from a network */
     static final int CMD_DISCONNECT                       = BASE + 73;
     /* Reconnect to a network */
@@ -342,16 +347,13 @@
     public static final int CMD_DISABLE_P2P_REQ           = BASE + 132;
     public static final int CMD_DISABLE_P2P_RSP           = BASE + 133;
 
-    private static final int CONNECT_MODE   = 1;
-    private static final int SCAN_ONLY_MODE = 2;
+    public static final int CONNECT_MODE                   = 1;
+    public static final int SCAN_ONLY_MODE                 = 2;
+    public static final int SCAN_ONLY_WITH_WIFI_OFF_MODE   = 3;
 
     private static final int SUCCESS = 1;
     private static final int FAILURE = -1;
 
-    /* Phone in emergency call back mode */
-    private static final int IN_ECM_STATE = 1;
-    private static final int NOT_IN_ECM_STATE = 0;
-
     /**
      * The maximum number of times we will retry a connection to an access point
      * for which we have failed in acquiring an IP address from DHCP. A value of
@@ -496,9 +498,6 @@
      */
     private final AtomicInteger mWifiApState = new AtomicInteger(WIFI_AP_STATE_DISABLED);
 
-    private final AtomicInteger mLastEnableUid = new AtomicInteger(Process.myUid());
-    private final AtomicInteger mLastApEnableUid = new AtomicInteger(Process.myUid());
-
     private static final int SCAN_REQUEST = 0;
     private static final String ACTION_START_SCAN =
         "com.android.server.WifiManager.action.START_SCAN";
@@ -623,7 +622,7 @@
                     @Override
                     public void onReceive(Context context, Intent intent) {
                        int counter = intent.getIntExtra(DELAYED_STOP_COUNTER, 0);
-                       sendMessage(obtainMessage(CMD_DELAYED_STOP_DRIVER, counter, 0));
+                       sendMessage(CMD_DELAYED_STOP_DRIVER, counter, 0);
                     }
                 },
                 new IntentFilter(ACTION_DELAYED_DRIVER_STOP));
@@ -714,8 +713,7 @@
     /**
      * TODO: doc
      */
-    public void setWifiEnabled(boolean enable) {
-        mLastEnableUid.set(Binder.getCallingUid());
+    public void setSupplicantRunning(boolean enable) {
         if (enable) {
             sendMessage(CMD_START_SUPPLICANT);
         } else {
@@ -726,10 +724,9 @@
     /**
      * TODO: doc
      */
-    public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enable) {
-        mLastApEnableUid.set(Binder.getCallingUid());
+    public void setHostApRunning(WifiConfiguration wifiConfig, boolean enable) {
         if (enable) {
-            sendMessage(obtainMessage(CMD_START_AP, wifiConfig));
+            sendMessage(CMD_START_AP, wifiConfig);
         } else {
             sendMessage(CMD_STOP_AP);
         }
@@ -818,27 +815,23 @@
     /**
      * TODO: doc
      */
-    public void setDriverStart(boolean enable, boolean ecm) {
+    public void setDriverStart(boolean enable) {
         if (enable) {
             sendMessage(CMD_START_DRIVER);
         } else {
-            sendMessage(obtainMessage(CMD_STOP_DRIVER, ecm ? IN_ECM_STATE : NOT_IN_ECM_STATE, 0));
+            sendMessage(CMD_STOP_DRIVER);
         }
     }
 
     public void captivePortalCheckComplete() {
-        sendMessage(obtainMessage(CMD_CAPTIVE_CHECK_COMPLETE));
+        sendMessage(CMD_CAPTIVE_CHECK_COMPLETE);
     }
 
     /**
      * TODO: doc
      */
-    public void setScanOnlyMode(boolean enable) {
-        if (enable) {
-            sendMessage(obtainMessage(CMD_SET_SCAN_MODE, SCAN_ONLY_MODE, 0));
-        } else {
-            sendMessage(obtainMessage(CMD_SET_SCAN_MODE, CONNECT_MODE, 0));
-        }
+    public void setOperationalMode(int mode) {
+        sendMessage(CMD_SET_OPERATIONAL_MODE, mode, 0);
     }
 
     /**
@@ -941,7 +934,7 @@
      * @param bssid BSSID of the network
      */
     public void addToBlacklist(String bssid) {
-        sendMessage(obtainMessage(CMD_BLACKLIST_NETWORK, bssid));
+        sendMessage(CMD_BLACKLIST_NETWORK, bssid);
     }
 
     /**
@@ -949,15 +942,15 @@
      *
      */
     public void clearBlacklist() {
-        sendMessage(obtainMessage(CMD_CLEAR_BLACKLIST));
+        sendMessage(CMD_CLEAR_BLACKLIST);
     }
 
     public void enableRssiPolling(boolean enabled) {
-       sendMessage(obtainMessage(CMD_ENABLE_RSSI_POLL, enabled ? 1 : 0, 0));
+       sendMessage(CMD_ENABLE_RSSI_POLL, enabled ? 1 : 0, 0);
     }
 
     public void enableBackgroundScanCommand(boolean enabled) {
-       sendMessage(obtainMessage(CMD_ENABLE_BACKGROUND_SCAN, enabled ? 1 : 0, 0));
+       sendMessage(CMD_ENABLE_BACKGROUND_SCAN, enabled ? 1 : 0, 0);
     }
 
     public void enableAllNetworks() {
@@ -969,7 +962,7 @@
      */
     public void startFilteringMulticastV4Packets() {
         mFilteringMulticastV4Packets.set(true);
-        sendMessage(obtainMessage(CMD_START_PACKET_FILTERING, MULTICAST_V4, 0));
+        sendMessage(CMD_START_PACKET_FILTERING, MULTICAST_V4, 0);
     }
 
     /**
@@ -977,21 +970,21 @@
      */
     public void stopFilteringMulticastV4Packets() {
         mFilteringMulticastV4Packets.set(false);
-        sendMessage(obtainMessage(CMD_STOP_PACKET_FILTERING, MULTICAST_V4, 0));
+        sendMessage(CMD_STOP_PACKET_FILTERING, MULTICAST_V4, 0);
     }
 
     /**
      * Start filtering Multicast v4 packets
      */
     public void startFilteringMulticastV6Packets() {
-        sendMessage(obtainMessage(CMD_START_PACKET_FILTERING, MULTICAST_V6, 0));
+        sendMessage(CMD_START_PACKET_FILTERING, MULTICAST_V6, 0);
     }
 
     /**
      * Stop filtering Multicast v4 packets
      */
     public void stopFilteringMulticastV6Packets() {
-        sendMessage(obtainMessage(CMD_STOP_PACKET_FILTERING, MULTICAST_V6, 0));
+        sendMessage(CMD_STOP_PACKET_FILTERING, MULTICAST_V6, 0);
     }
 
     /**
@@ -1001,7 +994,7 @@
      * @param enable true if enable, false otherwise
      */
     public void setHighPerfModeEnabled(boolean enable) {
-        sendMessage(obtainMessage(CMD_SET_HIGH_PERF_MODE, enable ? 1 : 0, 0));
+        sendMessage(CMD_SET_HIGH_PERF_MODE, enable ? 1 : 0, 0);
     }
 
     /**
@@ -1015,7 +1008,7 @@
                     Settings.Global.WIFI_COUNTRY_CODE,
                     countryCode);
         }
-        sendMessage(obtainMessage(CMD_SET_COUNTRY_CODE, countryCode));
+        sendMessage(CMD_SET_COUNTRY_CODE, countryCode);
     }
 
     /**
@@ -1029,7 +1022,7 @@
                     Settings.Global.WIFI_FREQUENCY_BAND,
                     band);
         }
-        sendMessage(obtainMessage(CMD_SET_FREQUENCY_BAND, band, 0));
+        sendMessage(CMD_SET_FREQUENCY_BAND, band, 0);
     }
 
     /**
@@ -1050,7 +1043,7 @@
      * Send a message indicating bluetooth adapter connection state changed
      */
     public void sendBluetoothAdapterStateChange(int state) {
-        sendMessage(obtainMessage(CMD_BLUETOOTH_ADAPTER_STATE_CHANGE, state, 0));
+        sendMessage(CMD_BLUETOOTH_ADAPTER_STATE_CHANGE, state, 0);
     }
 
     /**
@@ -1114,7 +1107,7 @@
         pw.println("mLastBssid " + mLastBssid);
         pw.println("mLastNetworkId " + mLastNetworkId);
         pw.println("mReconnectCount " + mReconnectCount);
-        pw.println("mIsScanMode " + mIsScanMode);
+        pw.println("mOperationalMode " + mOperationalMode);
         pw.println("mUserWantsSuspendOpt " + mUserWantsSuspendOpt);
         pw.println("mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled);
         pw.println("Supplicant status " + mWifiNative.status());
@@ -1136,11 +1129,11 @@
         if (screenOn) enableAllNetworks();
         if (mUserWantsSuspendOpt.get()) {
             if (screenOn) {
-                sendMessage(obtainMessage(CMD_SET_SUSPEND_OPT_ENABLED, 0, 0));
+                sendMessage(CMD_SET_SUSPEND_OPT_ENABLED, 0, 0);
             } else {
                 //Allow 2s for suspend optimizations to be set
                 mSuspendWakeLock.acquire(2000);
-                sendMessage(obtainMessage(CMD_SET_SUSPEND_OPT_ENABLED, 1, 0));
+                sendMessage(CMD_SET_SUSPEND_OPT_ENABLED, 1, 0);
             }
         }
         mScreenBroadcastReceived.set(true);
@@ -1914,7 +1907,7 @@
                 case WifiMonitor.WPS_OVERLAP_EVENT:
                 case CMD_BLACKLIST_NETWORK:
                 case CMD_CLEAR_BLACKLIST:
-                case CMD_SET_SCAN_MODE:
+                case CMD_SET_OPERATIONAL_MODE:
                 case CMD_SET_COUNTRY_CODE:
                 case CMD_SET_FREQUENCY_BAND:
                 case CMD_RSSI_POLL:
@@ -1929,6 +1922,7 @@
                 case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
                 case WifiWatchdogStateMachine.GOOD_LINK_DETECTED:
                 case CMD_NO_NETWORKS_PERIODIC_SCAN:
+                case CMD_DISABLE_P2P_RSP:
                     break;
                 case DhcpStateMachine.CMD_ON_QUIT:
                     mDhcpStateMachine = null;
@@ -1942,8 +1936,8 @@
                     }
                     break;
                 case WifiMonitor.DRIVER_HUNG_EVENT:
-                    setWifiEnabled(false);
-                    setWifiEnabled(true);
+                    setSupplicantRunning(false);
+                    setSupplicantRunning(true);
                     break;
                 case WifiManager.CONNECT_NETWORK:
                     replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
@@ -2143,7 +2137,7 @@
                 case CMD_STOP_AP:
                 case CMD_START_DRIVER:
                 case CMD_STOP_DRIVER:
-                case CMD_SET_SCAN_MODE:
+                case CMD_SET_OPERATIONAL_MODE:
                 case CMD_SET_COUNTRY_CODE:
                 case CMD_SET_FREQUENCY_BAND:
                 case CMD_START_PACKET_FILTERING:
@@ -2160,8 +2154,6 @@
     class SupplicantStartedState extends State {
         @Override
         public void enter() {
-            /* Initialize for connect mode operation at start */
-            mIsScanMode = false;
             /* Wifi is available as long as we have a connection to supplicant */
             mNetworkInfo.setIsAvailable(true);
 
@@ -2176,7 +2168,6 @@
         }
         @Override
         public boolean processMessage(Message message) {
-            WifiConfiguration config;
             switch(message.what) {
                 case CMD_STOP_SUPPLICANT:   /* Supplicant stopped by user */
                     if (mP2pSupported) {
@@ -2206,87 +2197,13 @@
                     boolean ok = mWifiNative.ping();
                     replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
                     break;
-                case CMD_ADD_OR_UPDATE_NETWORK:
-                    config = (WifiConfiguration) message.obj;
-                    replyToMessage(message, CMD_ADD_OR_UPDATE_NETWORK,
-                            mWifiConfigStore.addOrUpdateNetwork(config));
-                    break;
-                case CMD_REMOVE_NETWORK:
-                    ok = mWifiConfigStore.removeNetwork(message.arg1);
-                    replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
-                    break;
-                case CMD_ENABLE_NETWORK:
-                    ok = mWifiConfigStore.enableNetwork(message.arg1, message.arg2 == 1);
-                    replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
-                    break;
-                case CMD_ENABLE_ALL_NETWORKS:
-                    long time =  android.os.SystemClock.elapsedRealtime();
-                    if (time - mLastEnableAllNetworksTime > MIN_INTERVAL_ENABLE_ALL_NETWORKS_MS) {
-                        mWifiConfigStore.enableAllNetworks();
-                        mLastEnableAllNetworksTime = time;
-                    }
-                    break;
-                case WifiManager.DISABLE_NETWORK:
-                    if (mWifiConfigStore.disableNetwork(message.arg1,
-                            WifiConfiguration.DISABLED_UNKNOWN_REASON) == true) {
-                        replyToMessage(message, WifiManager.DISABLE_NETWORK_SUCCEEDED);
-                    } else {
-                        replyToMessage(message, WifiManager.DISABLE_NETWORK_FAILED,
-                                WifiManager.ERROR);
-                    }
-                    break;
-                case CMD_BLACKLIST_NETWORK:
-                    mWifiNative.addToBlacklist((String)message.obj);
-                    break;
-                case CMD_CLEAR_BLACKLIST:
-                    mWifiNative.clearBlacklist();
-                    break;
-                case CMD_SAVE_CONFIG:
-                    ok = mWifiConfigStore.saveConfig();
-                    replyToMessage(message, CMD_SAVE_CONFIG, ok ? SUCCESS : FAILURE);
-
-                    // Inform the backup manager about a data change
-                    IBackupManager ibm = IBackupManager.Stub.asInterface(
-                            ServiceManager.getService(Context.BACKUP_SERVICE));
-                    if (ibm != null) {
-                        try {
-                            ibm.dataChanged("com.android.providers.settings");
-                        } catch (Exception e) {
-                            // Try again later
-                        }
-                    }
-                    break;
-                case CMD_GET_CONFIGURED_NETWORKS:
-                    replyToMessage(message, message.what,
-                            mWifiConfigStore.getConfiguredNetworks());
-                    break;
                     /* Cannot start soft AP while in client mode */
                 case CMD_START_AP:
                     loge("Failed to start soft AP with a running supplicant");
                     setWifiApState(WIFI_AP_STATE_FAILED);
                     break;
-                case CMD_SET_SCAN_MODE:
-                    mIsScanMode = (message.arg1 == SCAN_ONLY_MODE);
-                    break;
-                case WifiManager.SAVE_NETWORK:
-                    config = (WifiConfiguration) message.obj;
-                    NetworkUpdateResult result = mWifiConfigStore.saveNetwork(config);
-                    if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) {
-                        replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
-                    } else {
-                        loge("Failed to save network");
-                        replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
-                                WifiManager.ERROR);
-                    }
-                    break;
-                case WifiManager.FORGET_NETWORK:
-                    if (mWifiConfigStore.forgetNetwork(message.arg1)) {
-                        replyToMessage(message, WifiManager.FORGET_NETWORK_SUCCEEDED);
-                    } else {
-                        loge("Failed to forget network");
-                        replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED,
-                                WifiManager.ERROR);
-                    }
+                case CMD_SET_OPERATIONAL_MODE:
+                    mOperationalMode = message.arg1;
                     break;
                 default:
                     return NOT_HANDLED;
@@ -2344,7 +2261,7 @@
                 case CMD_STOP_AP:
                 case CMD_START_DRIVER:
                 case CMD_STOP_DRIVER:
-                case CMD_SET_SCAN_MODE:
+                case CMD_SET_OPERATIONAL_MODE:
                 case CMD_SET_COUNTRY_CODE:
                 case CMD_SET_FREQUENCY_BAND:
                 case CMD_START_PACKET_FILTERING:
@@ -2452,7 +2369,7 @@
                 mWifiNative.stopFilteringMulticastV4Packets();
             }
 
-            if (mIsScanMode) {
+            if (mOperationalMode != CONNECT_MODE) {
                 mWifiNative.disconnect();
                 transitionTo(mScanModeState);
             } else {
@@ -2510,8 +2427,8 @@
                 case CMD_STOP_DRIVER:
                     int mode = message.arg1;
 
-                    /* Already doing a delayed stop && not in ecm state */
-                    if (mInDelayedStop && mode != IN_ECM_STATE) {
+                    /* Already doing a delayed stop */
+                    if (mInDelayedStop) {
                         if (DBG) log("Already in delayed stop");
                         break;
                     }
@@ -2519,20 +2436,15 @@
                     mDelayedStopCounter++;
                     if (DBG) log("Delayed stop message " + mDelayedStopCounter);
 
-                    if (mode == IN_ECM_STATE) {
-                        /* send a shut down immediately */
-                        sendMessage(obtainMessage(CMD_DELAYED_STOP_DRIVER, mDelayedStopCounter, 0));
-                    } else {
-                        /* send regular delayed shut down */
-                        Intent driverStopIntent = new Intent(ACTION_DELAYED_DRIVER_STOP, null);
-                        driverStopIntent.putExtra(DELAYED_STOP_COUNTER, mDelayedStopCounter);
-                        mDriverStopIntent = PendingIntent.getBroadcast(mContext,
-                                DRIVER_STOP_REQUEST, driverStopIntent,
-                                PendingIntent.FLAG_UPDATE_CURRENT);
+                    /* send regular delayed shut down */
+                    Intent driverStopIntent = new Intent(ACTION_DELAYED_DRIVER_STOP, null);
+                    driverStopIntent.putExtra(DELAYED_STOP_COUNTER, mDelayedStopCounter);
+                    mDriverStopIntent = PendingIntent.getBroadcast(mContext,
+                            DRIVER_STOP_REQUEST, driverStopIntent,
+                            PendingIntent.FLAG_UPDATE_CURRENT);
 
-                        mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
-                                + mDriverStopDelayMs, mDriverStopIntent);
-                    }
+                    mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
+                            + mDriverStopDelayMs, mDriverStopIntent);
                     break;
                 case CMD_START_DRIVER:
                     if (mInDelayedStop) {
@@ -2638,7 +2550,7 @@
                 case CMD_STOP_AP:
                 case CMD_START_DRIVER:
                 case CMD_STOP_DRIVER:
-                case CMD_SET_SCAN_MODE:
+                case CMD_SET_OPERATIONAL_MODE:
                 case CMD_SET_COUNTRY_CODE:
                 case CMD_SET_FREQUENCY_BAND:
                 case CMD_START_PACKET_FILTERING:
@@ -2713,30 +2625,42 @@
     }
 
     class ScanModeState extends State {
+        private int mLastOperationMode;
+        @Override
+        public void enter() {
+            mWifiConfigStore.disableAllNetworks();
+            mLastOperationMode = mOperationalMode;
+            if (mLastOperationMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) {
+                mWifiP2pChannel.sendMessage(CMD_DISABLE_P2P_REQ);
+                setWifiState(WIFI_STATE_DISABLED);
+            }
+        }
+        @Override
+        public void exit() {
+            if (mLastOperationMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) {
+                setWifiState(WIFI_STATE_ENABLED);
+                mWifiP2pChannel.sendMessage(CMD_ENABLE_P2P);
+            }
+            mWifiConfigStore.enableAllNetworks();
+            mWifiNative.reconnect();
+        }
         @Override
         public boolean processMessage(Message message) {
             switch(message.what) {
-                case CMD_SET_SCAN_MODE:
-                    if (message.arg1 == SCAN_ONLY_MODE) {
-                        /* Ignore */
-                        return HANDLED;
-                    } else {
-                        mWifiNative.reconnect();
-                        mIsScanMode = false;
+                case CMD_SET_OPERATIONAL_MODE:
+                    if (message.arg1 == CONNECT_MODE) {
+                        mOperationalMode = CONNECT_MODE;
                         transitionTo(mDisconnectedState);
+                    } else {
+                        // Nothing to do
+                        return HANDLED;
                     }
                     break;
+                // Handle scan. All the connection related commands are
+                // handled only in ConnectModeState
                 case CMD_START_SCAN:
                     startScanNative(WifiNative.SCAN_WITHOUT_CONNECTION_SETUP);
                     break;
-                    /* Ignore */
-                case CMD_DISCONNECT:
-                case CMD_RECONNECT:
-                case CMD_REASSOCIATE:
-                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
-                case WifiMonitor.NETWORK_CONNECTION_EVENT:
-                case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
-                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -2747,7 +2671,8 @@
     class ConnectModeState extends State {
         @Override
         public boolean processMessage(Message message) {
-            StateChangeResult stateChangeResult;
+            WifiConfiguration config;
+            boolean ok;
             switch(message.what) {
                 case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
                     mSupplicantStateTracker.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT);
@@ -2787,6 +2712,60 @@
                         mTemporarilyDisconnectWifi = false;
                     }
                     break;
+                case CMD_ADD_OR_UPDATE_NETWORK:
+                    config = (WifiConfiguration) message.obj;
+                    replyToMessage(message, CMD_ADD_OR_UPDATE_NETWORK,
+                            mWifiConfigStore.addOrUpdateNetwork(config));
+                    break;
+                case CMD_REMOVE_NETWORK:
+                    ok = mWifiConfigStore.removeNetwork(message.arg1);
+                    replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
+                    break;
+                case CMD_ENABLE_NETWORK:
+                    ok = mWifiConfigStore.enableNetwork(message.arg1, message.arg2 == 1);
+                    replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
+                    break;
+                case CMD_ENABLE_ALL_NETWORKS:
+                    long time =  android.os.SystemClock.elapsedRealtime();
+                    if (time - mLastEnableAllNetworksTime > MIN_INTERVAL_ENABLE_ALL_NETWORKS_MS) {
+                        mWifiConfigStore.enableAllNetworks();
+                        mLastEnableAllNetworksTime = time;
+                    }
+                    break;
+                case WifiManager.DISABLE_NETWORK:
+                    if (mWifiConfigStore.disableNetwork(message.arg1,
+                            WifiConfiguration.DISABLED_UNKNOWN_REASON) == true) {
+                        replyToMessage(message, WifiManager.DISABLE_NETWORK_SUCCEEDED);
+                    } else {
+                        replyToMessage(message, WifiManager.DISABLE_NETWORK_FAILED,
+                                WifiManager.ERROR);
+                    }
+                    break;
+                case CMD_BLACKLIST_NETWORK:
+                    mWifiNative.addToBlacklist((String)message.obj);
+                    break;
+                case CMD_CLEAR_BLACKLIST:
+                    mWifiNative.clearBlacklist();
+                    break;
+                case CMD_SAVE_CONFIG:
+                    ok = mWifiConfigStore.saveConfig();
+                    replyToMessage(message, CMD_SAVE_CONFIG, ok ? SUCCESS : FAILURE);
+
+                    // Inform the backup manager about a data change
+                    IBackupManager ibm = IBackupManager.Stub.asInterface(
+                            ServiceManager.getService(Context.BACKUP_SERVICE));
+                    if (ibm != null) {
+                        try {
+                            ibm.dataChanged("com.android.providers.settings");
+                        } catch (Exception e) {
+                            // Try again later
+                        }
+                    }
+                    break;
+                case CMD_GET_CONFIGURED_NETWORKS:
+                    replyToMessage(message, message.what,
+                            mWifiConfigStore.getConfiguredNetworks());
+                    break;
                     /* Do a redundant disconnect without transition */
                 case CMD_DISCONNECT:
                     mWifiNative.disconnect();
@@ -2804,7 +2783,7 @@
                      * For an existing network, a network id is passed
                      */
                     int netId = message.arg1;
-                    WifiConfiguration config = (WifiConfiguration) message.obj;
+                    config = (WifiConfiguration) message.obj;
 
                     /* Save the network config */
                     if (config != null) {
@@ -2826,26 +2805,46 @@
                         break;
                     }
                     break;
+                case WifiManager.SAVE_NETWORK:
+                    config = (WifiConfiguration) message.obj;
+                    NetworkUpdateResult result = mWifiConfigStore.saveNetwork(config);
+                    if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) {
+                        replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
+                    } else {
+                        loge("Failed to save network");
+                        replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
+                                WifiManager.ERROR);
+                    }
+                    break;
+                case WifiManager.FORGET_NETWORK:
+                    if (mWifiConfigStore.forgetNetwork(message.arg1)) {
+                        replyToMessage(message, WifiManager.FORGET_NETWORK_SUCCEEDED);
+                    } else {
+                        loge("Failed to forget network");
+                        replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED,
+                                WifiManager.ERROR);
+                    }
+                    break;
                 case WifiManager.START_WPS:
                     WpsInfo wpsInfo = (WpsInfo) message.obj;
-                    WpsResult result;
+                    WpsResult wpsResult;
                     switch (wpsInfo.setup) {
                         case WpsInfo.PBC:
-                            result = mWifiConfigStore.startWpsPbc(wpsInfo);
+                            wpsResult = mWifiConfigStore.startWpsPbc(wpsInfo);
                             break;
                         case WpsInfo.KEYPAD:
-                            result = mWifiConfigStore.startWpsWithPinFromAccessPoint(wpsInfo);
+                            wpsResult = mWifiConfigStore.startWpsWithPinFromAccessPoint(wpsInfo);
                             break;
                         case WpsInfo.DISPLAY:
-                            result = mWifiConfigStore.startWpsWithPinFromDevice(wpsInfo);
+                            wpsResult = mWifiConfigStore.startWpsWithPinFromDevice(wpsInfo);
                             break;
                         default:
-                            result = new WpsResult(Status.FAILURE);
+                            wpsResult = new WpsResult(Status.FAILURE);
                             loge("Invalid setup for WPS");
                             break;
                     }
-                    if (result.status == Status.SUCCESS) {
-                        replyToMessage(message, WifiManager.START_WPS_SUCCEEDED, result);
+                    if (wpsResult.status == Status.SUCCESS) {
+                        replyToMessage(message, WifiManager.START_WPS_SUCCEEDED, wpsResult);
                         transitionTo(mWpsRunningState);
                     } else {
                         loge("Failed to start WPS with config " + wpsInfo.toString());
@@ -2881,7 +2880,7 @@
         public void enter() {
             mRssiPollToken++;
             if (mEnableRssiPolling) {
-                sendMessage(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0));
+                sendMessage(CMD_RSSI_POLL, mRssiPollToken, 0);
             }
         }
 
@@ -2915,8 +2914,8 @@
                         transitionTo(mDisconnectingState);
                     }
                     break;
-                case CMD_SET_SCAN_MODE:
-                    if (message.arg1 == SCAN_ONLY_MODE) {
+                case CMD_SET_OPERATIONAL_MODE:
+                    if (message.arg1 != CONNECT_MODE) {
                         sendMessage(CMD_DISCONNECT);
                         deferMessage(message);
                     }
@@ -3152,8 +3151,8 @@
         @Override
         public boolean processMessage(Message message) {
             switch (message.what) {
-                case CMD_SET_SCAN_MODE:
-                    if (message.arg1 == SCAN_ONLY_MODE) {
+                case CMD_SET_OPERATIONAL_MODE:
+                    if (message.arg1 != CONNECT_MODE) {
                         deferMessage(message);
                     }
                     break;
@@ -3259,11 +3258,9 @@
                                 ++mPeriodicScanToken, 0), mSupplicantScanIntervalMs);
                     ret = NOT_HANDLED;
                     break;
-                case CMD_SET_SCAN_MODE:
-                    if (message.arg1 == SCAN_ONLY_MODE) {
-                        //Supplicant disconnect to prevent further connects
-                        mWifiNative.disconnect();
-                        mIsScanMode = true;
+                case CMD_SET_OPERATIONAL_MODE:
+                    if (message.arg1 != CONNECT_MODE) {
+                        mOperationalMode = message.arg1;
                         transitionTo(mScanModeState);
                     }
                     break;
@@ -3396,7 +3393,7 @@
                  * or put the state machine out of connect mode
                  */
                 case CMD_STOP_DRIVER:
-                case CMD_SET_SCAN_MODE:
+                case CMD_SET_OPERATIONAL_MODE:
                 case WifiManager.CONNECT_NETWORK:
                 case CMD_ENABLE_NETWORK:
                 case CMD_RECONNECT:
@@ -3459,7 +3456,7 @@
                 case CMD_STOP_AP:
                 case CMD_START_DRIVER:
                 case CMD_STOP_DRIVER:
-                case CMD_SET_SCAN_MODE:
+                case CMD_SET_OPERATIONAL_MODE:
                 case CMD_SET_COUNTRY_CODE:
                 case CMD_SET_FREQUENCY_BAND:
                 case CMD_START_PACKET_FILTERING:
@@ -3547,7 +3544,8 @@
                     if (message.arg1 == mTetherToken) {
                         loge("Failed to get tether update, shutdown soft access point");
                         transitionTo(mSoftApStartedState);
-                        sendMessage(CMD_STOP_AP);
+                        // Needs to be first thing handled
+                        sendMessageAtFrontOfQueue(CMD_STOP_AP);
                     }
                     break;
                 case CMD_START_SUPPLICANT:
@@ -3556,7 +3554,7 @@
                 case CMD_STOP_AP:
                 case CMD_START_DRIVER:
                 case CMD_STOP_DRIVER:
-                case CMD_SET_SCAN_MODE:
+                case CMD_SET_OPERATIONAL_MODE:
                 case CMD_SET_COUNTRY_CODE:
                 case CMD_SET_FREQUENCY_BAND:
                 case CMD_START_PACKET_FILTERING:
@@ -3578,7 +3576,7 @@
                     TetherStateChange stateChange = (TetherStateChange) message.obj;
                     if (!isWifiTethered(stateChange.active)) {
                         loge("Tethering reports wifi as untethered!, shut down soft Ap");
-                        setWifiApEnabled(null, false);
+                        setHostApRunning(null, false);
                     }
                     return HANDLED;
                 case CMD_STOP_AP:
@@ -3612,13 +3610,15 @@
                     if (isWifiTethered(stateChange.active)) break;
 
                     transitionTo(mSoftApStartedState);
-                    sendMessage(CMD_STOP_AP);
+                    // Needs to be first thing handled
+                    sendMessageAtFrontOfQueue(CMD_STOP_AP);
                     break;
                 case CMD_TETHER_NOTIFICATION_TIMED_OUT:
                     if (message.arg1 == mTetherToken) {
                         loge("Failed to get tether update, force stop access point");
                         transitionTo(mSoftApStartedState);
-                        sendMessage(CMD_STOP_AP);
+                        // Needs to be first thing handled
+                        sendMessageAtFrontOfQueue(CMD_STOP_AP);
                     }
                     break;
                 case CMD_START_SUPPLICANT:
@@ -3627,7 +3627,7 @@
                 case CMD_STOP_AP:
                 case CMD_START_DRIVER:
                 case CMD_STOP_DRIVER:
-                case CMD_SET_SCAN_MODE:
+                case CMD_SET_OPERATIONAL_MODE:
                 case CMD_SET_COUNTRY_CODE:
                 case CMD_SET_FREQUENCY_BAND:
                 case CMD_START_PACKET_FILTERING:
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index 55ea34f..81d2e11 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -252,4 +252,14 @@
     public void setDependencyMet(boolean met) {
         // not supported on this network
     }
+
+    @Override
+    public void addStackedLink(LinkProperties link) {
+        mLinkProperties.addStackedLink(link);
+    }
+
+    @Override
+    public void removeStackedLink(LinkProperties link) {
+        mLinkProperties.removeStackedLink(link);
+    }
 }
diff --git a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
index 53e6b51..eb47a4a 100644
--- a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
@@ -587,8 +587,9 @@
                     break;
 
                 case EVENT_WIFI_RADIO_STATE_CHANGE:
-                    if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING)
+                    if (msg.arg1 == WifiManager.WIFI_STATE_DISABLING) {
                         transitionTo(mNotConnectedState);
+                    }
                     break;
 
                 default: