Merge "Renamed INCLUDE_PROFILE param to ALLOW_PROFILE."
diff --git a/Android.mk b/Android.mk
index f752f44..5f3327c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -90,6 +90,7 @@
 	core/java/android/bluetooth/IBluetoothA2dp.aidl \
 	core/java/android/bluetooth/IBluetoothCallback.aidl \
 	core/java/android/bluetooth/IBluetoothHeadset.aidl \
+	core/java/android/bluetooth/IBluetoothHealthCallback.aidl \
 	core/java/android/bluetooth/IBluetoothPbap.aidl \
 	core/java/android/content/IClipboard.aidl \
 	core/java/android/content/IContentService.aidl \
@@ -244,6 +245,7 @@
 	frameworks/base/core/java/android/app/Notification.aidl \
 	frameworks/base/core/java/android/app/PendingIntent.aidl \
 	frameworks/base/core/java/android/bluetooth/BluetoothDevice.aidl \
+	frameworks/base/core/java/android/bluetooth/BluetoothHealthAppConfiguration.aidl \
 	frameworks/base/core/java/android/content/ComponentName.aidl \
 	frameworks/base/core/java/android/content/Intent.aidl \
 	frameworks/base/core/java/android/content/IntentSender.aidl \
diff --git a/api/current.txt b/api/current.txt
index f5b79bc..1395ef1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -601,8 +601,10 @@
     field public static final int layout_height = 16842997; // 0x10100f5
     field public static final int layout_margin = 16842998; // 0x10100f6
     field public static final int layout_marginBottom = 16843002; // 0x10100fa
+    field public static final int layout_marginEnd = 16843675; // 0x101039b
     field public static final int layout_marginLeft = 16842999; // 0x10100f7
     field public static final int layout_marginRight = 16843001; // 0x10100f9
+    field public static final int layout_marginStart = 16843674; // 0x101039a
     field public static final int layout_marginTop = 16843000; // 0x10100f8
     field public static final int layout_row = 16843643; // 0x101037b
     field public static final int layout_rowSpan = 16843644; // 0x101037c
@@ -17492,7 +17494,7 @@
 
   public final class KeyChain {
     ctor public KeyChain();
-    method public static void choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, java.lang.String[], java.security.Principal[], java.lang.String, int);
+    method public static void choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, java.lang.String[], java.security.Principal[], java.lang.String, int, java.lang.String);
     method public static java.security.cert.X509Certificate[] getCertificateChain(android.content.Context, java.lang.String) throws java.lang.InterruptedException, android.security.KeyChainException;
     method public static java.security.PrivateKey getPrivateKey(android.content.Context, java.lang.String) throws java.lang.InterruptedException, android.security.KeyChainException;
   }
@@ -22055,6 +22057,7 @@
     method protected boolean verifyDrawable(android.graphics.drawable.Drawable);
     method public boolean willNotCacheDrawing();
     method public boolean willNotDraw();
+    field public static android.util.Property ALPHA;
     field public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
     field public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
     field public static final int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000
@@ -22844,8 +22847,10 @@
     field public static final int TYPE_VIEW_HOVER_ENTER = 128; // 0x80
     field public static final int TYPE_VIEW_HOVER_EXIT = 256; // 0x100
     field public static final int TYPE_VIEW_LONG_CLICKED = 2; // 0x2
+    field public static final int TYPE_VIEW_SCROLLED = 4096; // 0x1000
     field public static final int TYPE_VIEW_SELECTED = 4; // 0x4
     field public static final int TYPE_VIEW_TEXT_CHANGED = 16; // 0x10
+    field public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED = 8192; // 0x2000
     field public static final int TYPE_WINDOW_CONTENT_CHANGED = 2048; // 0x800
     field public static final int TYPE_WINDOW_STATE_CHANGED = 32; // 0x20
   }
@@ -22894,6 +22899,7 @@
     method public boolean isFocused();
     method public boolean isLongClickable();
     method public boolean isPassword();
+    method public boolean isScrollable();
     method public boolean isSelected();
     method public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View);
     method public static android.view.accessibility.AccessibilityNodeInfo obtain();
@@ -22913,6 +22919,7 @@
     method public void setPackageName(java.lang.CharSequence);
     method public void setParent(android.view.View);
     method public void setPassword(boolean);
+    method public void setScrollable(boolean);
     method public void setSelected(boolean);
     method public void setSource(android.view.View);
     method public void setText(java.lang.CharSequence);
@@ -22934,13 +22941,17 @@
     method public int getItemCount();
     method public android.os.Parcelable getParcelableData();
     method public int getRemovedCount();
+    method public int getScrollX();
+    method public int getScrollY();
     method public android.view.accessibility.AccessibilityNodeInfo getSource();
     method public java.util.List<java.lang.CharSequence> getText();
+    method public int getToIndex();
     method public int getWindowId();
     method public boolean isChecked();
     method public boolean isEnabled();
     method public boolean isFullScreen();
     method public boolean isPassword();
+    method public boolean isScrollable();
     method public static android.view.accessibility.AccessibilityRecord obtain(android.view.accessibility.AccessibilityRecord);
     method public static android.view.accessibility.AccessibilityRecord obtain();
     method public void recycle();
@@ -22957,7 +22968,11 @@
     method public void setParcelableData(android.os.Parcelable);
     method public void setPassword(boolean);
     method public void setRemovedCount(int);
+    method public void setScrollX(int);
+    method public void setScrollY(int);
+    method public void setScrollable(boolean);
     method public void setSource(android.view.View);
+    method public void setToIndex(int);
   }
 
 }
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 424b70a..479b70a 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -33,6 +33,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.util.AndroidException;
 import android.view.IWindowManager;
 
@@ -199,6 +200,10 @@
                 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
             } else if (opt.equals("--grant-write-uri-permission")) {
                 intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+            } else if (opt.equals("--exclude-stopped-packages")) {
+                intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
+            } else if (opt.equals("--include-stopped-packages")) {
+                intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
             } else if (opt.equals("--debug-log-resolution")) {
                 intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
             } else if (opt.equals("--activity-brought-to-front")) {
@@ -227,6 +232,10 @@
                 intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
             } else if (opt.equals("--activity-single-top")) {
                 intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+            } else if (opt.equals("--activity-clear-task")) {
+                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+            } else if (opt.equals("--activity-task-on-home")) {
+                intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
             } else if (opt.equals("--receiver-registered-only")) {
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
             } else if (opt.equals("--receiver-replace-pending")) {
@@ -400,7 +409,8 @@
                 argKey = nextArgRequired();
                 argValue = nextArgRequired();
                 args.putString(argKey, argValue);
-            } else if (opt.equals("--no_window_animation")) {
+            } else if (opt.equals("--no_window_animation")
+                    || opt.equals("--no-window-animation")) {
                 no_window_animation = true;
             } else {
                 System.err.println("Error: Unknown option: " + opt);
@@ -440,15 +450,43 @@
         }
     }
 
+    static void removeWallOption() {
+        String props = SystemProperties.get("dalvik.vm.extra-opts");
+        if (props != null && props.contains("-Xprofile:wallclock")) {
+            props = props.replace("-Xprofile:wallclock", "");
+            props = props.trim();
+            SystemProperties.set("dalvik.vm.extra-opts", props);
+        }
+    }
+    
     private void runProfile() throws Exception {
         String profileFile = null;
         boolean start = false;
-        String process = nextArgRequired();
-        ParcelFileDescriptor fd = null;
-
+        boolean wall = false;
+        
+        String process = null;
+        
         String cmd = nextArgRequired();
         if ("start".equals(cmd)) {
             start = true;
+            wall = "--wall".equals(nextOption());
+            process = nextArgRequired();
+        } else if ("stop".equals(cmd)) {
+            process = nextArgRequired();
+        } else {
+            // Compatibility with old syntax: process is specified first.
+            process = cmd;
+            cmd = nextArgRequired();
+            if ("start".equals(cmd)) {
+                start = true;
+            } else if (!"stop".equals(cmd)) {
+                throw new IllegalArgumentException("Profile command " + process + " not valid");
+            }
+        }
+        
+        ParcelFileDescriptor fd = null;
+
+        if (start) {
             profileFile = nextArgRequired();
             try {
                 fd = ParcelFileDescriptor.open(
@@ -460,12 +498,27 @@
                 System.err.println("Error: Unable to open file: " + profileFile);
                 return;
             }
-        } else if (!"stop".equals(cmd)) {
-            throw new IllegalArgumentException("Profile command " + cmd + " not valid");
         }
 
-        if (!mAm.profileControl(process, start, profileFile, fd)) {
-            throw new AndroidException("PROFILE FAILED on process " + process);
+        try {
+            if (wall) {
+                // XXX doesn't work -- this needs to be set before booting.
+                String props = SystemProperties.get("dalvik.vm.extra-opts");
+                if (props == null || !props.contains("-Xprofile:wallclock")) {
+                    props = props + " -Xprofile:wallclock";
+                    //SystemProperties.set("dalvik.vm.extra-opts", props);
+                }
+            } else if (start) {
+                //removeWallOption();
+            }
+            if (!mAm.profileControl(process, start, profileFile, fd)) {
+                wall = false;
+                throw new AndroidException("PROFILE FAILED on process " + process);
+            }
+        } finally {
+            if (!wall) {
+                //removeWallOption();
+            }
         }
     }
 
@@ -1012,62 +1065,76 @@
     private static void showUsage() {
         System.err.println(
                 "usage: am [subcommand] [options]\n" +
+                "usage: am start [-D] [-W] <INTENT>\n" +
+                "       am startservice <INTENT>\n" +
+                "       am force-stop <PACKAGE>\n" +
+                "       am broadcast <INTENT>\n" +
+                "       am instrument [-r] [-e <NAME> <VALUE>] [-p] [-w]\n" +
+                "               [--no-window-animation] <COMPONENT>\n" +
+                "       am profile start <PROCESS> <FILE>\n" +
+                "       am profile stop <PROCESS>\n" +
+                "       am dumpheap [flags] <PROCESS> <FILE>\n" +
+                "       am monitor [--gdb <port>]\n" +
+                "       am screen-compat [on|off] <PACKAGE>\n" +
+                "       am display-size [reset|MxN]\n" +
                 "\n" +
-                "    start an Activity: am start [-D] [-W] <INTENT>\n" +
-                "        -D: enable debugging\n" +
-                "        -W: wait for launch to complete\n" +
+                "am start: start an Activity.  Options are:\n" +
+                "    -D: enable debugging\n" +
+                "    -W: wait for launch to complete\n" +
                 "\n" +
-                "    start a Service: am startservice <INTENT>\n" +
+                "am startservice: start a Service.\n" +
                 "\n" +
-                "    force stop everything associated with a package: force-stop <package>\n" +
+                "am force-stop: force stop everything associated with <PACKAGE>.\n" +
                 "\n" +
-                "    send a broadcast Intent: am broadcast <INTENT>\n" +
+                "am broadcast: send a broadcast Intent.\n" +
                 "\n" +
-                "    start an Instrumentation: am instrument [flags] <COMPONENT>\n" +
-                "        -r: print raw results (otherwise decode REPORT_KEY_STREAMRESULT)\n" +
-                "        -e <NAME> <VALUE>: set argument <NAME> to <VALUE>\n" +
-                "        -p <FILE>: write profiling data to <FILE>\n" +
-                "        -w: wait for instrumentation to finish before returning\n" +
+                "am instrument: start an Instrumentation.  Typically this target <COMPONENT>\n" +
+                "  is the form <TEST_PACKAGE>/<RUNNER_CLASS>.  Options are:\n" +
+                "    -r: print raw results (otherwise decode REPORT_KEY_STREAMRESULT).  Use with\n" +
+                "        [-e perf true] to generate raw output for performance measurements.\n" +
+                "    -e <NAME> <VALUE>: set argument <NAME> to <VALUE>.  For test runners a\n" +
+                "        common form is [-e <testrunner_flag> <value>[,<value>...]].\n" +
+                "    -p <FILE>: write profiling data to <FILE>\n" +
+                "    -w: wait for instrumentation to finish before returning.  Required for\n" +
+                "        test runners.\n" +
+                "    --no-window-animation: turn off window animations will running.\n" +
                 "\n" +
-                "    run a test package against an application: am instrument [flags] <TEST_PACKAGE>/<RUNNER_CLASS>\n" +
-                "        -e <testrunner_flag> <testrunner_value> [,<testrunner_value>]\n" +
-                "        -w wait for the test to finish (required)\n" +
-                "        -r use with -e perf true to generate raw output for performance measurements\n" +
+                "am profile: start and stop profiler on a process.\n" +
                 "\n" +
-                "    start profiling: am profile <PROCESS> start <FILE>\n" +
-                "    stop profiling: am profile <PROCESS> stop\n" +
-                "    dump heap: am dumpheap [flags] <PROCESS> <FILE>\n" +
-                "        -n: dump native heap instead of managed heap\n" +
+                "am dumpheap: dump the heap of a process.  Options are:\n" +
+                "    -n: dump native heap instead of managed heap\n" +
                 "\n" +
-                "    start monitoring: am monitor [--gdb <port>]\n" +
-                "        --gdb: start gdbserv on the given port at crash/ANR\n" +
+                "am monitor: start monitoring for crashes or ANRs.\n" +
+                "    --gdb: start gdbserv on the given port at crash/ANR\n" +
                 "\n" +
-                "    control screen compatibility: am screen-compat [on|off] [package]\n" +
+                "am screen-compat: control screen compatibility mode of <PACKAGE>.\n" +
                 "\n" +
-                "    override display size: am display-size [reset|MxN]\n" +
+                "am display-size: override display size.\n" +
                 "\n" +
-                "    <INTENT> specifications include these flags:\n" +
-                "        [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" +
-                "        [-c <CATEGORY> [-c <CATEGORY>] ...]\n" +
-                "        [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]\n" +
-                "        [--esn <EXTRA_KEY> ...]\n" +
-                "        [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]\n" +
-                "        [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]\n" +
-                "        [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]\n" +
-                "        [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]\n" +
-                "        [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]\n" +
-                "        [-n <COMPONENT>] [-f <FLAGS>]\n" +
-                "        [--grant-read-uri-permission] [--grant-write-uri-permission]\n" +
-                "        [--debug-log-resolution]\n" +
-                "        [--activity-brought-to-front] [--activity-clear-top]\n" +
-                "        [--activity-clear-when-task-reset] [--activity-exclude-from-recents]\n" +
-                "        [--activity-launched-from-history] [--activity-multiple-task]\n" +
-                "        [--activity-no-animation] [--activity-no-history]\n" +
-                "        [--activity-no-user-action] [--activity-previous-is-top]\n" +
-                "        [--activity-reorder-to-front] [--activity-reset-task-if-needed]\n" +
-                "        [--activity-single-top]\n" +
-                "        [--receiver-registered-only] [--receiver-replace-pending]\n" +
-                "        [<URI>]\n"
+                "<INTENT> specifications include these flags and arguments:\n" +
+                "    [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" +
+                "    [-c <CATEGORY> [-c <CATEGORY>] ...]\n" +
+                "    [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]\n" +
+                "    [--esn <EXTRA_KEY> ...]\n" +
+                "    [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]\n" +
+                "    [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]\n" +
+                "    [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]\n" +
+                "    [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]\n" +
+                "    [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]\n" +
+                "    [-n <COMPONENT>] [-f <FLAGS>]\n" +
+                "    [--grant-read-uri-permission] [--grant-write-uri-permission]\n" +
+                "    [--debug-log-resolution] [--exclude-stopped-packages]\n" +
+                "    [--include-stopped-packages]\n" +
+                "    [--activity-brought-to-front] [--activity-clear-top]\n" +
+                "    [--activity-clear-when-task-reset] [--activity-exclude-from-recents]\n" +
+                "    [--activity-launched-from-history] [--activity-multiple-task]\n" +
+                "    [--activity-no-animation] [--activity-no-history]\n" +
+                "    [--activity-no-user-action] [--activity-previous-is-top]\n" +
+                "    [--activity-reorder-to-front] [--activity-reset-task-if-needed]\n" +
+                "    [--activity-single-top] [--activity-clear-task]\n" +
+                "    [--activity-task-on-home]\n" +
+                "    [--receiver-registered-only] [--receiver-replace-pending]\n" +
+                "    [<URI>]\n"
                 );
     }
 }
diff --git a/cmds/keystore/keystore.cpp b/cmds/keystore/keystore.cpp
index bbd1a1b..1c1f37a 100644
--- a/cmds/keystore/keystore.cpp
+++ b/cmds/keystore/keystore.cpp
@@ -708,7 +708,7 @@
     uid_t euid;
     uint32_t perms;
 } users[] = {
-    {AID_SYSTEM,   ~0,         ~GET},
+    {AID_SYSTEM,   ~0,         ~0},
     {AID_VPN,      AID_SYSTEM, GET},
     {AID_WIFI,     AID_SYSTEM, GET},
     {AID_ROOT,     AID_SYSTEM, GET},
diff --git a/cmds/keystore/test-keystore b/cmds/keystore/test-keystore
index 82b276f..3be51b3 100755
--- a/cmds/keystore/test-keystore
+++ b/cmds/keystore/test-keystore
@@ -116,11 +116,12 @@
     expect "1 No error"
     expect "baz"
 
-    log "system does not have access to read any keys"
+    log "get baz"
     keystore system g baz
-    expect "6 Permission denied"
-    
-    log "however, root can read system user keys (as can wifi or vpn users)"
+    expect "1 No error"
+    expect "quux"
+
+    log "root can read system user keys (as can wifi or vpn users)"
     keystore root g baz
     expect "1 No error"
     expect "quux"
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index e433079..c980715 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -125,12 +125,12 @@
             return;
         }
 
-        if ("setInstallLocation".equals(op)) {
+        if ("set-install-location".equals(op)) {
             runSetInstallLocation();
             return;
         }
 
-        if ("getInstallLocation".equals(op)) {
+        if ("get-install-location".equals(op)) {
             runGetInstallLocation();
             return;
         }
@@ -1094,8 +1094,7 @@
     }
 
     private static void showUsage() {
-        System.err.println("usage: pm [list|path|install|uninstall]");
-        System.err.println("       pm list packages [-f] [-d] [-e] [-s] [-e] [-u] [FILTER]");
+        System.err.println("usage: pm list packages [-f] [-d] [-e] [-s] [-e] [-u] [FILTER]");
         System.err.println("       pm list permission-groups");
         System.err.println("       pm list permissions [-g] [-f] [-d] [-u] [GROUP]");
         System.err.println("       pm list instrumentation [-f] [TARGET-PACKAGE]");
@@ -1107,66 +1106,66 @@
         System.err.println("       pm clear PACKAGE");
         System.err.println("       pm enable PACKAGE_OR_COMPONENT");
         System.err.println("       pm disable PACKAGE_OR_COMPONENT");
-        System.err.println("       pm setInstallLocation [0/auto] [1/internal] [2/external]");
+        System.err.println("       pm disable-user PACKAGE_OR_COMPONENT");
+        System.err.println("       pm set-install-location [0/auto] [1/internal] [2/external]");
+        System.err.println("       pm get-install-location");
         System.err.println("       pm createUser USER_NAME");
         System.err.println("       pm removeUser USER_ID");
         System.err.println("");
-        System.err.println("The list packages command prints all packages, optionally only");
-        System.err.println("those whose package name contains the text in FILTER.  Options:");
-        System.err.println("  -f: see their associated file.");
-        System.err.println("  -d: filter to only show disbled packages.");
-        System.err.println("  -e: filter to only show enabled packages.");
-        System.err.println("  -s: filter to only show system packages.");
-        System.err.println("  -3: filter to only show third party packages.");
-        System.err.println("  -u: also include uninstalled packages.");
+        System.err.println("pm list packages: prints all packages, optionally only");
+        System.err.println("  those whose package name contains the text in FILTER.  Options:");
+        System.err.println("    -f: see their associated file.");
+        System.err.println("    -d: filter to only show disbled packages.");
+        System.err.println("    -e: filter to only show enabled packages.");
+        System.err.println("    -s: filter to only show system packages.");
+        System.err.println("    -3: filter to only show third party packages.");
+        System.err.println("    -u: also include uninstalled packages.");
         System.err.println("");
-        System.err.println("The list permission-groups command prints all known");
-        System.err.println("permission groups.");
+        System.err.println("pm list permission-groups: prints all known permission groups.");
         System.err.println("");
-        System.err.println("The list permissions command prints all known");
-        System.err.println("permissions, optionally only those in GROUP.  Options:");
-        System.err.println("  -g: organize by group.");
-        System.err.println("  -f: print all information.");
-        System.err.println("  -s: short summary.");
-        System.err.println("  -d: only list dangerous permissions.");
-        System.err.println("  -u: list only the permissions users will see.");
+        System.err.println("pm list permissions: prints all known permissions, optionally only");
+        System.err.println("  those in GROUP.  Options:");
+        System.err.println("    -g: organize by group.");
+        System.err.println("    -f: print all information.");
+        System.err.println("    -s: short summary.");
+        System.err.println("    -d: only list dangerous permissions.");
+        System.err.println("    -u: list only the permissions users will see.");
         System.err.println("");
-        System.err.println("The list instrumentation command prints all instrumentations,");
-        System.err.println("or only those that target a specified package.  Options:");
-        System.err.println("  -f: see their associated file.");
-        System.err.println("(Use this command to list all test packages, or use <TARGET-PACKAGE> ");
-        System.err.println(" to list the test packages for a particular application. The -f ");
-        System.err.println(" option lists the .apk file for the test package.)");
+        System.err.println("pm list instrumentation: use to list all test packages; optionally");
+        System.err.println("  supply <TARGET-PACKAGE> to list the test packages for a particular");
+        System.err.println("  application.  Options:");
+        System.err.println("    -f: list the .apk file for the test package.");
         System.err.println("");
-        System.err.println("The list features command prints all features of the system.");
+        System.err.println("pm list features: prints all features of the system.");
         System.err.println("");
-        System.err.println("The path command prints the path to the .apk of a package.");
+        System.err.println("pm path: print the path to the .apk of the given PACKAGE.");
         System.err.println("");
-        System.err.println("The install command installs a package to the system.  Options:");
-        System.err.println("  -l: install the package with FORWARD_LOCK.");
-        System.err.println("  -r: reinstall an exisiting app, keeping its data.");
-        System.err.println("  -t: allow test .apks to be installed.");
-        System.err.println("  -i: specify the installer package name.");
-        System.err.println("  -s: install package on sdcard.");
-        System.err.println("  -f: install package on internal flash.");
+        System.err.println("pm install: installs a package to the system.  Options:");
+        System.err.println("    -l: install the package with FORWARD_LOCK.");
+        System.err.println("    -r: reinstall an exisiting app, keeping its data.");
+        System.err.println("    -t: allow test .apks to be installed.");
+        System.err.println("    -i: specify the installer package name.");
+        System.err.println("    -s: install package on sdcard.");
+        System.err.println("    -f: install package on internal flash.");
         System.err.println("");
-        System.err.println("The uninstall command removes a package from the system. Options:");
-        System.err.println("  -k: keep the data and cache directories around.");
-        System.err.println("after the package removal.");
+        System.err.println("pm uninstall: removes a package from the system. Options:");
+        System.err.println("    -k: keep the data and cache directories around after package removal.");
         System.err.println("");
-        System.err.println("The clear command deletes all data associated with a package.");
+        System.err.println("pm clear: deletes all data associated with a package.");
         System.err.println("");
-        System.err.println("The enable and disable commands change the enabled state of");
-        System.err.println("a given package or component (written as \"package/class\").");
+        System.err.println("pm enable, disable, disable-user: these commands change the enabled state");
+        System.err.println("  of a given package or component (written as \"package/class\").");
         System.err.println("");
-        System.err.println("The getInstallLocation command gets the current install location");
-        System.err.println("  0 [auto]: Let system decide the best location");
-        System.err.println("  1 [internal]: Install on internal device storage");
-        System.err.println("  2 [external]: Install on external media");
+        System.err.println("pm get-install-location: returns the current install location.");
+        System.err.println("    0 [auto]: Let system decide the best location");
+        System.err.println("    1 [internal]: Install on internal device storage");
+        System.err.println("    2 [external]: Install on external media");
         System.err.println("");
-        System.err.println("The setInstallLocation command changes the default install location");
-        System.err.println("  0 [auto]: Let system decide the best location");
-        System.err.println("  1 [internal]: Install on internal device storage");
-        System.err.println("  2 [external]: Install on external media");
+        System.err.println("pm set-install-location: changes the default install location.");
+        System.err.println("  NOTE: this is only intended for debugging; using this can cause");
+        System.err.println("  applications to break and other undersireable behavior.");
+        System.err.println("    0 [auto]: Let system decide the best location");
+        System.err.println("    1 [internal]: Install on internal device storage");
+        System.err.println("    2 [external]: Install on external media");
     }
 }
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index a8c31f9..b993bd8 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1115,6 +1115,9 @@
         } else if (profile == BluetoothProfile.PAN) {
             BluetoothPan pan = new BluetoothPan(context, listener);
             return true;
+        } else if (profile == BluetoothProfile.HEALTH) {
+            BluetoothHealth health = new BluetoothHealth(context, listener);
+            return true;
         } else {
             return false;
         }
diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java
new file mode 100644
index 0000000..52efc07
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHealth.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.annotation.SdkConstant;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Public API for Bluetooth Health Profile.
+ *
+ * <p>BluetoothHealth is a proxy object for controlling the Bluetooth
+ * Service via IPC.
+ *
+ * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothHealth proxy object. Use
+ * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
+ * @hide
+ */
+public final class BluetoothHealth implements BluetoothProfile {
+    private static final String TAG = "BluetoothHealth";
+    private static final boolean DBG = false;
+
+    /**
+     * Health Profile Source Role - the health device.
+     */
+    public static final int SOURCE_ROLE = 1 << 0;
+
+    /**
+     * Health Profile Sink Role the device talking to the health device.
+     */
+    public static final int SINK_ROLE = 1 << 1;
+
+    /**
+     * Health Profile - Channel Type used - Reliable
+     */
+    public static final int CHANNEL_TYPE_RELIABLE = 10;
+
+    /**
+     * Health Profile - Channel Type used - Streaming
+     */
+    public static final int CHANNEL_TYPE_STREAMING = 11;
+
+    /**
+     * @hide
+     */
+    public static final int CHANNEL_TYPE_ANY = 12;
+
+    private final ArrayList<BluetoothHealthAppConfiguration> mAppConfigs =
+        new ArrayList<BluetoothHealthAppConfiguration>();
+
+    /**
+     * Register an application configuration that acts as a Health SINK.
+     * This is the configuration that will be used to communicate with health devices
+     * which will act as the {@link #SOURCE_ROLE}. This is an asynchronous call and so
+     * the callback is used to notify success or failure if the function returns true.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param name The friendly name associated with the application or configuration.
+     * @param dataType The dataType of the Source role of Health Profile to which
+     *                   the sink wants to connect to.
+     * @param callback A callback to indicate success or failure of the registration and
+     *               all operations done on this application configuration.
+     * @return If true, callback will be called.
+     */
+    public boolean registerSinkAppConfiguration(String name, int dataType,
+            IBluetoothHealthCallback callback) {
+        if (!isEnabled() || name == null) return false;
+
+        if (DBG) log("registerSinkApplication(" + name + ":" + dataType + ")");
+        return registerAppConfiguration(name, dataType, SINK_ROLE,
+                CHANNEL_TYPE_ANY, callback);
+    }
+
+    /**
+     * Register an application configuration that acts as a Health SINK or in a Health
+     * SOURCE role.This is an asynchronous call and so
+     * the callback is used to notify success or failure if the function returns true.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param name The friendly name associated with the application or configuration.
+     * @param dataType The dataType of the Source role of Health Profile.
+     * @param channelType The channel type. Will be one of
+     *                              {@link #CHANNEL_TYPE_RELIABLE}  or
+     *                              {@link #CHANNEL_TYPE_STREAMING}
+     * @param callback - A callback to indicate success or failure.
+     * @return If true, callback will be called.
+     * @hide
+     */
+    public boolean registerAppConfiguration(String name, int dataType, int role,
+            int channelType, IBluetoothHealthCallback callback) {
+        boolean result = false;
+        if (!isEnabled() || !checkAppParam(name, role, channelType, callback)) return result;
+
+        if (DBG) log("registerApplication(" + name + ":" + dataType + ")");
+        BluetoothHealthAppConfiguration config =
+                new BluetoothHealthAppConfiguration(name, dataType, role, channelType,
+                callback);
+
+        if (mService != null) {
+            try {
+                result = mService.registerAppConfiguration(config);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+
+        if (result) mAppConfigs.add(config);
+        return result;
+    }
+
+    /**
+     * Unregister an application configuration that has been registered using
+     * {@link #registerSinkAppConfiguration}
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param config  The health app configuration
+     * @return Success or failure.
+     * @hide
+     */
+    public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
+        boolean result = false;
+        if (mService != null && isEnabled() && isValidAppConfig(config)) {
+            try {
+                result = mService.unregisterAppConfiguration(config);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        if (result) mAppConfigs.remove(config);
+        return result;
+    }
+
+    /**
+     * Connect to a health device which has the {@link #SOURCE_ROLE}.
+     * This is an asynchrnous call. If this function returns true, the callback
+     * associated with the application configuration will be called.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device The remote Bluetooth device.
+     * @param config The application configuration which has been registed using
+     *        {@link #registerSinkAppConfiguration(String, int, IBluetoothHealthCallback) }
+     * @return If true, the callback associated with the application config will be called.
+     */
+    public boolean connectChannelToSource(BluetoothDevice device,
+            BluetoothHealthAppConfiguration config) {
+        if (mService != null && isEnabled() && isValidDevice(device) &&
+                isValidAppConfig(config)) {
+            try {
+                return mService.connectChannelToSource(device, config);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Connect to a health device which has the {@link #SINK_ROLE}.
+     * This is an asynchronous call. If this function returns true, the callback
+     * associated with the application configuration will be called.
+     *
+     *<p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device The remote Bluetooth device.
+     * @param config The application configuration which has been registed using
+     *        {@link #registerSinkAppConfiguration(String, int, IBluetoothHealthCallback) }
+     * @return If true, the callback associated with the application config will be called.
+     * @hide
+     */
+    public boolean connectChannelToSink(BluetoothDevice device,
+            BluetoothHealthAppConfiguration config, int channelType) {
+        if (mService != null && isEnabled() && isValidDevice(device) &&
+                isValidAppConfig(config)) {
+            try {
+                return mService.connectChannelToSink(device, config, channelType);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Disconnect a connected health channel.
+     * This is an asynchronous call. If this function returns true, the callback
+     * associated with the application configuration will be called.
+     *
+     *<p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device The remote Bluetooth device.
+     * @param config The application configuration which has been registed using
+     *        {@link #registerSinkAppConfiguration(String, int, IBluetoothHealthCallback) }
+     * @param fd The file descriptor that was associated with the channel.
+     * @return If true, the callback associated with the application config will be called.
+     * @hide
+     */
+    public boolean disconnectChannel(BluetoothDevice device,
+            BluetoothHealthAppConfiguration config, ParcelFileDescriptor fd) {
+        if (mService != null && isEnabled() && isValidDevice(device) &&
+                isValidAppConfig(config)) {
+            try {
+                return mService.disconnectChannel(device, config, fd);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Get the file descriptor of the main channel associated with the remote device
+     * and application configuration.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device The remote Bluetooth health device
+     * @param config The application configuration
+     * @return null on failure, ParcelFileDescriptor on success.
+     */
+
+    public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
+            BluetoothHealthAppConfiguration config) {
+        if (mService != null && isEnabled() && isValidDevice(device) &&
+                isValidAppConfig(config)) {
+            try {
+                return mService.getMainChannelFd(device, config);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return null;
+    }
+
+    /**
+     * Get the current connection state of the profile.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * This is not specific to any application configuration but represents the connection
+     * state of the local Bluetooth adapter with the remote device. This can be used
+     * by applications like status bar which would just like to know the state of the
+     * local adapter.
+     *
+     * @param device Remote bluetooth device.
+     * @return State of the profile connection. One of
+     *               {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
+     *               {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
+     */
+    public int getConnectionState(BluetoothDevice device) {
+        if (mService != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return mService.getHealthDeviceConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return STATE_DISCONNECTED;
+    }
+
+    /**
+     * Get connected devices for this specific profile.
+     *
+     * <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * This is not specific to any application configuration but represents the connection
+     * state of the local Bluetooth adapter for this profile. This can be used
+     * by applications like status bar which would just like to know the state of the
+     * local adapter.
+     * @return List of devices. The list will be empty on error.
+     */
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (mService != null && isEnabled()) {
+            try {
+                return mService.getConnectedHealthDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * Get a list of devices that match any of the given connection
+     * states.
+     *
+     * <p> If none of the devices match any of the given states,
+     * an empty list will be returned.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     * This is not specific to any application configuration but represents the connection
+     * state of the local Bluetooth adapter for this profile. This can be used
+     * by applications like status bar which would just like to know the state of the
+     * local adapter.
+     *
+     * @param states Array of states. States can be one of
+     *              {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
+     *              {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
+     * @return List of devices. The list will be empty on error.
+     */
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (mService != null && isEnabled()) {
+            try {
+                return mService.getHealthDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (mService == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+     /** Health Channel Connection State - Disconnected */
+    public static final int STATE_CHANNEL_DISCONNECTED  = 0;
+    /** Health Channel Connection State - Connecting */
+    public static final int STATE_CHANNEL_CONNECTING    = 1;
+    /** Health Channel Connection State - Connected */
+    public static final int STATE_CHANNEL_CONNECTED     = 2;
+    /** Health Channel Connection State - Disconnecting */
+    public static final int STATE_CHANNEL_DISCONNECTING = 3;
+
+    /** Health App Configuration registration success */
+    public static final int APPLICATION_REGISTRATION_SUCCESS = 0;
+    /** Health App Configuration registration failure */
+    public static final int APPLICATION_REGISTRATION_FAILURE = 1;
+    /** Health App Configuration un-registration success */
+    public static final int APPLICATION_UNREGISTRATION_SUCCESS = 2;
+    /** Health App Configuration un-registration failure */
+    public static final int APPLICATION_UNREGISTRATION_FAILURE = 3;
+
+    private Context mContext;
+    private ServiceListener mServiceListener;
+    private IBluetooth mService;
+    BluetoothAdapter mAdapter;
+
+    /**
+     * Create a BluetoothHealth proxy object.
+     */
+    /*package*/ BluetoothHealth(Context mContext, ServiceListener l) {
+        IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE);
+        mServiceListener = l;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (b != null) {
+            mService = IBluetooth.Stub.asInterface(b);
+            if (mServiceListener != null) {
+                mServiceListener.onServiceConnected(BluetoothProfile.HEALTH, this);
+            }
+        } else {
+            Log.w(TAG, "Bluetooth Service not available!");
+
+            // Instead of throwing an exception which prevents people from going
+            // into Wireless settings in the emulator. Let it crash later when it is actually used.
+            mService = null;
+        }
+    }
+
+    private boolean isEnabled() {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+
+        if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
+        log("Bluetooth is Not enabled");
+        return false;
+    }
+
+    private boolean isValidDevice(BluetoothDevice device) {
+        if (device == null) return false;
+
+        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+        return false;
+    }
+
+    private boolean isValidAppConfig(BluetoothHealthAppConfiguration config) {
+        if (!mAppConfigs.isEmpty() && mAppConfigs.contains(config)) return true;
+        log("Not a valid config: " + config);
+        return false;
+    }
+
+    private boolean checkAppParam(String name, int role, int channelType,
+            IBluetoothHealthCallback callback) {
+        if (name == null || (role != SOURCE_ROLE && role != SINK_ROLE) ||
+                (channelType != CHANNEL_TYPE_RELIABLE &&
+                channelType != CHANNEL_TYPE_STREAMING &&
+                channelType != CHANNEL_TYPE_ANY) || callback == null) {
+            return false;
+        }
+        if (role == SOURCE_ROLE && channelType == CHANNEL_TYPE_ANY) return false;
+        return true;
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothHealthAppConfiguration.aidl b/core/java/android/bluetooth/BluetoothHealthAppConfiguration.aidl
new file mode 100644
index 0000000..bc9e54f
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHealthAppConfiguration.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.bluetooth;
+
+parcelable BluetoothHealthAppConfiguration;
diff --git a/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java b/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java
new file mode 100644
index 0000000..b87aea5
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.bluetooth;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The Bluetooth Health Application Configuration that is used in conjunction with
+ * the {@link BluetoothHealth} class. This class represents an application configuration
+ * that the Bluetooth Health third party application will register to communicate with the
+ * remote Bluetooth health device.
+ *
+ * @hide
+ */
+public final class BluetoothHealthAppConfiguration implements Parcelable {
+    private final String mName;
+    private final int mDataType;
+    private final int mRole;
+    private final int mChannelType;
+    private final IBluetoothHealthCallback mCallback;
+
+    /**
+     * Constructor to register the SINK role
+     *
+     * @param name Friendly name associated with the application configuration
+     * @param dataType Data Type of the remote Bluetooth Health device
+     * @param callback Callback associated with the application configuration.
+     */
+    BluetoothHealthAppConfiguration(String name, int dataType, IBluetoothHealthCallback callback) {
+        mName = name;
+        mDataType = dataType;
+        mRole = BluetoothHealth.SINK_ROLE;
+        mChannelType = BluetoothHealth.CHANNEL_TYPE_ANY;
+        mCallback = callback;
+    }
+
+    /**
+     * Constructor to register the application configuration.
+     *
+     * @param name Friendly name associated with the application configuration
+     * @param dataType Data Type of the remote Bluetooth Health device
+     * @param role {@link BluetoothHealth.SOURCE_ROLE} or
+     *                     {@link BluetoothHealth.SINK_ROLE}
+     * @param callback Callback associated with the application configuration.
+     */
+    BluetoothHealthAppConfiguration(String name, int dataType, int role, int channelType,
+            IBluetoothHealthCallback callback) {
+        mName = name;
+        mDataType = dataType;
+        mRole = role;
+        mChannelType = channelType;
+        mCallback = callback;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof BluetoothHealthAppConfiguration) {
+            BluetoothHealthAppConfiguration config = (BluetoothHealthAppConfiguration) o;
+            // config.getName() can never be NULL
+            return mName.equals(config.getName()) &&
+                    mDataType == config.getDataType() &&
+                    mRole == config.getRole() &&
+                    mChannelType == config.getChannelType() &&
+                    mCallback.equals(config.getCallback());
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + (mName != null ? mName.hashCode() : 0);
+        result = 31 * result + mDataType;
+        result = 31 * result + mRole;
+        result = 31 * result + mChannelType;
+        result = 31 * result + (mCallback != null ? mCallback.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "BluetoothHealthAppConfiguration [mName = " + mName +
+            ",mDataType = " + mDataType + ", mRole = " + mRole + ",mChannelType = " +
+            mChannelType +  ",callback=" + mCallback +"]";
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Return the data type associated with this application configuration.
+     *
+     * @return dataType
+     */
+    public int getDataType() {
+        return mDataType;
+    }
+
+    /**
+     * Return the name of the application configuration.
+     *
+     * @return String name
+     */
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Return the role associated with this application configuration.
+     *
+     * @return One of {@link BluetoothHealth#SOURCE_ROLE} or
+     *                         {@link BluetoothHealth#SINK_ROLE}
+     */
+    public int getRole() {
+        return mRole;
+    }
+
+    /**
+     * Return the channel type associated with this application configuration.
+     *
+     * @return One of {@link BluetoothHealth#CHANNEL_TYPE_RELIABLE} or
+     *                         {@link BluetoothHealth#CHANNEL_TYPE_STREAMING} or
+     *                         {@link BluetoothHealth#CHANNEL_TYPE_ANY}.
+     */
+    public int getChannelType() {
+        return mChannelType;
+    }
+
+    /**
+     * Return the callback associated with this application configuration.
+     *
+     * @return IBluetoothHealthCallback
+     */
+    public IBluetoothHealthCallback getCallback() {
+        return mCallback;
+    }
+
+    public static final Parcelable.Creator<BluetoothHealthAppConfiguration> CREATOR =
+        new Parcelable.Creator<BluetoothHealthAppConfiguration>() {
+        public BluetoothHealthAppConfiguration createFromParcel(Parcel in) {
+            String name = in.readString();
+            int type = in.readInt();
+            int role = in.readInt();
+            int channelType = in.readInt();
+            IBluetoothHealthCallback callback =
+                IBluetoothHealthCallback.Stub.asInterface(in.readStrongBinder());
+            return new BluetoothHealthAppConfiguration(name, type, role, channelType,
+                    callback);
+        }
+        public BluetoothHealthAppConfiguration[] newArray(int size) {
+            return new BluetoothHealthAppConfiguration[size];
+        }
+    };
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(mName);
+        out.writeInt(mDataType);
+        out.writeInt(mRole);
+        out.writeInt(mChannelType);
+        out.writeStrongInterface(mCallback);
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 22555f0..6cd81fd 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -65,16 +65,22 @@
     public static final int A2DP = 2;
 
     /**
+     * Health Profile
+     * @hide
+     */
+    public static final int HEALTH = 3;
+
+    /**
      * Input Device Profile
      * @hide
      */
-    public static final int INPUT_DEVICE = 3;
+    public static final int INPUT_DEVICE = 4;
 
     /**
      * PAN Profile
      * @hide
      */
-    public static final int PAN = 4;
+    public static final int PAN = 5;
 
     /**
      * Default priority for devices that we try to auto-connect to and
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index d25f5d0..28b09b6 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -18,7 +18,9 @@
 
 import android.bluetooth.IBluetoothCallback;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHealthAppConfiguration;
 import android.os.ParcelUuid;
+import android.os.ParcelFileDescriptor;
 
 /**
  * System private API for talking with the Bluetooth service.
@@ -98,5 +100,17 @@
     boolean connectPanDevice(in BluetoothDevice device);
     boolean disconnectPanDevice(in BluetoothDevice device);
 
+    // HDP profile APIs
+    boolean registerAppConfiguration(in BluetoothHealthAppConfiguration config);
+    boolean unregisterAppConfiguration(in BluetoothHealthAppConfiguration config);
+    boolean connectChannelToSource(in BluetoothDevice device, in BluetoothHealthAppConfiguration config);
+    boolean connectChannelToSink(in BluetoothDevice device, in BluetoothHealthAppConfiguration config,
+        int channelType);
+    boolean disconnectChannel(in BluetoothDevice device, in BluetoothHealthAppConfiguration config, in ParcelFileDescriptor fd);
+    ParcelFileDescriptor getMainChannelFd(in BluetoothDevice device, in BluetoothHealthAppConfiguration config);
+    List<BluetoothDevice> getConnectedHealthDevices();
+    List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(in int[] states);
+    int getHealthDeviceConnectionState(in BluetoothDevice device);
+
     void sendConnectionStateChange(in BluetoothDevice device, int state, int prevState);
 }
diff --git a/core/java/android/bluetooth/IBluetoothHealthCallback.aidl b/core/java/android/bluetooth/IBluetoothHealthCallback.aidl
new file mode 100644
index 0000000..9fe5335
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothHealthCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHealthAppConfiguration;
+import android.os.ParcelFileDescriptor;
+
+/**
+ *@hide
+ */
+interface IBluetoothHealthCallback
+{
+    void onHealthAppConfigurationStatusChange(in BluetoothHealthAppConfiguration config, int status);
+    void onHealthChannelStateChange(in BluetoothHealthAppConfiguration config,
+        in BluetoothDevice device, int prevState, int newState, in ParcelFileDescriptor fd);
+}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index e927f6c..208869b 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1573,7 +1573,7 @@
 
         boolean hardwareAccelerated = sa.getBoolean(
                 com.android.internal.R.styleable.AndroidManifestApplication_hardwareAccelerated,
-                false);
+                owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH);
 
         if (sa.getBoolean(
                 com.android.internal.R.styleable.AndroidManifestApplication_hasCode,
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 0be1776..8a42693 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -288,7 +288,8 @@
      * you can call {@link #reconnect()} to reclaim the camera.
      *
      * <p>This must be done before calling
-     * {@link android.media.MediaRecorder#setCamera(Camera)}.
+     * {@link android.media.MediaRecorder#setCamera(Camera)}. This cannot be
+     * called after recording starts.
      *
      * <p>If you are not recording video, you probably do not need this method.
      *
@@ -301,6 +302,11 @@
      * Camera objects are locked by default unless {@link #unlock()} is
      * called.  Normally {@link #reconnect()} is used instead.
      *
+     * <p>Since API level 13, camera is automatically locked for applications in
+     * {@link android.media.MediaRecorder#start()}. Applications can use the
+     * camera (ex: zoom) after recording starts. There is no need to call this
+     * after recording starts or stops.
+     *
      * <p>If you are not recording video, you probably do not need this method.
      *
      * @throws RuntimeException if the camera cannot be re-locked (for
@@ -315,9 +321,10 @@
      * which will re-acquire the lock and allow you to continue using the
      * camera.
      *
-     * <p>This must be done after {@link android.media.MediaRecorder} is
-     * done recording if {@link android.media.MediaRecorder#setCamera(Camera)}
-     * was used.
+     * <p>Since API level 13, camera is automatically locked for applications in
+     * {@link android.media.MediaRecorder#start()}. Applications can use the
+     * camera (ex: zoom) after recording starts. There is no need to call this
+     * after recording starts or stops.
      *
      * <p>If you are not recording video, you probably do not need this method.
      *
@@ -827,7 +834,9 @@
      * <p>This method is only valid when preview is active (after
      * {@link #startPreview()}).  Preview will be stopped after the image is
      * taken; callers must call {@link #startPreview()} again if they want to
-     * re-start preview or take more pictures.
+     * re-start preview or take more pictures. This should not be called between
+     * {@link android.media.MediaRecorder#start()} and
+     * {@link android.media.MediaRecorder#stop()}.
      *
      * <p>After calling this method, you must not call {@link #startPreview()}
      * or take another picture until the JPEG callback has returned.
diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java
index 6f92247..a866436 100644
--- a/core/java/android/net/EthernetDataTracker.java
+++ b/core/java/android/net/EthernetDataTracker.java
@@ -53,6 +53,7 @@
     private AtomicInteger mDefaultGatewayAddr = new AtomicInteger(0);
     private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false);
 
+    private static boolean mLinkUp;
     private LinkProperties mLinkProperties;
     private LinkCapabilities mLinkCapabilities;
     private NetworkInfo mNetworkInfo;
@@ -74,8 +75,25 @@
             mTracker = tracker;
         }
 
-        public void interfaceLinkStatusChanged(String iface, boolean up) {
-            Log.d(TAG, "Interface " + iface + " link " + (up ? "up" : "down"));
+        public void interfaceStatusChanged(String iface, boolean up) {
+            Log.d(TAG, "Interface status changed: " + iface + (up ? "up" : "down"));
+        }
+
+        public void interfaceLinkStateChanged(String iface, boolean up) {
+            if (mIface.equals(iface) && mLinkUp != up) {
+                Log.d(TAG, "Interface " + iface + " link " + (up ? "up" : "down"));
+                mLinkUp = up;
+
+                // use DHCP
+                if (up) {
+                    mTracker.reconnect();
+                } else {
+                    NetworkUtils.stopDhcp(mIface);
+                    mTracker.mNetworkInfo.setIsAvailable(false);
+                    mTracker.mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED,
+                                                           null, null);
+                }
+            }
         }
 
         public void interfaceAdded(String iface) {
@@ -91,6 +109,7 @@
         mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORKTYPE, "");
         mLinkProperties = new LinkProperties();
         mLinkCapabilities = new LinkCapabilities();
+        mLinkUp = false;
 
         mNetworkInfo.setIsAvailable(false);
         setTeardownRequested(false);
@@ -182,14 +201,11 @@
         // register for notifications from NetworkManagement Service
         IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
         INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
-        mInterfaceObserver = new InterfaceObserver(this);
-        try {
-            service.registerObserver(mInterfaceObserver);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Could not register InterfaceObserver " + e);
-        }
 
-        // connect to an ethernet interface that already exists
+        mInterfaceObserver = new InterfaceObserver(this);
+
+        // enable and try to connect to an ethernet interface that
+        // already exists
         sIfaceMatch = context.getResources().getString(
             com.android.internal.R.string.config_ethernet_iface_regex);
         try {
@@ -197,6 +213,8 @@
             for (String iface : ifaces) {
                 if (iface.matches(sIfaceMatch)) {
                     mIface = iface;
+                    InterfaceConfiguration config = service.getInterfaceConfig(iface);
+                    mLinkUp = config.isActive();
                     reconnect();
                     break;
                 }
@@ -204,6 +222,12 @@
         } catch (RemoteException e) {
             Log.e(TAG, "Could not get list of interfaces " + e);
         }
+
+        try {
+            service.registerObserver(mInterfaceObserver);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not register InterfaceObserver " + e);
+        }
     }
 
     /**
diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl
index d30b63d..4436e6e 100644
--- a/core/java/android/net/INetworkManagementEventObserver.aidl
+++ b/core/java/android/net/INetworkManagementEventObserver.aidl
@@ -23,12 +23,21 @@
  */
 interface INetworkManagementEventObserver {
     /**
-     * Interface link status has changed.
+     * Interface configuration status has changed.
      *
      * @param iface The interface.
-     * @param link True if link is up.
+     * @param up True if the interface has been enabled.
      */
-    void interfaceLinkStatusChanged(String iface, boolean link);
+    void interfaceStatusChanged(String iface, boolean up);
+
+    /**
+     * Interface physical-layer link state has changed.  For Ethernet,
+     * this method is invoked when the cable is plugged in or unplugged.
+     *
+     * @param iface The interface.
+     * @param up  True if the physical link-layer connection signal is valid.
+     */
+    void interfaceLinkStateChanged(String iface, boolean up);
 
     /**
      * An interface has been added to the system
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 1b28aa2..6c7c58d 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -278,6 +278,16 @@
 
         /**
          * Current version under development.
+         *
+         * <p>Applications targeting this or a later release will get these
+         * new changes in behavior:</p>
+         * <ul>
+         * <li> 2d drawing hardware acceleration is now turned on by default.
+         * You can use
+         * {@link android.R.attr#hardwareAccelerated android:hardwareAccelerated}
+         * to turn it off if needed, although this is strongly discouraged since
+         * it will result in poor performance on larger screen devices.
+         * </ul>
          */
         public static final int ICE_CREAM_SANDWICH = CUR_DEVELOPMENT;
     }
diff --git a/core/java/android/pim/EventRecurrence.java b/core/java/android/pim/EventRecurrence.java
index cde7dac..830f63f 100644
--- a/core/java/android/pim/EventRecurrence.java
+++ b/core/java/android/pim/EventRecurrence.java
@@ -456,6 +456,19 @@
         return true;
     }
 
+    /**
+     * Determines whether this rule specifies a simple monthly rule by weekday, such as
+     * "FREQ=MONTHLY;BYDAY=3TU" (the 3rd Tuesday of every month).
+     * <p>
+     * Negative days, e.g. "FREQ=MONTHLY;BYDAY=-1TU" (the last Tuesday of every month),
+     * will cause "false" to be returned.
+     * <p>
+     * Rules that fire every week, such as "FREQ=MONTHLY;BYDAY=TU" (every Tuesday of every
+     * month) will cause "false" to be returned.  (Note these are usually expressed as
+     * WEEKLY rules, and hence are uncommon.)
+     *
+     * @return true if this rule is of the appropriate form
+     */
     public boolean repeatsMonthlyOnDayCount() {
         if (this.freq != MONTHLY) {
             return false;
@@ -465,6 +478,10 @@
             return false;
         }
 
+        if (bydayNum[0] <= 0) {
+            return false;
+        }
+
         return true;
     }
 
diff --git a/core/java/android/provider/SyncConstValue.java b/core/java/android/provider/SyncConstValue.java
index 2fcf315..027aed1 100644
--- a/core/java/android/provider/SyncConstValue.java
+++ b/core/java/android/provider/SyncConstValue.java
@@ -18,7 +18,7 @@
 
 /**
  * Columns for tables that are synced to a server.
- * @deprecated
+ * @deprecated Do not use.
  * @hide
  */
 public interface SyncConstValue
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index e72aaa7..a220007 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -989,6 +989,33 @@
                                                       BluetoothPan.LOCAL_NAP_ROLE);
     }
 
+    /**
+     * Called by native code on a PropertyChanged signal from
+     * org.bluez.HealthDevice.
+     *
+     * @param devicePath the object path of the remote device
+     * @param propValues Properties (Name-Value) of the Health Device.
+     */
+    private void onHealthDevicePropertyChanged(String devicePath, String[] propValues) {
+        log("Health Device : Name of Property is: " + propValues[0] + " Value:" + propValues[1]);
+        mBluetoothService.onHealthDevicePropertyChanged(devicePath, propValues[1]);
+    }
+
+    /**
+     * Called by native code on a ChannelCreated/Deleted signal from
+     * org.bluez.HealthDevice.
+     *
+     * @param devicePath the object path of the remote device
+     * @param channelPath the path of the health channel.
+     * @param exists Boolean to indicate if the channel was created or deleted.
+     */
+    private void onHealthDeviceChannelChanged(String devicePath, String channelPath,
+            boolean exists) {
+        log("Health Device : devicePath: " + devicePath + ":channelPath:" + channelPath +
+                ":exists" + exists);
+        mBluetoothService.onHealthDeviceChannelChanged(devicePath, channelPath, exists);
+    }
+
     private void onRestartRequired() {
         if (mBluetoothService.isEnabled()) {
             Log.e(TAG, "*** A serious error occurred (did bluetoothd crash?) - " +
diff --git a/core/java/android/server/BluetoothHealthProfileHandler.java b/core/java/android/server/BluetoothHealthProfileHandler.java
new file mode 100644
index 0000000..7f862e0
--- /dev/null
+++ b/core/java/android/server/BluetoothHealthProfileHandler.java
@@ -0,0 +1,600 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHealth;
+import android.bluetooth.BluetoothHealthAppConfiguration;
+import android.bluetooth.BluetoothHealth;
+import android.bluetooth.BluetoothInputDevice;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map.Entry;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * This handles all the operations on the Bluetooth Health profile.
+ * All functions are called by BluetoothService, as Bluetooth Service
+ * is the Service handler for the HDP profile.
+ *
+ * @hide
+ */
+final class BluetoothHealthProfileHandler {
+    private static final String TAG = "BluetoothHealthProfileHandler";
+    /*STOPSHIP*/
+    private static final boolean DBG = true;
+
+    private static BluetoothHealthProfileHandler sInstance;
+    private Context mContext;
+    private BluetoothService mBluetoothService;
+    private ArrayList<HealthChannel> mHealthChannels;
+    private HashMap <BluetoothHealthAppConfiguration, String> mHealthAppConfigs;
+    private HashMap <BluetoothDevice, Integer> mHealthDevices;
+
+    private static final int MESSAGE_REGISTER_APPLICATION = 0;
+    private static final int MESSAGE_UNREGISTER_APPLICATION = 1;
+    private static final int MESSAGE_CONNECT_CHANNEL = 2;
+
+    class HealthChannel {
+        private ParcelFileDescriptor mChannelFd;
+        private boolean mMainChannel;
+        private String mChannelPath;
+        private BluetoothDevice mDevice;
+        private BluetoothHealthAppConfiguration mConfig;
+        private int mState;
+        private int mChannelType;
+
+        HealthChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config,
+                ParcelFileDescriptor fd, boolean mainChannel, String channelPath) {
+             mChannelFd = fd;
+             mMainChannel = mainChannel;
+             mChannelPath = channelPath;
+             mDevice = device;
+             mConfig = config;
+             mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
+        }
+    }
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+            case MESSAGE_REGISTER_APPLICATION:
+                BluetoothHealthAppConfiguration registerApp =
+                    (BluetoothHealthAppConfiguration) msg.obj;
+                int role = registerApp.getRole();
+                String path = null;
+
+                if (role == BluetoothHealth.SINK_ROLE) {
+                    path = mBluetoothService.registerHealthApplicationNative(
+                            registerApp.getDataType(), getStringRole(role), registerApp.getName());
+                } else {
+                    path = mBluetoothService.registerHealthApplicationNative(
+                            registerApp.getDataType(), getStringRole(role), registerApp.getName(),
+                            getStringChannelType(registerApp.getChannelType()));
+                }
+
+                if (path == null) {
+                    callHealthApplicationStatusCallback(registerApp,
+                            BluetoothHealth.APPLICATION_REGISTRATION_FAILURE);
+                } else {
+                    mHealthAppConfigs.put(registerApp, path);
+                    callHealthApplicationStatusCallback(registerApp,
+                            BluetoothHealth.APPLICATION_REGISTRATION_SUCCESS);
+                }
+
+                break;
+            case MESSAGE_UNREGISTER_APPLICATION:
+                BluetoothHealthAppConfiguration unregisterApp =
+                    (BluetoothHealthAppConfiguration) msg.obj;
+                boolean result = mBluetoothService.unregisterHealthApplicationNative(
+                        mHealthAppConfigs.get(unregisterApp));
+                if (result) {
+                    callHealthApplicationStatusCallback(unregisterApp,
+                            BluetoothHealth.APPLICATION_UNREGISTRATION_SUCCESS);
+                } else {
+                    callHealthApplicationStatusCallback(unregisterApp,
+                            BluetoothHealth.APPLICATION_UNREGISTRATION_FAILURE);
+                }
+                break;
+            case MESSAGE_CONNECT_CHANNEL:
+                HealthChannel chan = (HealthChannel)msg.obj;
+                String deviceObjectPath =
+                    mBluetoothService.getObjectPathFromAddress(chan.mDevice.getAddress());
+                String configPath = mHealthAppConfigs.get(chan.mConfig);
+                String channelType = getStringChannelType(chan.mChannelType);
+
+                if (!mBluetoothService.createChannelNative(deviceObjectPath, configPath,
+                          channelType)) {
+                    int prevState = chan.mState;
+                    int state = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
+                    callHealthChannelCallback(chan.mConfig, chan.mDevice, prevState, state, null);
+                    mHealthChannels.remove(chan);
+                }
+            }
+        }
+    };
+
+    private BluetoothHealthProfileHandler(Context context, BluetoothService service) {
+        mContext = context;
+        mBluetoothService = service;
+        mHealthAppConfigs = new HashMap<BluetoothHealthAppConfiguration, String>();
+        mHealthChannels = new ArrayList<HealthChannel>();
+        mHealthDevices = new HashMap<BluetoothDevice, Integer>();
+    }
+
+    static synchronized BluetoothHealthProfileHandler getInstance(Context context,
+            BluetoothService service) {
+        if (sInstance == null) sInstance = new BluetoothHealthProfileHandler(context, service);
+        return sInstance;
+    }
+
+    boolean registerAppConfiguration(BluetoothHealthAppConfiguration config) {
+        Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_APPLICATION);
+        msg.obj = config;
+        mHandler.sendMessage(msg);
+        return true;
+    }
+
+    boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
+        String path = mHealthAppConfigs.get(config);
+        if (path == null) return false;
+
+        Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_APPLICATION);
+        msg.obj = config;
+        mHandler.sendMessage(msg);
+        return true;
+    }
+
+    boolean connectChannelToSource(BluetoothDevice device,
+            BluetoothHealthAppConfiguration config) {
+        return connectChannel(device, config, BluetoothHealth.CHANNEL_TYPE_ANY);
+    }
+
+    private HealthChannel getMainChannel(BluetoothDevice device,
+            BluetoothHealthAppConfiguration config) {
+        for (HealthChannel chan: mHealthChannels) {
+            if (chan.mDevice.equals(device) && chan.mConfig.equals(config)) {
+                if (chan.mMainChannel) return chan;
+            }
+        }
+        return null;
+    }
+
+    boolean connectChannel(BluetoothDevice device,
+            BluetoothHealthAppConfiguration config, int channelType) {
+        String deviceObjectPath =
+            mBluetoothService.getObjectPathFromAddress(device.getAddress());
+        if (deviceObjectPath == null) return false;
+
+        String configPath = mHealthAppConfigs.get(config);
+        if (configPath == null) return false;
+
+        HealthChannel chan = new HealthChannel(device, config, null, false, null);
+        chan.mState = BluetoothHealth.STATE_CHANNEL_CONNECTING;
+        chan.mChannelType = channelType;
+        mHealthChannels.add(chan);
+
+        int prevState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
+        int state = BluetoothHealth.STATE_CHANNEL_CONNECTING;
+        callHealthChannelCallback(config, device, prevState, state, null);
+
+        Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_CHANNEL);
+        msg.obj = chan;
+        mHandler.sendMessage(msg);
+
+        return true;
+    }
+
+    private String getStringChannelType(int type) {
+        if (type == BluetoothHealth.CHANNEL_TYPE_RELIABLE) {
+            return "Reliable";
+        } else if (type == BluetoothHealth.CHANNEL_TYPE_STREAMING) {
+            return "Streaming";
+        } else {
+            return "Any";
+        }
+    }
+
+    private String getStringRole(int role) {
+        if (role == BluetoothHealth.SINK_ROLE) {
+            return "Sink";
+        } else if (role == BluetoothHealth.SOURCE_ROLE) {
+            return "Streaming";
+        } else {
+            return null;
+        }
+    }
+
+    boolean disconnectChannel(BluetoothDevice device,
+            BluetoothHealthAppConfiguration config, ParcelFileDescriptor fd) {
+        HealthChannel chan = findChannelByFd(device, config, fd);
+        if (chan == null) return false;
+
+        String deviceObjectPath =
+                mBluetoothService.getObjectPathFromAddress(device.getAddress());
+        if (mBluetoothService.destroyChannelNative(deviceObjectPath, chan.mChannelPath)) {
+            int prevState = chan.mState;
+            chan.mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTING;
+            callHealthChannelCallback(config, device, prevState, chan.mState,
+                    chan.mChannelFd);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private HealthChannel findChannelByFd(BluetoothDevice device,
+            BluetoothHealthAppConfiguration config, ParcelFileDescriptor fd) {
+        for (HealthChannel chan : mHealthChannels) {
+            if (chan.mChannelFd.equals(fd) && chan.mDevice.equals(device) &&
+                    chan.mConfig.equals(config)) return chan;
+        }
+        return null;
+    }
+
+    private HealthChannel findChannelByPath(BluetoothDevice device,
+            BluetoothHealthAppConfiguration config, String path) {
+        for (HealthChannel chan : mHealthChannels) {
+            if (chan.mChannelPath.equals(path) && chan.mDevice.equals(device) &&
+                    chan.mConfig.equals(config)) return chan;
+        }
+        return null;
+    }
+
+    private List<HealthChannel> findChannelByStates(BluetoothDevice device, int[] states) {
+        List<HealthChannel> channels = new ArrayList<HealthChannel>();
+        for (HealthChannel chan: mHealthChannels) {
+            if (chan.mDevice.equals(device)) {
+                for (int state : states) {
+                    if (chan.mState == state) {
+                        channels.add(chan);
+                    }
+                }
+            }
+        }
+        return channels;
+    }
+
+    private HealthChannel findConnectingChannel(BluetoothDevice device,
+            BluetoothHealthAppConfiguration config) {
+        for (HealthChannel chan : mHealthChannels) {
+            if (chan.mDevice.equals(device) && chan.mConfig.equals(config) &&
+                chan.mState == BluetoothHealth.STATE_CHANNEL_CONNECTING) return chan;
+        }
+        return null;
+    }
+
+    ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
+            BluetoothHealthAppConfiguration config) {
+        HealthChannel chan = getMainChannel(device, config);
+        if (chan != null) return chan.mChannelFd;
+
+        String objectPath =
+                mBluetoothService.getObjectPathFromAddress(device.getAddress());
+        if (objectPath == null) return null;
+
+        String mainChannelPath = mBluetoothService.getMainChannelNative(objectPath);
+        if (mainChannelPath == null) return null;
+
+        // We had no record of the main channel but querying Bluez we got a
+        // main channel. We might not have received the PropertyChanged yet for
+        // the main channel creation so update our data structure here.
+        chan = findChannelByPath(device, config, mainChannelPath);
+        if (chan == null) {
+            errorLog("Main Channel present but we don't have any account of it:" +
+                    device +":" + config);
+            return null;
+        }
+        chan.mMainChannel = true;
+        return chan.mChannelFd;
+    }
+
+    /*package*/ void onHealthDevicePropertyChanged(String devicePath,
+            String channelPath) {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        String address = mBluetoothService.getAddressFromObjectPath(devicePath);
+        if (address == null) return;
+
+        //TODO: Fix this in Bluez
+        if (channelPath.equals("/")) {
+            // This means that the main channel is being destroyed.
+            return;
+        }
+
+        BluetoothDevice device = adapter.getRemoteDevice(address);
+        BluetoothHealthAppConfiguration config = findHealthApplication(device,
+                channelPath);
+        if (config != null) {
+            HealthChannel chan = findChannelByPath(device, config, channelPath);
+            if (chan == null) {
+                errorLog("Health Channel is not present:" + channelPath);
+            } else {
+                chan.mMainChannel = true;
+            }
+        }
+    }
+
+    private BluetoothHealthAppConfiguration findHealthApplication(
+            BluetoothDevice device, String channelPath) {
+        BluetoothHealthAppConfiguration config = null;
+        String configPath = mBluetoothService.getChannelApplicationNative(channelPath);
+
+        if (configPath == null) {
+            errorLog("No associated application for Health Channel:" + channelPath);
+            return null;
+        } else {
+            for (Entry<BluetoothHealthAppConfiguration, String> e :
+                    mHealthAppConfigs.entrySet()) {
+                if (e.getValue().equals(configPath)) {
+                    config = e.getKey();
+                }
+            }
+            if (config == null) {
+                errorLog("No associated application for application path:" + configPath);
+                return null;
+            }
+        }
+        return config;
+    }
+
+    /*package*/ void onHealthDeviceChannelChanged(String devicePath,
+            String channelPath, boolean exists) {
+        debugLog("onHealthDeviceChannelChanged: devicePath: " + devicePath +
+                "ChannelPath: " + channelPath + "Exists: " + exists);
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        String address = mBluetoothService.getAddressFromObjectPath(devicePath);
+        if (address == null) return;
+
+        BluetoothDevice device = adapter.getRemoteDevice(address);
+
+        BluetoothHealthAppConfiguration config = findHealthApplication(device,
+                channelPath);
+        int state, prevState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
+        ParcelFileDescriptor fd;
+        HealthChannel channel;
+
+        if (config != null) {
+             if (exists) {
+                 fd = mBluetoothService.getChannelFdNative(channelPath);
+
+                 if (fd == null) {
+                     errorLog("Error obtaining fd for channel:" + channelPath);
+                     return;
+                 }
+
+                 boolean mainChannel =
+                         getMainChannel(device, config) == null ? false : true;
+                 if (!mainChannel) {
+                     String mainChannelPath =
+                             mBluetoothService.getMainChannelNative(devicePath);
+                     if (mainChannelPath == null) {
+                         errorLog("Main Channel Path is null for devicePath:" + devicePath);
+                         return;
+                     }
+                     if (mainChannelPath.equals(channelPath)) mainChannel = true;
+                 }
+
+                 channel = findConnectingChannel(device, config);
+                 if (channel != null) {
+                    channel.mChannelFd = fd;
+                    channel.mMainChannel = mainChannel;
+                    channel.mChannelPath = channelPath;
+                    prevState = channel.mState;
+                 } else {
+                    channel = new HealthChannel(device, config, fd, mainChannel,
+                            channelPath);
+                    mHealthChannels.add(channel);
+                    prevState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
+                 }
+                 state = BluetoothHealth.STATE_CHANNEL_CONNECTED;
+             } else {
+                 channel = findChannelByPath(device, config, channelPath);
+                 if (channel == null) {
+                     errorLog("Channel not found:" + config + ":" + channelPath);
+                     return;
+                 }
+
+                 fd = channel.mChannelFd;
+                 // CLOSE FD
+                 mBluetoothService.releaseChannelFdNative(channel.mChannelPath);
+                 mHealthChannels.remove(channel);
+
+                 prevState = channel.mState;
+                 state = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
+             }
+             channel.mState = state;
+             callHealthChannelCallback(config, device, prevState, state, fd);
+        }
+    }
+
+    private void callHealthChannelCallback(BluetoothHealthAppConfiguration config,
+            BluetoothDevice device, int prevState, int state, ParcelFileDescriptor fd) {
+        broadcastHealthDeviceStateChange(device, prevState, state);
+
+        debugLog("Health Device Callback: " + device + " State Change: "
+                + prevState + "->" + state);
+        try {
+            config.getCallback().onHealthChannelStateChange(config, device, prevState,
+                    state, fd);
+        } catch (RemoteException e) {
+            errorLog("Error while making health channel state change callback: " + e);
+        }
+    }
+
+    private void callHealthApplicationStatusCallback(
+            BluetoothHealthAppConfiguration config, int status) {
+        debugLog("Health Device Application: " + config + " State Change: status:"
+                + status);
+        try {
+            config.getCallback().onHealthAppConfigurationStatusChange(config, status);
+        } catch (RemoteException e) {
+            errorLog("Error while making health app registration state change callback: " + e);
+        }
+    }
+
+    int getHealthDeviceConnectionState(BluetoothDevice device) {
+        if (mHealthDevices.get(device) == null) {
+            return BluetoothHealth.STATE_DISCONNECTED;
+        }
+        return mHealthDevices.get(device);
+    }
+
+    List<BluetoothDevice> getConnectedHealthDevices() {
+        List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(
+                    new int[] {BluetoothHealth.STATE_CONNECTED});
+        return devices;
+    }
+
+    List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(int[] states) {
+        List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(states);
+        return devices;
+    }
+
+    List<BluetoothDevice> lookupHealthDevicesMatchingStates(int[] states) {
+        List<BluetoothDevice> healthDevices = new ArrayList<BluetoothDevice>();
+
+        for (BluetoothDevice device: mHealthDevices.keySet()) {
+            int healthDeviceState = getHealthDeviceConnectionState(device);
+            for (int state : states) {
+                if (state == healthDeviceState) {
+                    healthDevices.add(device);
+                    break;
+                }
+            }
+        }
+        return healthDevices;
+    }
+
+    /**
+     * This function sends the intent for the updates on the connection status to the remote device.
+     * Note that multiple channels can be connected to the remote device by multiple applications.
+     * This sends an intent for the update to the device connection status and not the channel
+     * connection status. Only the following state transitions are possible:
+     *
+     * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTING}
+     * {@link BluetoothHealth#STATE_CONNECTING} to {@link BluetoothHealth#STATE_CONNECTED}
+     * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTING}
+     * {@link BluetoothHealth#STATE_DISCONNECTING} to {@link BluetoothHealth#STATE_DISCONNECTED}
+     * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTED}
+     * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTED}
+     * {@link BluetoothHealth#STATE_CONNECTING} to {{@link BluetoothHealth#STATE_DISCONNECTED}
+     *
+     * @param device
+     * @param prevChannelState
+     * @param newChannelState
+     * @hide
+     */
+    private void broadcastHealthDeviceStateChange(BluetoothDevice device, int prevChannelState,
+            int newChannelState) {
+        if (mHealthDevices.get(device) == null) {
+            mHealthDevices.put(device, BluetoothHealth.STATE_DISCONNECTED);
+        }
+
+        int currDeviceState = mHealthDevices.get(device);
+        int newDeviceState = convertState(newChannelState);
+
+        if (currDeviceState != newDeviceState) {
+            List<HealthChannel> chan;
+            switch (currDeviceState) {
+                case BluetoothHealth.STATE_DISCONNECTED:
+                    updateAndsendIntent(device, currDeviceState, newDeviceState);
+                    break;
+                case BluetoothHealth.STATE_CONNECTING:
+                    // Channel got connected.
+                    if (newDeviceState == BluetoothHealth.STATE_CONNECTED) {
+                        updateAndsendIntent(device, currDeviceState, newDeviceState);
+                    } else {
+                        // Channel got disconnected
+                        chan = findChannelByStates(device, new int [] {
+                                    BluetoothHealth.STATE_CHANNEL_CONNECTING,
+                                    BluetoothHealth.STATE_CHANNEL_DISCONNECTING});
+                        if (chan.isEmpty()) {
+                            updateAndsendIntent(device, currDeviceState, newDeviceState);
+                        }
+                    }
+                    break;
+                case BluetoothHealth.STATE_CONNECTED:
+                    // Channel got disconnected or is in disconnecting state.
+                    chan = findChannelByStates(device, new int [] {
+                                BluetoothHealth.STATE_CHANNEL_CONNECTING,
+                                BluetoothHealth.STATE_CHANNEL_CONNECTED});
+                    if (chan.isEmpty()) {
+                        updateAndsendIntent(device, currDeviceState, newDeviceState);
+                    }
+                case BluetoothHealth.STATE_DISCONNECTING:
+                    // Channel got disconnected.
+                    chan = findChannelByStates(device, new int [] {
+                                BluetoothHealth.STATE_CHANNEL_CONNECTING,
+                                BluetoothHealth.STATE_CHANNEL_DISCONNECTING});
+                    if (chan.isEmpty()) {
+                        updateAndsendIntent(device, currDeviceState, newDeviceState);
+                    }
+                    break;
+            }
+        }
+    }
+
+    private void updateAndsendIntent(BluetoothDevice device, int prevDeviceState,
+            int newDeviceState) {
+        mHealthDevices.put(device, newDeviceState);
+        mBluetoothService.sendConnectionStateChange(device, prevDeviceState, newDeviceState);
+    }
+
+    /**
+     * This function converts the channel connection state to device connection state.
+     *
+     * @param state
+     * @return
+     */
+    private int convertState(int state) {
+        switch (state) {
+            case BluetoothHealth.STATE_CHANNEL_CONNECTED:
+                return BluetoothHealth.STATE_CONNECTED;
+            case BluetoothHealth.STATE_CHANNEL_CONNECTING:
+                return BluetoothHealth.STATE_CONNECTING;
+            case BluetoothHealth.STATE_CHANNEL_DISCONNECTING:
+                return BluetoothHealth.STATE_DISCONNECTING;
+            case BluetoothHealth.STATE_CHANNEL_DISCONNECTED:
+                return BluetoothHealth.STATE_DISCONNECTED;
+        }
+        errorLog("Mismatch in Channel and Health Device State");
+        return -1;
+    }
+
+    private static void debugLog(String msg) {
+        if (DBG) Log.d(TAG, msg);
+    }
+
+    private static void errorLog(String msg) {
+        Log.e(TAG, msg);
+    }
+}
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index a4588ae..7f47ebc 100755
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -31,6 +31,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothDeviceProfileState;
 import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHealthAppConfiguration;
 import android.bluetooth.BluetoothInputDevice;
 import android.bluetooth.BluetoothPan;
 import android.bluetooth.BluetoothProfile;
@@ -49,6 +50,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
+import android.os.ParcelFileDescriptor;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -148,6 +150,7 @@
     private int mAdapterConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
     private BluetoothPanProfileHandler mBluetoothPanProfileHandler;
     private BluetoothInputProfileHandler mBluetoothInputProfileHandler;
+    private BluetoothHealthProfileHandler mBluetoothHealthProfileHandler;
 
     private static class RemoteService {
         public String address;
@@ -220,6 +223,7 @@
         mContext.registerReceiver(mReceiver, filter);
         mBluetoothInputProfileHandler = BluetoothInputProfileHandler.getInstance(mContext, this);
         mBluetoothPanProfileHandler = BluetoothPanProfileHandler.getInstance(mContext, this);
+        mBluetoothHealthProfileHandler = BluetoothHealthProfileHandler.getInstance(mContext, this);
     }
 
     public static synchronized String readDockBluetoothAddress() {
@@ -2077,6 +2081,106 @@
         }
     }
 
+    /**** Handlers for Health Device Profile ****/
+    // TODO: All these need to be converted to a state machine.
+
+    public boolean registerAppConfiguration(BluetoothHealthAppConfiguration config) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                "Need BLUETOOTH permission");
+        synchronized (mBluetoothHealthProfileHandler) {
+                return mBluetoothHealthProfileHandler.registerAppConfiguration(config);
+        }
+    }
+
+    public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                "Need BLUETOOTH permission");
+        synchronized (mBluetoothHealthProfileHandler) {
+                return mBluetoothHealthProfileHandler.unregisterAppConfiguration(config);
+        }
+    }
+
+
+    public boolean connectChannelToSource(BluetoothDevice device,
+            BluetoothHealthAppConfiguration config) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                "Need BLUETOOTH permission");
+        synchronized (mBluetoothHealthProfileHandler) {
+            return mBluetoothHealthProfileHandler.connectChannelToSource(device,
+                    config);
+        }
+    }
+
+    public boolean connectChannelToSink(BluetoothDevice device,
+            BluetoothHealthAppConfiguration config, int channelType) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                                                "Need BLUETOOTH permission");
+        synchronized (mBluetoothHealthProfileHandler) {
+            return mBluetoothHealthProfileHandler.connectChannel(device, config,
+                    channelType);
+        }
+    }
+
+    public boolean disconnectChannel(BluetoothDevice device,
+            BluetoothHealthAppConfiguration config, ParcelFileDescriptor fd) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                "Need BLUETOOTH permission");
+        synchronized (mBluetoothHealthProfileHandler) {
+            return mBluetoothHealthProfileHandler.disconnectChannel(device, config, fd);
+        }
+    }
+
+    public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
+            BluetoothHealthAppConfiguration config) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                "Need BLUETOOTH permission");
+        synchronized (mBluetoothHealthProfileHandler) {
+            return mBluetoothHealthProfileHandler.getMainChannelFd(device, config);
+        }
+    }
+
+    /*package*/ void onHealthDevicePropertyChanged(String devicePath,
+            String channelPath) {
+        synchronized (mBluetoothHealthProfileHandler) {
+            mBluetoothHealthProfileHandler.onHealthDevicePropertyChanged(devicePath,
+                    channelPath);
+        }
+    }
+
+    /*package*/ void onHealthDeviceChannelChanged(String devicePath,
+            String channelPath, boolean exists) {
+        synchronized(mBluetoothHealthProfileHandler) {
+            mBluetoothHealthProfileHandler.onHealthDeviceChannelChanged(devicePath,
+                    channelPath, exists);
+        }
+    }
+
+    public int getHealthDeviceConnectionState(BluetoothDevice device) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                "Need BLUETOOTH permission");
+        synchronized (mBluetoothHealthProfileHandler) {
+            return mBluetoothHealthProfileHandler.getHealthDeviceConnectionState(device);
+        }
+    }
+
+    public List<BluetoothDevice> getConnectedHealthDevices() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                "Need BLUETOOTH permission");
+        synchronized (mBluetoothHealthProfileHandler) {
+            return mBluetoothHealthProfileHandler.getConnectedHealthDevices();
+        }
+    }
+
+    public List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(
+            int[] states) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                "Need BLUETOOTH permission");
+        synchronized (mBluetoothHealthProfileHandler) {
+            return mBluetoothHealthProfileHandler.
+                    getHealthDevicesMatchingConnectionStates(states);
+        }
+    }
+
     public boolean connectHeadset(String address) {
         if (getBondState(address) != BluetoothDevice.BOND_BONDED) return false;
 
@@ -2375,4 +2479,16 @@
 
     private native int[] addReservedServiceRecordsNative(int[] uuuids);
     private native boolean removeReservedServiceRecordsNative(int[] handles);
+
+    // Health API
+    native String registerHealthApplicationNative(int dataType, String role, String name,
+            String channelType);
+    native String registerHealthApplicationNative(int dataType, String role, String name);
+    native boolean unregisterHealthApplicationNative(String path);
+    native boolean createChannelNative(String devicePath, String appPath, String channelType);
+    native boolean destroyChannelNative(String devicePath, String channelpath);
+    native String getMainChannelNative(String path);
+    native String getChannelApplicationNative(String channelPath);
+    native ParcelFileDescriptor getChannelFdNative(String channelPath);
+    native boolean releaseChannelFdNative(String channelPath);
 }
diff --git a/core/java/android/view/GLES20Layer.java b/core/java/android/view/GLES20Layer.java
index 69dfc2b..a491a0b 100644
--- a/core/java/android/view/GLES20Layer.java
+++ b/core/java/android/view/GLES20Layer.java
@@ -42,9 +42,15 @@
         return mLayer;
     }
 
+    @Override
     boolean copyInto(Bitmap bitmap) {
         return GLES20Canvas.nCopyLayer(mLayer, bitmap.mNativeBitmap);
-    }    
+    }
+    
+    @Override
+    void update(int width, int height, boolean isOpaque) {
+        super.update(width, height, isOpaque);
+    }
 
     @Override
     void destroy() {
diff --git a/core/java/android/view/GLES20TextureLayer.java b/core/java/android/view/GLES20TextureLayer.java
index 5ee292e..391d9f4 100644
--- a/core/java/android/view/GLES20TextureLayer.java
+++ b/core/java/android/view/GLES20TextureLayer.java
@@ -70,7 +70,9 @@
         return mSurface;
     }
 
+    @Override
     void update(int width, int height, boolean isOpaque) {
+        super.update(width, height, isOpaque);
         GLES20Canvas.nUpdateTextureLayer(mLayer, width, height, isOpaque, mSurface);
     }
 }
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index 86dec3f..dfb39ae 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
 
 /**
@@ -34,7 +35,7 @@
     int mWidth;
     int mHeight;
 
-    final boolean mOpaque;
+    boolean mOpaque;
 
     /**
      * Creates a new hardware layer with undefined dimensions.
@@ -92,7 +93,7 @@
     abstract boolean isValid();
 
     /**
-     * Resizes the layer, if necessary, to be at least as large
+     * Resize the layer, if necessary, to be at least as large
      * as the supplied dimensions.
      * 
      * @param width The new desired minimum width for this layer
@@ -124,4 +125,29 @@
      * @param currentCanvas
      */
     abstract void end(Canvas currentCanvas);
+
+    /**
+     * Copies this layer into the specified bitmap.
+     * 
+     * @param bitmap The bitmap to copy they layer into
+     * 
+     * @return True if the copy was successful, false otherwise
+     */
+    abstract boolean copyInto(Bitmap bitmap);
+
+    /**
+     * Update the layer's properties. This method should be used
+     * when the underlying storage is modified by an external entity.
+     * To change the underlying storage, use the {@link #resize(int, int)}
+     * method instead.
+     * 
+     * @param width The new width of this layer
+     * @param height The new height of this layer
+     * @param isOpaque Whether this layer is opaque
+     */
+    void update(int width, int height, boolean isOpaque) {
+        mWidth = width;
+        mHeight = height;
+        mOpaque = isOpaque;
+    }
 }
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index c6d011e..bbfb4c1 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -17,12 +17,11 @@
 
 package android.view;
 
-import android.graphics.Bitmap;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
-import android.os.*;
-import android.util.EventLog;
+import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.util.Log;
 
 import javax.microedition.khronos.egl.EGL10;
@@ -199,26 +198,6 @@
     abstract SurfaceTexture createSurfaceTexture(HardwareLayer layer);
 
     /**
-     * Updates the specified layer.
-     * 
-     * @param layer The hardware layer to update
-     * @param width The layer's width
-     * @param height The layer's height
-     * @param isOpaque Whether the layer is opaque
-     */
-    abstract void updateTextureLayer(HardwareLayer layer, int width, int height, boolean isOpaque);
-
-    /**
-     * Copies the content of the specified layer into the specified bitmap.
-     * 
-     * @param layer The hardware layer to copy
-     * @param bitmap The bitmap to copy the layer into
-     * 
-     * @return True if the copy was successful, false otherwise
-     */
-    abstract boolean copyLayer(HardwareLayer layer, Bitmap bitmap);    
-
-    /**
      * Initializes the hardware renderer for the specified surface and setup the
      * renderer for drawing, if needed. This is invoked when the ViewAncestor has
      * potentially lost the hardware renderer. The hardware renderer should be
@@ -342,6 +321,13 @@
         }
 
         /**
+         * Indicates whether this renderer instance can track and update dirty regions.
+         */
+        boolean hasDirtyRegions() {
+            return mDirtyRegions;
+        }
+
+        /**
          * Return a string for the EGL error code, or the hex representation
          * if the error is unknown.
          * 
@@ -634,19 +620,14 @@
         void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
                 Rect dirty) {
             if (canDraw()) {
-                if (!mDirtyRegions) {
+                if (!hasDirtyRegions()) {
                     dirty = null;
                 }
-
-                attachInfo.mDrawingTime = SystemClock.uptimeMillis();
                 attachInfo.mIgnoreDirtyState = true;
+                attachInfo.mDrawingTime = SystemClock.uptimeMillis();
+
                 view.mPrivateFlags |= View.DRAWN;
                 
-                long startTime;
-                if (ViewDebug.DEBUG_PROFILE_DRAWING) {
-                    startTime = SystemClock.elapsedRealtime();
-                }
-
                 final int surfaceState = checkCurrent();
                 if (surfaceState != SURFACE_STATE_ERROR) {
                     // We had to change the current surface and/or context, redraw everything
@@ -700,26 +681,9 @@
 
                     onPostDraw();
 
-                    if (ViewDebug.DEBUG_PROFILE_DRAWING) {
-                        EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
-                    }
-    
                     attachInfo.mIgnoreDirtyState = false;
 
-                    final long swapBuffersStartTime;
-                    if (ViewDebug.DEBUG_LATENCY) {
-                        swapBuffersStartTime = System.nanoTime();
-                    }
-
                     sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
-
-                    if (ViewDebug.DEBUG_LATENCY) {
-                        long now = System.nanoTime();
-                        Log.d(LOG_TAG, "Latency: Spent "
-                                + ((now - swapBuffersStartTime) * 0.000001f)
-                                + "ms waiting for eglSwapBuffers()");
-                    }
-
                     checkEglErrors();
                 }
             }
@@ -820,16 +784,6 @@
             return ((GLES20TextureLayer) layer).getSurfaceTexture();
         }
 
-        @Override
-        void updateTextureLayer(HardwareLayer layer, int width, int height, boolean isOpaque) {
-            ((GLES20TextureLayer) layer).update(width, height, isOpaque);
-        }
-
-        @Override
-        boolean copyLayer(HardwareLayer layer, Bitmap bitmap) {
-            return ((GLES20Layer) layer).copyInto(bitmap);
-        }
-
         static HardwareRenderer create(boolean translucent) {
             if (GLES20Canvas.isAvailable()) {
                 return new Gl20Renderer(translucent);
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 5dbda90..6c3d387 100755
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -2361,11 +2361,11 @@
      * Gets the {@link KeyCharacterMap} associated with the keyboard device.
      *
      * @return The associated key character map.
-     * @throws {@link UnavailableException} if the key character map
+     * @throws {@link KeyCharacterMap.UnavailableException} if the key character map
      * could not be loaded because it was malformed or the default key character map
      * is missing from the system.
      *
-     * @see {@link KeyCharacterMap#load}
+     * @see KeyCharacterMap#load
      */
     public final KeyCharacterMap getKeyCharacterMap() {
         return KeyCharacterMap.load(mDeviceId);
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index de398eb..d656f31 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -78,7 +78,7 @@
  *      }
  *
  *      public void onSurfaceTextureUpdated(SurfaceTexture surface) {
- *          // Ignored
+ *          // Invoked every time there's a new Camera preview frame
  *      }
  *  }
  * </pre>
@@ -102,12 +102,9 @@
 
     private boolean mOpaque = true;
 
-    private final Runnable mUpdateLayerAction = new Runnable() {
-        @Override
-        public void run() {
-            updateLayer();
-        }
-    };
+    private final Object[] mLock = new Object[0];
+    private boolean mUpdateLayer;
+
     private SurfaceTexture.OnFrameAvailableListener mUpdateListener;
 
     /**
@@ -170,7 +167,7 @@
     public void setOpaque(boolean opaque) {
         if (opaque != mOpaque) {
             mOpaque = opaque;
-            if (mLayer != null) updateLayer();
+            updateLayer();
         }
     }
 
@@ -188,7 +185,7 @@
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
 
-        if (isHardwareAccelerated() && mLayer != null) {
+        if (mLayer != null) {
             if (mListener != null) {
                 mListener.onSurfaceTextureDestroyed(mSurface);
             }
@@ -243,6 +240,7 @@
      */
     @Override
     public final void draw(Canvas canvas) {
+        applyUpdate();
     }
 
     /**
@@ -268,11 +266,11 @@
 
     @Override
     HardwareLayer getHardwareLayer() {
-        if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null) {
-            return null;
-        }
-
         if (mLayer == null) {
+            if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null) {
+                return null;
+            }
+
             mLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer(mOpaque);
             mSurface = mAttachInfo.mHardwareRenderer.createSurfaceTexture(mLayer);
             nSetDefaultBufferSize(mSurface, getWidth(), getHeight());
@@ -282,7 +280,10 @@
                 public void onFrameAvailable(SurfaceTexture surfaceTexture) {
                     // Per SurfaceTexture's documentation, the callback may be invoked
                     // from an arbitrary thread
-                    post(mUpdateLayerAction);
+                    synchronized (mLock) {
+                        mUpdateLayer = true;
+                    }
+                    postInvalidateDelayed(0);
                 }
             };
             mSurface.setOnFrameAvailableListener(mUpdateListener);
@@ -292,6 +293,8 @@
             }
         }
 
+        applyUpdate();
+
         return mLayer;
     }
 
@@ -313,17 +316,28 @@
     }
 
     private void updateLayer() {
-        if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null) {
+        mUpdateLayer = true;
+        invalidate();
+    }
+    
+    private void applyUpdate() {
+        if (mLayer == null) {
             return;
         }
 
-        mAttachInfo.mHardwareRenderer.updateTextureLayer(mLayer, getWidth(), getHeight(), mOpaque);
+        synchronized (mLock) {
+            if (mUpdateLayer) {
+                mUpdateLayer = false;
+            } else {
+                return;
+            }
+        }
+        
+        mLayer.update(getWidth(), getHeight(), mOpaque);
 
         if (mListener != null) {
             mListener.onSurfaceTextureUpdated(mSurface);
         }
-
-        invalidate();
     }
 
     /**
@@ -402,7 +416,7 @@
      */
     public Bitmap getBitmap(Bitmap bitmap) {
         if (bitmap != null && isAvailable()) {
-            mAttachInfo.mHardwareRenderer.copyLayer(mLayer, bitmap);
+            mLayer.copyInto(bitmap);
         }
         return bitmap;
     }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8184643..5743134 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2326,6 +2326,7 @@
     private CheckForLongPress mPendingCheckForLongPress;
     private CheckForTap mPendingCheckForTap = null;
     private PerformClick mPerformClick;
+    private SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent;
 
     private UnsetPressedState mUnsetPressedState;
 
@@ -3699,7 +3700,6 @@
      * @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent)
      */
     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
-
     }
 
     /**
@@ -3728,12 +3728,23 @@
         event.setEnabled(isEnabled());
         event.setContentDescription(mContentDescription);
 
-        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED && mAttachInfo != null) {
-            ArrayList<View> focusablesTempList = mAttachInfo.mFocusablesTempList;
-            getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD, FOCUSABLES_ALL);
-            event.setItemCount(focusablesTempList.size());
-            event.setCurrentItemIndex(focusablesTempList.indexOf(this));
-            focusablesTempList.clear();
+        final int eventType = event.getEventType();
+        switch (eventType) {
+            case AccessibilityEvent.TYPE_VIEW_FOCUSED: {
+                if (mAttachInfo != null) {
+                    ArrayList<View> focusablesTempList = mAttachInfo.mFocusablesTempList;
+                    getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD,
+                            FOCUSABLES_ALL);
+                    event.setItemCount(focusablesTempList.size());
+                    event.setCurrentItemIndex(focusablesTempList.indexOf(this));
+                    focusablesTempList.clear();
+                }
+            } break;
+            case AccessibilityEvent.TYPE_VIEW_SCROLLED: {
+                event.setScrollX(mScrollX);
+                event.setScrollY(mScrollY);
+                event.setItemCount(getHeight());
+            } break;
         }
     }
 
@@ -4345,19 +4356,25 @@
     }
 
     /**
-     * Set the layout direction for this view.
+     * Set the layout direction for this view. This will propagate a reset of layout direction
+     * resolution to the view's children and resolve layout direction for this view.
      *
      * @param layoutDirection One of {@link #LAYOUT_DIRECTION_LTR},
      *   {@link #LAYOUT_DIRECTION_RTL},
      *   {@link #LAYOUT_DIRECTION_INHERIT} or
      *   {@link #LAYOUT_DIRECTION_LOCALE}.
+     *
      * @attr ref android.R.styleable#View_layoutDirection
      *
      * @hide
      */
     @RemotableViewMethod
     public void setLayoutDirection(int layoutDirection) {
-        setFlags(layoutDirection, LAYOUT_DIRECTION_MASK);
+        if (getLayoutDirection() != layoutDirection) {
+            resetLayoutDirectionResolution();
+            // Setting the flag will also request a layout.
+            setFlags(layoutDirection, LAYOUT_DIRECTION_MASK);
+        }
     }
 
     /**
@@ -5121,12 +5138,7 @@
             mInputEventConsistencyVerifier.onTrackballEvent(event, 0);
         }
 
-        //Log.i("view", "view=" + this + ", " + event.toString());
-        if (onTrackballEvent(event)) {
-            return true;
-        }
-
-        return false;
+        return onTrackballEvent(event);
     }
 
     /**
@@ -5221,6 +5233,7 @@
                 break;
         }
 
+        //noinspection SimplifiableIfStatement
         if (mOnHoverListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                 && mOnHoverListener.onHover(this, event)) {
             return true;
@@ -5893,6 +5906,7 @@
      */
     private boolean isHoverable() {
         final int viewFlags = mViewFlags;
+        //noinspection SimplifiableIfStatement
         if ((viewFlags & ENABLED_MASK) == DISABLED) {
             return false;
         }
@@ -6162,6 +6176,16 @@
     }
 
     /**
+     * Remove the pending callback for sending a
+     * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event.
+     */
+    private void removeSendViewScrolledAccessibilityEventCallback() {
+        if (mSendViewScrolledAccessibilityEvent != null) {
+            removeCallbacks(mSendViewScrolledAccessibilityEvent);
+        }
+    }
+
+    /**
      * Sets the TouchDelegate for this View.
      */
     public void setTouchDelegate(TouchDelegate delegate) {
@@ -6334,6 +6358,10 @@
      * @param oldt Previous vertical scroll origin.
      */
     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+            postSendViewScrolledAccessibilityEventCallback();
+        }
+
         mBackgroundSizeChanged = true;
 
         final AttachInfo ai = mAttachInfo;
@@ -8260,6 +8288,22 @@
     }
 
     /**
+     * Post a callback to send a {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
+     * This event is sent at most once every
+     * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}.
+     */
+    private void postSendViewScrolledAccessibilityEventCallback() {
+        if (mSendViewScrolledAccessibilityEvent == null) {
+            mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent();
+        }
+        if (!mSendViewScrolledAccessibilityEvent.mIsPending) {
+            mSendViewScrolledAccessibilityEvent.mIsPending = true;
+            postDelayed(mSendViewScrolledAccessibilityEvent,
+                    ViewConfiguration.getSendRecurringAccessibilityEventsInterval());
+        }
+    }
+
+    /**
      * Called by a parent to request that a child update its values for mScrollX
      * and mScrollY if necessary. This will typically be done if the child is
      * animating a scroll using a {@link android.widget.Scroller Scroller}
@@ -8988,7 +9032,8 @@
     /**
      * Reset the resolved layout direction by clearing the corresponding flag
      */
-    private void resetLayoutDirectionResolution() {
+    void resetLayoutDirectionResolution() {
+        // Reset the current View resolution
         mPrivateFlags2 &= ~LAYOUT_DIRECTION_RESOLVED;
     }
 
@@ -9015,6 +9060,7 @@
         removeUnsetPressCallback();
         removeLongPressCallback();
         removePerformClickCallback();
+        removeSendViewScrolledAccessibilityEventCallback();
 
         destroyDrawingCache();
 
@@ -11826,6 +11872,10 @@
         mPrivateFlags |= FORCE_LAYOUT;
         mPrivateFlags |= INVALIDATED;
 
+        if (mLayoutParams != null) {
+            mLayoutParams.resolveWithDirection(getResolvedLayoutDirection());
+        }
+
         if (mParent != null && !mParent.isLayoutRequested()) {
             mParent.requestLayout();
         }
@@ -12847,7 +12897,7 @@
      * A Property wrapper around the <code>alpha</code> functionality handled by the
      * {@link View#setAlpha(float)} and {@link View#getAlpha()} methods.
      */
-    static Property<View, Float> ALPHA = new FloatProperty<View>("alpha") {
+    public static Property<View, Float> ALPHA = new FloatProperty<View>("alpha") {
         @Override
         public void setValue(View object, float value) {
             object.setAlpha(value);
@@ -13573,6 +13623,12 @@
         boolean mIgnoreDirtyState;
 
         /**
+         * This flag tracks when the mIgnoreDirtyState flag is set during draw(),
+         * to avoid clearing that flag prematurely.
+         */
+        boolean mSetIgnoreDirtyState = false;
+
+        /**
          * Indicates whether the view's window is currently in touch mode.
          */
         boolean mInTouchMode;
@@ -13806,6 +13862,18 @@
                 host.invalidate(true);
             }
         }
+    }
 
+    /**
+     * Resuable callback for sending
+     * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event.
+     */
+    private class SendViewScrolledAccessibilityEvent implements Runnable {
+        public volatile boolean mIsPending;
+
+        public void run() {
+            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+            mIsPending = false;
+        }
     }
 }
diff --git a/core/java/android/view/ViewAncestor.java b/core/java/android/view/ViewAncestor.java
index ad660c1..2b692f3 100644
--- a/core/java/android/view/ViewAncestor.java
+++ b/core/java/android/view/ViewAncestor.java
@@ -136,13 +136,6 @@
     static final ArrayList<ComponentCallbacks> sConfigCallbacks
             = new ArrayList<ComponentCallbacks>();
 
-    /**
-     * Delay before dispatching an accessibility event that the window
-     * content has changed. The window content is considered changed
-     * after a layout pass.
-     */
-    private static final long SEND_WINDOW_CONTENT_CHANGED_DELAY_MILLIS = 500;
-
     long mLastTrackballTime = 0;
     final TrackballAxis mTrackballAxisX = new TrackballAxis();
     final TrackballAxis mTrackballAxisY = new TrackballAxis();
@@ -284,14 +277,10 @@
 
     AccessibilityInteractionConnectionManager mAccessibilityInteractionConnectionManager;
 
-    SendWindowContentChanged mSendWindowContentChanged;
+    SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent;
 
     private final int mDensity;
 
-    // This flag tracks when the mIgnoreDirtyState flag is set during draw(), to avoid
-    // clearing that flag prematurely
-    private boolean mSetIgnoreDirtyState = false;
-
     /**
      * Consistency verifier for debugging purposes.
      */
@@ -676,7 +665,7 @@
             }
         }
         if (!mDirty.isEmpty() && !mDirty.contains(dirty)) {
-            mSetIgnoreDirtyState = true;
+            mAttachInfo.mSetIgnoreDirtyState = true;
             mAttachInfo.mIgnoreDirtyState = true;
         }
         mDirty.union(dirty);
@@ -1882,10 +1871,10 @@
                         }
                         canvas.setScreenDensity(scalingRequired
                                 ? DisplayMetrics.DENSITY_DEVICE : 0);
-                        mSetIgnoreDirtyState = false;
+                        mAttachInfo.mSetIgnoreDirtyState = false;
                         mView.draw(canvas);
                     } finally {
-                        if (!mSetIgnoreDirtyState) {
+                        if (!mAttachInfo.mSetIgnoreDirtyState) {
                             // Only clear the flag if it was not set during the mView.draw() call
                             mAttachInfo.mIgnoreDirtyState = false;
                         }
@@ -3696,14 +3685,19 @@
     /**
      * Post a callback to send a
      * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event.
+     * This event is send at most once every
+     * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}.
      */
     private void postSendWindowContentChangedCallback() {
-        if (mSendWindowContentChanged == null) {
-            mSendWindowContentChanged = new SendWindowContentChanged();
-        } else {
-            removeCallbacks(mSendWindowContentChanged);
+        if (mSendWindowContentChangedAccessibilityEvent == null) {
+            mSendWindowContentChangedAccessibilityEvent =
+                new SendWindowContentChangedAccessibilityEvent();
         }
-        postDelayed(mSendWindowContentChanged, SEND_WINDOW_CONTENT_CHANGED_DELAY_MILLIS);
+        if (!mSendWindowContentChangedAccessibilityEvent.mIsPending) {
+            mSendWindowContentChangedAccessibilityEvent.mIsPending = true;
+            postDelayed(mSendWindowContentChangedAccessibilityEvent,
+                    ViewConfiguration.getSendRecurringAccessibilityEventsInterval());
+        }
     }
 
     /**
@@ -3711,8 +3705,8 @@
      * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event.
      */
     private void removeSendWindowContentChangedCallback() {
-        if (mSendWindowContentChanged != null) {
-            removeCallbacks(mSendWindowContentChanged);
+        if (mSendWindowContentChangedAccessibilityEvent != null) {
+            removeCallbacks(mSendWindowContentChangedAccessibilityEvent);
         }
     }
 
@@ -4638,10 +4632,19 @@
         }
     }
 
-    private class SendWindowContentChanged implements Runnable {
+    private class SendWindowContentChangedAccessibilityEvent implements Runnable {
+        public volatile boolean mIsPending;
+
         public void run() {
             if (mView != null) {
-                mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+                // Send the event directly since we do not want to append the
+                // source text because this is the text for the entire window
+                // and we just want to notify that the content has changed.
+                AccessibilityEvent event = AccessibilityEvent.obtain(
+                        AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+                mView.onInitializeAccessibilityEvent(event);
+                AccessibilityManager.getInstance(mView.mContext).sendAccessibilityEvent(event);
+                mIsPending = false;
             }
         }
     }
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 5919150..f3a5050 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -176,6 +176,14 @@
     private static final int TOUCH_EXPLORATION_TAP_SLOP = 80;
 
     /**
+     * Delay before dispatching a recurring accessibility event in milliseconds.
+     * This delay guarantees that a recurring event will be send at most once
+     * during the {@link #SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS} time
+     * frame.
+     */
+    private static final long SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS = 400;
+
+    /**
      * The maximum size of View's drawing cache, expressed in bytes. This size
      * should be at least equal to the size of the screen in ARGB888 format.
      */
@@ -498,6 +506,19 @@
     }
 
     /**
+     * Interval for dispatching a recurring accessibility event in milliseconds.
+     * This interval guarantees that a recurring event will be send at most once
+     * during the {@link #getSendRecurringAccessibilityEventsInterval()} time frame.
+     *
+     * @return The delay in milliseconds.
+     *
+     * @hide
+     */
+    public static long getSendRecurringAccessibilityEventsInterval() {
+        return SEND_RECURRING_ACCESSIBILITY_EVENTS_INTERVAL_MILLIS;
+    }
+
+    /**
      * @return Distance a touch must be outside the bounds of a window for it
      * to be counted as outside the window for purposes of dismissing that
      * window.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index b9474e6..6405398 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4998,6 +4998,24 @@
     }
 
     /**
+     * This method will be called when we need to reset the layout direction resolution flag
+     *
+     */
+    @Override
+    void resetLayoutDirectionResolution() {
+        super.resetLayoutDirectionResolution();
+
+        // Take care of resetting the children resolution too
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getLayoutDirection() == LAYOUT_DIRECTION_INHERIT) {
+                child.resetLayoutDirectionResolution();
+            }
+        }
+    }
+
+    /**
      * Return true if the pressed state should be delayed for children or descendants of this
      * ViewGroup. Generally, this should be done for containers that can scroll, such as a List.
      * This prevents the pressed state from appearing when the user is actually trying to scroll
@@ -5157,6 +5175,21 @@
         }
 
         /**
+         * Resolve layout parameters depending on the layout direction. Subclasses that care about
+         * layoutDirection changes should override this method. The default implementation does
+         * nothing.
+         *
+         * @param layoutDirection the direction of the layout
+         *
+         * {@link View#LAYOUT_DIRECTION_LTR}
+         * {@link View#LAYOUT_DIRECTION_RTL}
+         *
+         * @hide
+         */
+        protected void resolveWithDirection(int layoutDirection) {
+        }
+
+        /**
          * Returns a String representation of this set of layout parameters.
          *
          * @param output the String to prepend to the internal representation
@@ -5197,30 +5230,56 @@
      */
     public static class MarginLayoutParams extends ViewGroup.LayoutParams {
         /**
-         * The left margin in pixels of the child.
+         * The left margin in pixels of the child. Whenever this value is changed, a call to
+         * {@link android.view.View#requestLayout()} needs to be done.
          */
         @ViewDebug.ExportedProperty(category = "layout")
         public int leftMargin;
 
         /**
-         * The top margin in pixels of the child.
+         * The top margin in pixels of the child. Whenever this value is changed, a call to
+         * {@link android.view.View#requestLayout()} needs to be done.
          */
         @ViewDebug.ExportedProperty(category = "layout")
         public int topMargin;
 
         /**
-         * The right margin in pixels of the child.
+         * The right margin in pixels of the child. Whenever this value is changed, a call to
+         * {@link android.view.View#requestLayout()} needs to be done.
          */
         @ViewDebug.ExportedProperty(category = "layout")
         public int rightMargin;
 
         /**
-         * The bottom margin in pixels of the child.
+         * The bottom margin in pixels of the child. Whenever this value is changed, a call to
+         * {@link android.view.View#requestLayout()} needs to be done.
          */
         @ViewDebug.ExportedProperty(category = "layout")
         public int bottomMargin;
 
         /**
+         * The start margin in pixels of the child.
+         *
+         * @hide
+         *
+         */
+        @ViewDebug.ExportedProperty(category = "layout")
+        protected int startMargin = DEFAULT_RELATIVE;
+
+        /**
+         * The end margin in pixels of the child.
+         *
+         * @hide
+         */
+        @ViewDebug.ExportedProperty(category = "layout")
+        protected int endMargin = DEFAULT_RELATIVE;
+
+        /**
+         * The default start and end margin.
+         */
+        static private final int DEFAULT_RELATIVE = Integer.MIN_VALUE;
+
+        /**
          * Creates a new set of layout parameters. The values are extracted from
          * the supplied attributes set and context.
          *
@@ -5252,6 +5311,10 @@
                         R.styleable.ViewGroup_MarginLayout_layout_marginRight, 0);
                 bottomMargin = a.getDimensionPixelSize(
                         R.styleable.ViewGroup_MarginLayout_layout_marginBottom, 0);
+                startMargin = a.getDimensionPixelSize(
+                        R.styleable.ViewGroup_MarginLayout_layout_marginStart, DEFAULT_RELATIVE);
+                endMargin = a.getDimensionPixelSize(
+                        R.styleable.ViewGroup_MarginLayout_layout_marginEnd, DEFAULT_RELATIVE);
             }
 
             a.recycle();
@@ -5277,6 +5340,8 @@
             this.topMargin = source.topMargin;
             this.rightMargin = source.rightMargin;
             this.bottomMargin = source.bottomMargin;
+            this.startMargin = source.startMargin;
+            this.endMargin = source.endMargin;
         }
 
         /**
@@ -5287,7 +5352,9 @@
         }
 
         /**
-         * Sets the margins, in pixels.
+         * Sets the margins, in pixels. A call to {@link android.view.View#requestLayout()} needs
+         * to be done so that the new margins are taken into account. Left and right margins may be
+         * overriden by {@link android.view.View#requestLayout()} depending on layout direction.
          *
          * @param left the left margin size
          * @param top the top margin size
@@ -5305,6 +5372,92 @@
             rightMargin = right;
             bottomMargin = bottom;
         }
+
+        /**
+         * Sets the relative margins, in pixels. A call to {@link android.view.View#requestLayout()}
+         * needs to be done so that the new relative margins are taken into account. Left and right
+         * margins may be overriden by {@link android.view.View#requestLayout()} depending on layout
+         * direction.
+         *
+         * @param start the start margin size
+         * @param top the top margin size
+         * @param end the right margin size
+         * @param bottom the bottom margin size
+         *
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginTop
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginBottom
+         *
+         * @hide
+         */
+        public void setMarginsRelative(int start, int top, int end, int bottom) {
+            startMargin = start;
+            topMargin = top;
+            endMargin = end;
+            bottomMargin = bottom;
+        }
+
+        /**
+         * Returns the start margin in pixels.
+         *
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
+         *
+         * @return the start margin in pixels.
+         *
+         * @hide
+         */
+        public int getMarginStart() {
+            return startMargin;
+        }
+
+        /**
+         * Returns the end margin in pixels.
+         *
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
+         *
+         * @return the end margin in pixels.
+         *
+         * @hide
+         */
+        public int getMarginEnd() {
+            return endMargin;
+        }
+
+        /**
+         * Check if margins are relative.
+         *
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
+         *
+         * @return true if either marginStart or marginEnd has been set
+         *
+         * @hide
+         */
+        public boolean isMarginRelative() {
+            return (startMargin != DEFAULT_RELATIVE) || (endMargin != DEFAULT_RELATIVE);
+        }
+
+        /**
+         * This will be called by {@link android.view.View#requestLayout()}. Left and Right margins
+         * maybe overriden depending on layout direction.
+         *
+         * @hide
+         */
+        @Override
+        protected void resolveWithDirection(int layoutDirection) {
+            switch(layoutDirection) {
+                case View.LAYOUT_DIRECTION_RTL:
+                    leftMargin = (endMargin > DEFAULT_RELATIVE) ? endMargin : leftMargin;
+                    rightMargin = (startMargin > DEFAULT_RELATIVE) ? startMargin : rightMargin;
+                    break;
+                case View.LAYOUT_DIRECTION_LTR:
+                default:
+                    leftMargin = (startMargin > DEFAULT_RELATIVE) ? startMargin : leftMargin;
+                    rightMargin = (endMargin > DEFAULT_RELATIVE) ? endMargin : rightMargin;
+                    break;
+            }
+        }
     }
 
     /* Describes a touched view and the ids of the pointers that it has captured.
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 30d6489..25f01a7 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -56,76 +56,132 @@
  * <b>View clicked</b> - represents the event of clicking on a {@link android.view.View}
  * like {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc. <br>
  * Type:{@link #TYPE_VIEW_CLICKED} <br>
- * Properties:
- * {@link #getClassName()},
- * {@link #getPackageName()},
- * {@link #getEventTime()},
- * {@link #getText()},
- * {@link #isChecked()},
- * {@link #isEnabled()},
- * {@link #isPassword()},
- * {@link #getItemCount()},
- * {@link #getCurrentItemIndex()}
+ * Properties:</br>
+ * <ul>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li> *
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ *   <li>{@link #getText()} - The text of the source.</li>
+ *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ *   <li>{@link #isPassword()} - Whether the source is password.</li>
+ *   <li>{@link #isChecked()} - Whether the source is checked.</li>
+ * </ul>
  * <p>
  * <b>View long clicked</b> - represents the event of long clicking on a {@link android.view.View}
  * like {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc. <br>
  * Type:{@link #TYPE_VIEW_LONG_CLICKED} <br>
- * Properties:
- * {@link #getClassName()},
- * {@link #getPackageName()},
- * {@link #getEventTime()},
- * {@link #getText()},
- * {@link #isChecked()},
- * {@link #isEnabled()},
- * {@link #isPassword()},
- * {@link #getItemCount()},
- * {@link #getCurrentItemIndex()}
+ * Properties:</br>
+ * <ul>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li> *
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ *   <li>{@link #getText()} - The text of the source.</li>
+ *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ *   <li>{@link #isPassword()} - Whether the source is password.</li>
+ *   <li>{@link #isChecked()} - Whether the source is checked.</li>
+ * </ul>
  * <p>
  * <b>View selected</b> - represents the event of selecting an item usually in
  * the context of an {@link android.widget.AdapterView}. <br>
  * Type: {@link #TYPE_VIEW_SELECTED} <br>
- * Properties:
- * {@link #getClassName()},
- * {@link #getPackageName()},
- * {@link #getEventTime()},
- * {@link #getText()},
- * {@link #isChecked()},
- * {@link #isEnabled()},
- * {@link #isPassword()},
- * {@link #getItemCount()},
- * {@link #getCurrentItemIndex()}
+ * Properties:</br>
+ * <ul>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li> *
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ *   <li>{@link #getText()} - The text of the source.</li>
+ *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ *   <li>{@link #isPassword()} - Whether the source is password.</li>
+ *   <li>{@link #isChecked()} - Whether the source is checked.</li>
+ *   <li>{@link #getItemCount()} -The number of selectable items of the source.</li>
+ *   <li>{@link #getCurrentItemIndex()} - The currently selected item index.</li>
+ * </ul>
+ * <p>
  * <p>
  * <b>View focused</b> - represents the event of focusing a
  * {@link android.view.View}. <br>
  * Type: {@link #TYPE_VIEW_FOCUSED} <br>
- * Properties:
- * {@link #getClassName()},
- * {@link #getPackageName()},
- * {@link #getEventTime()},
- * {@link #getText()},
- * {@link #isChecked()},
- * {@link #isEnabled()},
- * {@link #isPassword()},
- * {@link #getItemCount()},
- * {@link #getCurrentItemIndex()}
+ * Properties:</br>
+ * <ul>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li> *
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ *   <li>{@link #getText()} - The text of the source.</li>
+ *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ *   <li>{@link #isPassword()} - Whether the source is password.</li>
+ *   <li>{@link #isChecked()} - Whether the source is checked.</li>
+ *   <li>{@link #getItemCount()} -The number of focusable items on the screen.</li>
+ *   <li>{@link #getCurrentItemIndex()} - The currently focused item index.</li>
+ * </ul>
  * <p>
  * <b>View text changed</b> - represents the event of changing the text of an
  * {@link android.widget.EditText}. <br>
  * Type: {@link #TYPE_VIEW_TEXT_CHANGED} <br>
- * Properties:
- * {@link #getClassName()},
- * {@link #getPackageName()},
- * {@link #getEventTime()},
- * {@link #getText()},
- * {@link #isChecked()},
- * {@link #isEnabled()},
- * {@link #isPassword()},
- * {@link #getItemCount()},
- * {@link #getCurrentItemIndex()},
- * {@link #getFromIndex()},
- * {@link #getAddedCount()},
- * {@link #getRemovedCount()},
- * {@link #getBeforeText()}
+ * Properties:</br>
+ * <ul>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li> *
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ *   <li>{@link #getText()} - The text of the source.</li>
+ *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ *   <li>{@link #isPassword()} - Whether the source is password.</li>
+ *   <li>{@link #isChecked()} - Whether the source is checked.</li>
+ *   <li>{@link #getFromIndex()} - The text change start index.</li>
+ *   <li>{@link #getAddedCount()} - The number of added characters.</li>
+ *   <li>{@link #getRemovedCount()} - The number of removed characters.</li>
+ *   <li>{@link #getBeforeText()} - The text of the source before the change.</li>
+ * </ul>
+ * <p>
+ * <b>View text selection changed</b> - represents the event of changing the text
+ * selection of an {@link android.widget.EditText}.<br>
+ * Type: {@link #TYPE_VIEW_TEXT_SELECTION_CHANGED} <br>
+ * Properties:</br>
+ * <ul>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li> *
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ *   <li>{@link #getText()} - The text of the source.</li>
+ *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ *   <li>{@link #isPassword()} - Whether the source is password.</li>
+ *   <li>{@link #getFromIndex()} - The selection start index.</li>
+ *   <li>{@link #getToIndex()} - The selection end index.</li>
+ *   <li>{@link #getItemCount()} - The length of the source text.</li>
+ * <ul>
+ * <p>
+ * <b>View scrolled</b> - represents the event of scrolling a view. If
+ * the source is a descendant of {@link android.widget.AdapterView} the
+ * scroll is reported in terms of visible items - the first visible item,
+ * the last visible item, and the total items - because the the source
+ * is unaware if its pixel size since its adapter is responsible for
+ * creating views. In all other cases the scroll is reported as the current
+ * scroll on the X and Y axis respectively plus the height of the source in
+ * pixels.<br>
+ * Type: {@link #TYPE_VIEW_SCROLLED} <br>
+ * Properties:</br>
+ * <ul>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li> *
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ *   <li>{@link #getText()} - The text of the source.</li>
+ *   <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ *   <li>{@link #getScrollX()} - The horizontal offset of the source
+ *                                (without descendants of AdapterView)).</li>
+ *   <li>{@link #getScrollY()} - The vertical offset of the source
+ *                                (without descendants of AdapterView)).</li>
+ *   <li>{@link #getFromIndex()} - The index of the first visible item of the source
+ *                                 (for descendants of AdapterView).</li>
+ *   <li>{@link #getToIndex()} - The index of the last visible item of the source
+ *                               (for descendants of AdapterView).</li>
+ *   <li>{@link #getItemCount()} - The total items of the source (for descendants of AdapterView)
+ *                                 or the height of the source in pixels (all other cases).</li>
+ * <ul>
  * <p>
  * <b>TRANSITION TYPES</b> <br>
  * <p>
@@ -133,33 +189,40 @@
  * {@link android.widget.PopupWindow}, {@link android.view.Menu},
  * {@link android.app.Dialog}, etc. <br>
  * Type: {@link #TYPE_WINDOW_STATE_CHANGED} <br>
- * Properties:
- * {@link #getClassName()},
- * {@link #getPackageName()},
- * {@link #getEventTime()},
- * {@link #getText()}
+ * Properties:</br>
+ * <ul>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li> *
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ *   <li>{@link #getText()} - The text of the source.</li>
+ * </ul>
  * <p>
  * <b>Window content changed</b> - represents the event of change in the
  * content of a window. This change can be adding/removing view, changing
  * a view size, etc.<br>
  * Type: {@link #TYPE_WINDOW_CONTENT_CHANGED} <br>
- * Properties:
- * {@link #getClassName()},
- * {@link #getPackageName()},
- * {@link #getEventTime()},
- * {@link #getText()}
+ * Properties:</br>
+ * <ul>
+ *   <li>{@link #getSource()} - The source info (for registered clients).</li> *
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ * <ul>
  * <p>
  * <b>NOTIFICATION TYPES</b> <br>
  * <p>
  * <b>Notification state changed</b> - represents the event showing/hiding
  * {@link android.app.Notification}.
  * Type: {@link #TYPE_NOTIFICATION_STATE_CHANGED} <br>
- * Properties:
- * {@link #getClassName()},
- * {@link #getPackageName()},
- * {@link #getEventTime()},
- * {@link #getText()}
- * {@link #getParcelableData()}
+ * Properties:</br>
+ * <ul>
+ *   <li>{@link #getClassName()} - The class name of the source.</li>
+ *   <li>{@link #getPackageName()} - The package name of the source.</li>
+ *   <li>{@link #getEventTime()}  - The event time.</li>
+ *   <li>{@link #getText()} - The text of the source.</li>
+ *   <li>{@link #getParcelableData()} - The posted {@link android.app.Notification}.</li>
+ * </ul>
  * <p>
  * <b>Security note</b>
  * <p>
@@ -258,6 +321,16 @@
     public static final int TYPE_WINDOW_CONTENT_CHANGED = 0x00000800;
 
     /**
+     * Represents the event of scrolling a view.
+     */
+    public static final int TYPE_VIEW_SCROLLED = 0x00001000;
+
+    /**
+     * Represents the event of changing the selection in an {@link android.widget.EditText}.
+     */
+    public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED = 0x00002000;
+
+    /**
      * Mask for {@link AccessibilityEvent} all types.
      *
      * @see #TYPE_VIEW_CLICKED
@@ -564,6 +637,9 @@
         record.mCurrentItemIndex = parcel.readInt();
         record.mItemCount = parcel.readInt();
         record.mFromIndex = parcel.readInt();
+        record.mToIndex = parcel.readInt();
+        record.mScrollX = parcel.readInt();
+        record.mScrollY =  parcel.readInt();
         record.mAddedCount = parcel.readInt();
         record.mRemovedCount = parcel.readInt();
         record.mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
@@ -613,6 +689,9 @@
         parcel.writeInt(record.mCurrentItemIndex);
         parcel.writeInt(record.mItemCount);
         parcel.writeInt(record.mFromIndex);
+        parcel.writeInt(record.mToIndex);
+        parcel.writeInt(record.mScrollX);
+        parcel.writeInt(record.mScrollY);
         parcel.writeInt(record.mAddedCount);
         parcel.writeInt(record.mRemovedCount);
         TextUtils.writeToParcel(record.mClassName, parcel, flags);
@@ -657,8 +736,12 @@
                 builder.append("; IsPassword: " + record.isPassword());
                 builder.append("; IsChecked: " + record.isChecked());
                 builder.append("; IsFullScreen: " + record.isFullScreen());
+                builder.append("; Scrollable: " + record.isScrollable());
                 builder.append("; BeforeText: " + record.mBeforeText);
                 builder.append("; FromIndex: " + record.mFromIndex);
+                builder.append("; ToIndex: " + record.mToIndex);
+                builder.append("; ScrollX: " + record.mScrollX);
+                builder.append("; ScrollY: " + record.mScrollY);
                 builder.append("; AddedCount: " + record.mAddedCount);
                 builder.append("; RemovedCount: " + record.mRemovedCount);
                 builder.append("; ParcelableData: " + record.mParcelableData);
@@ -704,6 +787,10 @@
                 return "TYPE_TOUCH_EXPLORATION_GESTURE_END";
             case TYPE_WINDOW_CONTENT_CHANGED:
                 return "TYPE_WINDOW_CONTENT_CHANGED";
+            case TYPE_VIEW_TEXT_SELECTION_CHANGED:
+                return "TYPE_VIEW_TEXT_SELECTION_CHANGED";
+            case TYPE_VIEW_SCROLLED:
+                return "TYPE_VIEW_SCROLLED";
             default:
                 return null;
         }
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 555667b..dbbe7be 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -83,6 +83,8 @@
 
     private static final int PROPERTY_PASSWORD = 0x00000100;
 
+    private static final int PROPERTY_SCROLLABLE = 0x00000200;
+
     // Readable representations - lazily initialized.
     private static SparseArray<String> sActionSymbolicNames;
 
@@ -570,6 +572,27 @@
     }
 
     /**
+     * Gets if the node is scrollable.
+     *
+     * @return True if the node is scrollable, false otherwise.
+     */
+    public boolean isScrollable() {
+        return getBooleanProperty(PROPERTY_SCROLLABLE);
+    }
+
+    /**
+     * Sets if the node is scrollable.
+     *
+     * @param scrollable True if the node is scrollable, false otherwise.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setScrollable(boolean scrollable) {
+        enforceNotSealed();
+        setBooleanProperty(PROPERTY_SCROLLABLE, scrollable);
+    }
+
+    /**
      * Gets the package this node comes from.
      *
      * @return The package name.
@@ -1017,6 +1040,7 @@
         builder.append("; longClickable: ").append(isLongClickable());
         builder.append("; enabled: ").append(isEnabled());
         builder.append("; password: ").append(isPassword());
+        builder.append("; scrollable: " + isScrollable());
 
         builder.append("; [");
 
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index 9c495e21..b9815c5 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -40,6 +40,7 @@
     private static final int PROPERTY_ENABLED = 0x00000002;
     private static final int PROPERTY_PASSWORD = 0x00000004;
     private static final int PROPERTY_FULL_SCREEN = 0x00000080;
+    private static final int PROPERTY_SCROLLABLE = 0x00000100;
 
     // Housekeeping
     private static final int MAX_POOL_SIZE = 10;
@@ -54,6 +55,10 @@
     int mCurrentItemIndex;
     int mItemCount;
     int mFromIndex;
+    int mToIndex;
+    int mScrollX;
+    int mScrollY;
+
     int mAddedCount;
     int mRemovedCount;
     int mSourceViewId = View.NO_ID;
@@ -71,7 +76,6 @@
      * Hide constructor.
      */
     AccessibilityRecord() {
-
     }
 
     /**
@@ -85,6 +89,9 @@
         mCurrentItemIndex = record.mCurrentItemIndex;
         mItemCount = record.mItemCount;
         mFromIndex = record.mFromIndex;
+        mToIndex = record.mToIndex;
+        mScrollX = record.mScrollX;
+        mScrollY = record.mScrollY;
         mAddedCount = record.mAddedCount;
         mRemovedCount = record.mRemovedCount;
         mClassName = record.mClassName;
@@ -246,6 +253,27 @@
     }
 
     /**
+     * Gets if the source is scrollable.
+     *
+     * @return True if the source is scrollable, false otherwise.
+     */
+    public boolean isScrollable() {
+        return getBooleanProperty(PROPERTY_SCROLLABLE);
+    }
+
+    /**
+     * Sets if the source is scrollable.
+     *
+     * @param scrollable True if the source is scrollable, false otherwise.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setScrollable(boolean scrollable) {
+        enforceNotSealed();
+        setBooleanProperty(PROPERTY_SCROLLABLE, scrollable);
+    }
+
+    /**
      * Gets the number of items that can be visited.
      *
      * @return The number of items.
@@ -288,18 +316,24 @@
     }
 
     /**
-     * Gets the index of the first character of the changed sequence.
+     * Gets the index of the first character of the changed sequence,
+     * or the beginning of a text selection or the index of the first
+     * visible item when scrolling.
      *
-     * @return The index of the first character.
+     * @return The index of the first character or selection
+     *        start or the first visible item.
      */
     public int getFromIndex() {
         return mFromIndex;
     }
 
     /**
-     * Sets the index of the first character of the changed sequence.
+     * Sets the index of the first character of the changed sequence
+     * or the beginning of a text selection or the index of the first
+     * visible item when scrolling.
      *
-     * @param fromIndex The index of the first character.
+     * @param fromIndex The index of the first character or selection
+     *        start or the first visible item.
      *
      * @throws IllegalStateException If called from an AccessibilityService.
      */
@@ -309,6 +343,65 @@
     }
 
     /**
+     * Gets the index of text selection end or the index of the last
+     * visible item when scrolling.
+     *
+     * @return The index of selection end or last item index.
+     */
+    public int getToIndex() {
+        return mToIndex;
+    }
+
+    /**
+     * Sets the index of text selection end or the index of the last
+     * visible item when scrolling.
+     *
+     * @param toIndex The index of selection end or last item index.
+     */
+    public void setToIndex(int toIndex) {
+        enforceNotSealed();
+        mToIndex = toIndex;
+    }
+
+    /**
+     * Gets the scroll position of the source along the X axis.
+     *
+     * @return The scroll along the X axis.
+     */
+    public int getScrollX() {
+        return mScrollX;
+    }
+
+    /**
+     * Sets the scroll position of the source along the X axis.
+     *
+     * @param scrollX The scroll along the X axis.
+     */
+    public void setScrollX(int scrollX) {
+        enforceNotSealed();
+        mScrollX = scrollX;
+    }
+
+    /**
+     * Gets the scroll position of the source along the Y axis.
+     *
+     * @return The scroll along the Y axis.
+     */
+    public int getScrollY() {
+        return mScrollY;
+    }
+
+    /**
+     * Sets the scroll position of the source along the Y axis.
+     *
+     * @param scrollY The scroll along the Y axis.
+     */
+    public void setScrollY(int scrollY) {
+        enforceNotSealed();
+        mScrollY = scrollY;
+    }
+
+    /**
      * Gets the number of added characters.
      *
      * @return The number of added characters.
@@ -576,6 +669,9 @@
         mCurrentItemIndex = INVALID_POSITION;
         mItemCount = 0;
         mFromIndex = 0;
+        mToIndex = 0;
+        mScrollX = 0;
+        mScrollY = 0;
         mAddedCount = 0;
         mRemovedCount = 0;
         mClassName = null;
@@ -599,8 +695,12 @@
         builder.append("; IsPassword: " + getBooleanProperty(PROPERTY_PASSWORD));
         builder.append("; IsChecked: " + getBooleanProperty(PROPERTY_CHECKED));
         builder.append("; IsFullScreen: " + getBooleanProperty(PROPERTY_FULL_SCREEN));
+        builder.append("; Scrollable: " + getBooleanProperty(PROPERTY_SCROLLABLE));
         builder.append("; BeforeText: " + mBeforeText);
         builder.append("; FromIndex: " + mFromIndex);
+        builder.append("; ToIndex: " + mToIndex);
+        builder.append("; ScrollX: " + mScrollX);
+        builder.append("; ScrollY: " + mScrollY);
         builder.append("; AddedCount: " + mAddedCount);
         builder.append("; RemovedCount: " + mRemovedCount);
         builder.append("; ParcelableData: " + mParcelableData);
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 3a09e3b..7e41d36 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -2000,8 +2000,12 @@
         if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw start");
         draw.mBaseLayer = nativeRecordContent(draw.mInvalRegion, draw.mContentSize);
         if (draw.mBaseLayer == 0) {
-            if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw abort, resending draw message");
-            mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW));
+            if (mWebView != null && !mWebView.isPaused()) {
+                if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw abort, resending draw message");
+                mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW));
+            } else {
+                if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw abort, webview paused");
+            }
             return;
         }
         webkitDraw(draw);
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 3fe8149..1449b18 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -55,6 +55,8 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
@@ -1257,6 +1259,33 @@
         onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
     }
 
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        info.setScrollable(true);
+    }
+
+    @Override
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        super.onInitializeAccessibilityEvent(event);
+        event.setScrollable(true);
+        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+            event.setFromIndex(mFirstPosition);
+            event.setToIndex(mFirstPosition +  getChildCount());
+            event.setItemCount(mItemCount);
+        }
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        // Do not append text content to scroll events they are fired frequently
+        // and the client has already received another event type with the text.
+        if (event.getEventType() != AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+            super.dispatchPopulateAccessibilityEvent(event);
+        }
+        return false;
+    }
+
     /**
      * Indicates whether the children's drawing cache is used during a scroll.
      * By default, the drawing cache is enabled but this will consume more memory.
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index ce76bee..0ffd087 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -34,6 +34,8 @@
 import android.view.ViewGroup;
 import android.view.SoundEffectConstants;
 import android.view.ContextMenu.ContextMenuInfo;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.Transformation;
 
 /**
@@ -344,7 +346,34 @@
     int getChildHeight(View child) {
         return child.getMeasuredHeight();
     }
-    
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        info.setScrollable(true);
+    }
+
+    @Override
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        super.onInitializeAccessibilityEvent(event);
+        event.setScrollable(true);
+        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+            event.setFromIndex(mFirstPosition);
+            event.setToIndex(mFirstPosition +  getChildCount());
+            event.setItemCount(mItemCount);
+        }
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        // Do not append text content to scroll events they are fired frequently
+        // and the client has already received another event type with the text.
+        if (event.getEventType() != AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+            super.dispatchPopulateAccessibilityEvent(event);
+        }
+        return false;
+    }
+
     /**
      * Tracks a motion scroll. In reality, this is used to do just about any
      * movement to items (touch scroll, arrow-key scroll, set an item as selected).
@@ -382,7 +411,9 @@
         mRecycler.clear();
         
         setSelectionToCenterChild();
-        
+
+        onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
+
         invalidate();
     }
 
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 17b3bda..7c9be1e 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -35,6 +35,8 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.AnimationUtils;
 
 import java.util.List;
@@ -692,6 +694,28 @@
         awakenScrollBars();
     }
 
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        info.setScrollable(true);
+    }
+
+    @Override
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        super.onInitializeAccessibilityEvent(event);
+        event.setScrollable(true);
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        // Do not append text content to scroll events they are fired frequently
+        // and the client has already received another event type with the text.
+        if (event.getEventType() != AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+            super.dispatchPopulateAccessibilityEvent(event);
+        }
+        return false;
+    }
+
     private int getScrollRange() {
         int scrollRange = 0;
         if (getChildCount() > 0) {
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 27edb88..12775a4 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -36,6 +36,8 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.AnimationUtils;
 
 import java.util.List;
@@ -690,6 +692,28 @@
         awakenScrollBars();
     }
 
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        info.setScrollable(true);
+    }
+
+    @Override
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        super.onInitializeAccessibilityEvent(event);
+        event.setScrollable(true);
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        // Do not append text content to scroll events they are fired frequently
+        // and the client has already received another event type with the text.
+        if (event.getEventType() != AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+            super.dispatchPopulateAccessibilityEvent(event);
+        }
+        return false;
+    }
+
     private int getScrollRange() {
         int scrollRange = 0;
         if (getChildCount() > 0) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 17aea8b..85e7eec 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6834,9 +6834,9 @@
      * @param selEnd The new selection end location.
      */
     protected void onSelectionChanged(int selStart, int selEnd) {
-        // intentionally empty
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
     }
-    
+
     /**
      * Adds a TextWatcher to the list of those whose methods are called
      * whenever this TextView's text changes.
@@ -7922,6 +7922,12 @@
 
         final boolean isPassword = hasPasswordTransformationMethod();
         event.setPassword(isPassword);
+
+        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
+            event.setFromIndex(Selection.getSelectionStart(mText));
+            event.setToIndex(Selection.getSelectionEnd(mText));
+            event.setItemCount(mText.length());
+        }
     }
 
     @Override
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 8d75dff..8eb046e 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -988,8 +988,8 @@
             int ypos = 0;
             switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
                 case Gravity.CENTER_VERTICAL:
-                    final int paddedTop = mTop + getPaddingTop();
-                    final int paddedBottom = mBottom - getPaddingBottom();
+                    final int paddedTop = getPaddingTop();
+                    final int paddedBottom = mBottom - mTop - getPaddingBottom();
                     ypos = ((paddedBottom - paddedTop) - customView.getMeasuredHeight()) / 2;
                     break;
                 case Gravity.TOP:
@@ -1000,7 +1000,10 @@
                             - bottomMargin;
                     break;
             }
-            x += positionChild(customView, xpos, ypos, contentHeight);
+            final int customWidth = customView.getMeasuredWidth();
+            customView.layout(xpos, ypos, xpos + customWidth,
+                    ypos + customView.getMeasuredHeight());
+            x += customWidth;
         }
 
         if (mProgressView != null) {
diff --git a/core/jni/android/graphics/SurfaceTexture.cpp b/core/jni/android/graphics/SurfaceTexture.cpp
index 30ef8df..dd8b378 100644
--- a/core/jni/android/graphics/SurfaceTexture.cpp
+++ b/core/jni/android/graphics/SurfaceTexture.cpp
@@ -91,7 +91,8 @@
     virtual void onFrameAvailable();
 
 private:
-    static JNIEnv* getJNIEnv();
+    static JNIEnv* getJNIEnv(bool* needsDetach);
+    static void detachJNI();
 
     jobject mWeakThiz;
     jclass mClazz;
@@ -103,37 +104,57 @@
     mClazz((jclass)env->NewGlobalRef(clazz))
 {}
 
-JNIEnv* JNISurfaceTextureContext::getJNIEnv() {
-    JNIEnv* env;
-    JavaVMAttachArgs args = {JNI_VERSION_1_4, NULL, NULL};
-    JavaVM* vm = AndroidRuntime::getJavaVM();
-    int result = vm->AttachCurrentThread(&env, (void*) &args);
-    if (result != JNI_OK) {
-        LOGE("thread attach failed: %#x", result);
-        return NULL;
+JNIEnv* JNISurfaceTextureContext::getJNIEnv(bool* needsDetach) {
+    *needsDetach = false;
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    if (env == NULL) {
+        JavaVMAttachArgs args = {JNI_VERSION_1_4, NULL, NULL};
+        JavaVM* vm = AndroidRuntime::getJavaVM();
+        int result = vm->AttachCurrentThread(&env, (void*) &args);
+        if (result != JNI_OK) {
+            LOGE("thread attach failed: %#x", result);
+            return NULL;
+        }
+        *needsDetach = true;
     }
     return env;
 }
 
+void JNISurfaceTextureContext::detachJNI() {
+    JavaVM* vm = AndroidRuntime::getJavaVM();
+    int result = vm->DetachCurrentThread();
+    if (result != JNI_OK) {
+        LOGE("thread detach failed: %#x", result);
+    }
+}
+
 JNISurfaceTextureContext::~JNISurfaceTextureContext()
 {
-    JNIEnv* env = getJNIEnv();
+    bool needsDetach = false;
+    JNIEnv* env = getJNIEnv(&needsDetach);
     if (env != NULL) {
         env->DeleteGlobalRef(mWeakThiz);
         env->DeleteGlobalRef(mClazz);
     } else {
         LOGW("leaking JNI object references");
     }
+    if (needsDetach) {
+        detachJNI();
+    }
 }
 
 void JNISurfaceTextureContext::onFrameAvailable()
 {
-    JNIEnv *env = getJNIEnv();
+    bool needsDetach = false;
+    JNIEnv* env = getJNIEnv(&needsDetach);
     if (env != NULL) {
         env->CallStaticVoidMethod(mClazz, fields.postEvent, mWeakThiz);
     } else {
         LOGW("onFrameAvailable event will not posted");
     }
+    if (needsDetach) {
+        detachJNI();
+    }
 }
 
 // ----------------------------------------------------------------------------
diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
index a5b2006..6a13876 100644
--- a/core/jni/android/graphics/TextLayoutCache.cpp
+++ b/core/jni/android/graphics/TextLayoutCache.cpp
@@ -461,12 +461,30 @@
                         Vector<GlyphRun> glyphRuns;
                         jchar* runGlyphs;
                         size_t runGlyphsCount = 0;
-                        size_t runIndex = 0;
+                        int32_t end = start + count;
                         for (size_t i = 0; i < rc; ++i) {
                             int32_t startRun;
                             int32_t lengthRun;
                             UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
 
+                            if (startRun >= end) {
+                              break;
+                            }
+
+                            int32_t endRun = startRun + lengthRun;
+                            if (endRun <= start) {
+                              continue;
+                            }
+
+                            if (startRun < start) {
+                              startRun = start;
+                            }
+                            if (endRun > end) {
+                              endRun = end;
+                            }
+
+                            lengthRun = endRun - startRun;
+
                             int newFlags = (runDir == UBIDI_RTL) ? kDirection_RTL : kDirection_LTR;
                             jfloat runTotalAdvance = 0;
 #if DEBUG_GLYPHS
@@ -475,11 +493,10 @@
 #endif
                             computeRunValuesWithHarfbuzz(paint, chars, startRun,
                                     lengthRun, contextCount, newFlags,
-                                    outAdvances + runIndex, &runTotalAdvance,
+                                    outAdvances, &runTotalAdvance,
                                     &runGlyphs, &runGlyphsCount);
 
-                            runIndex += lengthRun;
-
+                            outAdvances += lengthRun;
                             *outTotalAdvance += runTotalAdvance;
                             *outGlyphsCount += runGlyphsCount;
 #if DEBUG_GLYPHS
diff --git a/core/jni/android_bluetooth_common.cpp b/core/jni/android_bluetooth_common.cpp
index 6ae3e35..5c6f5e8 100644
--- a/core/jni/android_bluetooth_common.cpp
+++ b/core/jni/android_bluetooth_common.cpp
@@ -81,6 +81,16 @@
     {"UUID", DBUS_TYPE_STRING},
 };
 
+static Properties health_device_properties[] = {
+    {"MainChannel", DBUS_TYPE_OBJECT_PATH},
+};
+
+static Properties health_channel_properties[] = {
+    {"Type", DBUS_TYPE_STRING},
+    {"Device", DBUS_TYPE_OBJECT_PATH},
+    {"Application", DBUS_TYPE_OBJECT_PATH},
+};
+
 typedef union {
     char *str_val;
     int int_val;
@@ -315,6 +325,22 @@
     return ret;
 }
 
+jint dbus_returns_unixfd(JNIEnv *env, DBusMessage *reply) {
+
+    DBusError err;
+    jint ret = -1;
+
+    dbus_error_init(&err);
+    if (!dbus_message_get_args(reply, &err,
+                               DBUS_TYPE_UNIX_FD, &ret,
+                               DBUS_TYPE_INVALID)) {
+        LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply);
+    }
+    dbus_message_unref(reply);
+    return ret;
+}
+
+
 jint dbus_returns_int32(JNIEnv *env, DBusMessage *reply) {
 
     DBusError err;
@@ -486,6 +512,55 @@
     dbus_message_iter_close_container(iter, &value_iter);
 }
 
+static void dict_append_entry(DBusMessageIter *dict,
+                        const char *key, int type, void *val)
+{
+        DBusMessageIter dict_entry;
+        dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY,
+                                                        NULL, &dict_entry);
+
+        dbus_message_iter_append_basic(&dict_entry, DBUS_TYPE_STRING, &key);
+        append_variant(&dict_entry, type, val);
+        dbus_message_iter_close_container(dict, &dict_entry);
+}
+
+static void append_dict_valist(DBusMessageIter *iterator, const char *first_key,
+                                va_list var_args)
+{
+        DBusMessageIter dict;
+        int val_type;
+        const char *val_key;
+        void *val;
+
+        dbus_message_iter_open_container(iterator, DBUS_TYPE_ARRAY,
+                        DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+                        DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+                        DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+        val_key = first_key;
+        while (val_key) {
+                val_type = va_arg(var_args, int);
+                val = va_arg(var_args, void *);
+                dict_append_entry(&dict, val_key, val_type, val);
+                val_key = va_arg(var_args, char *);
+        }
+
+        dbus_message_iter_close_container(iterator, &dict);
+}
+
+void append_dict_args(DBusMessage *reply, const char *first_key, ...)
+{
+        DBusMessageIter iter;
+        va_list var_args;
+
+        dbus_message_iter_init_append(reply, &iter);
+
+        va_start(var_args, first_key);
+        append_dict_valist(&iter, first_key, var_args);
+        va_end(var_args);
+}
+
+
 int get_property(DBusMessageIter iter, Properties *properties,
                   int max_num_properties, int *prop_index, property_value *value, int *len) {
     DBusMessageIter prop_val, array_val_iter;
@@ -737,6 +812,21 @@
                           sizeof(input_properties) / sizeof(Properties));
 }
 
+jobjectArray parse_health_device_properties(JNIEnv *env, DBusMessageIter *iter) {
+    return parse_properties(env, iter, (Properties *) &health_device_properties,
+                          sizeof(health_device_properties) / sizeof(Properties));
+}
+
+jobjectArray parse_health_device_property_change(JNIEnv *env, DBusMessage *msg) {
+    return parse_property_change(env, msg, (Properties *) &health_device_properties,
+                    sizeof(health_device_properties) / sizeof(Properties));
+}
+
+jobjectArray parse_health_channel_properties(JNIEnv *env, DBusMessageIter *iter) {
+    return parse_properties(env, iter, (Properties *) &health_channel_properties,
+                          sizeof(health_channel_properties) / sizeof(Properties));
+}
+
 int get_bdaddr(const char *str, bdaddr_t *ba) {
     char *d = ((char *)ba) + 5, *endp;
     int i;
diff --git a/core/jni/android_bluetooth_common.h b/core/jni/android_bluetooth_common.h
index 3364703..2f5fd5a 100644
--- a/core/jni/android_bluetooth_common.h
+++ b/core/jni/android_bluetooth_common.h
@@ -149,6 +149,7 @@
 
 jint dbus_returns_int32(JNIEnv *env, DBusMessage *reply);
 jint dbus_returns_uint32(JNIEnv *env, DBusMessage *reply);
+jint dbus_returns_unixfd(JNIEnv *env, DBusMessage *reply);
 jstring dbus_returns_string(JNIEnv *env, DBusMessage *reply);
 jboolean dbus_returns_boolean(JNIEnv *env, DBusMessage *reply);
 jobjectArray dbus_returns_array_of_strings(JNIEnv *env, DBusMessage *reply);
@@ -164,8 +165,13 @@
 jobjectArray parse_remote_device_property_change(JNIEnv *env, DBusMessage *msg);
 jobjectArray parse_adapter_property_change(JNIEnv *env, DBusMessage *msg);
 jobjectArray parse_input_properties(JNIEnv *env, DBusMessageIter *iter);
+jobjectArray parse_health_device_properties(JNIEnv *env, DBusMessageIter *iter);
+jobjectArray parse_health_channel_properties(JNIEnv *env, DBusMessageIter *iter);
 jobjectArray parse_input_property_change(JNIEnv *env, DBusMessage *msg);
 jobjectArray parse_pan_property_change(JNIEnv *env, DBusMessage *msg);
+jobjectArray parse_health_device_property_change(JNIEnv *env, DBusMessage *msg);
+
+void append_dict_args(DBusMessage *reply, const char *first_key, ...);
 void append_variant(DBusMessageIter *iter, int type, void *val);
 int get_bdaddr(const char *str, bdaddr_t *ba);
 void get_bdaddr_as_string(const bdaddr_t *ba, char *str);
diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp
index 59b97c2..fb25486 100644
--- a/core/jni/android_server_BluetoothEventLoop.cpp
+++ b/core/jni/android_server_BluetoothEventLoop.cpp
@@ -72,6 +72,8 @@
 static jmethodID method_onInputDeviceConnectionResult;
 static jmethodID method_onPanDevicePropertyChanged;
 static jmethodID method_onPanDeviceConnectionResult;
+static jmethodID method_onHealthDevicePropertyChanged;
+static jmethodID method_onHealthDeviceChannelChanged;
 
 typedef event_loop_native_data_t native_data_t;
 
@@ -139,6 +141,10 @@
                                                "(Ljava/lang/String;[Ljava/lang/String;)V");
     method_onPanDeviceConnectionResult = env->GetMethodID(clazz, "onPanDeviceConnectionResult",
                                                "(Ljava/lang/String;I)V");
+    method_onHealthDevicePropertyChanged = env->GetMethodID(clazz, "onHealthDevicePropertyChanged",
+                                               "(Ljava/lang/String;[Ljava/lang/String;)V");
+    method_onHealthDeviceChannelChanged = env->GetMethodID(clazz, "onHealthDeviceChannelChanged",
+                                               "(Ljava/lang/String;Ljava/lang/String;Z)V");
     method_onRequestOobData = env->GetMethodID(clazz, "onRequestOobData",
                                                "(Ljava/lang/String;I)V");
 
@@ -271,6 +277,15 @@
             LOG_AND_FREE_DBUS_ERROR(&err);
             return JNI_FALSE;
         }
+
+        dbus_bus_add_match(nat->conn,
+                "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".HealthDevice'",
+                &err);
+        if (dbus_error_is_set(&err)) {
+            LOG_AND_FREE_DBUS_ERROR(&err);
+            return JNI_FALSE;
+        }
+
         dbus_bus_add_match(nat->conn,
                 "type='signal',interface='org.bluez.AudioSink'",
                 &err);
@@ -459,6 +474,12 @@
             LOG_AND_FREE_DBUS_ERROR(&err);
         }
         dbus_bus_remove_match(nat->conn,
+                "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".HealthDevice'",
+                &err);
+        if (dbus_error_is_set(&err)) {
+            LOG_AND_FREE_DBUS_ERROR(&err);
+        }
+        dbus_bus_remove_match(nat->conn,
                 "type='signal',interface='org.bluez.audio.Manager'",
                 &err);
         if (dbus_error_is_set(&err)) {
@@ -985,6 +1006,58 @@
            LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
        }
        goto success;
+    } else if (dbus_message_is_signal(msg,
+                                     "org.bluez.HealthDevice",
+                                     "ChannelConnected")) {
+       const char *c_path = dbus_message_get_path(msg);
+       const char *c_channel_path;
+       jboolean exists = JNI_TRUE;
+       if (dbus_message_get_args(msg, &err,
+                                  DBUS_TYPE_OBJECT_PATH, &c_channel_path,
+                                  DBUS_TYPE_INVALID)) {
+           env->CallVoidMethod(nat->me,
+                               method_onHealthDeviceChannelChanged,
+                               env->NewStringUTF(c_path),
+                               env->NewStringUTF(c_channel_path),
+                               exists);
+       } else {
+           LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+       }
+       goto success;
+    } else if (dbus_message_is_signal(msg,
+                                     "org.bluez.NetworkServer",
+                                     "ChannelDeleted")) {
+
+       const char *c_path = dbus_message_get_path(msg);
+       const char *c_channel_path;
+       jboolean exists = JNI_FALSE;
+       if (dbus_message_get_args(msg, &err,
+                                  DBUS_TYPE_OBJECT_PATH, &c_channel_path,
+                                  DBUS_TYPE_INVALID)) {
+           env->CallVoidMethod(nat->me,
+                               method_onHealthDeviceChannelChanged,
+                               env->NewStringUTF(c_path),
+                               env->NewStringUTF(c_channel_path),
+                               exists);
+       } else {
+           LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+       }
+       goto success;
+    } else if (dbus_message_is_signal(msg,
+                                     "org.bluez.HealthDevice",
+                                     "PropertyChanged")) {
+        jobjectArray str_array =
+                    parse_health_device_property_change(env, msg);
+        if (str_array != NULL) {
+            const char *c_path = dbus_message_get_path(msg);
+            env->CallVoidMethod(nat->me,
+                                method_onHealthDevicePropertyChanged,
+                                env->NewStringUTF(c_path),
+                                str_array);
+       } else {
+           LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+       }
+       goto success;
     }
 
     ret = a2dp_event_filter(msg, env);
diff --git a/core/jni/android_server_BluetoothService.cpp b/core/jni/android_server_BluetoothService.cpp
index ac75634..036c34c 100644
--- a/core/jni/android_server_BluetoothService.cpp
+++ b/core/jni/android_server_BluetoothService.cpp
@@ -19,12 +19,16 @@
 #define DBUS_INPUT_IFACE BLUEZ_DBUS_BASE_IFC ".Input"
 #define DBUS_NETWORK_IFACE BLUEZ_DBUS_BASE_IFC ".Network"
 #define DBUS_NETWORKSERVER_IFACE BLUEZ_DBUS_BASE_IFC ".NetworkServer"
-
+#define DBUS_HEALTH_MANAGER_PATH "/org/bluez"
+#define DBUS_HEALTH_MANAGER_IFACE BLUEZ_DBUS_BASE_IFC ".HealthManager"
+#define DBUS_HEALTH_DEVICE_IFACE BLUEZ_DBUS_BASE_IFC ".HealthDevice"
+#define DBUS_HEALTH_CHANNEL_IFACE BLUEZ_DBUS_BASE_IFC ".HealthChannel"
 
 #define LOG_TAG "BluetoothService.cpp"
 
 #include "android_bluetooth_common.h"
 #include "android_runtime/AndroidRuntime.h"
+#include "android_util_Binder.h"
 #include "JNIHelp.h"
 #include "jni.h"
 #include "utils/Log.h"
@@ -1255,6 +1259,400 @@
     return JNI_FALSE;
 }
 
+static jstring registerHealthApplicationNative(JNIEnv *env, jobject object,
+                                           jint dataType, jstring role,
+                                           jstring name, jstring channelType) {
+    LOGV("%s", __FUNCTION__);
+    jstring path = NULL;
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        const char *c_role = env->GetStringUTFChars(role, NULL);
+        const char *c_name = env->GetStringUTFChars(name, NULL);
+        const char *c_channel_type = env->GetStringUTFChars(channelType, NULL);
+        char *c_path;
+        DBusMessage *msg, *reply;
+        DBusError err;
+        dbus_error_init(&err);
+
+        msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC,
+                                            DBUS_HEALTH_MANAGER_PATH,
+                                            DBUS_HEALTH_MANAGER_IFACE,
+                                            "CreateApplication");
+
+        if (msg == NULL) {
+            LOGE("Could not allocate D-Bus message object!");
+            return NULL;
+        }
+
+        /* append arguments */
+        append_dict_args(msg,
+                         "DataType", DBUS_TYPE_UINT16, &dataType,
+                         "Role", DBUS_TYPE_STRING, &c_role,
+                         "Description", DBUS_TYPE_STRING, &c_name,
+                         "ChannelType", DBUS_TYPE_STRING, &c_channel_type,
+                         DBUS_TYPE_INVALID);
+
+
+        /* Make the call. */
+        reply = dbus_connection_send_with_reply_and_block(nat->conn, msg, -1, &err);
+
+        env->ReleaseStringUTFChars(role, c_role);
+        env->ReleaseStringUTFChars(name, c_name);
+        env->ReleaseStringUTFChars(channelType, c_channel_type);
+
+        if (!reply) {
+            if (dbus_error_is_set(&err)) {
+                LOG_AND_FREE_DBUS_ERROR(&err);
+            }
+        } else {
+            if (!dbus_message_get_args(reply, &err,
+                                      DBUS_TYPE_OBJECT_PATH, &c_path,
+                                      DBUS_TYPE_INVALID)) {
+                if (dbus_error_is_set(&err)) {
+                    LOG_AND_FREE_DBUS_ERROR(&err);
+                }
+            } else {
+               path = env->NewStringUTF(c_path);
+            }
+            dbus_message_unref(reply);
+        }
+    }
+#endif
+    return path;
+}
+
+static jstring registerSinkHealthApplicationNative(JNIEnv *env, jobject object,
+                                           jint dataType, jstring role,
+                                           jstring name) {
+    LOGV("%s", __FUNCTION__);
+    jstring path = NULL;
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        const char *c_role = env->GetStringUTFChars(role, NULL);
+        const char *c_name = env->GetStringUTFChars(name, NULL);
+        char *c_path;
+
+        DBusMessage *msg, *reply;
+        DBusError err;
+        dbus_error_init(&err);
+
+        msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC,
+                                            DBUS_HEALTH_MANAGER_PATH,
+                                            DBUS_HEALTH_MANAGER_IFACE,
+                                            "CreateApplication");
+
+        if (msg == NULL) {
+            LOGE("Could not allocate D-Bus message object!");
+            return NULL;
+        }
+
+        /* append arguments */
+        append_dict_args(msg,
+                         "DataType", DBUS_TYPE_UINT16, &dataType,
+                         "Role", DBUS_TYPE_STRING, &c_role,
+                         "Description", DBUS_TYPE_STRING, &c_name,
+                         DBUS_TYPE_INVALID);
+
+
+        /* Make the call. */
+        reply = dbus_connection_send_with_reply_and_block(nat->conn, msg, -1, &err);
+
+        env->ReleaseStringUTFChars(role, c_role);
+        env->ReleaseStringUTFChars(name, c_name);
+
+        if (!reply) {
+            if (dbus_error_is_set(&err)) {
+                LOG_AND_FREE_DBUS_ERROR(&err);
+            }
+        } else {
+            LOGE("--_Call made getting the patch...");
+            if (!dbus_message_get_args(reply, &err,
+                                      DBUS_TYPE_OBJECT_PATH, &c_path,
+                                      DBUS_TYPE_INVALID)) {
+                if (dbus_error_is_set(&err)) {
+                    LOG_AND_FREE_DBUS_ERROR(&err);
+                }
+            } else {
+                path = env->NewStringUTF(c_path);
+                LOGE("----Path is %s", c_path);
+            }
+            dbus_message_unref(reply);
+        }
+    }
+#endif
+    return path;
+}
+
+static jboolean unregisterHealthApplicationNative(JNIEnv *env, jobject object,
+                                                    jstring path) {
+    LOGV("%s", __FUNCTION__);
+    jboolean result = JNI_FALSE;
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        const char *c_path = env->GetStringUTFChars(path, NULL);
+        DBusError err;
+        dbus_error_init(&err);
+        DBusMessage *reply =
+            dbus_func_args_timeout(env, nat->conn, -1,
+                                   DBUS_HEALTH_MANAGER_PATH,
+                                   DBUS_HEALTH_MANAGER_IFACE, "DestroyApplication",
+                                   DBUS_TYPE_OBJECT_PATH, &c_path,
+                                   DBUS_TYPE_INVALID);
+
+        env->ReleaseStringUTFChars(path, c_path);
+
+        if (!reply) {
+            if (dbus_error_is_set(&err)) {
+                LOG_AND_FREE_DBUS_ERROR(&err);
+            }
+        } else {
+            result = JNI_TRUE;
+        }
+    }
+#endif
+    return result;
+}
+
+static jboolean createChannelNative(JNIEnv *env, jobject object,
+                                       jstring devicePath, jstring appPath, jstring config) {
+    LOGV("%s", __FUNCTION__);
+    jboolean result = JNI_FALSE;
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+
+    if (nat) {
+        DBusError err;
+        dbus_error_init(&err);
+
+        const char *c_device_path = env->GetStringUTFChars(devicePath, NULL);
+        const char *c_app_path = env->GetStringUTFChars(appPath, NULL);
+        const char *c_config = env->GetStringUTFChars(config, NULL);
+        LOGE("Params...%s, %s, %s \n", c_device_path, c_app_path, c_config);
+
+        DBusMessage *reply  = dbus_func_args(env, nat->conn,
+                                             c_device_path,
+                                             DBUS_HEALTH_DEVICE_IFACE,
+                                             "CreateChannel",
+                                             DBUS_TYPE_OBJECT_PATH, &c_app_path,
+                                             DBUS_TYPE_STRING, &c_config,
+                                             DBUS_TYPE_INVALID);
+
+
+        env->ReleaseStringUTFChars(devicePath, c_device_path);
+        env->ReleaseStringUTFChars(appPath, c_app_path);
+        env->ReleaseStringUTFChars(config, c_config);
+
+        if (!reply) {
+            if (dbus_error_is_set(&err)) {
+                LOG_AND_FREE_DBUS_ERROR(&err);
+            }
+        } else {
+            result = JNI_TRUE;
+        }
+    }
+#endif
+    return result;
+}
+
+static jboolean destroyChannelNative(JNIEnv *env, jobject object, jstring devicePath,
+                                     jstring channelPath) {
+    LOGE("%s", __FUNCTION__);
+    jboolean result = JNI_FALSE;
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+
+    if (nat) {
+        DBusError err;
+        dbus_error_init(&err);
+
+        const char *c_device_path = env->GetStringUTFChars(devicePath, NULL);
+        const char *c_channel_path = env->GetStringUTFChars(channelPath, NULL);
+
+        DBusMessage *reply = dbus_func_args(env, nat->conn,
+                                            c_device_path,
+                                            DBUS_HEALTH_DEVICE_IFACE,
+                                            "DestroyChannel",
+                                            DBUS_TYPE_OBJECT_PATH, &c_channel_path,
+                                            DBUS_TYPE_INVALID);
+
+        env->ReleaseStringUTFChars(devicePath, c_device_path);
+        env->ReleaseStringUTFChars(channelPath, c_channel_path);
+
+        if (!reply) {
+            if (dbus_error_is_set(&err)) {
+                LOG_AND_FREE_DBUS_ERROR(&err);
+            }
+        } else {
+            result = JNI_TRUE;
+        }
+    }
+#endif
+    return result;
+}
+
+static jstring getMainChannelNative(JNIEnv *env, jobject object, jstring devicePath) {
+    LOGE("%s", __FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        const char *c_device_path = env->GetStringUTFChars(devicePath, NULL);
+        DBusError err;
+        dbus_error_init(&err);
+
+        LOGE("---Args %s", c_device_path);
+        DBusMessage *reply = dbus_func_args(env, nat->conn,
+                           c_device_path,
+                           DBUS_HEALTH_DEVICE_IFACE, "GetProperties",
+                           DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(devicePath, c_device_path);
+
+        if (!reply) {
+            if (dbus_error_is_set(&err)) {
+                LOG_AND_FREE_DBUS_ERROR(&err);
+            }
+        } else {
+            DBusMessageIter iter;
+            jobjectArray str_array = NULL;
+            if (dbus_message_iter_init(reply, &iter))
+                str_array = parse_health_device_properties(env, &iter);
+            dbus_message_unref(reply);
+            jstring path = (jstring) env->GetObjectArrayElement(str_array, 1);
+
+            return path;
+        }
+    }
+#endif
+    return NULL;
+}
+
+static jstring getChannelApplicationNative(JNIEnv *env, jobject object, jstring channelPath) {
+    LOGE("%s", __FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        const char *c_channel_path = env->GetStringUTFChars(channelPath, NULL);
+        DBusError err;
+        dbus_error_init(&err);
+
+        LOGE("---Args %s", c_channel_path);
+
+        DBusMessage *reply = dbus_func_args(env, nat->conn,
+                                            c_channel_path,
+                                            DBUS_HEALTH_CHANNEL_IFACE, "GetProperties",
+                                            DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(channelPath, c_channel_path);
+
+        if (!reply) {
+            if (dbus_error_is_set(&err)) {
+                LOG_AND_FREE_DBUS_ERROR(&err);
+            }
+        } else {
+            DBusMessageIter iter;
+            jobjectArray str_array = NULL;
+            if (dbus_message_iter_init(reply, &iter))
+                str_array = parse_health_channel_properties(env, &iter);
+            dbus_message_unref(reply);
+
+            jint len = env->GetArrayLength(str_array);
+
+            jstring name, path;
+            const char *c_name;
+
+            for (int i = 0; i < len; i+=2) {
+                name = (jstring) env->GetObjectArrayElement(str_array, i);
+                c_name = env->GetStringUTFChars(name, NULL);
+
+                if (!strcmp(c_name, "Application")) {
+                    path = (jstring) env->GetObjectArrayElement(str_array, i+1);
+                    LOGE("----Path is %s", env->GetStringUTFChars(path, NULL));
+                    env->ReleaseStringUTFChars(name, c_name);
+                    return path;
+                }
+                env->ReleaseStringUTFChars(name, c_name);
+            }
+        }
+    }
+#endif
+    return NULL;
+}
+
+static jboolean releaseChannelFdNative(JNIEnv *env, jobject object, jstring channelPath) {
+    LOGV("%s", __FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        const char *c_channel_path = env->GetStringUTFChars(channelPath, NULL);
+        DBusError err;
+        dbus_error_init(&err);
+
+        DBusMessage *reply = dbus_func_args(env, nat->conn,
+                                            c_channel_path,
+                                            DBUS_HEALTH_CHANNEL_IFACE, "Release",
+                                            DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(channelPath, c_channel_path);
+
+        return reply ? JNI_TRUE : JNI_FALSE;
+    }
+#endif
+    return JNI_FALSE;
+}
+
+static jobject getChannelFdNative(JNIEnv *env, jobject object, jstring channelPath) {
+    LOGV("%s", __FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+    native_data_t *nat = get_native_data(env, object);
+    if (nat) {
+        const char *c_channel_path = env->GetStringUTFChars(channelPath, NULL);
+        int32_t fd;
+        DBusError err;
+        dbus_error_init(&err);
+
+        DBusMessage *reply = dbus_func_args(env, nat->conn,
+                                            c_channel_path,
+                                            DBUS_HEALTH_CHANNEL_IFACE, "Acquire",
+                                            DBUS_TYPE_INVALID);
+        env->ReleaseStringUTFChars(channelPath, c_channel_path);
+
+        if (!reply) {
+            if (dbus_error_is_set(&err)) {
+                LOG_AND_FREE_DBUS_ERROR(&err);
+            }
+            return NULL;
+        }
+
+        fd = dbus_returns_unixfd(env, reply);
+        if (fd == -1) return NULL;
+
+        LOGE("---got fd %d\n", fd);
+        // Create FileDescriptor object
+        jobject fileDesc = jniCreateFileDescriptor(env, fd);
+        if (fileDesc == NULL) {
+            // FileDescriptor constructor has thrown an exception
+            releaseChannelFdNative(env, object, channelPath);
+            LOGE("---File Desc is null");
+            return NULL;
+        }
+
+        // Wrap it in a ParcelFileDescriptor
+        jobject parcelFileDesc = newParcelFileDescriptor(env, fileDesc);
+        if (parcelFileDesc == NULL) {
+            // ParcelFileDescriptor constructor has thrown an exception
+            releaseChannelFdNative(env, object, channelPath);
+            LOGE("---Parcel File Desc is null");
+            return NULL;
+        }
+
+        return parcelFileDesc;
+    }
+#endif
+    return NULL;
+}
+
+
+
 static JNINativeMethod sMethods[] = {
      /* name, signature, funcPtr */
     {"classInitNative", "()V", (void*)classInitNative},
@@ -1317,6 +1715,24 @@
     {"disconnectPanDeviceNative", "(Ljava/lang/String;)Z", (void *)disconnectPanDeviceNative},
     {"disconnectPanServerDeviceNative", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
               (void *)disconnectPanServerDeviceNative},
+    // Health function
+    {"registerHealthApplicationNative",
+              "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
+              (void *)registerHealthApplicationNative},
+    {"registerHealthApplicationNative",
+            "(ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
+            (void *)registerSinkHealthApplicationNative},
+
+    {"unregisterHealthApplicationNative", "(Ljava/lang/String;)Z",
+              (void *)unregisterHealthApplicationNative},
+    {"createChannelNative", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
+              (void *)createChannelNative},
+    {"destroyChannelNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void *)destroyChannelNative},
+    {"getMainChannelNative", "(Ljava/lang/String;)Ljava/lang/String;", (void *)getMainChannelNative},
+    {"getChannelApplicationNative", "(Ljava/lang/String;)Ljava/lang/String;",
+              (void *)getChannelApplicationNative},
+    {"getChannelFdNative", "(Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;", (void *)getChannelFdNative},
+    {"releaseChannelFdNative", "(Ljava/lang/String;)Z", (void *)releaseChannelFdNative},
 };
 
 
diff --git a/core/res/res/layout/keyguard_screen_password_landscape.xml b/core/res/res/layout/keyguard_screen_password_landscape.xml
index 937d5dc..8ba08f6 100644
--- a/core/res/res/layout/keyguard_screen_password_landscape.xml
+++ b/core/res/res/layout/keyguard_screen_password_landscape.xml
@@ -24,7 +24,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
-    android:rowCount="10"
+    android:rowCount="11"
     android:id="@+id/root"
     android:clipChildren="false">
 
@@ -33,7 +33,8 @@
 
     <com.android.internal.widget.DigitalClock android:id="@+id/time"
         android:layout_marginTop="16dip"
-        android:layout_marginBottom="8dip">
+        android:layout_marginBottom="8dip"
+        android:layout_gravity="right">
 
        <!-- Because we can't have multi-tone fonts, we render two TextViews, one on
         top of the other. Hence the redundant layout... -->
@@ -67,120 +68,129 @@
         android:id="@+id/date"
         android:layout_below="@id/time"
         android:layout_marginTop="6dip"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
-        android:layout_gravity="left"
+        android:layout_gravity="right"
         />
 
     <TextView
         android:id="@+id/alarm_status"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:drawablePadding="4dip"
         android:layout_marginTop="4dip"
-        android:layout_gravity="left"
+        android:layout_gravity="right"
         />
 
     <TextView
         android:id="@+id/status1"
         android:layout_marginTop="4dip"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:drawablePadding="4dip"
-        android:layout_gravity="left"
+        android:layout_gravity="right"
         />
 
     <TextView
         android:id="@+id/status2"
         android:layout_marginTop="4dip"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:drawablePadding="4dip"
-        android:layout_gravity="left"
+        android:layout_gravity="right"
         />
 
     <TextView
         android:id="@+id/screenLocked"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:gravity="center"
         android:layout_marginTop="4dip"
         android:drawablePadding="4dip"
-        android:layout_gravity="left"
+        android:layout_gravity="right"
         />
 
     <Space android:height="20dip"/>
 
-    <LinearLayout android:orientation="vertical" >
-
-         <TextView
-            android:id="@+id/carrier"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignParentTop="true"
-            android:singleLine="true"
-            android:ellipsize="marquee"
-            android:layout_gravity="bottom|left"
-            android:textAppearance="?android:attr/textAppearanceMedium"
-            android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
-            android:textColor="?android:attr/textColorSecondary"
-        />
-
-        <!-- "emergency calls only" shown when sim is missing or PUKd -->
-        <TextView
-            android:id="@+id/emergencyCallText"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_alignParentTop="true"
-            android:layout_marginTop="20dip"
-            android:singleLine="true"
-            android:ellipsize="marquee"
-            android:text="@string/emergency_calls_only"
-            android:textAppearance="?android:attr/textAppearanceMedium"
-            android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
-            android:textColor="?android:attr/textColorSecondary"
-        />
-
-        <Button
-            android:id="@+id/emergencyCallButton"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:drawableLeft="@drawable/ic_emergency"
-            android:text="@string/lockscreen_emergency_call"
-            style="@style/Widget.Button.Transparent"
-            android:drawablePadding="8dip"
-            android:layout_marginRight="80dip"
-            android:visibility="visible"
-        />
-    </LinearLayout>
-
-    <Space android:height="20dip"/>
-
-    <!-- Column 1 -->
-    <Space android:width="20dip" android:layout_columnWeight="1" android:layout_rowSpan="10" />
-
-    <!-- Column 2 - password entry field and PIN keyboard -->
-    <EditText android:id="@+id/passwordEntry"
-        android:layout_height="wrap_content"
-        android:layout_width="match_parent"
+    <TextView
+        android:id="@+id/carrier"
         android:singleLine="true"
-        android:textStyle="normal"
-        android:inputType="textPassword"
-        android:gravity="center"
-        android:textSize="24sp"
+        android:ellipsize="marquee"
+        android:layout_gravity="right"
         android:textAppearance="?android:attr/textAppearanceMedium"
-        android:background="@drawable/lockscreen_password_field_dark"
-        android:textColor="?android:attr/textColorPrimary"
-        />
+        android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+        android:textColor="?android:attr/textColorSecondary"
+    />
 
-    <!-- Numeric keyboard -->
-    <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard"
-        android:layout_width="300dip"
-        android:layout_height="300dip"
-        android:background="#40000000"
-        android:layout_marginTop="5dip"
-        android:keyBackground="@drawable/btn_keyboard_key_fulltrans"
+    <!-- "emergency calls only" shown when sim is missing or PUKd -->
+    <TextView
+        android:id="@+id/emergencyCallText"
+        android:layout_marginTop="20dip"
+        android:layout_gravity="right"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:text="@string/emergency_calls_only"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+        android:textColor="?android:attr/textColorSecondary"
+    />
+
+    <Button
+        android:id="@+id/emergencyCallButton"
+        android:layout_gravity="right"
+        android:drawableLeft="@drawable/lockscreen_emergency_button"
+        android:text="@string/lockscreen_emergency_call"
+        style="?android:attr/buttonBarButtonStyle"
+        android:drawablePadding="8dip"
         android:visibility="visible"
     />
 
+    <!-- Column 1 -->
+    <Space android:layout_columnWeight="1" android:layout_rowSpan="11" />
+
+    <!-- Column 2 - password entry field and PIN keyboard -->
+    <LinearLayout
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:orientation="vertical"
+        android:layout_gravity="center|fill"
+        android:layout_rowSpan="11">
+
+        <EditText android:id="@+id/passwordEntry"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:singleLine="true"
+            android:textStyle="normal"
+            android:inputType="textPassword"
+            android:layout_gravity="center"
+            android:textSize="24sp"
+            android:minEms="8"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:background="@drawable/lockscreen_password_field_dark"
+            android:textColor="?android:attr/textColorPrimary"
+            />
+
+        <!-- Numeric keyboard -->
+        <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard"
+            android:layout_width="250dip"
+            android:layout_height="100dip"
+            android:layout_gravity="center"
+            android:background="#40000000"
+            android:layout_marginTop="5dip"
+            android:keyBackground="@drawable/btn_keyboard_key_fulltrans"
+            android:visibility="visible"
+        />
+
+    </LinearLayout>
+
 </GridLayout>
diff --git a/core/res/res/layout/keyguard_screen_password_portrait.xml b/core/res/res/layout/keyguard_screen_password_portrait.xml
index 30747bc..6953ac8 100644
--- a/core/res/res/layout/keyguard_screen_password_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_password_portrait.xml
@@ -41,10 +41,10 @@
         android:layout_marginTop="4dip"
         android:layout_marginRight="16dip"
         android:layout_gravity="right"
-        android:drawableLeft="@drawable/ic_emergency"
+        android:drawableLeft="@drawable/lockscreen_emergency_button"
         android:drawablePadding="8dip"
         android:text="@string/lockscreen_emergency_call"
-        style="@style/Widget.Button.Transparent"
+        style="?android:attr/buttonBarButtonStyle"
     />
 
     <!-- bottom: password -->
diff --git a/core/res/res/layout/keyguard_screen_status_land.xml b/core/res/res/layout/keyguard_screen_status_land.xml
index 60dd9ff..021faa3 100644
--- a/core/res/res/layout/keyguard_screen_status_land.xml
+++ b/core/res/res/layout/keyguard_screen_status_land.xml
@@ -73,6 +73,8 @@
             android:id="@+id/date"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:ellipsize="marquee"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"/>
 
@@ -81,6 +83,8 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginLeft="30dip"
+            android:singleLine="true"
+            android:ellipsize="marquee"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"/>
 
@@ -92,6 +96,8 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_alignParentTop="true"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:layout_marginTop="10dip"
@@ -106,6 +112,8 @@
         android:layout_height="wrap_content"
         android:layout_marginTop="10dip"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         />
 
diff --git a/core/res/res/layout/keyguard_screen_status_port.xml b/core/res/res/layout/keyguard_screen_status_port.xml
index e72df5b..f84e61c2 100644
--- a/core/res/res/layout/keyguard_screen_status_port.xml
+++ b/core/res/res/layout/keyguard_screen_status_port.xml
@@ -70,6 +70,8 @@
             android:id="@+id/date"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:ellipsize="marquee"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"/>
 
@@ -78,6 +80,8 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginLeft="16dip"
+            android:singleLine="true"
+            android:ellipsize="marquee"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
             android:drawablePadding="4dip"/>
@@ -90,6 +94,8 @@
         android:layout_height="wrap_content"
         android:layout_gravity="right"
         android:layout_marginTop="4dip"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:textAppearance="?android:attr/textAppearanceMedium"
         />
@@ -101,6 +107,8 @@
         android:layout_height="wrap_content"
         android:layout_gravity="right"
         android:layout_alignParentTop="true"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:layout_marginTop="4dip"
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock.xml b/core/res/res/layout/keyguard_screen_tab_unlock.xml
index 020bb27..1e2abf9 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock.xml
@@ -80,6 +80,8 @@
             android:id="@+id/date"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:ellipsize="marquee"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
             />
@@ -88,6 +90,8 @@
             android:id="@+id/alarm_status"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:ellipsize="marquee"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
             android:drawablePadding="4dip"
@@ -105,6 +109,8 @@
         android:layout_marginLeft="16dip"
         android:layout_marginRight="16dip"
         android:layout_alignParentRight="true"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:drawablePadding="4dip"
@@ -119,6 +125,8 @@
         android:layout_marginLeft="16dip"
         android:layout_marginRight="16dip"
         android:layout_alignParentRight="true"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:drawablePadding="4dip"
@@ -133,6 +141,8 @@
         android:layout_marginLeft="16dip"
         android:layout_marginRight="16dip"
         android:layout_alignParentRight="true"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:layout_marginTop="12dip"
@@ -147,14 +157,13 @@
         android:layout_height="wrap_content"
         android:layout_marginTop="4dip"
         android:layout_marginRight="16dip"
-        android:drawableLeft="@drawable/ic_emergency"
+        android:drawableLeft="@drawable/lockscreen_emergency_button"
         android:layout_alignParentRight="true"
         android:layout_below="@id/screenLocked"
-        style="@style/Widget.Button.Transparent"
+        style="?android:attr/buttonBarButtonStyle"
         android:drawablePadding="4dip"
         android:text="@string/lockscreen_emergency_call"
         android:visibility="visible"
-        android:background="@null"
         />
 
     <RelativeLayout
@@ -207,6 +216,8 @@
             android:layout_marginTop="0dip"
             android:layout_marginRight="8dip"
             android:text="@string/emergency_calls_only"
+            android:singleLine="true"
+            android:ellipsize="marquee"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
             android:textColor="?android:attr/textColorSecondary"
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
index 930ac33..5588adc 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
@@ -68,6 +68,8 @@
         android:id="@+id/date"
         android:layout_below="@id/time"
         android:layout_marginTop="6dip"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:layout_gravity="right"
@@ -75,6 +77,8 @@
 
     <TextView
         android:id="@+id/alarm_status"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:drawablePadding="4dip"
@@ -85,6 +89,8 @@
     <TextView
         android:id="@+id/status1"
         android:layout_marginTop="4dip"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:drawablePadding="4dip"
@@ -94,6 +100,8 @@
     <TextView
         android:id="@+id/status2"
         android:layout_marginTop="4dip"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:drawablePadding="4dip"
@@ -102,6 +110,8 @@
 
     <TextView
         android:id="@+id/screenLocked"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:gravity="center"
@@ -113,7 +123,8 @@
     <Space android:height="20dip"/>
 
     <LinearLayout android:orientation="vertical"
-        android:gravity="fill">
+        android:layout_gravity="right"
+        android:gravity="fill_horizontal">
 
          <TextView
             android:id="@+id/carrier"
@@ -148,11 +159,10 @@
             android:id="@+id/emergencyCallButton"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:drawableLeft="@drawable/ic_emergency"
+            android:drawableLeft="@drawable/lockscreen_emergency_button"
             android:text="@string/lockscreen_emergency_call"
-            style="@style/Widget.Button.Transparent"
+            style="?android:attr/buttonBarButtonStyle"
             android:drawablePadding="8dip"
-            android:layout_marginRight="80dip"
             android:visibility="visible"
             android:layout_gravity="right"
         />
diff --git a/core/res/res/layout/keyguard_screen_unlock_landscape.xml b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
index 8319b8a..d0538dd 100644
--- a/core/res/res/layout/keyguard_screen_unlock_landscape.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
@@ -26,7 +26,7 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:rowCount="8">
+    android:rowCount="7">
 
     <!-- Column 0: Time, date and status -->
     <com.android.internal.widget.DigitalClock android:id="@+id/time"
@@ -68,6 +68,8 @@
         android:id="@+id/date"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:layout_gravity="right"
@@ -77,6 +79,8 @@
         android:id="@+id/alarm_status"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:layout_gravity="right"
@@ -87,6 +91,8 @@
         android:id="@+id/status1"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:layout_gravity="right"
@@ -100,31 +106,43 @@
 
     <TextView android:id="@+id/carrier"
         android:layout_gravity="right"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:singleLine="true"
         android:ellipsize="marquee"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         />
 
-    <Button android:id="@+id/emergencyCallButton"
-        android:layout_gravity="right"
-        style="@style/Widget.Button.Transparent"
-        android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
-        android:text="@string/lockscreen_emergency_call"
-        android:drawableLeft="@drawable/lockscreen_emergency_button"
-        android:drawablePadding="0dip"
-        android:background="@null"
-    />
+    <LinearLayout
+        style="?android:attr/buttonBarStyle"
+        android:orientation="vertical"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="right">
 
-    <Button android:id="@+id/forgotPatternButton"
-        android:layout_gravity="right"
-        style="@style/Widget.Button.Transparent"
-        android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
-        android:text="@string/lockscreen_forgot_pattern_button_text"
-        android:drawableLeft="@drawable/lockscreen_forgot_password_button"
-        android:drawablePadding="0dip"
-        android:background="@null"
-    />
+        <Button android:id="@+id/emergencyCallButton"
+            style="?android:attr/buttonBarButtonStyle"
+            android:layout_gravity="right"
+            android:layout_width="wrap_content"
+            android:layout_height="0dip"
+            android:layout_weight="1"
+            android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+            android:text="@string/lockscreen_emergency_call"
+            android:drawableLeft="@drawable/lockscreen_emergency_button"
+            android:drawablePadding="0dip"
+        />
+
+        <Button android:id="@+id/forgotPatternButton"
+            style="?android:attr/buttonBarButtonStyle"
+            android:layout_gravity="right"
+            android:layout_width="wrap_content"
+            android:layout_height="0dip"
+            android:layout_weight="1"
+            android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+            android:text="@string/lockscreen_forgot_pattern_button_text"
+            android:drawableLeft="@drawable/lockscreen_forgot_password_button"
+            android:drawablePadding="0dip"
+        />
+    </LinearLayout>
 
     <!-- Column 1: lock pattern -->
     <com.android.internal.widget.LockPatternView android:id="@+id/lockPattern"
@@ -134,6 +152,6 @@
          android:layout_marginRight="8dip"
          android:layout_marginBottom="8dip"
          android:layout_marginLeft="8dip"
-         android:layout_rowSpan="8"/>
+         android:layout_rowSpan="7"/>
 
 </GridLayout>
diff --git a/core/res/res/layout/keyguard_screen_unlock_portrait.xml b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
index bbe2006..774f830 100644
--- a/core/res/res/layout/keyguard_screen_unlock_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
@@ -68,6 +68,8 @@
             android:id="@+id/date"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:ellipsize="marquee"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
             />
@@ -77,6 +79,8 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginLeft="16dip"
+            android:singleLine="true"
+            android:ellipsize="marquee"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
             android:drawablePadding="4dip"
@@ -89,22 +93,22 @@
         android:id="@+id/status1"
         android:layout_gravity="right"
         android:layout_marginRight="@dimen/keyguard_lockscreen_status_line_font_right_margin"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:drawablePadding="4dip"
-        android:singleLine="true"
-        android:ellipsize="marquee"
         />
 
     <TextView
         android:id="@+id/status2"
         android:layout_gravity="right"
         android:layout_marginRight="@dimen/keyguard_lockscreen_status_line_font_right_margin"
+        android:singleLine="true"
+        android:ellipsize="marquee"
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
         android:drawablePadding="4dip"
-        android:singleLine="true"
-        android:ellipsize="marquee"
         android:visibility="gone"
         />
 
@@ -136,30 +140,33 @@
     <!-- Footer: an emergency call button and an initially hidden "Forgot pattern" button -->
     <LinearLayout
         android:orientation="horizontal"
-        android:layout_gravity="fill_horizontal">
+        android:layout_width="match_parent"
+        style="?android:attr/buttonBarStyle"
+        android:gravity="center"
+        android:weightSum="2">
 
         <Button android:id="@+id/emergencyCallButton"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
             android:layout_gravity="center_horizontal"
-            style="@style/Widget.Button.Transparent"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            style="?android:attr/buttonBarButtonStyle"
             android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
             android:text="@string/lockscreen_emergency_call"
             android:drawableLeft="@drawable/lockscreen_emergency_button"
             android:drawablePadding="0dip"
-            android:background="@null"
         />
 
         <Button android:id="@+id/forgotPatternButton"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
             android:layout_gravity="center_horizontal"
-            style="@style/Widget.Button.Transparent"
+            android:layout_width="0dip"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            style="?android:attr/buttonBarButtonStyle"
             android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
             android:text="@string/lockscreen_forgot_pattern_button_text"
             android:drawableLeft="@drawable/lockscreen_forgot_password_button"
             android:drawablePadding="0dip"
-            android:background="@null"
         />
 
     </LinearLayout>
diff --git a/core/res/res/values-land/dimens.xml b/core/res/res/values-land/dimens.xml
index c500fd3..ec2313c 100644
--- a/core/res/res/values-land/dimens.xml
+++ b/core/res/res/values-land/dimens.xml
@@ -39,6 +39,6 @@
     <dimen name="action_bar_subtitle_text_size">12dp</dimen>
 
     <!-- Size of clock font in LockScreen on Unsecure unlock screen. -->
-    <dimen name="keyguard_lockscreen_clock_font_size">80sp</dimen>
+    <dimen name="keyguard_lockscreen_clock_font_size">70sp</dimen>
 
 </resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 58d25d6..2f714f6 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2125,6 +2125,12 @@
         <!--  Specifies extra space on the bottom side of this view.
               This space is outside this view's bounds. -->
         <attr name="layout_marginBottom" format="dimension"  />
+        <!--  Specifies extra space on the start side of this view.
+              This space is outside this view's bounds. -->
+        <attr name="layout_marginStart" format="dimension"  />
+        <!--  Specifies extra space on the end side of this view.
+              This space is outside this view's bounds. -->
+        <attr name="layout_marginEnd" format="dimension"  />
     </declare-styleable>
 
     <!-- Use <code>input-method</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index f7974e9..c9ac57a 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -235,12 +235,13 @@
     <attr name="vmSafeMode" format="boolean" />
 
     <!-- <p>Flag indicating whether the application's rendering should be hardware
-         accelerated if possible. This flag is turned off by default, both for
-         applications and activities.</p>
+         accelerated if possible. This flag is turned on by default for applications
+         that are targeting {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}
+         or later.</p>
          <p>This flag can be set on the application and any activity declared
          in the manifest. When enabled for the application, each activity is
          automatically assumed to be hardware accelerated. This flag can be
-         overriden in the activity tags, either turning it off (if on for the
+         overridden in the activity tags, either turning it off (if on for the
          application) or on (if off for the application.)</p>
          <p>When this flag is turned on for an activity (either directly or via
          the application tag), every window created from the activity, including
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a8b7b75..0ad3184 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -115,6 +115,14 @@
         <item>"mobile_cbs,12,0,2,60000,true"</item>
     </string-array>
 
+    <!-- Array of ConnectivityManager.TYPE_xxxx constants for networks that may only
+         be controlled by systemOrSignature apps.  -->
+    <integer-array translatable="false" name="config_protectedNetworks">
+        <item>10</item>
+        <item>11</item>
+        <item>12</item>
+    </integer-array>
+
     <!-- This string array should be overridden by the device to present a list of radio
          attributes.  This is used by the connectivity manager to decide which networks can coexist
          based on the hardware -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 580c204..ec191dd 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1778,4 +1778,7 @@
   <public type="attr" name="paddingStart"/>
   <public type="attr" name="paddingEnd"/>
 
+  <public type="attr" name="layout_marginStart"/>
+  <public type="attr" name="layout_marginEnd"/>
+
 </resources>
diff --git a/core/tests/coretests/src/android/net/http/DefaultHttpClientTest.java b/core/tests/coretests/src/android/net/http/DefaultHttpClientTest.java
index da77298..cf9e6e6 100644
--- a/core/tests/coretests/src/android/net/http/DefaultHttpClientTest.java
+++ b/core/tests/coretests/src/android/net/http/DefaultHttpClientTest.java
@@ -28,9 +28,17 @@
 import java.io.StringWriter;
 import junit.framework.TestCase;
 import org.apache.http.HttpResponse;
+import org.apache.http.auth.AuthenticationException;
+import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.auth.DigestScheme;
 import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicHeader;
 
+/**
+ * Tests for various regressions and problems with DefaultHttpClient. This is
+ * not a comprehensive test!
+ */
 public final class DefaultHttpClientTest extends TestCase {
 
     private MockWebServer server = new MockWebServer();
@@ -87,4 +95,40 @@
         reader.close();
         return writer.toString();
     }
+
+    // http://code.google.com/p/android/issues/detail?id=16051
+    public void testDigestSchemeAlgorithms() throws Exception {
+        authenticateDigestAlgorithm("MD5");
+        authenticateDigestAlgorithm("MD5-sess");
+        authenticateDigestAlgorithm("md5");
+        authenticateDigestAlgorithm("md5-sess");
+        authenticateDigestAlgorithm("md5-SESS");
+        authenticateDigestAlgorithm("MD5-SESS");
+        try {
+            authenticateDigestAlgorithm("MD5-");
+        } catch (AuthenticationException expected) {
+        }
+        try {
+            authenticateDigestAlgorithm("MD6");
+        } catch (AuthenticationException expected) {
+        }
+        try {
+            authenticateDigestAlgorithm("MD");
+        } catch (AuthenticationException expected) {
+        }
+        try {
+            authenticateDigestAlgorithm("");
+        } catch (AuthenticationException expected) {
+        }
+    }
+
+    private void authenticateDigestAlgorithm(String algorithm) throws Exception {
+        String challenge = "Digest realm=\"protected area\", "
+                + "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
+                + "algorithm=" + algorithm;
+        DigestScheme digestScheme = new DigestScheme();
+        digestScheme.processChallenge(new BasicHeader("WWW-Authenticate", challenge));
+        HttpGet get = new HttpGet();
+        digestScheme.authenticate(new UsernamePasswordCredentials("username", "password"), get);
+    }
 }
diff --git a/core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java b/core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java
index aedfbad..2ed7c52 100644
--- a/core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java
+++ b/core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java
@@ -95,7 +95,7 @@
     private WebView mWebView;
 
     /** Used for caching the default bindings so they can be restored. */
-    private String mDefaultKeyBindings;
+    private static String sDefaultKeyBindings;
 
     /** The received selection string for assertion checking. */
     private static String sReceivedSelectionString = SELECTION_STRING_UNKNOWN;
@@ -1696,7 +1696,7 @@
      */
     private void injectTestWebContentKeyBindings() {
         ContentResolver contentResolver = getActivity().getContentResolver();
-        mDefaultKeyBindings = Settings.Secure.getString(contentResolver,
+        sDefaultKeyBindings = Settings.Secure.getString(contentResolver,
                 Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS);
         Settings.Secure.putString(contentResolver,
                 Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS, TEST_KEY_DINDINGS);
@@ -1708,7 +1708,7 @@
     private void restoreDefaultWebContentKeyBindings() {
         Settings.Secure.putString(getActivity().getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS,
-                mDefaultKeyBindings);
+                sDefaultKeyBindings);
     }
 
     /**
diff --git a/core/tests/notificationtests/Android.mk b/core/tests/notificationtests/Android.mk
new file mode 100644
index 0000000..be2e6bf
--- /dev/null
+++ b/core/tests/notificationtests/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+# Include all test java files.
+LOCAL_SRC_FILES := \
+	$(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_PACKAGE_NAME := NotificationStressTests
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/core/tests/notificationtests/AndroidManifest.xml b/core/tests/notificationtests/AndroidManifest.xml
new file mode 100644
index 0000000..51e530a
--- /dev/null
+++ b/core/tests/notificationtests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.notification.tests" >
+
+    <application >
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="android.test.InstrumentationTestRunner"
+        android:targetPackage="com.android.notification.tests"
+        android:label="Notification Stress Tests" />
+
+</manifest>
diff --git a/core/tests/notificationtests/src/android/app/NotificationStressTest.java b/core/tests/notificationtests/src/android/app/NotificationStressTest.java
new file mode 100644
index 0000000..52ea1c4
--- /dev/null
+++ b/core/tests/notificationtests/src/android/app/NotificationStressTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.test.InstrumentationTestCase;
+import android.test.RepetitiveTest;
+import android.test.TimedTest;
+
+import java.util.Random;
+
+/**
+ * Test which spams notification manager with a large number of notifications, for both stress and
+ * performance testing.
+ */
+public class NotificationStressTest extends InstrumentationTestCase {
+
+    private static final int NUM_ITERATIONS = 200;
+    private static final int[] ICONS = new int[] {
+        android.R.drawable.stat_notify_call_mute,
+        android.R.drawable.stat_notify_chat,
+        android.R.drawable.stat_notify_error,
+        android.R.drawable.stat_notify_missed_call,
+        android.R.drawable.stat_notify_more,
+        android.R.drawable.stat_notify_sdcard,
+        android.R.drawable.stat_notify_sdcard_prepare,
+        android.R.drawable.stat_notify_sdcard_usb,
+        android.R.drawable.stat_notify_sync,
+        android.R.drawable.stat_notify_sync_noanim,
+        android.R.drawable.stat_notify_voicemail,
+    };
+
+    private final Random mRandom = new Random();
+    private Context mContext;
+    private NotificationManager mNotificationManager;
+    private int notifyId = 0;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = getInstrumentation().getContext();
+        mNotificationManager = (NotificationManager) mContext.getSystemService(
+                Context.NOTIFICATION_SERVICE);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mNotificationManager.cancelAll();
+    }
+
+    @RepetitiveTest(numIterations=NUM_ITERATIONS)
+    public void testNotificationStress() {
+        // Cancel one of every five notifications to vary load on notification manager
+        if (notifyId % 5 == 4) {
+            mNotificationManager.cancel(notifyId - 4);
+        }
+        sendNotification(notifyId++, "testNotificationStressNotify");
+    }
+
+    private void sendNotification(int id, CharSequence text) {
+        // Create "typical" notification with random icon
+        Notification notification = new Notification(ICONS[mRandom.nextInt(ICONS.length)], text,
+                System.currentTimeMillis());
+        // Fill in arbitrary content
+        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com"));
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+        CharSequence title = text + " " + id;
+        CharSequence subtitle = String.valueOf(System.currentTimeMillis());
+        notification.setLatestEventInfo(mContext, title, subtitle, pendingIntent);
+        mNotificationManager.notify(id, notification);
+        SystemClock.sleep(10);
+    }
+}
diff --git a/docs/html/guide/developing/device.jd b/docs/html/guide/developing/device.jd
index 3127e10..cb5a0b6 100644
--- a/docs/html/guide/developing/device.jd
+++ b/docs/html/guide/developing/device.jd
@@ -145,6 +145,9 @@
     <td>Kyocera</td>
     <td><code>0482</code></td></tr>
   <tr>
+    <td>Lenevo</td>
+    <td><code>17EF</code></td></tr>
+  <tr>
     <td>LG</td>
     <td><code>1004</code></td></tr>
   <tr>
diff --git a/docs/html/sdk/oem-usb.jd b/docs/html/sdk/oem-usb.jd
index 27e742a..3c2ba8b 100644
--- a/docs/html/sdk/oem-usb.jd
+++ b/docs/html/sdk/oem-usb.jd
@@ -80,6 +80,12 @@
     <td><a href="http://www.kyocera-wireless.com/support/phone_drivers.htm">http://www.kyocera-wireless.com/support/phone_drivers.htm</a>
     </td>
   </tr>
+  <tr>
+    <td>Lenevo</td>
+    <td><a href="http://developer.lenovomm.com/developer/download.jsp"
+        >http://developer.lenovomm.com/developer/download.jsp</a>
+    </td>
+  </tr>
   <tr><td>LGE</td>	<td><a
 href="http://www.lg.com/us/mobile-phones/mobile-support/mobile-lg-mobile-phone-support.jsp">http://www.lg.com/us/mobile-phones/mobile-support/mobile-lg-mobile-phone-support.jsp</a></td>
 </tr><tr><td>Motorola</td>	<td><a
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index a9414e8..b0f7fd3 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -167,6 +167,7 @@
         }
         if (mCurrDrawable != null) {
             mCurrDrawable.jumpToCurrentState();
+            mCurrDrawable.setAlpha(mAlpha);
         }
         if (mExitAnimationEnd != 0) {
             mExitAnimationEnd = 0;
diff --git a/include/camera/Camera.h b/include/camera/Camera.h
index 7106bfa..f701280 100644
--- a/include/camera/Camera.h
+++ b/include/camera/Camera.h
@@ -18,9 +18,11 @@
 #define ANDROID_HARDWARE_CAMERA_H
 
 #include <utils/Timers.h>
-#include <camera/ICameraClient.h>
 #include <gui/ISurfaceTexture.h>
 #include <system/camera.h>
+#include <camera/ICameraClient.h>
+#include <camera/ICameraRecordingProxy.h>
+#include <camera/ICameraRecordingProxyListener.h>
 
 namespace android {
 
@@ -70,7 +72,7 @@
     static  status_t    getCameraInfo(int cameraId,
                                       struct CameraInfo* cameraInfo);
     static  sp<Camera>  connect(int cameraId);
-                        ~Camera();
+            virtual     ~Camera();
             void        init();
 
             status_t    reconnect();
@@ -129,8 +131,11 @@
             status_t    storeMetaDataInBuffers(bool enabled);
 
             void        setListener(const sp<CameraListener>& listener);
+            void        setRecordingProxyListener(const sp<ICameraRecordingProxyListener>& listener);
             void        setPreviewCallbackFlags(int preview_callback_flag);
 
+            sp<ICameraRecordingProxy> getRecordingProxy();
+
     // ICameraClient interface
     virtual void        notifyCallback(int32_t msgType, int32_t ext, int32_t ext2);
     virtual void        dataCallback(int32_t msgType, const sp<IMemory>& dataPtr);
@@ -138,6 +143,20 @@
 
     sp<ICamera>         remote();
 
+    class RecordingProxy : public BnCameraRecordingProxy
+    {
+    public:
+        RecordingProxy(const sp<Camera>& camera);
+
+        // ICameraRecordingProxy interface
+        virtual status_t startRecording(const sp<ICameraRecordingProxyListener>& listener);
+        virtual void stopRecording();
+        virtual void releaseRecordingFrame(const sp<IMemory>& mem);
+
+    private:
+        sp<Camera>         mCamera;
+    };
+
 private:
                         Camera();
                         Camera(const Camera&);
@@ -162,12 +181,12 @@
             status_t            mStatus;
 
             sp<CameraListener>  mListener;
+            sp<ICameraRecordingProxyListener>  mRecordingProxyListener;
 
             friend class DeathNotifier;
 
             static  Mutex               mLock;
             static  sp<ICameraService>  mCameraService;
-
 };
 
 }; // namespace android
diff --git a/include/camera/ICameraRecordingProxy.h b/include/camera/ICameraRecordingProxy.h
new file mode 100644
index 0000000..2aac284
--- /dev/null
+++ b/include/camera/ICameraRecordingProxy.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_ICAMERA_RECORDING_PROXY_H
+#define ANDROID_HARDWARE_ICAMERA_RECORDING_PROXY_H
+
+#include <binder/IInterface.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+class ICameraRecordingProxyListener;
+class IMemory;
+class Parcel;
+
+/*
+ * The purpose of ICameraRecordingProxy and ICameraRecordingProxyListener is to
+ * allow applications using the camera during recording.
+ *
+ * Camera service allows only one client at a time. Since camcorder application
+ * needs to own the camera to do things like zoom, the media recorder cannot
+ * access the camera directly during recording. So ICameraRecordingProxy is a
+ * proxy of ICamera, which allows the media recorder to start/stop the recording
+ * and release recording frames. ICameraRecordingProxyListener is an interface
+ * that allows the recorder to receive video frames during recording.
+ *
+ * ICameraRecordingProxy
+ *   startRecording()
+ *   stopRecording()
+ *   releaseRecordingFrame()
+ *
+ * ICameraRecordingProxyListener
+ *   dataCallbackTimestamp()
+
+ * The camcorder app opens the camera and starts the preview. The app passes
+ * ICamera and ICameraRecordingProxy to the media recorder by
+ * MediaRecorder::setCamera(). The recorder uses ICamera to setup the camera in
+ * MediaRecorder::start(). After setup, the recorder disconnects from camera
+ * service. The recorder calls ICameraRecordingProxy::startRecording() and
+ * passes a ICameraRecordingProxyListener to the app. The app connects back to
+ * camera service and starts the recording. The app owns the camera and can do
+ * things like zoom. The media recorder receives the video frames from the
+ * listener and releases them by ICameraRecordingProxy::releaseRecordingFrame.
+ * The recorder calls ICameraRecordingProxy::stopRecording() to stop the
+ * recording.
+ *
+ * The call sequences are as follows:
+ * 1. The app: Camera.unlock().
+ * 2. The app: MediaRecorder.setCamera().
+ * 3. Start recording
+ *    (1) The app: MediaRecorder.start().
+ *    (2) The recorder: ICamera.unlock() and ICamera.disconnect().
+ *    (3) The recorder: ICameraRecordingProxy.startRecording().
+ *    (4) The app: ICamera.reconnect().
+ *    (5) The app: ICamera.startRecording().
+ * 4. During recording
+ *    (1) The recorder: receive frames from ICameraRecordingProxyListener.dataCallbackTimestamp()
+ *    (2) The recorder: release frames by ICameraRecordingProxy.releaseRecordingFrame().
+ * 5. Stop recording
+ *    (1) The app: MediaRecorder.stop()
+ *    (2) The recorder: ICameraRecordingProxy.stopRecording().
+ *    (3) The app: ICamera.stopRecording().
+ */
+
+class ICameraRecordingProxy: public IInterface
+{
+public:
+    DECLARE_META_INTERFACE(CameraRecordingProxy);
+
+    virtual status_t        startRecording(const sp<ICameraRecordingProxyListener>& listener) = 0;
+    virtual void            stopRecording() = 0;
+    virtual void            releaseRecordingFrame(const sp<IMemory>& mem) = 0;
+};
+
+// ----------------------------------------------------------------------------
+
+class BnCameraRecordingProxy: public BnInterface<ICameraRecordingProxy>
+{
+public:
+    virtual status_t    onTransact( uint32_t code,
+                                    const Parcel& data,
+                                    Parcel* reply,
+                                    uint32_t flags = 0);
+};
+
+}; // namespace android
+
+#endif
diff --git a/include/camera/ICameraRecordingProxyListener.h b/include/camera/ICameraRecordingProxyListener.h
new file mode 100644
index 0000000..b6c0624
--- /dev/null
+++ b/include/camera/ICameraRecordingProxyListener.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_ICAMERA_RECORDING_PROXY_LISTENER_H
+#define ANDROID_HARDWARE_ICAMERA_RECORDING_PROXY_LISTENER_H
+
+#include <binder/IInterface.h>
+#include <stdint.h>
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+class Parcel;
+class IMemory;
+
+class ICameraRecordingProxyListener: public IInterface
+{
+public:
+    DECLARE_META_INTERFACE(CameraRecordingProxyListener);
+
+    virtual void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType,
+                                       const sp<IMemory>& data) = 0;
+};
+
+// ----------------------------------------------------------------------------
+
+class BnCameraRecordingProxyListener: public BnInterface<ICameraRecordingProxyListener>
+{
+public:
+    virtual status_t    onTransact( uint32_t code,
+                                    const Parcel& data,
+                                    Parcel* reply,
+                                    uint32_t flags = 0);
+};
+
+}; // namespace android
+
+#endif
diff --git a/include/media/IMediaRecorder.h b/include/media/IMediaRecorder.h
index 28be7c1..a73267d 100644
--- a/include/media/IMediaRecorder.h
+++ b/include/media/IMediaRecorder.h
@@ -24,6 +24,7 @@
 
 class Surface;
 class ICamera;
+class ICameraRecordingProxy;
 class IMediaRecorderClient;
 
 class IMediaRecorder: public IInterface
@@ -31,28 +32,29 @@
 public:
     DECLARE_META_INTERFACE(MediaRecorder);
 
-    virtual	status_t		setCamera(const sp<ICamera>& camera) = 0;
-    virtual	status_t		setPreviewSurface(const sp<Surface>& surface) = 0;
-    virtual	status_t		setVideoSource(int vs) = 0;
-    virtual	status_t		setAudioSource(int as) = 0;
-    virtual	status_t		setOutputFormat(int of) = 0;
-    virtual	status_t		setVideoEncoder(int ve) = 0;
-    virtual	status_t		setAudioEncoder(int ae) = 0;
-    virtual	status_t		setOutputFile(const char* path) = 0;
-    virtual	status_t		setOutputFile(int fd, int64_t offset, int64_t length) = 0;
-    virtual	status_t		setOutputFileAuxiliary(int fd) = 0;
-    virtual	status_t		setVideoSize(int width, int height) = 0;
-    virtual	status_t		setVideoFrameRate(int frames_per_second) = 0;
-    virtual     status_t                setParameters(const String8& params) = 0;
-    virtual     status_t                setListener(const sp<IMediaRecorderClient>& listener) = 0;
-    virtual	status_t		prepare() = 0;
-    virtual	status_t		getMaxAmplitude(int* max) = 0;
-    virtual	status_t		start() = 0;
-    virtual	status_t		stop() = 0;
-    virtual	status_t		reset() = 0;
-    virtual status_t        init() = 0;
-    virtual status_t        close() = 0;
-    virtual	status_t		release() = 0;
+    virtual status_t setCamera(const sp<ICamera>& camera,
+                               const sp<ICameraRecordingProxy>& proxy) = 0;
+    virtual status_t setPreviewSurface(const sp<Surface>& surface) = 0;
+    virtual status_t setVideoSource(int vs) = 0;
+    virtual status_t setAudioSource(int as) = 0;
+    virtual status_t setOutputFormat(int of) = 0;
+    virtual status_t setVideoEncoder(int ve) = 0;
+    virtual status_t setAudioEncoder(int ae) = 0;
+    virtual status_t setOutputFile(const char* path) = 0;
+    virtual status_t setOutputFile(int fd, int64_t offset, int64_t length) = 0;
+    virtual status_t setOutputFileAuxiliary(int fd) = 0;
+    virtual status_t setVideoSize(int width, int height) = 0;
+    virtual status_t setVideoFrameRate(int frames_per_second) = 0;
+    virtual status_t setParameters(const String8& params) = 0;
+    virtual status_t setListener(const sp<IMediaRecorderClient>& listener) = 0;
+    virtual status_t prepare() = 0;
+    virtual status_t getMaxAmplitude(int* max) = 0;
+    virtual status_t start() = 0;
+    virtual status_t stop() = 0;
+    virtual status_t reset() = 0;
+    virtual status_t init() = 0;
+    virtual status_t close() = 0;
+    virtual status_t release() = 0;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/include/media/MediaRecorderBase.h b/include/media/MediaRecorderBase.h
index 7e22a24..1c08969 100644
--- a/include/media/MediaRecorderBase.h
+++ b/include/media/MediaRecorderBase.h
@@ -24,6 +24,7 @@
 
 namespace android {
 
+class ICameraRecordingProxy;
 class Surface;
 
 struct MediaRecorderBase {
@@ -38,7 +39,8 @@
     virtual status_t setVideoEncoder(video_encoder ve) = 0;
     virtual status_t setVideoSize(int width, int height) = 0;
     virtual status_t setVideoFrameRate(int frames_per_second) = 0;
-    virtual status_t setCamera(const sp<ICamera>& camera) = 0;
+    virtual status_t setCamera(const sp<ICamera>& camera,
+                               const sp<ICameraRecordingProxy>& proxy) = 0;
     virtual status_t setPreviewSurface(const sp<Surface>& surface) = 0;
     virtual status_t setOutputFile(const char *path) = 0;
     virtual status_t setOutputFile(int fd, int64_t offset, int64_t length) = 0;
diff --git a/include/media/mediarecorder.h b/include/media/mediarecorder.h
index 36bf34e..af12d3c 100644
--- a/include/media/mediarecorder.h
+++ b/include/media/mediarecorder.h
@@ -30,6 +30,7 @@
 class Surface;
 class IMediaRecorder;
 class ICamera;
+class ICameraRecordingProxy;
 
 typedef void (*media_completion_f)(status_t status, void *cookie);
 
@@ -202,7 +203,7 @@
 
     void        died();
     status_t    initCheck();
-    status_t    setCamera(const sp<ICamera>& camera);
+    status_t    setCamera(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy);
     status_t    setPreviewSurface(const sp<Surface>& surface);
     status_t    setVideoSource(int vs);
     status_t    setAudioSource(int as);
diff --git a/include/media/stagefright/CameraSource.h b/include/media/stagefright/CameraSource.h
index bb25bae3..80b7c1c 100644
--- a/include/media/stagefright/CameraSource.h
+++ b/include/media/stagefright/CameraSource.h
@@ -21,6 +21,7 @@
 #include <media/stagefright/MediaBuffer.h>
 #include <media/stagefright/MediaSource.h>
 #include <camera/ICamera.h>
+#include <camera/ICameraRecordingProxyListener.h>
 #include <camera/CameraParameters.h>
 #include <utils/List.h>
 #include <utils/RefBase.h>
@@ -68,6 +69,7 @@
      * @return NULL on error.
      */
     static CameraSource *CreateFromCamera(const sp<ICamera> &camera,
+                                          const sp<ICameraRecordingProxy> &proxy,
                                           int32_t cameraId,
                                           Size videoSize,
                                           int32_t frameRate,
@@ -111,6 +113,23 @@
     virtual void signalBufferReturned(MediaBuffer* buffer);
 
 protected:
+    class ProxyListener: public BnCameraRecordingProxyListener {
+    public:
+        ProxyListener(const sp<CameraSource>& source);
+        virtual void dataCallbackTimestamp(int64_t timestampUs, int32_t msgType,
+                const sp<IMemory> &data);
+
+    private:
+        sp<CameraSource> mSource;
+    };
+
+    // isBinderAlive needs linkToDeath to work.
+    class DeathNotifier: public IBinder::DeathRecipient {
+    public:
+        DeathNotifier() {}
+        virtual void binderDied(const wp<IBinder>& who);
+    };
+
     enum CameraFlags {
         FLAGS_SET_CAMERA = 1L << 0,
         FLAGS_HOT_CAMERA = 1L << 1,
@@ -123,6 +142,8 @@
     status_t mInitCheck;
 
     sp<Camera>   mCamera;
+    sp<ICameraRecordingProxy>   mCameraRecordingProxy;
+    sp<DeathNotifier> mDeathNotifier;
     sp<Surface>  mSurface;
     sp<MetaData> mMeta;
 
@@ -132,7 +153,8 @@
     bool mStarted;
     int32_t mNumFramesEncoded;
 
-    CameraSource(const sp<ICamera>& camera, int32_t cameraId,
+    CameraSource(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy,
+                 int32_t cameraId,
                  Size videoSize, int32_t frameRate,
                  const sp<Surface>& surface,
                  bool storeMetaDataInVideoBuffers);
@@ -172,10 +194,12 @@
     void releaseOneRecordingFrame(const sp<IMemory>& frame);
 
 
-    status_t init(const sp<ICamera>& camera, int32_t cameraId,
-                Size videoSize, int32_t frameRate,
-                bool storeMetaDataInVideoBuffers);
-    status_t isCameraAvailable(const sp<ICamera>& camera, int32_t cameraId);
+    status_t init(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy,
+                  int32_t cameraId, Size videoSize, int32_t frameRate,
+                  bool storeMetaDataInVideoBuffers);
+    status_t isCameraAvailable(const sp<ICamera>& camera,
+                               const sp<ICameraRecordingProxy>& proxy,
+                               int32_t cameraId);
     status_t isCameraColorFormatSupported(const CameraParameters& params);
     status_t configureCamera(CameraParameters* params,
                     int32_t width, int32_t height,
diff --git a/include/media/stagefright/CameraSourceTimeLapse.h b/include/media/stagefright/CameraSourceTimeLapse.h
index 0e5d534..f07ebba 100644
--- a/include/media/stagefright/CameraSourceTimeLapse.h
+++ b/include/media/stagefright/CameraSourceTimeLapse.h
@@ -33,6 +33,7 @@
 public:
     static CameraSourceTimeLapse *CreateFromCamera(
         const sp<ICamera> &camera,
+        const sp<ICameraRecordingProxy> &proxy,
         int32_t cameraId,
         Size videoSize,
         int32_t videoFrameRate,
@@ -132,6 +133,7 @@
 
     CameraSourceTimeLapse(
         const sp<ICamera> &camera,
+        const sp<ICameraRecordingProxy> &proxy,
         int32_t cameraId,
         Size videoSize,
         int32_t videoFrameRate,
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index fd6c22c..ab4b9e0 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -71,6 +71,13 @@
     /** Data type for PKCS12. */
     public static final String PKCS12 = "PKCS12";
 
+    // historically used by Android
+    public static final String EXTENSION_CRT = ".crt";
+    public static final String EXTENSION_P12 = ".p12";
+    // commonly used on Windows
+    public static final String EXTENSION_CER = ".cer";
+    public static final String EXTENSION_PFX = ".pfx";
+
     /**
      * Convert objects to a PEM format, which is used for
      * CA_CERTIFICATE, USER_CERTIFICATE, and USER_PRIVATE_KEY
@@ -130,6 +137,15 @@
         return intent;
     }
 
+    public void install(Context context) {
+        try {
+            Intent intent = createInstallIntent();
+            context.startActivity(intent);
+        } catch (ActivityNotFoundException e) {
+            Log.w(LOGTAG, e.toString());
+        }
+    }
+
     public void install(Context context, KeyPair pair) {
         try {
             Intent intent = createInstallIntent();
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index 4f1596d..18011e6 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -22,6 +22,7 @@
 import android.accounts.AuthenticatorException;
 import android.accounts.OperationCanceledException;
 import android.app.Activity;
+import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -93,6 +94,26 @@
     public static final String EXTRA_RESPONSE = "response";
 
     /**
+     * @hide Also used by KeyChainActivity implementation
+     */
+    public static final String EXTRA_HOST = "host";
+
+    /**
+     * @hide Also used by KeyChainActivity implementation
+     */
+    public static final String EXTRA_PORT = "port";
+
+    /**
+     * @hide Also used by KeyChainActivity implementation
+     */
+    public static final String EXTRA_ALIAS = "alias";
+
+    /**
+     * @hide Also used by KeyChainActivity implementation
+     */
+    public static final String EXTRA_SENDER = "sender";
+
+    /**
      * Launches an {@code Activity} for the user to select the alias
      * for a private key and certificate pair for authentication. The
      * selected alias or null will be returned via the
@@ -106,6 +127,9 @@
      * <p>{@code host} and {@code port} may be used to give the user
      * more context about the server requesting the credentials.
      *
+     * <p>{@code alias} allows the chooser to preselect an existing
+     * alias which will still be subject to user confirmation.
+     *
      * <p>This method requires the caller to hold the permission
      * {@link android.Manifest.permission#USE_CREDENTIALS}.
      *
@@ -123,14 +147,17 @@
      *     certificate, or null if unavailable.
      * @param port The port number of the server requesting the
      *     certificate, or -1 if unavailable.
+     * @param alias The alias to preselect if available, or null if
+     *     unavailable.
      */
     public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response,
                                              String[] keyTypes, Principal[] issuers,
-                                             String host, int port) {
+                                             String host, int port,
+                                             String alias) {
         /*
-         * TODO currently keyTypes, issuers, host, and port are
-         * unused. They are meant to follow the semantics and purpose
-         * of X509KeyManager method arguments.
+         * TODO currently keyTypes, issuers are unused. They are meant
+         * to follow the semantics and purpose of X509KeyManager
+         * method arguments.
          *
          * keyTypes would allow the list to be filtered and typically
          * will be set correctly by the server. In practice today,
@@ -142,11 +169,6 @@
          * server. Others will send none. If this is used, if there
          * are no matches after applying the constraint, it should be
          * ignored.
-         *
-         * host and port may be shown to the user if available, but it
-         * should be clear that they are not validated values, perhaps
-         * shown along with requesting application identity to clarify
-         * the source of the request.
          */
         if (activity == null) {
             throw new NullPointerException("activity == null");
@@ -156,6 +178,11 @@
         }
         Intent intent = new Intent("com.android.keychain.CHOOSER");
         intent.putExtra(EXTRA_RESPONSE, new AliasResponse(activity, response));
+        intent.putExtra(EXTRA_HOST, host);
+        intent.putExtra(EXTRA_PORT, port);
+        intent.putExtra(EXTRA_ALIAS, alias);
+        // the PendingIntent is used to get calling package name
+        intent.putExtra(EXTRA_SENDER, PendingIntent.getActivity(activity, 0, new Intent(), 0));
         activity.startActivity(intent);
     }
 
diff --git a/libs/camera/Android.mk b/libs/camera/Android.mk
index b17b3d2..dc00957 100644
--- a/libs/camera/Android.mk
+++ b/libs/camera/Android.mk
@@ -6,7 +6,9 @@
 	CameraParameters.cpp \
 	ICamera.cpp \
 	ICameraClient.cpp \
-	ICameraService.cpp
+	ICameraService.cpp \
+	ICameraRecordingProxy.cpp \
+	ICameraRecordingProxyListener.cpp
 
 LOCAL_SHARED_LIBRARIES := \
 	libcutils \
diff --git a/libs/camera/Camera.cpp b/libs/camera/Camera.cpp
index 5eb48da..3c00db5 100644
--- a/libs/camera/Camera.cpp
+++ b/libs/camera/Camera.cpp
@@ -19,11 +19,12 @@
 #define LOG_TAG "Camera"
 #include <utils/Log.h>
 #include <utils/threads.h>
-
+#include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/IMemory.h>
 
 #include <camera/Camera.h>
+#include <camera/ICameraRecordingProxyListener.h>
 #include <camera/ICameraService.h>
 
 #include <surfaceflinger/Surface.h>
@@ -236,6 +237,10 @@
 void Camera::stopRecording()
 {
     LOGV("stopRecording");
+    {
+        Mutex::Autolock _l(mLock);
+        mRecordingProxyListener.clear();
+    }
     sp <ICamera> c = mCamera;
     if (c == 0) return;
     c->stopRecording();
@@ -327,6 +332,12 @@
     mListener = listener;
 }
 
+void Camera::setRecordingProxyListener(const sp<ICameraRecordingProxyListener>& listener)
+{
+    Mutex::Autolock _l(mLock);
+    mRecordingProxyListener = listener;
+}
+
 void Camera::setPreviewCallbackFlags(int flag)
 {
     LOGV("setPreviewCallbackFlags");
@@ -364,6 +375,19 @@
 // callback from camera service when timestamped frame is ready
 void Camera::dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr)
 {
+    // If recording proxy listener is registered, forward the frame and return.
+    // The other listener (mListener) is ignored because the receiver needs to
+    // call releaseRecordingFrame.
+    sp<ICameraRecordingProxyListener> proxylistener;
+    {
+        Mutex::Autolock _l(mLock);
+        proxylistener = mRecordingProxyListener;
+    }
+    if (proxylistener != NULL) {
+        proxylistener->dataCallbackTimestamp(timestamp, msgType, dataPtr);
+        return;
+    }
+
     sp<CameraListener> listener;
     {
         Mutex::Autolock _l(mLock);
@@ -389,4 +413,34 @@
     LOGW("Camera server died!");
 }
 
+sp<ICameraRecordingProxy> Camera::getRecordingProxy() {
+    LOGV("getProxy");
+    return new RecordingProxy(this);
+}
+
+status_t Camera::RecordingProxy::startRecording(const sp<ICameraRecordingProxyListener>& listener)
+{
+    LOGV("RecordingProxy::startRecording");
+    mCamera->setRecordingProxyListener(listener);
+    mCamera->reconnect();
+    return mCamera->startRecording();
+}
+
+void Camera::RecordingProxy::stopRecording()
+{
+    LOGV("RecordingProxy::stopRecording");
+    mCamera->stopRecording();
+}
+
+void Camera::RecordingProxy::releaseRecordingFrame(const sp<IMemory>& mem)
+{
+    LOGV("RecordingProxy::releaseRecordingFrame");
+    mCamera->releaseRecordingFrame(mem);
+}
+
+Camera::RecordingProxy::RecordingProxy(const sp<Camera>& camera)
+{
+    mCamera = camera;
+}
+
 }; // namespace android
diff --git a/libs/camera/ICameraRecordingProxy.cpp b/libs/camera/ICameraRecordingProxy.cpp
new file mode 100644
index 0000000..64b6a5c
--- /dev/null
+++ b/libs/camera/ICameraRecordingProxy.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "ICameraRecordingProxy"
+#include <camera/ICameraRecordingProxy.h>
+#include <camera/ICameraRecordingProxyListener.h>
+#include <binder/IMemory.h>
+#include <binder/Parcel.h>
+#include <stdint.h>
+#include <utils/Log.h>
+
+namespace android {
+
+enum {
+    START_RECORDING = IBinder::FIRST_CALL_TRANSACTION,
+    STOP_RECORDING,
+    RELEASE_RECORDING_FRAME,
+};
+
+
+class BpCameraRecordingProxy: public BpInterface<ICameraRecordingProxy>
+{
+public:
+    BpCameraRecordingProxy(const sp<IBinder>& impl)
+        : BpInterface<ICameraRecordingProxy>(impl)
+    {
+    }
+
+    status_t startRecording(const sp<ICameraRecordingProxyListener>& listener)
+    {
+        LOGV("startRecording");
+        Parcel data, reply;
+        data.writeInterfaceToken(ICameraRecordingProxy::getInterfaceDescriptor());
+        data.writeStrongBinder(listener->asBinder());
+        remote()->transact(START_RECORDING, data, &reply);
+        return reply.readInt32();
+    }
+
+    void stopRecording()
+    {
+        LOGV("stopRecording");
+        Parcel data, reply;
+        data.writeInterfaceToken(ICameraRecordingProxy::getInterfaceDescriptor());
+        remote()->transact(STOP_RECORDING, data, &reply);
+    }
+
+    void releaseRecordingFrame(const sp<IMemory>& mem)
+    {
+        LOGV("releaseRecordingFrame");
+        Parcel data, reply;
+        data.writeInterfaceToken(ICameraRecordingProxy::getInterfaceDescriptor());
+        data.writeStrongBinder(mem->asBinder());
+        remote()->transact(RELEASE_RECORDING_FRAME, data, &reply);
+    }
+};
+
+IMPLEMENT_META_INTERFACE(CameraRecordingProxy, "android.hardware.ICameraRecordingProxy");
+
+// ----------------------------------------------------------------------
+
+status_t BnCameraRecordingProxy::onTransact(
+    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
+{
+    switch(code) {
+        case START_RECORDING: {
+            LOGV("START_RECORDING");
+            CHECK_INTERFACE(ICameraRecordingProxy, data, reply);
+            sp<ICameraRecordingProxyListener> listener =
+                interface_cast<ICameraRecordingProxyListener>(data.readStrongBinder());
+            reply->writeInt32(startRecording(listener));
+            return NO_ERROR;
+        } break;
+        case STOP_RECORDING: {
+            LOGV("STOP_RECORDING");
+            CHECK_INTERFACE(ICameraRecordingProxy, data, reply);
+            stopRecording();
+            return NO_ERROR;
+        } break;
+        case RELEASE_RECORDING_FRAME: {
+            LOGV("RELEASE_RECORDING_FRAME");
+            CHECK_INTERFACE(ICameraRecordingProxy, data, reply);
+            sp<IMemory> mem = interface_cast<IMemory>(data.readStrongBinder());
+            releaseRecordingFrame(mem);
+            return NO_ERROR;
+        } break;
+
+        default:
+            return BBinder::onTransact(code, data, reply, flags);
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
diff --git a/libs/camera/ICameraRecordingProxyListener.cpp b/libs/camera/ICameraRecordingProxyListener.cpp
new file mode 100644
index 0000000..f8cece5
--- /dev/null
+++ b/libs/camera/ICameraRecordingProxyListener.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "ICameraRecordingProxyListener"
+#include <camera/ICameraRecordingProxyListener.h>
+#include <binder/IMemory.h>
+#include <binder/Parcel.h>
+#include <utils/Log.h>
+
+namespace android {
+
+enum {
+    DATA_CALLBACK_TIMESTAMP = IBinder::FIRST_CALL_TRANSACTION,
+};
+
+class BpCameraRecordingProxyListener: public BpInterface<ICameraRecordingProxyListener>
+{
+public:
+    BpCameraRecordingProxyListener(const sp<IBinder>& impl)
+        : BpInterface<ICameraRecordingProxyListener>(impl)
+    {
+    }
+
+    void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& imageData)
+    {
+        LOGV("dataCallback");
+        Parcel data, reply;
+        data.writeInterfaceToken(ICameraRecordingProxyListener::getInterfaceDescriptor());
+        data.writeInt64(timestamp);
+        data.writeInt32(msgType);
+        data.writeStrongBinder(imageData->asBinder());
+        remote()->transact(DATA_CALLBACK_TIMESTAMP, data, &reply, IBinder::FLAG_ONEWAY);
+    }
+};
+
+IMPLEMENT_META_INTERFACE(CameraRecordingProxyListener, "android.hardware.ICameraRecordingProxyListener");
+
+// ----------------------------------------------------------------------
+
+status_t BnCameraRecordingProxyListener::onTransact(
+    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
+{
+    switch(code) {
+        case DATA_CALLBACK_TIMESTAMP: {
+            LOGV("DATA_CALLBACK_TIMESTAMP");
+            CHECK_INTERFACE(ICameraRecordingProxyListener, data, reply);
+            nsecs_t timestamp = data.readInt64();
+            int32_t msgType = data.readInt32();
+            sp<IMemory> imageData = interface_cast<IMemory>(data.readStrongBinder());
+            dataCallbackTimestamp(timestamp, msgType, imageData);
+            return NO_ERROR;
+        } break;
+        default:
+            return BBinder::onTransact(code, data, reply, flags);
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index b0d8cf9..563d7e4 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -57,7 +57,7 @@
     LOGD("Enabling debug mode %d", mDebugLevel);
 
 #if RENDER_LAYERS_AS_REGIONS
-    LOGD("Layers will be composited as regions");
+    INIT_LOGD("Layers will be composited as regions");
 #endif
 }
 
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index dfb5d3b..e034a868 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -166,30 +166,6 @@
 // Layers management
 ///////////////////////////////////////////////////////////////////////////////
 
-Layer* LayerRenderer::createTextureLayer(bool isOpaque) {
-    LAYER_RENDERER_LOGD("Creating new texture layer");
-
-    Layer* layer = new Layer(0, 0);
-    layer->isCacheable = false;
-    layer->isTextureLayer = true;
-    layer->blend = !isOpaque;
-    layer->empty = true;
-    layer->fbo = 0;
-    layer->colorFilter = NULL;
-    layer->fbo = 0;
-    layer->layer.set(0.0f, 0.0f, 0.0f, 0.0f);
-    layer->texCoords.set(0.0f, 1.0f, 0.0f, 1.0f);
-    layer->alpha = 255;
-    layer->mode = SkXfermode::kSrcOver_Mode;
-    layer->colorFilter = NULL;
-    layer->region.clear();
-
-    glActiveTexture(GL_TEXTURE0);
-    glGenTextures(1, &layer->texture);
-
-    return layer;
-}
-
 Layer* LayerRenderer::createLayer(uint32_t width, uint32_t height, bool isOpaque) {
     LAYER_RENDERER_LOGD("Creating new layer %dx%d", width, height);
 
@@ -269,6 +245,41 @@
     return true;
 }
 
+static void setTextureParameters(Layer* layer) {
+    glBindTexture(layer->renderTarget, layer->texture);
+
+    glTexParameteri(layer->renderTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(layer->renderTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+    glTexParameteri(layer->renderTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(layer->renderTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+}
+
+Layer* LayerRenderer::createTextureLayer(bool isOpaque) {
+    LAYER_RENDERER_LOGD("Creating new texture layer");
+
+    Layer* layer = new Layer(0, 0);
+    layer->isCacheable = false;
+    layer->isTextureLayer = true;
+    layer->blend = !isOpaque;
+    layer->empty = true;
+    layer->fbo = 0;
+    layer->colorFilter = NULL;
+    layer->fbo = 0;
+    layer->layer.set(0.0f, 0.0f, 0.0f, 0.0f);
+    layer->texCoords.set(0.0f, 1.0f, 0.0f, 1.0f);
+    layer->alpha = 255;
+    layer->mode = SkXfermode::kSrcOver_Mode;
+    layer->colorFilter = NULL;
+    layer->region.clear();
+    layer->renderTarget = GL_NONE; // see ::updateTextureLayer()
+
+    glActiveTexture(GL_TEXTURE0);
+    glGenTextures(1, &layer->texture);
+
+    return layer;
+}
+
 void LayerRenderer::updateTextureLayer(Layer* layer, uint32_t width, uint32_t height,
         bool isOpaque, GLenum renderTarget, float* transform) {
     if (layer) {
@@ -279,15 +290,11 @@
         layer->region.set(width, height);
         layer->regionRect.set(0.0f, 0.0f, width, height);
         layer->texTransform.load(transform);
-        layer->renderTarget = renderTarget;
 
-        glBindTexture(layer->renderTarget, layer->texture);
-
-        glTexParameteri(layer->renderTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-        glTexParameteri(layer->renderTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
-        glTexParameteri(layer->renderTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-        glTexParameteri(layer->renderTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+        if (renderTarget != layer->renderTarget) {
+            layer->renderTarget = renderTarget;
+            setTextureParameters(layer);
+        }
     }
 }
 
diff --git a/libs/utils/BackupData.cpp b/libs/utils/BackupData.cpp
index f963058..87912639 100644
--- a/libs/utils/BackupData.cpp
+++ b/libs/utils/BackupData.cpp
@@ -285,7 +285,8 @@
             break;
         }
         default:
-            LOGD("Chunk header at %d has invalid type: 0x%08x", (int)m_pos, (int)m_header.type);
+            LOGD("Chunk header at %d has invalid type: 0x%08x",
+                    (int)(m_pos - sizeof(m_header)), (int)m_header.type);
             m_status = EINVAL;
     }
     
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index dd45111..6f42596 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -16,17 +16,16 @@
 
 package android.media;
 
-import android.media.CamcorderProfile;
 import android.hardware.Camera;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.util.Log;
 import android.view.Surface;
-import java.io.IOException;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
+
 import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.lang.ref.WeakReference;
 
 /**
@@ -112,7 +111,8 @@
     /**
      * Sets a Camera to use for recording. Use this function to switch
      * quickly between preview and capture mode without a teardown of
-     * the camera object. Must call before prepare().
+     * the camera object. {@link android.hardware.Camera#unlock()} should be
+     * called before this. Must call before prepare().
      *
      * @param c the Camera to use for recording
      */
@@ -718,6 +718,11 @@
      * Begins capturing and encoding data to the file specified with
      * setOutputFile(). Call this after prepare().
      *
+     * <p>Since API level 13, if applications set a camera via
+     * {@link #setCamera(Camera)}, the apps can use the camera after this method
+     * call. The apps do not need to lock the camera again. The apps should not
+     * start another recording session during recording.
+     *
      * @throws IllegalStateException if it is called before
      * prepare().
      */
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index 2f7d7ee..12391c8 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -158,7 +158,7 @@
     }
     sp<Camera> c = get_native_camera(env, camera, NULL);
     sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
-    process_media_recorder_call(env, mr->setCamera(c->remote()),
+    process_media_recorder_call(env, mr->setCamera(c->remote(), c->getRecordingProxy()),
             "java/lang/RuntimeException", "setCamera failed.");
 }
 
diff --git a/media/libmedia/IMediaRecorder.cpp b/media/libmedia/IMediaRecorder.cpp
index 59cd1b7..a44ef5a 100644
--- a/media/libmedia/IMediaRecorder.cpp
+++ b/media/libmedia/IMediaRecorder.cpp
@@ -60,12 +60,13 @@
     {
     }
 
-    status_t setCamera(const sp<ICamera>& camera)
+    status_t setCamera(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy)
     {
-        LOGV("setCamera(%p)", camera.get());
+        LOGV("setCamera(%p,%p)", camera.get(), proxy.get());
         Parcel data, reply;
         data.writeInterfaceToken(IMediaRecorder::getInterfaceDescriptor());
         data.writeStrongBinder(camera->asBinder());
+        data.writeStrongBinder(proxy->asBinder());
         remote()->transact(SET_CAMERA, data, &reply);
         return reply.readInt32();
     }
@@ -434,7 +435,9 @@
             LOGV("SET_CAMERA");
             CHECK_INTERFACE(IMediaRecorder, data, reply);
             sp<ICamera> camera = interface_cast<ICamera>(data.readStrongBinder());
-            reply->writeInt32(setCamera(camera));
+            sp<ICameraRecordingProxy> proxy =
+                interface_cast<ICameraRecordingProxy>(data.readStrongBinder());
+            reply->writeInt32(setCamera(camera, proxy));
             return NO_ERROR;
         } break;
         default:
diff --git a/media/libmedia/mediarecorder.cpp b/media/libmedia/mediarecorder.cpp
index 0100a17..9e4edd0 100644
--- a/media/libmedia/mediarecorder.cpp
+++ b/media/libmedia/mediarecorder.cpp
@@ -28,9 +28,9 @@
 
 namespace android {
 
-status_t MediaRecorder::setCamera(const sp<ICamera>& camera)
+status_t MediaRecorder::setCamera(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy)
 {
-    LOGV("setCamera(%p)", camera.get());
+    LOGV("setCamera(%p,%p)", camera.get(), proxy.get());
     if(mMediaRecorder == NULL) {
         LOGE("media recorder is not initialized yet");
         return INVALID_OPERATION;
@@ -40,7 +40,7 @@
         return INVALID_OPERATION;
     }
 
-    status_t ret = mMediaRecorder->setCamera(camera);
+    status_t ret = mMediaRecorder->setCamera(camera, proxy);
     if (OK != ret) {
         LOGV("setCamera failed: %d", ret);
         mCurrentState = MEDIA_RECORDER_ERROR;
diff --git a/media/libmediaplayerservice/MediaRecorderClient.cpp b/media/libmediaplayerservice/MediaRecorderClient.cpp
index 29cc019..115db1a 100644
--- a/media/libmediaplayerservice/MediaRecorderClient.cpp
+++ b/media/libmediaplayerservice/MediaRecorderClient.cpp
@@ -57,7 +57,8 @@
     return ok;
 }
 
-status_t MediaRecorderClient::setCamera(const sp<ICamera>& camera)
+status_t MediaRecorderClient::setCamera(const sp<ICamera>& camera,
+                                        const sp<ICameraRecordingProxy>& proxy)
 {
     LOGV("setCamera");
     Mutex::Autolock lock(mLock);
@@ -65,7 +66,7 @@
         LOGE("recorder is not initialized");
         return NO_INIT;
     }
-    return mRecorder->setCamera(camera);
+    return mRecorder->setCamera(camera, proxy);
 }
 
 status_t MediaRecorderClient::setPreviewSurface(const sp<Surface>& surface)
diff --git a/media/libmediaplayerservice/MediaRecorderClient.h b/media/libmediaplayerservice/MediaRecorderClient.h
index fded98e..bbca529 100644
--- a/media/libmediaplayerservice/MediaRecorderClient.h
+++ b/media/libmediaplayerservice/MediaRecorderClient.h
@@ -24,11 +24,13 @@
 
 class MediaRecorderBase;
 class MediaPlayerService;
+class ICameraRecordingProxy;
 
 class MediaRecorderClient : public BnMediaRecorder
 {
 public:
-    virtual     status_t        setCamera(const sp<ICamera>& camera);
+    virtual     status_t        setCamera(const sp<ICamera>& camera,
+                                          const sp<ICameraRecordingProxy>& proxy);
     virtual     status_t        setPreviewSurface(const sp<Surface>& surface);
     virtual     status_t        setVideoSource(int vs);
     virtual     status_t        setAudioSource(int as);
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index 978571c..b003476 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -198,14 +198,20 @@
     return OK;
 }
 
-status_t StagefrightRecorder::setCamera(const sp<ICamera> &camera) {
+status_t StagefrightRecorder::setCamera(const sp<ICamera> &camera,
+                                        const sp<ICameraRecordingProxy> &proxy) {
     LOGV("setCamera");
     if (camera == 0) {
         LOGE("camera is NULL");
         return BAD_VALUE;
     }
+    if (proxy == 0) {
+        LOGE("camera proxy is NULL");
+        return BAD_VALUE;
+    }
 
     mCamera = camera;
+    mCameraProxy = proxy;
     return OK;
 }
 
@@ -1235,15 +1241,17 @@
     videoSize.height = mVideoHeight;
     if (mCaptureTimeLapse) {
         mCameraSourceTimeLapse = CameraSourceTimeLapse::CreateFromCamera(
-                mCamera, mCameraId,
+                mCamera, mCameraProxy, mCameraId,
                 videoSize, mFrameRate, mPreviewSurface,
                 mTimeBetweenTimeLapseFrameCaptureUs);
         *cameraSource = mCameraSourceTimeLapse;
     } else {
         *cameraSource = CameraSource::CreateFromCamera(
-                mCamera, mCameraId, videoSize, mFrameRate,
+                mCamera, mCameraProxy, mCameraId, videoSize, mFrameRate,
                 mPreviewSurface, true /*storeMetaDataInVideoBuffers*/);
     }
+    mCamera.clear();
+    mCameraProxy.clear();
     if (*cameraSource == NULL) {
         return UNKNOWN_ERROR;
     }
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index aa67aa7..cb9c406 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -27,6 +27,7 @@
 namespace android {
 
 class Camera;
+class ICameraRecordingProxy;
 class CameraSource;
 class CameraSourceTimeLapse;
 class MediaSourceSplitter;
@@ -48,7 +49,7 @@
     virtual status_t setVideoEncoder(video_encoder ve);
     virtual status_t setVideoSize(int width, int height);
     virtual status_t setVideoFrameRate(int frames_per_second);
-    virtual status_t setCamera(const sp<ICamera>& camera);
+    virtual status_t setCamera(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy);
     virtual status_t setPreviewSurface(const sp<Surface>& surface);
     virtual status_t setOutputFile(const char *path);
     virtual status_t setOutputFile(int fd, int64_t offset, int64_t length);
@@ -66,6 +67,7 @@
 
 private:
     sp<ICamera> mCamera;
+    sp<ICameraRecordingProxy> mCameraProxy;
     sp<Surface> mPreviewSurface;
     sp<IMediaRecorderClient> mListener;
     sp<MediaWriter> mWriter, mWriterAux;
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index 61bb2a8..bdffce9 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -115,19 +115,20 @@
     size.height = -1;
 
     sp<ICamera> camera;
-    return new CameraSource(camera, 0, size, -1, NULL, false);
+    return new CameraSource(camera, NULL, 0, size, -1, NULL, false);
 }
 
 // static
 CameraSource *CameraSource::CreateFromCamera(
     const sp<ICamera>& camera,
+    const sp<ICameraRecordingProxy>& proxy,
     int32_t cameraId,
     Size videoSize,
     int32_t frameRate,
     const sp<Surface>& surface,
     bool storeMetaDataInVideoBuffers) {
 
-    CameraSource *source = new CameraSource(camera, cameraId,
+    CameraSource *source = new CameraSource(camera, proxy, cameraId,
                     videoSize, frameRate, surface,
                     storeMetaDataInVideoBuffers);
     return source;
@@ -135,6 +136,7 @@
 
 CameraSource::CameraSource(
     const sp<ICamera>& camera,
+    const sp<ICameraRecordingProxy>& proxy,
     int32_t cameraId,
     Size videoSize,
     int32_t frameRate,
@@ -153,11 +155,10 @@
       mNumGlitches(0),
       mGlitchDurationThresholdUs(200000),
       mCollectStats(false) {
-
     mVideoSize.width  = -1;
     mVideoSize.height = -1;
 
-    mInitCheck = init(camera, cameraId,
+    mInitCheck = init(camera, proxy, cameraId,
                     videoSize, frameRate,
                     storeMetaDataInVideoBuffers);
 }
@@ -167,24 +168,32 @@
 }
 
 status_t CameraSource::isCameraAvailable(
-    const sp<ICamera>& camera, int32_t cameraId) {
+    const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy,
+    int32_t cameraId) {
 
     if (camera == 0) {
         mCamera = Camera::connect(cameraId);
+        if (mCamera == 0) return -EBUSY;
+        // If proxy is not passed in by applications, still use the proxy of
+        // our own Camera to simplify the code.
+        mCameraRecordingProxy = mCamera->getRecordingProxy();
         mCameraFlags &= ~FLAGS_HOT_CAMERA;
     } else {
+        // We get the proxy from Camera, not ICamera. We need to get the proxy
+        // to the remote Camera owned by the application. Here mCamera is a
+        // local Camera object created by us. We cannot use the proxy from
+        // mCamera here.
         mCamera = Camera::create(camera);
+        if (mCamera == 0) return -EBUSY;
+        mCameraRecordingProxy = proxy;
         mCameraFlags |= FLAGS_HOT_CAMERA;
     }
 
-    // Is camera available?
-    if (mCamera == 0) {
-        LOGE("Camera connection could not be established.");
-        return -EBUSY;
-    }
-    if (!(mCameraFlags & FLAGS_HOT_CAMERA)) {
-        mCamera->lock();
-    }
+    mCamera->lock();
+    mDeathNotifier = new DeathNotifier();
+    // isBinderAlive needs linkToDeath to work.
+    mCameraRecordingProxy->asBinder()->linkToDeath(mDeathNotifier);
+
     return OK;
 }
 
@@ -447,6 +456,7 @@
  */
 status_t CameraSource::init(
         const sp<ICamera>& camera,
+        const sp<ICameraRecordingProxy>& proxy,
         int32_t cameraId,
         Size videoSize,
         int32_t frameRate,
@@ -455,7 +465,8 @@
     status_t err = OK;
     int64_t token = IPCThreadState::self()->clearCallingIdentity();
 
-    if ((err  = isCameraAvailable(camera, cameraId)) != OK) {
+    if ((err = isCameraAvailable(camera, proxy, cameraId)) != OK) {
+        LOGE("Camera connection could not be established.");
         return err;
     }
     CameraParameters params(mCamera->getParameters());
@@ -521,8 +532,14 @@
 }
 
 void CameraSource::startCameraRecording() {
-    CHECK_EQ(OK, mCamera->startRecording());
-    CHECK(mCamera->recordingEnabled());
+    // Reset the identity to the current thread because media server owns the
+    // camera and recording is started by the applications. The applications
+    // will connect to the camera in ICameraRecordingProxy::startRecording.
+    int64_t token = IPCThreadState::self()->clearCallingIdentity();
+    mCamera->unlock();
+    mCamera.clear();
+    IPCThreadState::self()->restoreCallingIdentity(token);
+    CHECK_EQ(OK, mCameraRecordingProxy->startRecording(new ProxyListener(this)));
 }
 
 status_t CameraSource::start(MetaData *meta) {
@@ -544,20 +561,14 @@
         mStartTimeUs = startTimeUs;
     }
 
-    // Call setListener first before calling startCameraRecording()
-    // to avoid recording frames being dropped.
-    int64_t token = IPCThreadState::self()->clearCallingIdentity();
-    mCamera->setListener(new CameraSourceListener(this));
     startCameraRecording();
-    IPCThreadState::self()->restoreCallingIdentity(token);
 
     mStarted = true;
     return OK;
 }
 
 void CameraSource::stopCameraRecording() {
-    mCamera->setListener(NULL);
-    mCamera->stopRecording();
+    mCameraRecordingProxy->stopRecording();
 }
 
 void CameraSource::releaseCamera() {
@@ -566,9 +577,9 @@
         LOGV("Camera was cold when we started, stopping preview");
         mCamera->stopPreview();
     }
-    mCamera->unlock();
     mCamera.clear();
-    mCamera = 0;
+    mCameraRecordingProxy->asBinder()->unlinkToDeath(mDeathNotifier);
+    mCameraRecordingProxy.clear();
     mCameraFlags = 0;
 }
 
@@ -578,7 +589,6 @@
     mStarted = false;
     mFrameAvailableCondition.signal();
 
-    int64_t token = IPCThreadState::self()->clearCallingIdentity();
     releaseQueuedFrames();
     while (!mFramesBeingEncoded.empty()) {
         if (NO_ERROR !=
@@ -589,7 +599,6 @@
     }
     stopCameraRecording();
     releaseCamera();
-    IPCThreadState::self()->restoreCallingIdentity(token);
 
     if (mCollectStats) {
         LOGI("Frames received/encoded/dropped: %d/%d/%d in %lld us",
@@ -607,8 +616,8 @@
 }
 
 void CameraSource::releaseRecordingFrame(const sp<IMemory>& frame) {
-    if (mCamera != NULL) {
-        mCamera->releaseRecordingFrame(frame);
+    if (mCameraRecordingProxy != NULL) {
+        mCameraRecordingProxy->releaseRecordingFrame(frame);
     }
 }
 
@@ -627,9 +636,7 @@
 }
 
 void CameraSource::releaseOneRecordingFrame(const sp<IMemory>& frame) {
-    int64_t token = IPCThreadState::self()->clearCallingIdentity();
     releaseRecordingFrame(frame);
-    IPCThreadState::self()->restoreCallingIdentity(token);
 }
 
 void CameraSource::signalBufferReturned(MediaBuffer *buffer) {
@@ -669,7 +676,11 @@
         Mutex::Autolock autoLock(mLock);
         while (mStarted && mFramesReceived.empty()) {
             if (NO_ERROR !=
-                mFrameAvailableCondition.waitRelative(mLock, 3000000000LL)) {
+                mFrameAvailableCondition.waitRelative(mLock, 1000000000LL)) {
+                if (!mCameraRecordingProxy->asBinder()->isBinderAlive()) {
+                    LOGW("camera recording proxy is gone");
+                    return ERROR_END_OF_STREAM;
+                }
                 LOGW("Timed out waiting for incoming camera video frames: %lld us",
                     mLastFrameTimestampUs);
             }
@@ -745,4 +756,17 @@
     return mIsMetaDataStoredInVideoBuffers;
 }
 
+CameraSource::ProxyListener::ProxyListener(const sp<CameraSource>& source) {
+    mSource = source;
+}
+
+void CameraSource::ProxyListener::dataCallbackTimestamp(
+        nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr) {
+    mSource->dataCallbackTimestamp(timestamp / 1000, msgType, dataPtr);
+}
+
+void CameraSource::DeathNotifier::binderDied(const wp<IBinder>& who) {
+    LOGI("Camera recording proxy died");
+}
+
 }  // namespace android
diff --git a/media/libstagefright/CameraSourceTimeLapse.cpp b/media/libstagefright/CameraSourceTimeLapse.cpp
index cc22574..fe78c46 100644
--- a/media/libstagefright/CameraSourceTimeLapse.cpp
+++ b/media/libstagefright/CameraSourceTimeLapse.cpp
@@ -39,6 +39,7 @@
 // static
 CameraSourceTimeLapse *CameraSourceTimeLapse::CreateFromCamera(
         const sp<ICamera> &camera,
+        const sp<ICameraRecordingProxy> &proxy,
         int32_t cameraId,
         Size videoSize,
         int32_t videoFrameRate,
@@ -46,7 +47,7 @@
         int64_t timeBetweenTimeLapseFrameCaptureUs) {
 
     CameraSourceTimeLapse *source = new
-            CameraSourceTimeLapse(camera, cameraId,
+            CameraSourceTimeLapse(camera, proxy, cameraId,
                 videoSize, videoFrameRate, surface,
                 timeBetweenTimeLapseFrameCaptureUs);
 
@@ -61,12 +62,13 @@
 
 CameraSourceTimeLapse::CameraSourceTimeLapse(
         const sp<ICamera>& camera,
+        const sp<ICameraRecordingProxy>& proxy,
         int32_t cameraId,
         Size videoSize,
         int32_t videoFrameRate,
         const sp<Surface>& surface,
         int64_t timeBetweenTimeLapseFrameCaptureUs)
-    : CameraSource(camera, cameraId, videoSize, videoFrameRate, surface, true),
+    : CameraSource(camera, proxy, cameraId, videoSize, videoFrameRate, surface, true),
       mTimeBetweenTimeLapseFrameCaptureUs(timeBetweenTimeLapseFrameCaptureUs),
       mTimeBetweenTimeLapseVideoFramesUs(1E6/videoFrameRate),
       mLastTimeLapseFrameRealTimestampUs(0),
@@ -315,7 +317,7 @@
         pthread_attr_destroy(&attr);
     } else {
         LOGV("start time lapse recording using video camera");
-        CHECK_EQ(OK, mCamera->startRecording());
+        CameraSource::startCameraRecording();
     }
 }
 
@@ -337,8 +339,7 @@
         // play the recording sound.
         mCamera->sendCommand(CAMERA_CMD_PLAY_RECORDING_SOUND, 0, 0);
     } else {
-        mCamera->setListener(NULL);
-        mCamera->stopRecording();
+        CameraSource::stopCameraRecording();
     }
     if (mLastReadBufferCopy) {
         mLastReadBufferCopy->release();
@@ -347,9 +348,8 @@
 }
 
 void CameraSourceTimeLapse::releaseRecordingFrame(const sp<IMemory>& frame) {
-    if (!mUseStillCameraForTimeLapse &&
-        mCamera != NULL) {
-        mCamera->releaseRecordingFrame(frame);
+    if (!mUseStillCameraForTimeLapse) {
+        CameraSource::releaseRecordingFrame(frame);
     }
 }
 
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 314425f..cd97302 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -4216,6 +4216,10 @@
                             mNode, OMX_IndexConfigCommonOutputCrop,
                             &rect, sizeof(rect));
 
+                CODEC_LOGI(
+                        "video dimensions are %ld x %ld",
+                        video_def->nFrameWidth, video_def->nFrameHeight);
+
                 if (err == OK) {
                     CHECK_GE(rect.nLeft, 0);
                     CHECK_GE(rect.nTop, 0);
@@ -4230,6 +4234,10 @@
                             rect.nTop,
                             rect.nLeft + rect.nWidth - 1,
                             rect.nTop + rect.nHeight - 1);
+
+                    CODEC_LOGI(
+                            "Crop rect is %ld x %ld @ (%ld, %ld)",
+                            rect.nWidth, rect.nHeight, rect.nLeft, rect.nTop);
                 } else {
                     mOutputFormat->setRect(
                             kKeyCropRect,
@@ -4238,7 +4246,6 @@
                             video_def->nFrameHeight - 1);
                 }
             }
-
             break;
         }
 
diff --git a/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp b/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp
index 830d2e0..ec7bd1c 100644
--- a/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp
+++ b/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp
@@ -139,7 +139,8 @@
 }
 
 status_t SoftAVC::initDecoder() {
-    if (H264SwDecInit(&mHandle, 1) == H264SWDEC_OK) {
+    // Force decoder to output buffers in display order.
+    if (H264SwDecInit(&mHandle, 0) == H264SWDEC_OK) {
         return OK;
     }
     return UNKNOWN_ERROR;
diff --git a/media/libstagefright/codecs/on2/h264dec/SoftAVC.h b/media/libstagefright/codecs/on2/h264dec/SoftAVC.h
index 3439efd..1cc85e8 100644
--- a/media/libstagefright/codecs/on2/h264dec/SoftAVC.h
+++ b/media/libstagefright/codecs/on2/h264dec/SoftAVC.h
@@ -52,7 +52,7 @@
         kInputPortIndex   = 0,
         kOutputPortIndex  = 1,
         kNumInputBuffers  = 8,
-        kNumOutputBuffers = 16,
+        kNumOutputBuffers = 2,
     };
 
     enum EOSStatus {
diff --git a/media/libstagefright/colorconversion/ColorConverter.cpp b/media/libstagefright/colorconversion/ColorConverter.cpp
index fd933cc..5cc3f78 100644
--- a/media/libstagefright/colorconversion/ColorConverter.cpp
+++ b/media/libstagefright/colorconversion/ColorConverter.cpp
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
+//#define LOG_NDEBUG 0
+#define LOG_TAG "ColorConverter"
+#include <utils/Log.h>
+
 #include <media/stagefright/ColorConverter.h>
 #include <media/stagefright/MediaDebug.h>
 #include <media/stagefright/MediaErrors.h>
@@ -424,61 +428,30 @@
 
 status_t ColorConverter::convertTIYUV420PackedSemiPlanar(
         const BitmapParams &src, const BitmapParams &dst) {
-
-/*
-The TIYUV420PackedSemiPlanar format is same as YUV420PackedSemiPlanar but with
-additional padding as shown in the diagram below. The padded width and padded
-height is different for different compression formats and it is read from the
-codec. In this color conversion routine, the padded resolution is obtained from
-src bitmap.
-
- ------------------------------------
-|                                    |
-|                                    |
-|      -------------------------     |
-|     |                         |    |
-|     |                         |    |
-|     |          Y DATA         |    |
-|     |                         |    |
-|     |                         |    |
-|     |                         |    |
-|      -------------------------     |
-|                                    |
-|      ------------                  |
-|     |            |                 |
-|     |            |                 |
-|     |  UV DATA   |                 |
-|     |            |                 |
-|     |            |                 |
-|     |            |                 |
-|      ------------                  |
-|                                    |
-|                                    |
- ------------------------------------
-*/
-
-    LOGV("src.mCropLeft = %d src.mCropTop =%d src.mWidth = %d src.mHeight = %d"
-        " dst.mWidth = %d dst.mHeight = %d", src.mCropLeft , src.mCropTop,
-        src.mWidth, src.mHeight, dst.mWidth, dst.mHeight);
-
-    size_t offset = (src.mWidth * src.mCropTop) + src.mCropLeft;
-
     uint8_t *kAdjustedClip = initClip();
 
-    uint32_t *dst_ptr = (uint32_t *)dst.mBits;
+    if (!((dst.mWidth & 3) == 0
+            && (src.mCropLeft & 1) == 0
+            && src.cropWidth() == dst.cropWidth()
+            && src.cropHeight() == dst.cropHeight())) {
+        return ERROR_UNSUPPORTED;
+    }
+
+    uint32_t *dst_ptr = (uint32_t *)dst.mBits
+        + (dst.mCropTop * dst.mWidth + dst.mCropLeft) / 2;
+
     const uint8_t *src_y = (const uint8_t *)src.mBits;
-    const uint8_t *src_u = (const uint8_t *)(src_y-offset) + (src.mWidth * src.mHeight);
-    src_u += ( ( src.mWidth * (src.mCropTop/2) ) + src.mCropLeft );
-    const uint8_t *src_v = src_u + 1;
 
-    for (size_t y = 0; y < dst.mHeight; ++y) {
-        for (size_t x = 0; x < dst.mWidth; x += 2) {
+    const uint8_t *src_u =
+        (const uint8_t *)src_y + src.mWidth * (src.mHeight - src.mCropTop / 2);
 
-            signed y1 = (signed)src_y[x] - 16;    //Y pixel
-            signed y2 = (signed)src_y[x + 1] - 16; //2nd Y pixel
+    for (size_t y = 0; y < src.cropHeight(); ++y) {
+        for (size_t x = 0; x < src.cropWidth(); x += 2) {
+            signed y1 = (signed)src_y[x] - 16;
+            signed y2 = (signed)src_y[x + 1] - 16;
 
-            signed u = (signed)src_u[x & ~1] - 128;   //U component
-            signed v = (signed)src_u[(x & ~1) + 1] - 128; //V component
+            signed u = (signed)src_u[x & ~1] - 128;
+            signed v = (signed)src_u[(x & ~1) + 1] - 128;
 
             signed u_b = u * 517;
             signed u_g = -u * 100;
@@ -502,19 +475,21 @@
 
             uint32_t rgb2 =
                 ((kAdjustedClip[r2] >> 3) << 11)
-                | ((kAdjustedClip[g1] >> 2) << 5)
-                | (kAdjustedClip[b1] >> 3);
+                | ((kAdjustedClip[g2] >> 2) << 5)
+                | (kAdjustedClip[b2] >> 3);
 
             dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
         }
 
-        src_y += src.mWidth; //increment Y-pixel line
-        if (y&1) {
-          src_u += src.mWidth; //increment U-V line
+        src_y += src.mWidth;
+
+        if (y & 1) {
+            src_u += src.mWidth;
         }
 
         dst_ptr += dst.mWidth / 2;
     }
+
     return OK;
 }
 
diff --git a/media/libstagefright/colorconversion/SoftwareRenderer.cpp b/media/libstagefright/colorconversion/SoftwareRenderer.cpp
index a4e8ee4..a4ca32d 100644
--- a/media/libstagefright/colorconversion/SoftwareRenderer.cpp
+++ b/media/libstagefright/colorconversion/SoftwareRenderer.cpp
@@ -50,6 +50,9 @@
         mCropBottom = mHeight - 1;
     }
 
+    mCropWidth = mCropRight - mCropLeft + 1;
+    mCropHeight = mCropBottom - mCropTop + 1;
+
     int32_t rotationDegrees;
     if (!meta->findInt32(kKeyRotation, &rotationDegrees)) {
         rotationDegrees = 0;
@@ -60,17 +63,18 @@
 
     switch (mColorFormat) {
         case OMX_COLOR_FormatYUV420Planar:
+        case OMX_TI_COLOR_FormatYUV420PackedSemiPlanar:
         {
             halFormat = HAL_PIXEL_FORMAT_YV12;
-            bufWidth = (mWidth + 1) & ~1;
-            bufHeight = (mHeight + 1) & ~1;
+            bufWidth = (mCropWidth + 1) & ~1;
+            bufHeight = (mCropHeight + 1) & ~1;
             break;
         }
 
         default:
             halFormat = HAL_PIXEL_FORMAT_RGB_565;
-            bufWidth = mWidth;
-            bufHeight = mHeight;
+            bufWidth = mCropWidth;
+            bufHeight = mCropHeight;
 
             mConverter = new ColorConverter(
                     mColorFormat, OMX_COLOR_Format16bitRGB565);
@@ -79,8 +83,8 @@
     }
 
     CHECK(mNativeWindow != NULL);
-    CHECK(mWidth > 0);
-    CHECK(mHeight > 0);
+    CHECK(mCropWidth > 0);
+    CHECK(mCropHeight > 0);
     CHECK(mConverter == NULL || mConverter->isValid());
 
     CHECK_EQ(0,
@@ -109,14 +113,6 @@
         CHECK_EQ(0, native_window_set_buffers_transform(
                     mNativeWindow.get(), transform));
     }
-
-    android_native_rect_t crop;
-    crop.left = mCropLeft;
-    crop.top = mCropTop;
-    crop.right = mCropRight + 1;
-    crop.bottom = mCropBottom + 1;
-
-    CHECK_EQ(0, native_window_set_crop(mNativeWindow.get(), &crop));
 }
 
 SoftwareRenderer::~SoftwareRenderer() {
@@ -142,7 +138,7 @@
 
     GraphicBufferMapper &mapper = GraphicBufferMapper::get();
 
-    Rect bounds(mWidth, mHeight);
+    Rect bounds(mCropWidth, mCropHeight);
 
     void *dst;
     CHECK_EQ(0, mapper.lock(
@@ -152,13 +148,11 @@
         mConverter->convert(
                 data,
                 mWidth, mHeight,
-                0, 0, mWidth - 1, mHeight - 1,
+                mCropLeft, mCropTop, mCropRight, mCropBottom,
                 dst,
                 buf->stride, buf->height,
-                0, 0, mWidth - 1, mHeight - 1);
-    } else {
-        CHECK_EQ(mColorFormat, OMX_COLOR_FormatYUV420Planar);
-
+                0, 0, mCropWidth - 1, mCropHeight - 1);
+    } else if (mColorFormat == OMX_COLOR_FormatYUV420Planar) {
         const uint8_t *src_y = (const uint8_t *)data;
         const uint8_t *src_u = (const uint8_t *)data + mWidth * mHeight;
         const uint8_t *src_v = src_u + (mWidth / 2 * mHeight / 2);
@@ -170,22 +164,57 @@
         uint8_t *dst_v = dst_y + dst_y_size;
         uint8_t *dst_u = dst_v + dst_c_size;
 
-        for (int y = 0; y < mHeight; ++y) {
-            memcpy(dst_y, src_y, mWidth);
+        for (int y = 0; y < mCropHeight; ++y) {
+            memcpy(dst_y, src_y, mCropWidth);
 
             src_y += mWidth;
             dst_y += buf->stride;
         }
 
-        for (int y = 0; y < (mHeight + 1) / 2; ++y) {
-            memcpy(dst_u, src_u, (mWidth + 1) / 2);
-            memcpy(dst_v, src_v, (mWidth + 1) / 2);
+        for (int y = 0; y < (mCropHeight + 1) / 2; ++y) {
+            memcpy(dst_u, src_u, (mCropWidth + 1) / 2);
+            memcpy(dst_v, src_v, (mCropWidth + 1) / 2);
 
             src_u += mWidth / 2;
             src_v += mWidth / 2;
             dst_u += dst_c_stride;
             dst_v += dst_c_stride;
         }
+    } else {
+        CHECK_EQ(mColorFormat, OMX_TI_COLOR_FormatYUV420PackedSemiPlanar);
+
+        const uint8_t *src_y =
+            (const uint8_t *)data;
+
+        const uint8_t *src_uv =
+            (const uint8_t *)data + mWidth * (mHeight - mCropTop / 2);
+
+        uint8_t *dst_y = (uint8_t *)dst;
+
+        size_t dst_y_size = buf->stride * buf->height;
+        size_t dst_c_stride = ALIGN(buf->stride / 2, 16);
+        size_t dst_c_size = dst_c_stride * buf->height / 2;
+        uint8_t *dst_v = dst_y + dst_y_size;
+        uint8_t *dst_u = dst_v + dst_c_size;
+
+        for (int y = 0; y < mCropHeight; ++y) {
+            memcpy(dst_y, src_y, mCropWidth);
+
+            src_y += mWidth;
+            dst_y += buf->stride;
+        }
+
+        for (int y = 0; y < (mCropHeight + 1) / 2; ++y) {
+            size_t tmp = (mCropWidth + 1) / 2;
+            for (size_t x = 0; x < tmp; ++x) {
+                dst_u[x] = src_uv[2 * x];
+                dst_v[x] = src_uv[2 * x + 1];
+            }
+
+            src_uv += mWidth;
+            dst_u += dst_c_stride;
+            dst_v += dst_c_stride;
+        }
     }
 
     CHECK_EQ(0, mapper.unlock(buf->handle));
diff --git a/media/libstagefright/include/SoftwareRenderer.h b/media/libstagefright/include/SoftwareRenderer.h
index 78037b97..8f2ea95 100644
--- a/media/libstagefright/include/SoftwareRenderer.h
+++ b/media/libstagefright/include/SoftwareRenderer.h
@@ -47,6 +47,7 @@
     sp<ANativeWindow> mNativeWindow;
     int32_t mWidth, mHeight;
     int32_t mCropLeft, mCropTop, mCropRight, mCropBottom;
+    int32_t mCropWidth, mCropHeight;
 
     SoftwareRenderer(const SoftwareRenderer &);
     SoftwareRenderer &operator=(const SoftwareRenderer &);
diff --git a/media/libstagefright/omx/tests/OMXHarness.cpp b/media/libstagefright/omx/tests/OMXHarness.cpp
index a404f1f..d4354db 100644
--- a/media/libstagefright/omx/tests/OMXHarness.cpp
+++ b/media/libstagefright/omx/tests/OMXHarness.cpp
@@ -485,24 +485,20 @@
     };
     static const MimeToURL kMimeToURL[] = {
         { "video/avc",
-          "file:///sdcard/media_api/video/H264_AAC.3gp" },
-        { "video/mp4v-es", "file:///sdcard/media_api/video/gingerkids.MP4" },
+          "file:///sdcard/media_api/video/H264_500_AAC_128.3gp" },
+        { "video/mp4v-es", "file:///sdcard/media_api/video/MPEG4_320_AAC_64.mp4" },
         { "video/3gpp",
           "file:///sdcard/media_api/video/H263_500_AMRNB_12.3gp" },
         { "audio/3gpp",
           "file:///sdcard/media_api/video/H263_500_AMRNB_12.3gp" },
-        { "audio/amr-wb",
-          "file:///sdcard/media_api/music/"
-          "AI_AMR-WB_12.65kbps(13kbps)_16khz_mono_NMC.awb" },
+        { "audio/amr-wb", NULL },
         { "audio/mp4a-latm",
-          "file:///sdcard/media_api/video/H264_AAC.3gp" },
+          "file:///sdcard/media_api/video/H263_56_AAC_24.3gp" },
         { "audio/mpeg",
-          "file:///sdcard/media_api/music/MP3CBR.mp3" },
-        { "audio/vorbis",
-          "file:///sdcard/media_api/metaDataTestMedias/OGG/"
-          "When You Say Nothing At All.ogg" },
+          "file:///sdcard/media_api/music/MP3_48KHz_128kbps_s_1_17_CBR.mp3" },
+        { "audio/vorbis", NULL },
         { "video/x-vnd.on2.vp8",
-          "file:///sdcard/media_api/webm/big-buck-bunny_trailer.webm" },
+          "file:///sdcard/media_api/video/big-buck-bunny_trailer.webm" },
         { MEDIA_MIMETYPE_AUDIO_G711_ALAW, "file:///sdcard/M1F1-Alaw-AFsp.wav" },
         { MEDIA_MIMETYPE_AUDIO_G711_MLAW,
           "file:///sdcard/M1F1-mulaw-AFsp.wav" },
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 0c4ef7d..a9aa31b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -589,7 +589,12 @@
     private void restoreWifiSupplicant(String filename, BackupDataInput data) {
         byte[] bytes = new byte[data.getDataSize()];
         if (bytes.length <= 0) return;
-        restoreWifiSupplicant(filename, bytes, bytes.length);
+        try {
+            data.readEntityData(bytes, 0, data.getDataSize());
+            restoreWifiSupplicant(filename, bytes, bytes.length);
+        } catch (IOException e) {
+            Log.w(TAG, "Unable to read supplicant data");
+        }
     }
 
     private void restoreWifiSupplicant(String filename, byte[] bytes, int size) {
diff --git a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java b/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java
index cd79b60..6573286 100644
--- a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java
@@ -183,8 +183,7 @@
 
         mStatusView = new StatusView(this, mUpdateMonitor, mLockPatternUtils);
         // This shows up when no other information is required on status1
-        mStatusView.setHelpMessage(R.string.lockscreen_pattern_instructions,
-                StatusView.LOCK_ICON);
+        //mStatusView.setHelpMessage(R.string.lockscreen_pattern_instructions,StatusView.LOCK_ICON);
 
         mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern);
 
diff --git a/policy/src/com/android/internal/policy/impl/StatusView.java b/policy/src/com/android/internal/policy/impl/StatusView.java
index 46ce5a3..79f81ff 100644
--- a/policy/src/com/android/internal/policy/impl/StatusView.java
+++ b/policy/src/com/android/internal/policy/impl/StatusView.java
@@ -40,7 +40,7 @@
 
     private String mInstructions = null;
     private TextView mStatus1;
-    private TextView mPropertyOf;
+    private TextView mOwnerInfo;
 
     private boolean mHasCarrier;
     private boolean mHasDate;
@@ -105,7 +105,7 @@
         mStatus1 = (TextView) findViewById(R.id.status1);
         mAlarmStatus = (TextView) findViewById(R.id.alarm_status);
         mAlarmStatus.setCompoundDrawablesWithIntrinsicBounds(ALARM_ICON, 0, 0, 0);
-        mPropertyOf = (TextView) findViewById(R.id.propertyOf);
+        mOwnerInfo = (TextView) findViewById(R.id.propertyOf);
 
         resetStatusInfo(updateMonitor, lockPatternUtils);
 
@@ -153,20 +153,22 @@
     void updateStatusLines(boolean showStatusLines) {
         if (!showStatusLines) {
             mStatus1.setVisibility(showStatusLines ? View.VISIBLE : View.INVISIBLE);
-            mAlarmStatus.setVisibility(showStatusLines ? View.VISIBLE : View.INVISIBLE);
+            mAlarmStatus.setVisibility(showStatusLines ? View.VISIBLE : View.GONE);
             return;
         }
 
         // Update owner info
-        if (mPropertyOf != null) {
-            ContentResolver res = getContext().getContentResolver();
-            String info = Settings.Secure.getString(res, Settings.Secure.LOCK_SCREEN_OWNER_INFO);
-            boolean enabled = Settings.Secure.getInt(res,
-                    Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 1) != 0;
-
-            mPropertyOf.setText(info);
-            mPropertyOf.setVisibility(enabled && !TextUtils.isEmpty(info) ?
-                    View.VISIBLE : View.INVISIBLE);
+        final ContentResolver res = getContext().getContentResolver();
+        final boolean ownerInfoEnabled = Settings.Secure.getInt(res,
+                Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 1) != 0;
+        String ownerInfo = null;
+        if (ownerInfoEnabled) {
+            ownerInfo = Settings.Secure.getString(res, Settings.Secure.LOCK_SCREEN_OWNER_INFO);
+            if (mOwnerInfo != null) {
+                mOwnerInfo.setText(ownerInfo);
+                mOwnerInfo.setVisibility(ownerInfoEnabled && !TextUtils.isEmpty(ownerInfo) ?
+                        View.VISIBLE : View.INVISIBLE);
+            }
         }
 
         // Update Alarm status
@@ -175,7 +177,7 @@
             mAlarmStatus.setText(nextAlarm);
             mAlarmStatus.setVisibility(View.VISIBLE);
         } else {
-            mAlarmStatus.setVisibility(View.INVISIBLE);
+            mAlarmStatus.setVisibility(View.GONE);
         }
 
         // Update Status1
@@ -204,15 +206,16 @@
             } else {
                 mStatus1.setVisibility(View.INVISIBLE);
             }
+        } else if (mHelpMessageId != 0) {
+            mStatus1.setText(mHelpMessageId);
+            mStatus1.setCompoundDrawablesWithIntrinsicBounds(mHelpIconId, 0,0, 0);
+            mStatus1.setVisibility(View.VISIBLE);
+        } else if (ownerInfoEnabled && mOwnerInfo == null && ownerInfo != null) {
+            mStatus1.setText(ownerInfo);
+            mStatus1.setCompoundDrawablesWithIntrinsicBounds(0, 0,0, 0);
+            mStatus1.setVisibility(View.VISIBLE);
         } else {
-            // nothing specific to show; show help message and icon, if provided
-            if (mHelpMessageId != 0) {
-                mStatus1.setText(mHelpMessageId);
-                mStatus1.setCompoundDrawablesWithIntrinsicBounds(mHelpIconId, 0,0, 0);
-                mStatus1.setVisibility(View.VISIBLE);
-            } else {
-                mStatus1.setVisibility(View.INVISIBLE);
-            }
+            mStatus1.setVisibility(View.INVISIBLE);
         }
     }
 
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index a011ae2..9b09983 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -420,6 +420,10 @@
     // allow anyone to use camera (after they lock the camera)
     status_t result = checkPid();
     if (result == NO_ERROR) {
+        if (mHardware->recordingEnabled()) {
+            LOGE("Not allowed to unlock camera during recording.");
+            return INVALID_OPERATION;
+        }
         mClientPid = 0;
         LOG1("clear mCameraClient (pid %d)", callingPid);
         // we need to remove the reference to ICameraClient so that when the app
@@ -756,6 +760,11 @@
     status_t result = checkPidAndHardware();
     if (result != NO_ERROR) return result;
 
+    if (mHardware->recordingEnabled()) {
+        LOGE("Cannot take picture during recording.");
+        return INVALID_OPERATION;
+    }
+
     if ((msgType & CAMERA_MSG_RAW_IMAGE) &&
         (msgType & CAMERA_MSG_RAW_IMAGE_NOTIFY)) {
         LOGE("CAMERA_MSG_RAW_IMAGE and CAMERA_MSG_RAW_IMAGE_NOTIFY"
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index e6f443a..8ac8f2b 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -131,8 +131,6 @@
      */
     private List mNetRequestersPids[];
 
-    private WifiWatchdogService mWifiWatchdogService;
-
     // priority order of the nettrackers
     // (excluding dynamically set mNetworkPreference)
     // TODO - move mNetworkTypePreference into this
@@ -278,6 +276,9 @@
     }
     RadioAttributes[] mRadioAttributes;
 
+    // the set of network types that can only be enabled by system/sig apps
+    List mProtectedNetworks;
+
     public ConnectivityService(
             Context context, INetworkManagementService netd, INetworkPolicyManager policyManager) {
         if (DBG) log("ConnectivityService starting up");
@@ -381,6 +382,17 @@
             }
         }
 
+        mProtectedNetworks = new ArrayList<Integer>();
+        int[] protectedNetworks = context.getResources().getIntArray(
+                com.android.internal.R.array.config_protectedNetworks);
+        for (int p : protectedNetworks) {
+            if ((mNetConfigs[p] != null) && (mProtectedNetworks.contains(p) == false)) {
+                mProtectedNetworks.add(p);
+            } else {
+                if (DBG) loge("Ignoring protectedNetwork " + p);
+            }
+        }
+
         // high priority first
         mPriorityList = new int[mNetworksDefined];
         {
@@ -432,10 +444,6 @@
                 wifiService.checkAndStartWifi();
                 mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst;
                 wst.startMonitoring(context, mHandler);
-
-                //TODO: as part of WWS refactor, create only when needed
-                mWifiWatchdogService = new WifiWatchdogService(context);
-
                 break;
             case ConnectivityManager.TYPE_MOBILE:
                 mNetTrackers[netType] = new MobileDataStateTracker(netType,
@@ -802,6 +810,11 @@
                 usedNetworkType = networkType;
             }
         }
+
+        if (mProtectedNetworks.contains(usedNetworkType)) {
+            enforceConnectivityInternalPermission();
+        }
+
         NetworkStateTracker network = mNetTrackers[usedNetworkType];
         if (network != null) {
             Integer currentPid = new Integer(getCallingPid());
@@ -1012,6 +1025,10 @@
      */
     public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress) {
         enforceChangePermission();
+        if (mProtectedNetworks.contains(networkType)) {
+            enforceConnectivityInternalPermission();
+        }
+
         if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
             return false;
         }
@@ -1129,7 +1146,8 @@
     }
 
     public void setDataDependency(int networkType, boolean met) {
-        enforceChangePermission();
+        enforceConnectivityInternalPermission();
+
         if (DBG) {
             log("setDataDependency(" + networkType + ", " + met + ")");
         }
diff --git a/services/java/com/android/server/DnsPinger.java b/services/java/com/android/server/DnsPinger.java
new file mode 100644
index 0000000..5cfda7e
--- /dev/null
+++ b/services/java/com/android/server/DnsPinger.java
@@ -0,0 +1,188 @@
+/*
+ * 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.server;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.SocketTimeoutException;
+import java.util.Collection;
+import java.util.Random;
+
+/**
+ * Performs a simple DNS "ping" by sending a "server status" query packet to the
+ * DNS server. As long as the server replies, we consider it a success.
+ * <p>
+ * We do not use a simple hostname lookup because that could be cached and the
+ * API may not differentiate between a time out and a failure lookup (which we
+ * really care about).
+ * <p>
+ * TODO : More general API.  Wifi is currently hard coded
+ * TODO : Choice of DNS query location - current looks up www.android.com
+ *
+ * @hide
+ */
+public final class DnsPinger {
+    private static final boolean V = true;
+
+    /** Number of bytes for the query */
+    private static final int DNS_QUERY_BASE_SIZE = 33;
+
+    /** The DNS port */
+    private static final int DNS_PORT = 53;
+
+    /** Used to generate IDs */
+    private static Random sRandom = new Random();
+
+    private ConnectivityManager mConnectivityManager = null;
+    private ContentResolver mContentResolver;
+    private Context mContext;
+
+    private String TAG;
+
+    public DnsPinger(String TAG, Context context) {
+        mContext = context;
+        mContentResolver = context.getContentResolver();
+        this.TAG = TAG;
+    }
+
+    /**
+     * Gets the first DNS of the current Wifi AP.
+     * @return The first DNS of the current AP.
+     */
+    public InetAddress getDns() {
+        if (mConnectivityManager == null) {
+            mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
+                    Context.CONNECTIVITY_SERVICE);
+        }
+
+        LinkProperties linkProperties = mConnectivityManager.getLinkProperties(
+                ConnectivityManager.TYPE_WIFI);
+        if (linkProperties == null)
+            return null;
+
+        Collection<InetAddress> dnses = linkProperties.getDnses();
+        if (dnses == null || dnses.size() == 0)
+            return null;
+
+        return dnses.iterator().next();
+    }
+
+    /**
+     * @return time to response. Negative value on error.
+     */
+    public long pingDns(InetAddress dnsAddress, int timeout) {
+        DatagramSocket socket = null;
+        try {
+            socket = new DatagramSocket();
+
+            // Set some socket properties
+            socket.setSoTimeout(timeout);
+
+            byte[] buf = new byte[DNS_QUERY_BASE_SIZE];
+            fillQuery(buf);
+
+            // Send the DNS query
+
+            DatagramPacket packet = new DatagramPacket(buf,
+                    buf.length, dnsAddress, DNS_PORT);
+            long start = SystemClock.elapsedRealtime();
+            socket.send(packet);
+
+            // Wait for reply (blocks for the above timeout)
+            DatagramPacket replyPacket = new DatagramPacket(buf, buf.length);
+            socket.receive(replyPacket);
+
+            // If a timeout occurred, an exception would have been thrown. We
+            // got a reply!
+            return SystemClock.elapsedRealtime() - start;
+
+        } catch (SocketTimeoutException e) {
+            // Squelch this exception.
+            return -1;
+        } catch (Exception e) {
+            if (V) {
+                Slog.v(TAG, "DnsPinger.pingDns got socket exception: ", e);
+            }
+            return -2;
+        } finally {
+            if (socket != null) {
+                socket.close();
+            }
+        }
+
+    }
+
+    private static void fillQuery(byte[] buf) {
+
+        /*
+         * See RFC2929 (though the bit tables in there are misleading for us.
+         * For example, the recursion desired bit is the 0th bit for us, but
+         * looking there it would appear as the 7th bit of the byte
+         */
+
+        // Make sure it's all zeroed out
+        for (int i = 0; i < buf.length; i++)
+            buf[i] = 0;
+
+        // Form a query for www.android.com
+
+        // [0-1] bytes are an ID, generate random ID for this query
+        buf[0] = (byte) sRandom.nextInt(256);
+        buf[1] = (byte) sRandom.nextInt(256);
+
+        // [2-3] bytes are for flags.
+        buf[2] = 1; // Recursion desired
+
+        // [4-5] bytes are for the query count
+        buf[5] = 1; // One query
+
+        // [6-7] [8-9] [10-11] are all counts of other fields we don't use
+
+        // [12-15] for www
+        writeString(buf, 12, "www");
+
+        // [16-23] for android
+        writeString(buf, 16, "android");
+
+        // [24-27] for com
+        writeString(buf, 24, "com");
+
+        // [29-30] bytes are for QTYPE, set to 1
+        buf[30] = 1;
+
+        // [31-32] bytes are for QCLASS, set to 1
+        buf[32] = 1;
+    }
+
+    private static void writeString(byte[] buf, int startPos, String string) {
+        int pos = startPos;
+
+        // Write the length first
+        buf[pos++] = (byte) string.length();
+        for (int i = 0; i < string.length(); i++) {
+            buf[pos++] = (byte) string.charAt(i);
+        }
+    }
+}
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 2b01c5e..e730d0d 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -141,12 +141,26 @@
     }
 
     /**
-     * Notify our observers of an interface link status change
+     * Notify our observers of an interface status change
      */
-    private void notifyInterfaceLinkStatusChanged(String iface, boolean link) {
+    private void notifyInterfaceStatusChanged(String iface, boolean up) {
         for (INetworkManagementEventObserver obs : mObservers) {
             try {
-                obs.interfaceLinkStatusChanged(iface, link);
+                obs.interfaceStatusChanged(iface, up);
+            } catch (Exception ex) {
+                Slog.w(TAG, "Observer notifier failed", ex);
+            }
+        }
+    }
+
+    /**
+     * Notify our observers of an interface link state change
+     * (typically, an Ethernet cable has been plugged-in or unplugged).
+     */
+    private void notifyInterfaceLinkStateChanged(String iface, boolean up) {
+        for (INetworkManagementEventObserver obs : mObservers) {
+            try {
+                obs.interfaceLinkStateChanged(iface, up);
             } catch (Exception ex) {
                 Slog.w(TAG, "Observer notifier failed", ex);
             }
@@ -207,6 +221,7 @@
                  * Format: "NNN Iface added <name>"
                  *         "NNN Iface removed <name>"
                  *         "NNN Iface changed <name> <up/down>"
+                 *         "NNN Iface linkstatus <name> <up/down>"
                  */
                 if (cooked.length < 4 || !cooked[1].equals("Iface")) {
                     throw new IllegalStateException(
@@ -219,7 +234,10 @@
                     notifyInterfaceRemoved(cooked[3]);
                     return true;
                 } else if (cooked[2].equals("changed") && cooked.length == 5) {
-                    notifyInterfaceLinkStatusChanged(cooked[3], cooked[4].equals("up"));
+                    notifyInterfaceStatusChanged(cooked[3], cooked[4].equals("up"));
+                    return true;
+                } else if (cooked[2].equals("linkstate") && cooked.length == 5) {
+                    notifyInterfaceLinkStateChanged(cooked[3], cooked[4].equals("up"));
                     return true;
                 }
                 throw new IllegalStateException(
diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java
index 7266d7d..d81dfdb 100644
--- a/services/java/com/android/server/ThrottleService.java
+++ b/services/java/com/android/server/ThrottleService.java
@@ -179,14 +179,17 @@
             mIface = iface;
         }
 
-        public void interfaceLinkStatusChanged(String iface, boolean link) {
-            if (link) {
+        public void interfaceStatusChanged(String iface, boolean up) {
+            if (up) {
                 if (TextUtils.equals(iface, mIface)) {
                     mHandler.obtainMessage(mMsg).sendToTarget();
                 }
             }
         }
 
+        public void interfaceLinkStateChanged(String iface, boolean up) {
+        }
+
         public void interfaceAdded(String iface) {
             // TODO - an interface added in the UP state should also trigger a StatusChanged
             // notification..
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index cb55451..7725891 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -342,6 +342,7 @@
      * Protected by mWifiStateTracker lock.
      */
     private final WorkSource mTmpWorkSource = new WorkSource();
+    private WifiWatchdogService mWifiWatchdogService;
 
     WifiService(Context context) {
         mContext = context;
@@ -431,6 +432,9 @@
         Slog.i(TAG, "WifiService starting up with Wi-Fi " +
                 (wifiEnabled ? "enabled" : "disabled"));
         setWifiEnabled(wifiEnabled);
+
+        //TODO: as part of WWS refactor, create only when needed
+        mWifiWatchdogService = new WifiWatchdogService(mContext);
     }
 
     private boolean testAndClearWifiSavedState() {
@@ -1155,6 +1159,10 @@
         pw.println();
         pw.println("Locks held:");
         mLocks.dump(pw);
+
+        pw.println();
+        pw.println("WifiWatchdogService dump");
+        mWifiWatchdogService.dump(pw);
     }
 
     private class WifiLock extends DeathRecipient {
diff --git a/services/java/com/android/server/WifiWatchdogService.java b/services/java/com/android/server/WifiWatchdogService.java
index 56bfbe0..0b79478 100644
--- a/services/java/com/android/server/WifiWatchdogService.java
+++ b/services/java/com/android/server/WifiWatchdogService.java
@@ -22,1429 +22,743 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.database.ContentObserver;
-import android.net.ConnectivityManager;
-import android.net.LinkProperties;
 import android.net.NetworkInfo;
+import android.net.Uri;
 import android.net.wifi.ScanResult;
+import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.net.Uri;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+import android.os.SystemClock;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Slog;
 
 import java.io.BufferedInputStream;
-import java.io.InputStream;
 import java.io.IOException;
-import java.net.DatagramPacket;
-import java.net.DatagramSocket;
+import java.io.InputStream;
+import java.io.PrintWriter;
 import java.net.HttpURLConnection;
-import java.net.InetAddress;
-import java.net.SocketException;
-import java.net.SocketTimeoutException;
-import java.net.UnknownHostException;
 import java.net.URL;
-import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
-import java.util.Random;
 import java.util.Scanner;
 
 /**
  * {@link WifiWatchdogService} monitors the initial connection to a Wi-Fi
  * network with multiple access points. After the framework successfully
- * connects to an access point, the watchdog verifies whether the DNS server is
- * reachable. If not, the watchdog blacklists the current access point, leading
- * to a connection on another access point within the same network.
+ * connects to an access point, the watchdog verifies connectivity by 'pinging'
+ * the configured DNS server using {@link DnsPinger}.
  * <p>
- * The watchdog has a few safeguards:
- * <ul>
- * <li>Only monitor networks with multiple access points
- * <li>Only check at most {@link #getMaxApChecks()} different access points
- * within the network before giving up
+ * On DNS check failure, the BSSID is blacklisted if it is reasonably likely
+ * that another AP might have internet access; otherwise the SSID is disabled.
  * <p>
- * The watchdog checks for connectivity on an access point by ICMP pinging the
- * DNS. There are settings that allow disabling the watchdog, or tweaking the
- * acceptable packet loss (and other various parameters).
- * <p>
- * The core logic of the watchdog is done on the main watchdog thread. Wi-Fi
- * callbacks can come in on other threads, so we must queue messages to the main
- * watchdog thread's handler. Most (if not all) state is only written to from
- * the main thread.
+ * On DNS success, the WatchdogService initiates a walled garden check via an
+ * http get. A browser windows is activated if a walled garden is detected.
  * 
- * {@hide}
+ * @hide
  */
 public class WifiWatchdogService {
-    private static final String TAG = "WifiWatchdogService";
-    private static final boolean V = false;
-    private static final boolean D = true;
+
+    private static final String WWS_TAG = "WifiWatchdogService";
+
+    private static final boolean VDBG = true;
+    private static final boolean DBG = true;
+
+    // Used for verbose logging
+    private String mDNSCheckLogStr;
 
     private Context mContext;
     private ContentResolver mContentResolver;
     private WifiManager mWifiManager;
-    private ConnectivityManager mConnectivityManager;
 
-    /**
-     * The main watchdog thread.
-     */
-    private WifiWatchdogThread mThread;
-    /**
-     * The handler for the main watchdog thread.
-     */
     private WifiWatchdogHandler mHandler;
 
-    private ContentObserver mContentObserver;
+    private DnsPinger mDnsPinger;
+
+    private IntentFilter mIntentFilter;
+    private BroadcastReceiver mBroadcastReceiver;
+    private boolean mBroadcastsEnabled;
+
+    private static final int WIFI_SIGNAL_LEVELS = 4;
 
     /**
-     * The current watchdog state. Only written from the main thread!
+     * Low signal is defined as less than or equal to cut off
      */
-    private WatchdogState mState = WatchdogState.IDLE;
-    /**
-     * The SSID of the network that the watchdog is currently monitoring. Only
-     * touched in the main thread!
-     */
-    private String mSsid;
-    /**
-     * The number of access points in the current network ({@link #mSsid}) that
-     * have been checked. Only touched in the main thread, using getter/setter methods.
-     */
-    private int mBssidCheckCount;
-    /** Whether the current AP check should be canceled. */
-    private boolean mShouldCancel;
+    private static final int LOW_SIGNAL_CUTOFF = 0;
+
+    private static final long MIN_LOW_SIGNAL_CHECK_INTERVAL = 2 * 60 * 1000;
+    private static final long MIN_SINGLE_DNS_CHECK_INTERVAL = 10 * 60 * 1000;
+    private static final long MIN_WALLED_GARDEN_INTERVAL = 15 * 60 * 1000;
+
+    private static final int MAX_CHECKS_PER_SSID = 7;
+    private static final int NUM_DNS_PINGS = 5;
+    private static double MIN_RESPONSE_RATE = 0.50;
+
+    // TODO : Adjust multiple DNS downward to 250 on repeated failure
+    // private static final int MULTI_DNS_PING_TIMEOUT_MS = 250;
+
+    private static final int DNS_PING_TIMEOUT_MS = 1000;
+    private static final long DNS_PING_INTERVAL = 250;
+
+    private static final long BLACKLIST_FOLLOWUP_INTERVAL = 15 * 1000;
+
+    private Status mStatus = new Status();
+
+    private static class Status {
+        String bssid = "";
+        String ssid = "";
+
+        HashSet<String> allBssids = new HashSet<String>();
+        int numFullDNSchecks = 0;
+
+        long lastSingleCheckTime = -24 * 60 * 60 * 1000;
+        long lastWalledGardenCheckTime = -24 * 60 * 60 * 1000;
+
+        WatchdogState state = WatchdogState.INACTIVE;
+
+        // Info for dns check
+        int dnsCheckTries = 0;
+        int dnsCheckSuccesses = 0;
+
+        public int signal = -200;
+
+    }
+
+    private enum WatchdogState {
+        /**
+         * Full DNS check in progress
+         */
+        DNS_FULL_CHECK,
+
+        /**
+         * Walled Garden detected, will pop up browser next round.
+         */
+        WALLED_GARDEN_DETECTED,
+
+        /**
+         * DNS failed, will blacklist/disable AP next round
+         */
+        DNS_CHECK_FAILURE,
+
+        /**
+         * Online or displaying walled garden auth page
+         */
+        CHECKS_COMPLETE,
+
+        /**
+         * Watchdog idle, network has been blacklisted or received disconnect
+         * msg
+         */
+        INACTIVE,
+
+        BLACKLISTED_AP
+    }
 
     WifiWatchdogService(Context context) {
         mContext = context;
         mContentResolver = context.getContentResolver();
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
-        createThread();
-        
-        // The content observer to listen needs a handler, which createThread creates
+        mDnsPinger = new DnsPinger("WifiWatchdogServer.DnsPinger", context);
+
+        HandlerThread handlerThread = new HandlerThread("WifiWatchdogServiceThread");
+        handlerThread.start();
+        mHandler = new WifiWatchdogHandler(handlerThread.getLooper());
+
+        setupNetworkReceiver();
+
+        // The content observer to listen needs a handler, which createThread
+        // creates
         registerForSettingsChanges();
+
+        // Start things off
         if (isWatchdogEnabled()) {
-            registerForWifiBroadcasts();
+            mHandler.sendEmptyMessage(WifiWatchdogHandler.MESSAGE_CONTEXT_EVENT);
         }
-        
-        if (V) {
-            myLogV("WifiWatchdogService: Created");
-        }
+    }
+
+    /**
+     *
+     */
+    private void setupNetworkReceiver() {
+        mBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+                    mHandler.sendMessage(mHandler.obtainMessage(
+                            WifiWatchdogHandler.MESSAGE_NETWORK_EVENT,
+                            intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO)
+                            ));
+                } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
+                    mHandler.sendEmptyMessage(WifiWatchdogHandler.RSSI_CHANGE_EVENT);
+                } else if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+                    mHandler.sendEmptyMessage(WifiWatchdogHandler.SCAN_RESULTS_AVAILABLE);
+                } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+                    mHandler.sendMessage(mHandler.obtainMessage(
+                            WifiWatchdogHandler.WIFI_STATE_CHANGE,
+                            intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 4)));
+                }
+            }
+        };
+
+        mIntentFilter = new IntentFilter();
+        mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
+        mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
     }
 
     /**
      * Observes the watchdog on/off setting, and takes action when changed.
      */
     private void registerForSettingsChanges() {
-        ContentResolver contentResolver = mContext.getContentResolver();
-        contentResolver.registerContentObserver(
-                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON), false,
-                mContentObserver = new ContentObserver(mHandler) {
+        ContentObserver contentObserver = new ContentObserver(mHandler) {
             @Override
             public void onChange(boolean selfChange) {
-                if (isWatchdogEnabled()) {
-                    registerForWifiBroadcasts();
-                } else {
-                    unregisterForWifiBroadcasts();
-                    if (mHandler != null) {
-                        mHandler.disableWatchdog();
+                mHandler.sendEmptyMessage((WifiWatchdogHandler.MESSAGE_CONTEXT_EVENT));
+            }
+        };
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON),
+                false, contentObserver);
+    }
+
+    private void handleNewConnection() {
+        WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+        String newSsid = wifiInfo.getSSID();
+        String newBssid = wifiInfo.getBSSID();
+
+        if (VDBG) {
+            Slog.v(WWS_TAG, String.format("handleConnected:: old (%s, %s) ==> new (%s, %s)",
+                    mStatus.ssid, mStatus.bssid, newSsid, newBssid));
+        }
+
+        if (TextUtils.isEmpty(newSsid) || TextUtils.isEmpty(newBssid)) {
+            return;
+        }
+
+        if (!TextUtils.equals(mStatus.ssid, newSsid)) {
+            mStatus = new Status();
+            mStatus.ssid = newSsid;
+        }
+
+        mStatus.bssid = newBssid;
+        mStatus.allBssids.add(newBssid);
+        mStatus.signal = WifiManager.calculateSignalLevel(wifiInfo.getRssi(), WIFI_SIGNAL_LEVELS);
+
+        initDnsFullCheck();
+    }
+
+    public void updateRssi() {
+        WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+        if (!TextUtils.equals(mStatus.ssid, wifiInfo.getSSID()) ||
+                !TextUtils.equals(mStatus.bssid, wifiInfo.getBSSID())) {
+            return;
+        }
+
+        mStatus.signal = WifiManager.calculateSignalLevel(wifiInfo.getRssi(), WIFI_SIGNAL_LEVELS);
+    }
+
+    /**
+     * Single step in state machine
+     */
+    private void handleStateStep() {
+        // Slog.v(WWS_TAG, "handleStateStep:: " + mStatus.state);
+
+        switch (mStatus.state) {
+            case DNS_FULL_CHECK:
+                if (VDBG) {
+                    Slog.v(WWS_TAG, "DNS_FULL_CHECK: " + mDNSCheckLogStr);
+                }
+
+                long pingResponseTime = mDnsPinger.pingDns(mDnsPinger.getDns(),
+                        DNS_PING_TIMEOUT_MS);
+
+                mStatus.dnsCheckTries++;
+                if (pingResponseTime >= 0)
+                    mStatus.dnsCheckSuccesses++;
+
+                if (DBG) {
+                    if (pingResponseTime >= 0) {
+                        mDNSCheckLogStr += " | " + pingResponseTime;
+                    } else {
+                        mDNSCheckLogStr += " | " + "x";
                     }
                 }
+
+                switch (currentDnsCheckStatus()) {
+                    case SUCCESS:
+                        if (DBG) {
+                            Slog.d(WWS_TAG, mDNSCheckLogStr + " -- Success");
+                        }
+                        doWalledGardenCheck();
+                        break;
+                    case FAILURE:
+                        if (DBG) {
+                            Slog.d(WWS_TAG, mDNSCheckLogStr + " -- Failure");
+                        }
+                        mStatus.state = WatchdogState.DNS_CHECK_FAILURE;
+                        break;
+                    case INCOMPLETE:
+                        // Taking no action
+                        break;
+                }
+                break;
+            case DNS_CHECK_FAILURE:
+                WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+                if (!mStatus.ssid.equals(wifiInfo.getSSID()) ||
+                        !mStatus.bssid.equals(wifiInfo.getBSSID())) {
+                    Slog.i(WWS_TAG, "handleState DNS_CHECK_FAILURE:: network has changed!");
+                    mStatus.state = WatchdogState.INACTIVE;
+                    break;
+                }
+
+                if (mStatus.numFullDNSchecks >= mStatus.allBssids.size() ||
+                        mStatus.numFullDNSchecks >= MAX_CHECKS_PER_SSID) {
+                    disableAP(wifiInfo);
+                } else {
+                    blacklistAP();
+                }
+                break;
+            case WALLED_GARDEN_DETECTED:
+                popUpBrowser();
+                mStatus.state = WatchdogState.CHECKS_COMPLETE;
+                break;
+            case BLACKLISTED_AP:
+                WifiInfo wifiInfo2 = mWifiManager.getConnectionInfo();
+                if (wifiInfo2.getSupplicantState() != SupplicantState.COMPLETED) {
+                    Slog.d(WWS_TAG,
+                            "handleState::BlacklistedAP - offline, but didn't get disconnect!");
+                    mStatus.state = WatchdogState.INACTIVE;
+                    break;
+                }
+                if (mStatus.bssid.equals(wifiInfo2.getBSSID())) {
+                    Slog.d(WWS_TAG, "handleState::BlacklistedAP - connected to same bssid");
+                    if (!handleSingleDnsCheck()) {
+                        disableAP(wifiInfo2);
+                        break;
+                    }
+                }
+
+                Slog.d(WWS_TAG, "handleState::BlacklistedAP - Simiulating a new connection");
+                handleNewConnection();
+                break;
+        }
+    }
+
+    private void doWalledGardenCheck() {
+        if (!isWalledGardenTestEnabled()) {
+            if (VDBG)
+                Slog.v(WWS_TAG, "Skipping walled garden check - disabled");
+            mStatus.state = WatchdogState.CHECKS_COMPLETE;
+            return;
+        }
+        long waitTime = waitTime(MIN_WALLED_GARDEN_INTERVAL,
+                mStatus.lastWalledGardenCheckTime);
+        if (waitTime > 0) {
+            if (VDBG) {
+                Slog.v(WWS_TAG, "Skipping walled garden check - wait " +
+                        waitTime + " ms.");
             }
-        });
+            mStatus.state = WatchdogState.CHECKS_COMPLETE;
+            return;
+        }
+
+        mStatus.lastWalledGardenCheckTime = SystemClock.elapsedRealtime();
+        if (isWalledGardenConnection()) {
+            if (DBG)
+                Slog.d(WWS_TAG,
+                        "Walled garden test complete - walled garden detected");
+            mStatus.state = WatchdogState.WALLED_GARDEN_DETECTED;
+        } else {
+            if (DBG)
+                Slog.d(WWS_TAG, "Walled garden test complete - online");
+            mStatus.state = WatchdogState.CHECKS_COMPLETE;
+        }
+    }
+
+    private boolean handleSingleDnsCheck() {
+        mStatus.lastSingleCheckTime = SystemClock.elapsedRealtime();
+        long responseTime = mDnsPinger.pingDns(mDnsPinger.getDns(),
+                DNS_PING_TIMEOUT_MS);
+        if (DBG) {
+            Slog.d(WWS_TAG, "Ran a single DNS ping. Response time: " + responseTime);
+        }
+        if (responseTime < 0) {
+            return false;
+        }
+        return true;
+
+    }
+
+    /**
+     * @return Delay in MS before next single DNS check can proceed.
+     */
+    private long timeToNextScheduledDNSCheck() {
+        if (mStatus.signal > LOW_SIGNAL_CUTOFF) {
+            return waitTime(MIN_SINGLE_DNS_CHECK_INTERVAL, mStatus.lastSingleCheckTime);
+        } else {
+            return waitTime(MIN_LOW_SIGNAL_CHECK_INTERVAL, mStatus.lastSingleCheckTime);
+        }
+    }
+
+    /**
+     * Helper to return wait time left given a min interval and last run
+     * 
+     * @param interval minimum wait interval
+     * @param lastTime last time action was performed in
+     *            SystemClock.elapsedRealtime()
+     * @return non negative time to wait
+     */
+    private static long waitTime(long interval, long lastTime) {
+        long wait = interval + lastTime - SystemClock.elapsedRealtime();
+        return wait > 0 ? wait : 0;
+    }
+
+    private void popUpBrowser() {
+        Uri uri = Uri.parse("http://www.google.com");
+        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+        intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+                Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+    }
+
+    private void disableAP(WifiInfo info) {
+        // TODO : Unban networks if they had low signal ?
+        Slog.i(WWS_TAG, String.format("Disabling current SSID, %s [bssid %s].  " +
+                "numChecks %d, numAPs %d", mStatus.ssid, mStatus.bssid,
+                mStatus.numFullDNSchecks, mStatus.allBssids.size()));
+        mWifiManager.disableNetwork(info.getNetworkId());
+        mStatus.state = WatchdogState.INACTIVE;
+    }
+
+    private void blacklistAP() {
+        Slog.i(WWS_TAG, String.format("Blacklisting current BSSID %s [ssid %s].  " +
+                "numChecks %d, numAPs %d", mStatus.bssid, mStatus.ssid,
+                mStatus.numFullDNSchecks, mStatus.allBssids.size()));
+
+        mWifiManager.addToBlacklist(mStatus.bssid);
+        mWifiManager.reassociate();
+        mStatus.state = WatchdogState.BLACKLISTED_AP;
+    }
+
+    /**
+     * Checks the scan for new BBIDs using current mSsid
+     */
+    private void updateBssids() {
+        String curSsid = mStatus.ssid;
+        HashSet<String> bssids = mStatus.allBssids;
+        List<ScanResult> results = mWifiManager.getScanResults();
+        int oldNumBssids = bssids.size();
+
+        if (results == null) {
+            if (VDBG) {
+                Slog.v(WWS_TAG, "updateBssids: Got null scan results!");
+            }
+            return;
+        }
+
+        for (ScanResult result : results) {
+            if (result != null && curSsid.equals(result.SSID))
+                bssids.add(result.BSSID);
+        }
+
+        // if (VDBG && bssids.size() - oldNumBssids > 0) {
+        // Slog.v(WWS_TAG,
+        // String.format("updateBssids:: Found %d new APs (total %d) on SSID %s",
+        // bssids.size() - oldNumBssids, bssids.size(), curSsid));
+        // }
+    }
+
+    enum DnsCheckStatus {
+        SUCCESS,
+        FAILURE,
+        INCOMPLETE
+    }
+
+    /**
+     * Computes the current results of the dns check, ends early if outcome is
+     * assured.
+     */
+    private DnsCheckStatus currentDnsCheckStatus() {
+        /**
+         * After a full ping count, if we have more responses than this cutoff,
+         * the outcome is success; else it is 'failure'.
+         */
+        double pingResponseCutoff = MIN_RESPONSE_RATE * NUM_DNS_PINGS;
+        int remainingChecks = NUM_DNS_PINGS - mStatus.dnsCheckTries;
+
+        /**
+         * Our final success count will be at least this big, so we're
+         * guaranteed to succeed.
+         */
+        if (mStatus.dnsCheckSuccesses >= pingResponseCutoff) {
+            return DnsCheckStatus.SUCCESS;
+        }
+
+        /**
+         * Our final count will be at most the current count plus the remaining
+         * pings - we're guaranteed to fail.
+         */
+        if (remainingChecks + mStatus.dnsCheckSuccesses < pingResponseCutoff) {
+            return DnsCheckStatus.FAILURE;
+        }
+
+        return DnsCheckStatus.INCOMPLETE;
+    }
+
+    private void initDnsFullCheck() {
+        if (DBG) {
+            Slog.d(WWS_TAG, "Starting DNS pings at " + SystemClock.elapsedRealtime());
+        }
+        mStatus.numFullDNSchecks++;
+        mStatus.dnsCheckSuccesses = 0;
+        mStatus.dnsCheckTries = 0;
+        mStatus.state = WatchdogState.DNS_FULL_CHECK;
+
+        if (DBG) {
+            mDNSCheckLogStr = String.format("Dns Check %d.  Pinging %s on ssid [%s]: ",
+                    mStatus.numFullDNSchecks, mDnsPinger.getDns().getHostAddress(),
+                    mStatus.ssid);
+        }
+    }
+
+    /**
+     * DNS based detection techniques do not work at all hotspots. The one sure
+     * way to check a walled garden is to see if a URL fetch on a known address
+     * fetches the data we expect
+     */
+    private boolean isWalledGardenConnection() {
+        InputStream in = null;
+        HttpURLConnection urlConnection = null;
+        try {
+            URL url = new URL(getWalledGardenUrl());
+            urlConnection = (HttpURLConnection) url.openConnection();
+            in = new BufferedInputStream(urlConnection.getInputStream());
+            Scanner scanner = new Scanner(in);
+            if (scanner.findInLine(getWalledGardenPattern()) != null) {
+                return false;
+            } else {
+                return true;
+            }
+        } catch (IOException e) {
+            return false;
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                }
+            }
+            if (urlConnection != null)
+                urlConnection.disconnect();
+        }
+    }
+
+    /**
+     * There is little logic inside this class, instead methods of the form
+     * "handle___" are called in the main {@link WifiWatchdogService}.
+     */
+    private class WifiWatchdogHandler extends Handler {
+        /**
+         * Major network event, object is NetworkInfo
+         */
+        static final int MESSAGE_NETWORK_EVENT = 1;
+        /**
+         * Change in settings, no object
+         */
+        static final int MESSAGE_CONTEXT_EVENT = 2;
+
+        /**
+         * Change in signal strength
+         */
+        static final int RSSI_CHANGE_EVENT = 3;
+        static final int SCAN_RESULTS_AVAILABLE = 4;
+
+        static final int WIFI_STATE_CHANGE = 5;
+
+        /**
+         * Single step of state machine. One DNS check, or one WalledGarden
+         * check, or one external action. We separate out external actions to
+         * increase chance of detecting that a check failure is caused by change
+         * in network status. Messages should have an arg1 which to sync status
+         * messages.
+         */
+        static final int CHECK_SEQUENCE_STEP = 10;
+        static final int SINGLE_DNS_CHECK = 11;
+
+        /**
+         * @param looper
+         */
+        public WifiWatchdogHandler(Looper looper) {
+            super(looper);
+        }
+
+        boolean singleCheckQueued = false;
+        long queuedSingleDnsCheckArrival;
+
+        /**
+         * Sends a singleDnsCheck message with shortest time - guards against
+         * multiple.
+         */
+        private boolean queueSingleDnsCheck() {
+            long delay = timeToNextScheduledDNSCheck();
+            long newArrival = delay + SystemClock.elapsedRealtime();
+            if (singleCheckQueued && queuedSingleDnsCheckArrival <= newArrival)
+                return true;
+            queuedSingleDnsCheckArrival = newArrival;
+            singleCheckQueued = true;
+            removeMessages(SINGLE_DNS_CHECK);
+            return sendMessageDelayed(obtainMessage(SINGLE_DNS_CHECK), delay);
+        }
+
+        boolean checkSequenceQueued = false;
+        long queuedCheckSequenceArrival;
+
+        /**
+         * Sends a state_machine_step message if the delay requested is lower
+         * than the current delay.
+         */
+        private boolean sendCheckSequenceStep(long delay) {
+            long newArrival = delay + SystemClock.elapsedRealtime();
+            if (checkSequenceQueued && queuedCheckSequenceArrival <= newArrival)
+                return true;
+            queuedCheckSequenceArrival = newArrival;
+            checkSequenceQueued = true;
+            removeMessages(CHECK_SEQUENCE_STEP);
+            return sendMessageDelayed(obtainMessage(CHECK_SEQUENCE_STEP), delay);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case CHECK_SEQUENCE_STEP:
+                    checkSequenceQueued = false;
+                    handleStateStep();
+                    if (mStatus.state == WatchdogState.CHECKS_COMPLETE) {
+                        queueSingleDnsCheck();
+                    } else if (mStatus.state == WatchdogState.DNS_FULL_CHECK) {
+                        sendCheckSequenceStep(DNS_PING_INTERVAL);
+                    } else if (mStatus.state == WatchdogState.BLACKLISTED_AP) {
+                        sendCheckSequenceStep(BLACKLIST_FOLLOWUP_INTERVAL);
+                    } else if (mStatus.state != WatchdogState.INACTIVE) {
+                        sendCheckSequenceStep(0);
+                    }
+                    return;
+                case MESSAGE_NETWORK_EVENT:
+                    if (!mBroadcastsEnabled) {
+                        Slog.e(WWS_TAG,
+                                "MessageNetworkEvent - WatchdogService not enabled... returning");
+                        return;
+                    }
+                    NetworkInfo info = (NetworkInfo) msg.obj;
+                    switch (info.getState()) {
+                        case DISCONNECTED:
+                            mStatus.state = WatchdogState.INACTIVE;
+                            return;
+                        case CONNECTED:
+                            handleNewConnection();
+                            sendCheckSequenceStep(0);
+                    }
+                    return;
+                case SINGLE_DNS_CHECK:
+                    singleCheckQueued = false;
+                    if (mStatus.state != WatchdogState.CHECKS_COMPLETE) {
+                        Slog.d(WWS_TAG, "Single check returning, curState: " + mStatus.state);
+                        break;
+                    }
+
+                    if (!handleSingleDnsCheck()) {
+                        initDnsFullCheck();
+                        sendCheckSequenceStep(0);
+                    } else {
+                        queueSingleDnsCheck();
+                    }
+
+                    break;
+                case RSSI_CHANGE_EVENT:
+                    updateRssi();
+                    if (mStatus.state == WatchdogState.CHECKS_COMPLETE)
+                        queueSingleDnsCheck();
+                    break;
+                case SCAN_RESULTS_AVAILABLE:
+                    updateBssids();
+                    break;
+                case WIFI_STATE_CHANGE:
+                    if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) {
+                        Slog.i(WWS_TAG, "WifiStateDisabling -- Resetting WatchdogState");
+                        mStatus = new Status();
+                    }
+                    break;
+                case MESSAGE_CONTEXT_EVENT:
+                    if (isWatchdogEnabled() && !mBroadcastsEnabled) {
+                        mContext.registerReceiver(mBroadcastReceiver, mIntentFilter);
+                        mBroadcastsEnabled = true;
+                        Slog.i(WWS_TAG, "WifiWatchdogService enabled");
+                    } else if (!isWatchdogEnabled() && mBroadcastsEnabled) {
+                        mContext.unregisterReceiver(mBroadcastReceiver);
+                        removeMessages(SINGLE_DNS_CHECK);
+                        removeMessages(CHECK_SEQUENCE_STEP);
+                        mBroadcastsEnabled = false;
+                        Slog.i(WWS_TAG, "WifiWatchdogService disabled");
+                    }
+                    break;
+            }
+        }
+    }
+
+    public void dump(PrintWriter pw) {
+        pw.print("WatchdogStatus: ");
+        pw.print("State " + mStatus.state);
+        pw.println(", network [" + mStatus.ssid + ", " + mStatus.bssid + "]");
+        pw.print("checkCount " + mStatus.numFullDNSchecks);
+        pw.print(", bssids: " + mStatus.allBssids.size());
+        pw.print(", hasCheckMessages? " +
+                mHandler.hasMessages(WifiWatchdogHandler.CHECK_SEQUENCE_STEP));
+        pw.println(" hasSingleCheckMessages? " +
+                mHandler.hasMessages(WifiWatchdogHandler.SINGLE_DNS_CHECK));
+    }
+
+    /**
+     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED
+     */
+    private Boolean isWalledGardenTestEnabled() {
+        return Settings.Secure.getInt(mContentResolver,
+                Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, 1) == 1;
+    }
+
+    /**
+     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_URL
+     */
+    private String getWalledGardenUrl() {
+        String url = Settings.Secure.getString(mContentResolver,
+                Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL);
+        if (TextUtils.isEmpty(url))
+            return "http://www.google.com/";
+        return url;
+    }
+
+    /**
+     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_PATTERN
+     */
+    private String getWalledGardenPattern() {
+        String pattern = Settings.Secure.getString(mContentResolver,
+                Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN);
+        if (TextUtils.isEmpty(pattern))
+            return "<title>.*Google.*</title>";
+        return pattern;
     }
 
     /**
      * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ON
      */
     private boolean isWatchdogEnabled() {
-        return Settings.Secure.getInt(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1;
-    }
-    
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_AP_COUNT
-     */
-    private int getApCount() {
         return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_AP_COUNT, 2);
-    }
-    
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT
-     */
-    private int getInitialIgnoredPingCount() {
-        return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT , 2);
-    }
-
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_COUNT
-     */
-    private int getPingCount() {
-        return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_PING_COUNT, 4);
-    }
-    
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_TIMEOUT_MS
-     */
-    private int getPingTimeoutMs() {
-        return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS, 500);
-    }
-    
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_DELAY_MS
-     */
-    private int getPingDelayMs() {
-        return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_PING_DELAY_MS, 250);
-    }
-
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED
-     */
-     private Boolean isWalledGardenTestEnabled() {
-        return Settings.Secure.getInt(mContentResolver,
-                 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, 1) == 1;
-     }
-
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_URL
-     */
-     private String getWalledGardenUrl() {
-        String url = Settings.Secure.getString(mContentResolver,
-                 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL);
-        if (TextUtils.isEmpty(url)) return "http://www.google.com/";
-        return url;
-     }
-
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_PATTERN
-     */
-     private String getWalledGardenPattern() {
-        String pattern = Settings.Secure.getString(mContentResolver,
-                 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN);
-        if (TextUtils.isEmpty(pattern)) return "<title>.*Google.*</title>";
-        return pattern;
-     }
-    
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE
-     */
-    private int getAcceptablePacketLossPercentage() {
-        return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE, 25);
-    }
-    
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS
-     */
-    private int getMaxApChecks() {
-        return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_MAX_AP_CHECKS, 7);
-    }
-    
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED
-     */
-    private boolean isBackgroundCheckEnabled() {
-        return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED, 1) == 1;
-    }
-    
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS
-     */
-    private int getBackgroundCheckDelayMs() {
-        return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS, 60000);
-    }
-    
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS
-     */
-    private int getBackgroundCheckTimeoutMs() {
-        return Settings.Secure.getInt(mContentResolver,
-            Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS, 1000);
-    }
-
-    /**
-     * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WATCH_LIST
-     * @return the comma-separated list of SSIDs
-     */
-    private String getWatchList() {
-        return Settings.Secure.getString(mContentResolver,
-                Settings.Secure.WIFI_WATCHDOG_WATCH_LIST);
-    }
-    
-    /**
-     * Registers to receive the necessary Wi-Fi broadcasts.
-     */
-    private void registerForWifiBroadcasts() {
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
-        intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
-        mContext.registerReceiver(mReceiver, intentFilter);
-    }
-
-    /**
-     * Unregisters from receiving the Wi-Fi broadcasts.
-     */
-    private void unregisterForWifiBroadcasts() {
-        mContext.unregisterReceiver(mReceiver);
-    }
-
-    /**
-     * Creates the main watchdog thread, including waiting for the handler to be
-     * created.
-     */
-    private void createThread() {
-        mThread = new WifiWatchdogThread();
-        mThread.start();
-        waitForHandlerCreation();
-    }
-
-    /**
-     * Unregister broadcasts and quit the watchdog thread
-     */
-    //TODO: Change back to running WWS when needed
-//    private void quit() {
-//        unregisterForWifiBroadcasts();
-//        mContext.getContentResolver().unregisterContentObserver(mContentObserver);
-//        mHandler.removeAllActions();
-//        mHandler.getLooper().quit();
-//    }
-
-    /**
-     * Waits for the main watchdog thread to create the handler.
-     */
-    private void waitForHandlerCreation() {
-        synchronized(this) {
-            while (mHandler == null) {
-                try {
-                    // Wait for the handler to be set by the other thread
-                    wait();
-                } catch (InterruptedException e) {
-                    Slog.e(TAG, "Interrupted while waiting on handler.");
-                }
-            }
-        }
-    }
-
-    // Utility methods
-    
-    /**
-     * Logs with the current thread.
-     */
-    private static void myLogV(String message) {
-        Slog.v(TAG, "(" + Thread.currentThread().getName() + ") " + message);
-    }
-    
-    private static void myLogD(String message) {
-        Slog.d(TAG, "(" + Thread.currentThread().getName() + ") " + message);
-    }
-    
-    /**
-     * Gets the first DNS of the current AP.
-     * 
-     * @return The first DNS of the current AP.
-     */
-    private InetAddress getDns() {
-        if (mConnectivityManager == null) {
-            mConnectivityManager = (ConnectivityManager)mContext.getSystemService(
-                    Context.CONNECTIVITY_SERVICE);
-        }
-
-        LinkProperties linkProperties = mConnectivityManager.getLinkProperties(
-                ConnectivityManager.TYPE_WIFI);
-        if (linkProperties == null) return null;
-
-        Collection<InetAddress> dnses = linkProperties.getDnses();
-        if (dnses == null || dnses.size() == 0) return null;
-
-        return dnses.iterator().next();
-    }
-
-    /**
-     * Checks whether the DNS can be reached using multiple attempts according
-     * to the current setting values.
-     * 
-     * @return Whether the DNS is reachable
-     */
-    private boolean checkDnsConnectivity() {
-        InetAddress dns = getDns();
-        if (dns == null) {
-            if (V) {
-                myLogV("checkDnsConnectivity: Invalid DNS, returning false");
-            }
-            return false;
-        }
-
-        if (V) {
-            myLogV("checkDnsConnectivity: Checking " + dns.getHostAddress() + " for connectivity");
-        }
-
-        int numInitialIgnoredPings = getInitialIgnoredPingCount();
-        int numPings = getPingCount();
-        int pingDelay = getPingDelayMs();
-        int acceptableLoss = getAcceptablePacketLossPercentage();
-
-        /** See {@link Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT} */
-        int ignoredPingCounter = 0;
-        int pingCounter = 0;
-        int successCounter = 0;
-
-        // No connectivity check needed
-        if (numPings == 0) {
-            return true;
-        }
-
-        // Do the initial pings that we ignore
-        for (; ignoredPingCounter < numInitialIgnoredPings; ignoredPingCounter++) {
-            if (shouldCancel()) return false;
-
-            boolean dnsAlive = DnsPinger.isDnsReachable(dns, getPingTimeoutMs());
-            if (dnsAlive) {
-                /*
-                 * Successful "ignored" pings are *not* ignored (they count in the total number
-                 * of pings), but failures are really ignored.
-                 */
-
-                // TODO: This is confusing logic and should be rewitten
-                // Here, successful 'ignored' pings are interpreted as a success in the below loop
-                pingCounter++;
-                successCounter++;
-            }
-
-            if (V) {
-                Slog.v(TAG, (dnsAlive ? "  +" : "  Ignored: -"));
-            }
-
-            if (shouldCancel()) return false;
-
-            try {
-                Thread.sleep(pingDelay);
-            } catch (InterruptedException e) {
-                Slog.w(TAG, "Interrupted while pausing between pings", e);
-            }
-        }
-
-        // Do the pings that we use to measure packet loss
-        for (; pingCounter < numPings; pingCounter++) {
-            if (shouldCancel()) return false;
-
-            if (DnsPinger.isDnsReachable(dns, getPingTimeoutMs())) {
-                successCounter++;
-                if (V) {
-                    Slog.v(TAG, "  +");
-                }
-            } else {
-                if (V) {
-                    Slog.v(TAG, "  -");
-                }
-            }
-
-            if (shouldCancel()) return false;
-
-            try {
-                Thread.sleep(pingDelay);
-            } catch (InterruptedException e) {
-                Slog.w(TAG, "Interrupted while pausing between pings", e);
-            }
-        }
-
-        //TODO: Integer division might cause problems down the road...
-        int packetLossPercentage = 100 * (numPings - successCounter) / numPings;
-        if (D) {
-            Slog.d(TAG, packetLossPercentage
-                    + "% packet loss (acceptable is " + acceptableLoss + "%)");
-        }
-
-        return !shouldCancel() && (packetLossPercentage <= acceptableLoss);
-    }
-
-    private boolean backgroundCheckDnsConnectivity() {
-        InetAddress dns = getDns();
-
-        if (dns == null) {
-            if (V) {
-                myLogV("backgroundCheckDnsConnectivity: DNS is empty, returning false");
-            }
-            return false;
-        }
-
-        if (V) {
-            myLogV("backgroundCheckDnsConnectivity: Background checking " +
-                    dns.getHostAddress() + " for connectivity");
-        }
-
-        return DnsPinger.isDnsReachable(dns, getBackgroundCheckTimeoutMs());
-    }
-
-    /**
-     * Signals the current action to cancel.
-     */
-    private void cancelCurrentAction() {
-        mShouldCancel = true;
-    }
-    
-    /**
-     * Helper to check whether to cancel. 
-     * 
-     * @return Whether to cancel processing the action.
-     */
-    private boolean shouldCancel() {
-        if (V && mShouldCancel) {
-            myLogV("shouldCancel: Cancelling");
-        }
-        
-        return mShouldCancel;
-    }
-    
-    // Wi-Fi initiated callbacks (could be executed in another thread)
-
-    /**
-     * Called when connected to an AP (this can be the next AP in line, or
-     * it can be a completely different network).
-     * 
-     * @param ssid The SSID of the access point.
-     * @param bssid The BSSID of the access point.
-     */
-    private void onConnected(String ssid, String bssid) {
-        if (V) {
-            myLogV("onConnected: SSID: " + ssid + ", BSSID: " + bssid);
-        }
-
-        /*
-         * The current action being processed by the main watchdog thread is now
-         * stale, so cancel it.
-         */
-        cancelCurrentAction();
-        
-        if ((mSsid == null) || !mSsid.equals(ssid)) {
-            /*
-             * This is a different network than what the main watchdog thread is
-             * processing, dispatch the network change message on the main thread.
-             */
-            mHandler.dispatchNetworkChanged(ssid);
-        }
-        
-        if (requiresWatchdog(ssid, bssid)) {
-            if (D) {
-                myLogD(ssid + " (" + bssid + ") requires the watchdog");
-            }
-
-            // This access point requires a watchdog, so queue the check on the main thread
-            mHandler.checkAp(new AccessPoint(ssid, bssid));
-            
-        } else {
-            if (D) {
-                myLogD(ssid + " (" + bssid + ") does not require the watchdog");
-            }
-
-            // This access point does not require a watchdog, so queue idle on the main thread
-            mHandler.idle();
-        }
-        if (isWalledGardenTestEnabled()) mHandler.checkWalledGarden(ssid);
-    }
-    
-    /**
-     * Called when Wi-Fi is enabled.
-     */
-    private void onEnabled() {
-        cancelCurrentAction();
-        // Queue a hard-reset of the state on the main thread
-        mHandler.reset();
-    }
-    
-    /**
-     * Called when disconnected (or some other event similar to being disconnected).
-     */
-    private void onDisconnected() {
-        if (V) {
-            myLogV("onDisconnected");
-        }
-        
-        /*
-         * Disconnected from an access point, the action being processed by the
-         * watchdog thread is now stale, so cancel it.
-         */
-        cancelCurrentAction();
-        // Dispatch the disconnected to the main watchdog thread
-        mHandler.dispatchDisconnected();
-        // Queue the action to go idle
-        mHandler.idle();
-    }
-
-    /**
-     * Checks whether an access point requires watchdog monitoring.
-     * 
-     * @param ssid The SSID of the access point.
-     * @param bssid The BSSID of the access point.
-     * @return Whether the access point/network should be monitored by the
-     *         watchdog.
-     */
-    private boolean requiresWatchdog(String ssid, String bssid) {
-        if (V) {
-            myLogV("requiresWatchdog: SSID: " + ssid + ", BSSID: " + bssid);
-        }
-        
-        WifiInfo info = null;
-        if (ssid == null) {
-            /*
-             * This is called from a Wi-Fi callback, so assume the WifiInfo does
-             * not have stale data.
-             */
-            info = mWifiManager.getConnectionInfo();
-            ssid = info.getSSID();
-            if (ssid == null) {
-                // It's still null, give up
-                if (V) {
-                    Slog.v(TAG, "  Invalid SSID, returning false");
-                }
-                return false;
-            }
-        }
-        
-        if (TextUtils.isEmpty(bssid)) {
-            // Similar as above
-            if (info == null) {
-                info = mWifiManager.getConnectionInfo();
-            }
-            bssid = info.getBSSID();
-            if (TextUtils.isEmpty(bssid)) {
-                // It's still null, give up
-                if (V) {
-                    Slog.v(TAG, "  Invalid BSSID, returning false");
-                }
-                return false;
-            }
-        }
-
-        if (!isOnWatchList(ssid)) {
-            if (V) {
-                Slog.v(TAG, "  SSID not on watch list, returning false");
-            }
-            return false;
-        }
-
-        // The watchdog only monitors networks with multiple APs
-        if (!hasRequiredNumberOfAps(ssid)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    private boolean isOnWatchList(String ssid) {
-        String watchList;
-
-        if (ssid == null || (watchList = getWatchList()) == null) {
-            return false;
-        }
-
-        String[] list = watchList.split(" *, *");
-
-        for (String name : list) {
-            if (ssid.equals(name)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-    
-    /**
-     * Checks if the current scan results have multiple access points with an SSID.
-     * 
-     * @param ssid The SSID to check.
-     * @return Whether the SSID has multiple access points.
-     */
-    private boolean hasRequiredNumberOfAps(String ssid) {
-        List<ScanResult> results = mWifiManager.getScanResults();
-        if (results == null) {
-            if (V) {
-                myLogV("hasRequiredNumberOfAps: Got null scan results, returning false");
-            }
-            return false;
-        }
-        
-        int numApsRequired = getApCount();
-        int numApsFound = 0;
-        int resultsSize = results.size();
-        for (int i = 0; i < resultsSize; i++) {
-            ScanResult result = results.get(i);
-            if (result == null) continue;
-            if (result.SSID == null) continue;
-            
-            if (result.SSID.equals(ssid)) {
-                numApsFound++;
-                
-                if (numApsFound >= numApsRequired) {
-                    if (V) {
-                        myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning true");
-                    }
-                    return true;
-                }
-            }
-        }
-        
-        if (V) {
-            myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning false");
-        }
-        return false;
-    }
-    
-    // Watchdog logic (assume all of these methods will be in our main thread)
-    
-    /**
-     * Handles a Wi-Fi network change (for example, from networkA to networkB).
-     */
-    private void handleNetworkChanged(String ssid) {
-        // Set the SSID being monitored to the new SSID 
-        mSsid = ssid;
-        // Set various state to that when being idle 
-        setIdleState(true);
-    }
-    
-    /**
-     * Handles checking whether an AP is a "good" AP.  If not, it will be blacklisted.
-     * 
-     * @param ap The access point to check.
-     */
-    private void handleCheckAp(AccessPoint ap) {
-        // Reset the cancel state since this is the entry point of this action
-        mShouldCancel = false;
-        
-        if (V) {
-            myLogV("handleCheckAp: AccessPoint: " + ap);
-        }
-        
-        // Make sure we are not sleeping
-        if (mState == WatchdogState.SLEEP) {
-            if (V) {
-                Slog.v(TAG, "  Sleeping (in " + mSsid + "), so returning");
-            }
-            return;
-        }
-        
-        mState = WatchdogState.CHECKING_AP;
-        
-        /*
-         * Checks to make sure we haven't exceeded the max number of checks
-         * we're allowed per network
-         */
-        incrementBssidCheckCount();
-        if (getBssidCheckCount() > getMaxApChecks()) {
-            if (V) {
-                Slog.v(TAG, "  Passed the max attempts (" + getMaxApChecks()
-                        + "), going to sleep for " + mSsid);
-            }
-            mHandler.sleep(mSsid);
-            return;
-        }
-
-        // Do the check
-        boolean isApAlive = checkDnsConnectivity();
-        
-        if (V) {
-            Slog.v(TAG, "  Is it alive: " + isApAlive);
-        }
-
-        // Take action based on results
-        if (isApAlive) {
-            handleApAlive(ap);
-        } else {
-            handleApUnresponsive(ap);
-        }
-    }
-
-    /**
-     * Handles the case when an access point is alive.
-     * 
-     * @param ap The access point.
-     */
-    private void handleApAlive(AccessPoint ap) {
-        // Check whether we are stale and should cancel
-        if (shouldCancel()) return;
-        // We're satisfied with this AP, so go idle
-        setIdleState(false);
-        
-        if (D) {
-            myLogD("AP is alive: " + ap.toString());
-        }
-        
-        // Queue the next action to be a background check
-        mHandler.backgroundCheckAp(ap);
-    }
-    
-    /**
-     * Handles an unresponsive AP by blacklisting it.
-     * 
-     * @param ap The access point.
-     */
-    private void handleApUnresponsive(AccessPoint ap) {
-        // Check whether we are stale and should cancel
-        if (shouldCancel()) return;
-        // This AP is "bad", switch to another
-        mState = WatchdogState.SWITCHING_AP;
-
-        if (D) {
-            myLogD("AP is dead: " + ap.toString());
-        }
-        
-        // Black list this "bad" AP, this will cause an attempt to connect to another
-        blacklistAp(ap.bssid);
-        // Initiate an association to an alternate AP
-        mWifiManager.reassociate();
-    }
-
-    private void blacklistAp(String bssid) {
-        if (TextUtils.isEmpty(bssid)) {
-            return;
-        }
-        
-        // Before taking action, make sure we should not cancel our processing
-        if (shouldCancel()) return;
-        
-        mWifiManager.addToBlacklist(bssid);
-
-        if (D) {
-            myLogD("Blacklisting " + bssid);
-        }
-    }
-
-    /**
-     * Handles a single background check. If it fails, it should trigger a
-     * normal check. If it succeeds, it should queue another background check.
-     * 
-     * @param ap The access point to do a background check for. If this is no
-     *        longer the current AP, it is okay to return without any
-     *        processing.
-     */
-    private void handleBackgroundCheckAp(AccessPoint ap) {
-        // Reset the cancel state since this is the entry point of this action
-        mShouldCancel = false;
-        
-        if (V) {
-            myLogV("handleBackgroundCheckAp: AccessPoint: " + ap);
-        }
-        
-        // Make sure we are not sleeping
-        if (mState == WatchdogState.SLEEP) {
-            if (V) {
-                Slog.v(TAG, "  handleBackgroundCheckAp: Sleeping (in " + mSsid + "), so returning");
-            }
-            return;
-        }
-        
-        // Make sure the AP we're supposed to be background checking is still the active one
-        WifiInfo info = mWifiManager.getConnectionInfo();
-        if (info.getSSID() == null || !info.getSSID().equals(ap.ssid)) {
-            if (V) {
-                myLogV("handleBackgroundCheckAp: We are no longer connected to "
-                        + ap + ", and instead are on " + info);
-            }
-            return;
-        }
-        
-        if (info.getBSSID() == null || !info.getBSSID().equals(ap.bssid)) {
-            if (V) {
-                myLogV("handleBackgroundCheckAp: We are no longer connected to "
-                        + ap + ", and instead are on " + info);
-            }
-            return;
-        }
-
-        // Do the check
-        boolean isApAlive = backgroundCheckDnsConnectivity();
-        
-        if (V && !isApAlive) {
-            Slog.v(TAG, "  handleBackgroundCheckAp: Is it alive: " + isApAlive);
-        }
-
-        if (shouldCancel()) {
-            return;
-        }
-        
-        // Take action based on results
-        if (isApAlive) {
-            // Queue another background check
-            mHandler.backgroundCheckAp(ap);
-            
-        } else {
-            if (D) {
-                myLogD("Background check failed for " + ap.toString());
-            }
-            
-            // Queue a normal check, so it can take proper action
-            mHandler.checkAp(ap);
-        }
-    }
-    
-    /**
-     * Handles going to sleep for this network. Going to sleep means we will not
-     * monitor this network anymore.
-     * 
-     * @param ssid The network that will not be monitored anymore.
-     */
-    private void handleSleep(String ssid) {
-        // Make sure the network we're trying to sleep in is still the current network
-        if (ssid != null && ssid.equals(mSsid)) {
-            mState = WatchdogState.SLEEP;
-
-            if (D) {
-                myLogD("Going to sleep for " + ssid);
-            }
-            
-            /*
-             * Before deciding to go to sleep, we may have checked a few APs
-             * (and blacklisted them). Clear the blacklist so the AP with best
-             * signal is chosen.
-             */
-            mWifiManager.clearBlacklist();
-            
-            if (V) {
-                myLogV("handleSleep: Set state to SLEEP and cleared blacklist");
-            }
-        }
-    }
-
-    /**
-     * Handles an access point disconnection.
-     */
-    private void handleDisconnected() {
-        /*
-         * We purposefully do not change mSsid to null. This is to handle
-         * disconnected followed by connected better (even if there is some
-         * duration in between). For example, if the watchdog went to sleep in a
-         * network, and then the phone goes to sleep, when the phone wakes up we
-         * still want to be in the sleeping state. When the phone went to sleep,
-         * we would have gotten a disconnected event which would then set mSsid
-         * = null. This is bad, since the following connect would cause us to do
-         * the "network is good?" check all over again. */
-        
-        /* 
-         * Set the state as if we were idle (don't come out of sleep, only
-         * hard reset and network changed should do that.
-         */
-        setIdleState(false);
-    }
-
-    /**
-     * Handles going idle. Idle means we are satisfied with the current state of
-     * things, but if a new connection occurs we'll re-evaluate.
-     */
-    private void handleIdle() {
-        // Reset the cancel state since this is the entry point for this action
-        mShouldCancel = false;
-        
-        if (V) {
-            myLogV("handleSwitchToIdle");
-        }
-        
-        // If we're sleeping, don't do anything
-        if (mState == WatchdogState.SLEEP) {
-            Slog.v(TAG, "  Sleeping (in " + mSsid + "), so returning");
-            return;
-        }
-        
-        // Set the idle state
-        setIdleState(false);
-        
-        if (V) {
-            Slog.v(TAG, "  Set state to IDLE");
-        }
-    }
-    
-    /**
-     * Sets the state as if we are going idle.
-     */
-    private void setIdleState(boolean forceIdleState) {
-        // Setting idle state does not kick us out of sleep unless the forceIdleState is set
-        if (forceIdleState || (mState != WatchdogState.SLEEP)) {
-            mState = WatchdogState.IDLE;
-        }
-        resetBssidCheckCount();
-    }
-
-    /**
-     * Handles a hard reset. A hard reset is rarely used, but when used it
-     * should revert anything done by the watchdog monitoring.
-     */
-    private void handleReset() {
-        mWifiManager.clearBlacklist();
-        setIdleState(true);
-    }
-    
-    // Inner classes
-
-    /**
-     * Possible states for the watchdog to be in.
-     */
-    private static enum WatchdogState {
-        /** The watchdog is currently idle, but it is still responsive to future AP checks in this network. */
-        IDLE,
-        /** The watchdog is sleeping, so it will not try any AP checks for the network. */
-        SLEEP,
-        /** The watchdog is currently checking an AP for connectivity. */
-        CHECKING_AP,
-        /** The watchdog is switching to another AP in the network. */
-        SWITCHING_AP
-    }
-
-    private int getBssidCheckCount() {
-        return mBssidCheckCount;
-    }
-
-    private void incrementBssidCheckCount() {
-        mBssidCheckCount++;
-    }
-
-    private void resetBssidCheckCount() {
-        this.mBssidCheckCount = 0;
-    }
-
-    /**
-     * The main thread for the watchdog monitoring. This will be turned into a
-     * {@link Looper} thread.
-     */
-    private class WifiWatchdogThread extends Thread {
-        WifiWatchdogThread() {
-            super("WifiWatchdogThread");
-        }
-        
-        @Override
-        public void run() {
-            // Set this thread up so the handler will work on it
-            Looper.prepare();
-            
-            synchronized(WifiWatchdogService.this) {
-                mHandler = new WifiWatchdogHandler();
-
-                // Notify that the handler has been created
-                WifiWatchdogService.this.notify();
-            }
-            
-            // Listen for messages to the handler
-            Looper.loop();
-        }
-    }
-
-    /**
-     * The main thread's handler. There are 'actions', and just general 
-     * 'messages'. There should only ever be one 'action' in the queue (aside
-     * from the one being processed, if any). There may be multiple messages in
-     * the queue. So, actions are replaced by more recent actions, where as
-     * messages will be executed for sure. Messages end up being used to just
-     * change some state, and not really take any action.
-     * <p>
-     * There is little logic inside this class, instead methods of the form
-     * "handle___" are called in the main {@link WifiWatchdogService}.
-     */
-    private class WifiWatchdogHandler extends Handler {
-        /** Check whether the AP is "good".  The object will be an {@link AccessPoint}. */
-        static final int ACTION_CHECK_AP = 1;
-        /** Go into the idle state. */
-        static final int ACTION_IDLE = 2;
-        /**
-         * Performs a periodic background check whether the AP is still "good".
-         * The object will be an {@link AccessPoint}.
-         */
-        static final int ACTION_BACKGROUND_CHECK_AP = 3;
-        /** Check whether the connection is a walled garden */
-        static final int ACTION_CHECK_WALLED_GARDEN = 4;
-
-        /**
-         * Go to sleep for the current network. We are conservative with making
-         * this a message rather than action. We want to make sure our main
-         * thread sees this message, but if it were an action it could be
-         * removed from the queue and replaced by another action. The main
-         * thread will ensure when it sees the message that the state is still
-         * valid for going to sleep.
-         * <p>
-         * For an explanation of sleep, see {@link android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS}.
-         */
-        static final int MESSAGE_SLEEP = 101;
-        /** Disables the watchdog. */
-        static final int MESSAGE_DISABLE_WATCHDOG = 102;
-        /** The network has changed. */
-        static final int MESSAGE_NETWORK_CHANGED = 103;
-        /** The current access point has disconnected. */
-        static final int MESSAGE_DISCONNECTED = 104;
-        /** Performs a hard-reset on the watchdog state. */
-        static final int MESSAGE_RESET = 105;
-
-        /* Walled garden detection */
-        private String mLastSsid;
-        private long mLastTime;
-        private final long MIN_WALLED_GARDEN_TEST_INTERVAL = 15 * 60 * 1000; //15 minutes
-
-        void checkWalledGarden(String ssid) {
-            sendMessage(obtainMessage(ACTION_CHECK_WALLED_GARDEN, ssid));
-        }
-
-        void checkAp(AccessPoint ap) {
-            removeAllActions();
-            sendMessage(obtainMessage(ACTION_CHECK_AP, ap));
-        }
-        
-        void backgroundCheckAp(AccessPoint ap) {
-            if (!isBackgroundCheckEnabled()) return;
-            
-            removeAllActions();
-            sendMessageDelayed(obtainMessage(ACTION_BACKGROUND_CHECK_AP, ap),
-                    getBackgroundCheckDelayMs());
-        }
-        
-        void idle() {
-            removeAllActions();
-            sendMessage(obtainMessage(ACTION_IDLE));
-        }
-        
-        void sleep(String ssid) {
-            removeAllActions();
-            sendMessage(obtainMessage(MESSAGE_SLEEP, ssid));
-        }
-        
-        void disableWatchdog() {
-            removeAllActions();
-            sendMessage(obtainMessage(MESSAGE_DISABLE_WATCHDOG));
-        }
-        
-        void dispatchNetworkChanged(String ssid) {
-            removeAllActions();
-            sendMessage(obtainMessage(MESSAGE_NETWORK_CHANGED, ssid));
-        }
-
-        void dispatchDisconnected() {
-            removeAllActions();
-            sendMessage(obtainMessage(MESSAGE_DISCONNECTED));
-        }
-
-        void reset() {
-            removeAllActions();
-            sendMessage(obtainMessage(MESSAGE_RESET));
-        }
-        
-        private void removeAllActions() {
-            removeMessages(ACTION_CHECK_AP);
-            removeMessages(ACTION_IDLE);
-            removeMessages(ACTION_BACKGROUND_CHECK_AP);
-        }
-        
-        @Override
-        public void handleMessage(Message msg) {
-            if (V) {
-                myLogV("handleMessage: " + msg.what);
-            }
-            switch (msg.what) {
-                case MESSAGE_NETWORK_CHANGED:
-                    handleNetworkChanged((String) msg.obj);
-                    break;
-                case ACTION_CHECK_AP:
-                    handleCheckAp((AccessPoint) msg.obj);
-                    break;
-                case ACTION_BACKGROUND_CHECK_AP:
-                    handleBackgroundCheckAp((AccessPoint) msg.obj);
-                    break;
-                case ACTION_CHECK_WALLED_GARDEN:
-                    handleWalledGardenCheck((String) msg.obj);
-                    break;
-                case MESSAGE_SLEEP:
-                    handleSleep((String) msg.obj);
-                    break;
-                case ACTION_IDLE:
-                    handleIdle();
-                    break;
-                case MESSAGE_DISABLE_WATCHDOG:
-                    handleIdle();
-                    break;
-                case MESSAGE_DISCONNECTED:
-                    handleDisconnected();
-                    break;
-                case MESSAGE_RESET:
-                    handleReset();
-                    break;
-            }
-        }
-
-        /**
-         * DNS based detection techniques do not work at all hotspots. The one sure way to check
-         * a walled garden is to see if a URL fetch on a known address fetches the data we
-         * expect
-         */
-        private boolean isWalledGardenConnection() {
-            InputStream in = null;
-            HttpURLConnection  urlConnection = null;
-            try {
-                URL url = new URL(getWalledGardenUrl());
-                urlConnection = (HttpURLConnection) url.openConnection();
-                in = new BufferedInputStream(urlConnection.getInputStream());
-                Scanner scanner = new Scanner(in);
-                if (scanner.findInLine(getWalledGardenPattern()) != null) {
-                    return false;
-                } else {
-                    return true;
-                }
-            } catch (IOException e) {
-                return false;
-            } finally {
-                if (in != null) {
-                    try {
-                        in.close();
-                    } catch (IOException e) {
-                    }
-                }
-                if (urlConnection != null) urlConnection.disconnect();
-            }
-        }
-
-        private void handleWalledGardenCheck(String ssid) {
-            long currentTime = System.currentTimeMillis();
-            //Avoid a walled garden test on the same network if one was already done
-            //within MIN_WALLED_GARDEN_TEST_INTERVAL. This will handle scenarios where
-            //there are frequent network disconnections
-            if (ssid.equals(mLastSsid) &&
-                    (currentTime - mLastTime) < MIN_WALLED_GARDEN_TEST_INTERVAL) {
-                return;
-            }
-
-            mLastTime = currentTime;
-            mLastSsid = ssid;
-
-            if (isWalledGardenConnection()) {
-                Uri uri = Uri.parse("http://www.google.com");
-                Intent intent = new Intent(Intent.ACTION_VIEW, uri);
-                intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
-                        Intent.FLAG_ACTIVITY_NEW_TASK);
-                mContext.startActivity(intent);
-            }
-        }
-    }
-
-    /**
-     * Receives Wi-Fi broadcasts.
-     * <p>
-     * There is little logic in this class, instead methods of the form "on___"
-     * are called in the {@link WifiWatchdogService}.
-     */
-    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
-                handleNetworkStateChanged(
-                        (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO));
-            } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
-                handleWifiStateChanged(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
-                        WifiManager.WIFI_STATE_UNKNOWN));
-            }
-        }
-
-        private void handleNetworkStateChanged(NetworkInfo info) {
-            if (V) {
-                myLogV("Receiver.handleNetworkStateChanged: NetworkInfo: "
-                        + info);
-            }
-            
-            switch (info.getState()) {
-                case CONNECTED:
-                    WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
-                    if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) {
-                        if (V) {
-                            myLogV("handleNetworkStateChanged: Got connected event but SSID or BSSID are null. SSID: "
-                                + wifiInfo.getSSID()
-                                + ", BSSID: "
-                                + wifiInfo.getBSSID() + ", ignoring event");
-                        }
-                        return;
-                    }
-                    onConnected(wifiInfo.getSSID(), wifiInfo.getBSSID());
-                    break;
-
-                case DISCONNECTED:
-                    onDisconnected();
-                    break;
-            }
-        }
-
-        private void handleWifiStateChanged(int wifiState) {
-            if (wifiState == WifiManager.WIFI_STATE_DISABLED) {
-                onDisconnected();
-            } else if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
-                onEnabled();
-            }
-        }
-    };
-
-    /**
-     * Describes an access point by its SSID and BSSID.
-     *
-     */
-    private static class AccessPoint {
-        String ssid;
-        String bssid;
-        
-        /**
-         * @param ssid cannot be null
-         * @param bssid cannot be null
-         */
-        AccessPoint(String ssid, String bssid) {
-            if (ssid == null || bssid == null) {
-                Slog.e(TAG, String.format("(%s) INVALID ACCESSPOINT: (%s, %s)",
-                        Thread.currentThread().getName(),ssid,bssid));
-            }
-            this.ssid = ssid;
-            this.bssid = bssid;
-        }
-        
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof AccessPoint)) return false;
-            AccessPoint otherAp = (AccessPoint) o;
-
-            // Either we both have a null, or our SSIDs and BSSIDs are equal
-            return ssid.equals(otherAp.ssid) && bssid.equals(otherAp.bssid);
-        }
-        
-        @Override
-        public int hashCode() {
-            return ssid.hashCode() + bssid.hashCode();
-        }
-
-        @Override
-        public String toString() {
-            return ssid + " (" + bssid + ")";
-        }
-    }
-
-    /**
-     * Performs a simple DNS "ping" by sending a "server status" query packet to
-     * the DNS server. As long as the server replies, we consider it a success.
-     * <p>
-     * We do not use a simple hostname lookup because that could be cached and
-     * the API may not differentiate between a time out and a failure lookup
-     * (which we really care about).
-     */
-    private static class DnsPinger {
-        
-        /** Number of bytes for the query */
-        private static final int DNS_QUERY_BASE_SIZE = 33;
-        
-        /** The DNS port */
-        private static final int DNS_PORT = 53;
-        
-        /** Used to generate IDs */
-        private static Random sRandom = new Random();
-
-        static boolean isDnsReachable(InetAddress dnsAddress, int timeout) {
-            DatagramSocket socket = null;
-            try {
-                socket = new DatagramSocket();
-
-                // Set some socket properties
-                socket.setSoTimeout(timeout);
-
-                byte[] buf = new byte[DNS_QUERY_BASE_SIZE];
-                fillQuery(buf);
-
-                // Send the DNS query
-
-                DatagramPacket packet = new DatagramPacket(buf,
-                        buf.length, dnsAddress, DNS_PORT);
-                socket.send(packet);
-
-                // Wait for reply (blocks for the above timeout)
-                DatagramPacket replyPacket = new DatagramPacket(buf, buf.length);
-                socket.receive(replyPacket);
-
-                // If a timeout occurred, an exception would have been thrown.  We got a reply!
-                return true;
-
-            } catch (SocketException e) {
-                if (V) {
-                    Slog.v(TAG, "DnsPinger.isReachable received SocketException", e);
-                }
-                return false;
-
-            } catch (UnknownHostException e) {
-                if (V) {
-                    Slog.v(TAG, "DnsPinger.isReachable is unable to resolve the DNS host", e);
-                }
-                return false;
-
-            } catch (SocketTimeoutException e) {
-                return false;
-                
-            } catch (IOException e) {
-                if (V) {
-                    Slog.v(TAG, "DnsPinger.isReachable got an IOException", e);
-                }
-                return false;
-                
-            } catch (Exception e) {
-                if (V) {
-                    Slog.d(TAG, "DnsPinger.isReachable got an unknown exception", e);
-                }
-                return false;
-            } finally {
-                if (socket != null) {
-                    socket.close();
-                }
-            }
-        }
-        
-        private static void fillQuery(byte[] buf) {
-
-            /*
-             * See RFC2929 (though the bit tables in there are misleading for
-             * us. For example, the recursion desired bit is the 0th bit for us,
-             * but looking there it would appear as the 7th bit of the byte
-             */
-
-            // Make sure it's all zeroed out
-            for (int i = 0; i < buf.length; i++) buf[i] = 0;
-
-            // Form a query for www.android.com
-            
-            // [0-1] bytes are an ID, generate random ID for this query
-            buf[0] = (byte) sRandom.nextInt(256); 
-            buf[1] = (byte) sRandom.nextInt(256); 
-            
-            // [2-3] bytes are for flags.
-            buf[2] = 1; // Recursion desired
-
-            // [4-5] bytes are for the query count
-            buf[5] = 1; // One query 
-            
-            // [6-7] [8-9] [10-11] are all counts of other fields we don't use
-
-            // [12-15] for www
-            writeString(buf, 12, "www");
-            
-            // [16-23] for android
-            writeString(buf, 16, "android");
-            
-            // [24-27] for com
-            writeString(buf, 24, "com");
-            
-            // [29-30] bytes are for QTYPE, set to 1 
-            buf[30] = 1;
-
-            // [31-32] bytes are for QCLASS, set to 1 
-            buf[32] = 1;
-        }
-        
-        private static void writeString(byte[] buf, int startPos, String string) {
-            int pos = startPos;
-            
-            // Write the length first
-            buf[pos++] = (byte) string.length();
-            for (int i = 0; i < string.length(); i++) {
-                buf[pos++] = (byte) string.charAt(i);
-            }
-        }
+                Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1;
     }
 }
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index f6dd43a..a2019fc 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1131,7 +1131,9 @@
             | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
             | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
             | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.TYPE_VIEW_SELECTED
-            | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
+            | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
+            | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
+            | AccessibilityEvent.TYPE_VIEW_SCROLLED;
 
         private int mRetrievalAlowingWindowId;
 
diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java
index aab189a6..1af7015 100644
--- a/services/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/java/com/android/server/accessibility/TouchExplorer.java
@@ -26,7 +26,6 @@
 import android.os.Handler;
 import android.os.SystemClock;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 import android.view.WindowManagerPolicy;
@@ -73,15 +72,6 @@
     private static final int STATE_DRAGGING = 0x00000002;
     private static final int STATE_DELEGATING = 0x00000004;
 
-    // Human readable symbolic names for the states of the explorer.
-    private static final SparseArray<String> sStateSymbolicNames = new SparseArray<String>();
-    static {
-        SparseArray<String> symbolicNames = sStateSymbolicNames;
-        symbolicNames.append(STATE_TOUCH_EXPLORING, "STATE_TOUCH_EXPLORING");
-        symbolicNames.append(STATE_DRAGGING, "STATE_DRAGING");
-        symbolicNames.append(STATE_DELEGATING, "STATE_DELEGATING");
-    }
-
     // Invalid pointer ID.
     private static final int INVALID_POINTER_ID = -1;
 
@@ -189,7 +179,7 @@
         if (DEBUG) {
             Slog.d(LOG_TAG_RECEIVED, "Received event: " + event + ", policyFlags=0x"
                     + Integer.toHexString(policyFlags));
-            Slog.d(LOG_TAG_STATE, sStateSymbolicNames.get(mCurrentState));
+            Slog.d(LOG_TAG_STATE, getStateSymbolicName(mCurrentState));
         }
 
         // Keep track of the pointers's state.
@@ -708,8 +698,7 @@
     private void sendActionDownAndUp(MotionEvent prototype, int policyFlags) {
         final PointerProperties[] pointerProperties = mTempPointerProperties;
         final PointerCoords[] pointerCoords = mTempPointerCoords;
-        final int pointerId = mPointerTracker.getLastReceivedUpPointerId();
-        final int pointerIndex = prototype.findPointerIndex(pointerId);
+        final int pointerIndex = prototype.getActionIndex();
 
         // Send down.
         prototype.getPointerProperties(pointerIndex, pointerProperties[0]);
@@ -884,6 +873,25 @@
     }
 
     /**
+     * Gets the symbolic name of a state.
+     *
+     * @param state A state.
+     * @return The state symbolic name.
+     */
+    private static String getStateSymbolicName(int state) {
+        switch (state) {
+            case STATE_TOUCH_EXPLORING:
+                return "STATE_TOUCH_EXPLORING";
+            case STATE_DRAGGING:
+                return "STATE_DRAGGING";
+            case STATE_DELEGATING:
+                return "STATE_DELEGATING";
+            default:
+                throw new IllegalArgumentException("Unknown state: " + state);
+        }
+    }
+
+    /**
      * Helper class for tracking pointers and more specifically which of
      * them are currently down, which are active, and which are delivered
      * to the view hierarchy. The enclosing {@link TouchExplorer} uses the
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index 911cac2..5f6c963 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -189,8 +189,8 @@
         mDnsServers[1] = DNS_DEFAULT_SERVER2;
     }
 
-    public void interfaceLinkStatusChanged(String iface, boolean link) {
-        if (DEBUG) Log.d(TAG, "interfaceLinkStatusChanged " + iface + ", " + link);
+    public void interfaceStatusChanged(String iface, boolean up) {
+        if (DEBUG) Log.d(TAG, "interfaceStatusChanged " + iface + ", " + up);
         boolean found = false;
         boolean usb = false;
         if (isWifi(iface)) {
@@ -205,7 +205,7 @@
 
         synchronized (mIfaces) {
             TetherInterfaceSM sm = mIfaces.get(iface);
-            if (link) {
+            if (up) {
                 if (sm == null) {
                     sm = new TetherInterfaceSM(iface, mLooper, usb);
                     mIfaces.put(iface, sm);
@@ -220,6 +220,11 @@
         }
     }
 
+    public void interfaceLinkStateChanged(String iface, boolean up) {
+        if (DEBUG) Log.d(TAG, "interfaceLinkStateChanged " + iface + ", " + up);
+        interfaceStatusChanged(iface, up);
+    }
+
     private boolean isUsb(String iface) {
         for (String regex : mTetherableUsbRegexs) {
             if (iface.matches(regex)) return true;
diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java
index 47813f8..db3b61e 100644
--- a/services/java/com/android/server/connectivity/Vpn.java
+++ b/services/java/com/android/server/connectivity/Vpn.java
@@ -173,7 +173,11 @@
     }
 
     // INetworkManagementEventObserver.Stub
-    public void interfaceLinkStatusChanged(String name, boolean up) {
+    public void interfaceStatusChanged(String name, boolean up) {
+    }
+
+    // INetworkManagementEventObserver.Stub
+    public void interfaceLinkStateChanged(String name, boolean up) {
     }
 
     // INetworkManagementEventObserver.Stub
diff --git a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
index ffabb7b..dea67f3 100644
--- a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
+++ b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
@@ -94,6 +94,9 @@
      */
     public PhoneNumberFormattingTextWatcher(String countryCode) {
         if (countryCode == null) throw new IllegalArgumentException();
+        // TODO: remove this once CountryDetector.detectCountry().getCountryIso() is fixed to always
+        // return uppercase. Tracked at b/4941319.
+        countryCode = countryCode.toUpperCase(Locale.ENGLISH);
         mFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode);
     }
 
diff --git a/tests/BiDiTests/res/layout/view_group_margin_mixed.xml b/tests/BiDiTests/res/layout/view_group_margin_mixed.xml
new file mode 100644
index 0000000..5845b38
--- /dev/null
+++ b/tests/BiDiTests/res/layout/view_group_margin_mixed.xml
@@ -0,0 +1,241 @@
+<?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/view_group_margin_mixed"
+             android:layout_width="fill_parent"
+             android:layout_height="fill_parent"
+             android:background="#FFFFFFFF">
+
+    <LinearLayout android:orientation="vertical"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content">
+
+        <LinearLayout android:orientation="vertical"
+                      android:layout_width="match_parent"
+                      android:layout_height="wrap_content"
+                      android:layoutDirection="ltr">
+
+            <LinearLayout android:orientation="horizontal"
+                          android:layout_width="match_parent"
+                          android:layout_height="wrap_content"
+                          android:background="#FF0000FF">
+
+                <FrameLayout
+                        android:layout_width="80dp"
+                        android:layout_height="80dp"
+                        android:layout_marginLeft="0dip"
+                        android:layout_marginRight="0dip"
+                        android:layout_marginTop="0dip"
+                        android:layout_marginBottom="0dip"
+                        android:background="#FFFF0000">
+                </FrameLayout>
+
+                <FrameLayout
+                        android:layout_width="80dp"
+                        android:layout_height="80dp"
+                        android:background="#FF00FF00">
+                </FrameLayout>
+            </LinearLayout>
+
+            <LinearLayout android:orientation="horizontal"
+                          android:layout_width="match_parent"
+                          android:layout_height="wrap_content"
+                          android:background="#FF000000">
+
+                <FrameLayout
+                        android:layout_width="80dp"
+                        android:layout_height="80dp"
+                        android:layout_marginLeft="10dip"
+                        android:layout_marginStart="5dip"
+                        android:layout_marginRight="20dip"
+                        android:layout_marginEnd="5dip"
+                        android:layout_marginTop="5dip"
+                        android:layout_marginBottom="5dip"
+                        android:background="#FFFF0000">
+                </FrameLayout>
+
+                <FrameLayout
+                        android:layout_width="80dp"
+                        android:layout_height="80dp"
+                        android:background="#FF00FF00">
+                </FrameLayout>
+            </LinearLayout>
+
+        </LinearLayout>
+
+        <LinearLayout android:orientation="vertical"
+                      android:layout_width="match_parent"
+                      android:layout_height="wrap_content"
+                      android:layoutDirection="rtl">
+
+            <LinearLayout android:orientation="horizontal"
+                          android:layout_width="match_parent"
+                          android:layout_height="wrap_content"
+                          android:background="#FF0000FF">
+
+                <FrameLayout
+                        android:layout_width="80dp"
+                        android:layout_height="80dp"
+                        android:layout_marginLeft="0dip"
+                        android:layout_marginRight="0dip"
+                        android:layout_marginTop="0dip"
+                        android:layout_marginBottom="0dip"
+                        android:background="#FFFF0000">
+                </FrameLayout>
+
+                <FrameLayout
+                        android:layout_width="80dp"
+                        android:layout_height="80dp"
+                        android:background="#FF00FF00">
+                </FrameLayout>
+            </LinearLayout>
+
+            <LinearLayout android:orientation="horizontal"
+                          android:layout_width="match_parent"
+                          android:layout_height="wrap_content"
+                          android:background="#FF000000">
+
+                <FrameLayout
+                        android:layout_width="80dp"
+                        android:layout_height="80dp"
+                        android:layout_marginLeft="10dip"
+                        android:layout_marginStart="5dip"
+                        android:layout_marginRight="20dip"
+                        android:layout_marginEnd="5dip"
+                        android:layout_marginTop="5dip"
+                        android:layout_marginBottom="5dip"
+                        android:background="#FFFF0000">
+                </FrameLayout>
+
+                <FrameLayout
+                        android:layout_width="80dp"
+                        android:layout_height="80dp"
+                        android:background="#FF00FF00">
+                </FrameLayout>
+            </LinearLayout>
+
+        </LinearLayout>
+
+        <LinearLayout android:orientation="vertical"
+                      android:layout_width="match_parent"
+                      android:layout_height="wrap_content"
+                      android:layoutDirection="inherit">
+
+            <LinearLayout android:orientation="horizontal"
+                          android:layout_width="match_parent"
+                          android:layout_height="wrap_content"
+                          android:background="#FF0000FF">
+
+                <FrameLayout
+                        android:layout_width="80dp"
+                        android:layout_height="80dp"
+                        android:layout_marginLeft="0dip"
+                        android:layout_marginRight="0dip"
+                        android:layout_marginTop="0dip"
+                        android:layout_marginBottom="0dip"
+                        android:background="#FFFF0000">
+                </FrameLayout>
+
+                <FrameLayout
+                        android:layout_width="80dp"
+                        android:layout_height="80dp"
+                        android:background="#FF00FF00">
+                </FrameLayout>
+            </LinearLayout>
+
+            <LinearLayout android:orientation="horizontal"
+                          android:layout_width="match_parent"
+                          android:layout_height="wrap_content"
+                          android:background="#FF000000">
+
+                <FrameLayout
+                        android:layout_width="80dp"
+                        android:layout_height="80dp"
+                        android:layout_marginLeft="10dip"
+                        android:layout_marginStart="5dip"
+                        android:layout_marginRight="20dip"
+                        android:layout_marginEnd="5dip"
+                        android:layout_marginTop="5dip"
+                        android:layout_marginBottom="5dip"
+                        android:background="#FFFF0000">
+                </FrameLayout>
+
+                <FrameLayout
+                        android:layout_width="80dp"
+                        android:layout_height="80dp"
+                        android:background="#FF00FF00">
+                </FrameLayout>
+            </LinearLayout>
+
+        </LinearLayout>
+
+        <LinearLayout android:orientation="vertical"
+                      android:layout_width="match_parent"
+                      android:layout_height="wrap_content"
+                      android:layoutDirection="locale">
+
+            <LinearLayout android:orientation="horizontal"
+                          android:layout_width="match_parent"
+                          android:layout_height="wrap_content"
+                          android:background="#FF0000FF">
+
+                <FrameLayout
+                        android:layout_width="80dp"
+                        android:layout_height="80dp"
+                        android:layout_marginLeft="0dip"
+                        android:layout_marginRight="0dip"
+                        android:layout_marginTop="0dip"
+                        android:layout_marginBottom="0dip"
+                        android:background="#FFFF0000">
+                </FrameLayout>
+
+                <FrameLayout
+                        android:layout_width="80dp"
+                        android:layout_height="80dp"
+                        android:background="#FF00FF00">
+                </FrameLayout>
+            </LinearLayout>
+
+            <LinearLayout android:orientation="horizontal"
+                          android:layout_width="match_parent"
+                          android:layout_height="wrap_content"
+                          android:background="#FF000000">
+
+                <FrameLayout
+                        android:layout_width="80dp"
+                        android:layout_height="80dp"
+                        android:layout_marginLeft="10dip"
+                        android:layout_marginStart="5dip"
+                        android:layout_marginRight="20dip"
+                        android:layout_marginEnd="5dip"
+                        android:layout_marginTop="5dip"
+                        android:layout_marginBottom="5dip"
+                        android:background="#FFFF0000">
+                </FrameLayout>
+
+                <FrameLayout
+                        android:layout_width="80dp"
+                        android:layout_height="80dp"
+                        android:background="#FF00FF00">
+                </FrameLayout>
+            </LinearLayout>
+
+        </LinearLayout>
+
+    </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 c2683c2..b1e494a 100644
--- a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
@@ -106,29 +106,34 @@
         addItem(result, "Linear LTR", BiDiTestLinearLayoutLtr.class, R.id.linear_layout_ltr);
         addItem(result, "Linear RTL", BiDiTestLinearLayoutRtl.class, R.id.linear_layout_rtl);
         addItem(result, "Linear LOC", BiDiTestLinearLayoutLocale.class, R.id.linear_layout_locale);
-        
+
         addItem(result, "Frame LTR", BiDiTestFrameLayoutLtr.class, R.id.frame_layout_ltr);
         addItem(result, "Frame RTL", BiDiTestFrameLayoutRtl.class, R.id.frame_layout_rtl);
         addItem(result, "Frame LOC", BiDiTestFrameLayoutLocale.class, R.id.frame_layout_locale);
-        
+
         addItem(result, "Relative LTR", BiDiTestRelativeLayoutLtr.class, R.id.relative_layout_ltr);
         addItem(result, "Relative RTL", BiDiTestRelativeLayoutRtl.class, R.id.relative_layout_rtl);
-        
+
         addItem(result, "Relative2 LTR", BiDiTestRelativeLayout2Ltr.class, R.id.relative_layout_2_ltr);
         addItem(result, "Relative2 RTL", BiDiTestRelativeLayout2Rtl.class, R.id.relative_layout_2_rtl);
         addItem(result, "Relative2 LOC", BiDiTestRelativeLayout2Locale.class, R.id.relative_layout_2_locale);
-        
+
         addItem(result, "Table LTR", BiDiTestTableLayoutLtr.class, R.id.table_layout_ltr);
         addItem(result, "Table RTL", BiDiTestTableLayoutRtl.class, R.id.table_layout_rtl);
         addItem(result, "Table LOC", BiDiTestTableLayoutLocale.class, R.id.table_layout_locale);
 
+        addItem(result, "ViewPadding", BiDiTestViewPadding.class, R.id.view_padding);
+        addItem(result, "ViewPadding MIXED", BiDiTestViewPaddingMixed.class, R.id.view_padding_mixed);
+
+        addItem(result, "Padding", BiDiTestViewPadding.class, R.id.view_padding);
+        addItem(result, "Padding MIXED", BiDiTestViewPaddingMixed.class, R.id.view_padding_mixed);
+
+        addItem(result, "Margin MIXED", BiDiTestViewGroupMarginMixed.class, R.id.view_group_margin_mixed);
+
         addItem(result, "TextView LTR", BiDiTestTextViewLtr.class, R.id.textview_ltr);
         addItem(result, "TextView RTL", BiDiTestTextViewRtl.class, R.id.textview_rtl);
         addItem(result, "TextView LOC", BiDiTestTextViewLocale.class, R.id.textview_locale);
 
-        addItem(result, "ViewPadding", BiDiTestViewPadding.class, R.id.view_padding);
-        addItem(result, "ViewPadding MIXED", BiDiTestViewPaddingMixed.class, R.id.view_padding_mixed);
-
         return result;
     }
 
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestViewGroupMarginMixed.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestViewGroupMarginMixed.java
new file mode 100644
index 0000000..ce8c380
--- /dev/null
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestViewGroupMarginMixed.java
@@ -0,0 +1,17 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+package com.android.bidi;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class BiDiTestViewGroupMarginMixed extends Fragment {
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.view_group_margin_mixed, container, false);
+    }
+}
diff --git a/tests/CoreTests/MODULE_LICENSE_APACHE2 b/tests/CoreTests/MODULE_LICENSE_APACHE2
deleted file mode 100644
index e69de29..0000000
--- a/tests/CoreTests/MODULE_LICENSE_APACHE2
+++ /dev/null
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index c650021..7aa0617 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -31,6 +31,15 @@
         android:hardwareAccelerated="true">
 
         <activity
+                android:name="OpaqueActivity"
+                android:label="_Opaque">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
                 android:name="GetBitmapActivity"
                 android:label="_GetBitmap">
             <intent-filter>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java
index 723f3e8..e1ca756 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java
@@ -19,13 +19,17 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.app.Activity;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.SurfaceTexture;
-import android.opengl.GLES20;
+import android.opengl.GLUtils;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.TextureView;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
 import javax.microedition.khronos.egl.EGL10;
@@ -36,6 +40,12 @@
 import javax.microedition.khronos.egl.EGLSurface;
 import javax.microedition.khronos.opengles.GL;
 
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import static android.opengl.GLES20.*;
+
 @SuppressWarnings({"UnusedDeclaration"})
 public class GLTextureViewActivity extends Activity implements TextureView.SurfaceTextureListener {
     private RenderThread mRenderThread;
@@ -48,12 +58,14 @@
         mTextureView = new TextureView(this);
         mTextureView.setSurfaceTextureListener(this);
 
-        setContentView(mTextureView, new FrameLayout.LayoutParams(500, 400, Gravity.CENTER));
+        setContentView(mTextureView, new FrameLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
+                Gravity.CENTER));
     }
 
     @Override
     public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
-        mRenderThread = new RenderThread(surface);
+        mRenderThread = new RenderThread(getResources(), surface);
         mRenderThread.start();
 
         mTextureView.setCameraDistance(5000);
@@ -93,13 +105,12 @@
         private static final String LOG_TAG = "GLTextureView";
 
         static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
-        static final int EGL_SURFACE_TYPE = 0x3033;
-        static final int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400;
         static final int EGL_OPENGL_ES2_BIT = 4;
 
         private volatile boolean mFinished;
 
-        private SurfaceTexture mSurface;
+        private final Resources mResources;
+        private final SurfaceTexture mSurface;
         
         private EGL10 mEgl;
         private EGLDisplay mEglDisplay;
@@ -108,26 +119,96 @@
         private EGLSurface mEglSurface;
         private GL mGL;
 
-        RenderThread(SurfaceTexture surface) {
+        RenderThread(Resources resources, SurfaceTexture surface) {
+            mResources = resources;
             mSurface = surface;
         }
 
+        private static final String sSimpleVS =
+                "attribute vec4 position;\n" +
+                "attribute vec2 texCoords;\n" +
+                "varying vec2 outTexCoords;\n" +
+                "\nvoid main(void) {\n" +
+                "    outTexCoords = texCoords;\n" +
+                "    gl_Position = position;\n" +
+                "}\n\n";
+        private static final String sSimpleFS =
+                "precision mediump float;\n\n" +
+                "varying vec2 outTexCoords;\n" +
+                "uniform sampler2D texture;\n" +
+                "\nvoid main(void) {\n" +
+                "    gl_FragColor = texture2D(texture, outTexCoords);\n" +
+                "}\n\n";
+
+        private static final int FLOAT_SIZE_BYTES = 4;
+        private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
+        private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
+        private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
+        private final float[] mTriangleVerticesData = {
+                // X, Y, Z, U, V
+                -1.0f, -1.0f, 0, 0.f, 0.f,
+                1.0f, -1.0f, 0, 1.f, 0.f,
+                -1.0f,  1.0f, 0, 0.f, 1.f,
+                1.0f,   1.0f, 0, 1.f, 1.f,
+        };
+
         @Override
         public void run() {
             initGL();
+            
+            FloatBuffer triangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length
+                    * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
+            triangleVertices.put(mTriangleVerticesData).position(0);
 
-            float red = 1.0f;
+            int texture = loadTexture(R.drawable.large_photo);
+            int program = buildProgram(sSimpleVS, sSimpleFS);
+
+            int attribPosition = glGetAttribLocation(program, "position");
+            checkGlError();
+
+            int attribTexCoords = glGetAttribLocation(program, "texCoords");
+            checkGlError();
+
+            int uniformTexture = glGetUniformLocation(program, "texture");
+            checkGlError();
+
+            glBindTexture(GL_TEXTURE_2D, texture);
+            checkGlError();
+
+            glUseProgram(program);
+            checkGlError();
+
+            glEnableVertexAttribArray(attribPosition);
+            checkGlError();
+
+            glEnableVertexAttribArray(attribTexCoords);
+            checkGlError();
+
+            glUniform1i(texture, 0);
+            checkGlError();
+            
             while (!mFinished) {
                 checkCurrent();
 
                 Log.d(LOG_TAG, "Rendering frame");
 
-                GLES20.glClearColor(red, 0.0f, 0.0f, 1.0f);
+                glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
                 checkGlError();
 
-                GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+                glClear(GL_COLOR_BUFFER_BIT);
                 checkGlError();
 
+                // drawQuad
+                triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
+                glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false,
+                        TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
+
+                triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
+                glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false,
+                        TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
+
+                glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
                 if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
                     throw new RuntimeException("Cannot swap buffers");
                 }
@@ -138,14 +219,90 @@
                 } catch (InterruptedException e) {
                     // Ignore
                 }
-
-                red += 0.021f;
-                if (red > 1.0f) red = 0.0f;
             }
 
             finishGL();
         }
 
+        private int loadTexture(int resource) {
+            int[] textures = new int[1];
+
+            glActiveTexture(GL_TEXTURE0);
+            glGenTextures(1, textures, 0);
+            checkGlError();
+
+            int texture = textures[0];
+            glBindTexture(GL_TEXTURE_2D, texture);
+            checkGlError();
+            
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+            Bitmap bitmap = BitmapFactory.decodeResource(mResources, resource);
+
+            GLUtils.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap, GL_UNSIGNED_BYTE, 0);
+            checkGlError();
+
+            bitmap.recycle();
+
+            return texture;
+        }
+        
+        private int buildProgram(String vertex, String fragment) {
+            int vertexShader = buildShader(vertex, GL_VERTEX_SHADER);
+            if (vertexShader == 0) return 0;
+
+            int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER);
+            if (fragmentShader == 0) return 0;
+
+            int program = glCreateProgram();
+            glAttachShader(program, vertexShader);
+            checkGlError();
+
+            glAttachShader(program, fragmentShader);
+            checkGlError();
+
+            glLinkProgram(program);
+            checkGlError();
+
+            int[] status = new int[1];
+            glGetProgramiv(program, GL_LINK_STATUS, status, 0);
+            if (status[0] != GL_TRUE) {
+                String error = glGetProgramInfoLog(program);
+                Log.d(LOG_TAG, "Error while linking program:\n" + error);
+                glDeleteShader(vertexShader);
+                glDeleteShader(fragmentShader);
+                glDeleteProgram(program);
+                return 0;
+            }
+
+            return program;
+        }
+        
+        private int buildShader(String source, int type) {
+            int shader = glCreateShader(type);
+
+            glShaderSource(shader, source);
+            checkGlError();
+
+            glCompileShader(shader);
+            checkGlError();
+
+            int[] status = new int[1];
+            glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0);
+            if (status[0] != GL_TRUE) {
+                String error = glGetShaderInfoLog(shader);
+                Log.d(LOG_TAG, "Error while compiling shader:\n" + error);
+                glDeleteShader(shader);
+                return 0;
+            }
+            
+            return shader;
+        }
+
         private void checkEglError() {
             int error = mEgl.eglGetError();
             if (error != EGL10.EGL_SUCCESS) {
@@ -154,8 +311,8 @@
         }
 
         private void checkGlError() {
-            int error = GLES20.glGetError();
-            if (error != GLES20.GL_NO_ERROR) {
+            int error = glGetError();
+            if (error != GL_NO_ERROR) {
                 Log.w(LOG_TAG, "GL error = 0x" + Integer.toHexString(error));
             }
         }
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/OpaqueActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/OpaqueActivity.java
new file mode 100644
index 0000000..af45f29
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/OpaqueActivity.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.PorterDuff;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.FrameLayout;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class OpaqueActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        final OpaqueView view = new OpaqueView(this);
+        setContentView(view, new FrameLayout.LayoutParams(100, 100, Gravity.CENTER));
+    }
+
+    public static class OpaqueView extends View {
+        public OpaqueView(Context c) {
+            super(c);
+            setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    invalidate();
+                    Log.d("OpaqueView", "Invalidate");
+                }
+            });
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            canvas.drawColor(0xffff0000, PorterDuff.Mode.SRC);
+        }
+
+        @Override
+        public boolean isOpaque() {
+            return true;
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java
index 01ee90a..c857ded 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java
@@ -17,7 +17,6 @@
 package com.android.test.hwui;
 
 import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.app.Activity;
 import android.graphics.SurfaceTexture;
 import android.hardware.Camera;
diff --git a/voip/java/android/net/sip/ISipSessionListener.aidl b/voip/java/android/net/sip/ISipSessionListener.aidl
index 5920bca..690700c 100644
--- a/voip/java/android/net/sip/ISipSessionListener.aidl
+++ b/voip/java/android/net/sip/ISipSessionListener.aidl
@@ -72,6 +72,14 @@
     void onCallBusy(in ISipSession session);
 
     /**
+     * Called when the call is being transferred to a new one.
+     *
+     * @param newSession the new session that the call will be transferred to
+     * @param sessionDescription the new peer's session description
+     */
+    void onCallTransferring(in ISipSession newSession, String sessionDescription);
+
+    /**
      * Called when an error occurs during session initialization and
      * termination.
      *
diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java
index b46f8268..2666b69 100644
--- a/voip/java/android/net/sip/SipAudioCall.java
+++ b/voip/java/android/net/sip/SipAudioCall.java
@@ -170,6 +170,7 @@
     private SipProfile mLocalProfile;
     private SipAudioCall.Listener mListener;
     private SipSession mSipSession;
+    private SipSession mTransferringSession;
 
     private long mSessionId = System.currentTimeMillis();
     private String mPeerSd;
@@ -347,6 +348,27 @@
         }
     }
 
+    private synchronized void transferToNewSession() {
+        if (mTransferringSession == null) return;
+        SipSession origin = mSipSession;
+        mSipSession = mTransferringSession;
+        mTransferringSession = null;
+
+        // stop the replaced call.
+        if (mAudioStream != null) {
+            mAudioStream.join(null);
+        } else {
+            try {
+                mAudioStream = new AudioStream(InetAddress.getByName(
+                        getLocalIp()));
+            } catch (Throwable t) {
+                Log.i(TAG, "transferToNewSession(): " + t);
+            }
+        }
+        if (origin != null) origin.endCall();
+        startAudio();
+    }
+
     private SipSession.Listener createListener() {
         return new SipSession.Listener() {
             @Override
@@ -404,6 +426,13 @@
                 mPeerSd = sessionDescription;
                 Log.v(TAG, "onCallEstablished()" + mPeerSd);
 
+                // TODO: how to notify the UI that the remote party is changed
+                if ((mTransferringSession != null)
+                        && (session == mTransferringSession)) {
+                    transferToNewSession();
+                    return;
+                }
+
                 Listener listener = mListener;
                 if (listener != null) {
                     try {
@@ -420,7 +449,17 @@
 
             @Override
             public void onCallEnded(SipSession session) {
-                Log.d(TAG, "sip call ended: " + session);
+                Log.d(TAG, "sip call ended: " + session + " mSipSession:" + mSipSession);
+                // reset the trasnferring session if it is the one.
+                if (session == mTransferringSession) {
+                    mTransferringSession = null;
+                    return;
+                }
+                // or ignore the event if the original session is being
+                // transferred to the new one.
+                if ((mTransferringSession != null) ||
+                    (session != mSipSession)) return;
+
                 Listener listener = mListener;
                 if (listener != null) {
                     try {
@@ -489,6 +528,22 @@
             public void onRegistrationDone(SipSession session, int duration) {
                 // irrelevant
             }
+
+            @Override
+            public void onCallTransferring(SipSession newSession,
+                    String sessionDescription) {
+                Log.v(TAG, "onCallTransferring mSipSession:"
+                        + mSipSession + " newSession:" + newSession);
+                mTransferringSession = newSession;
+                // session changing request
+                try {
+                    String answer = createAnswer(sessionDescription).encode();
+                    newSession.answerCall(answer, SESSION_TIMEOUT);
+                } catch (Throwable e) {
+                    Log.e(TAG, "onCallTransferring()", e);
+                    newSession.endCall();
+                }
+            }
         };
     }
 
diff --git a/voip/java/android/net/sip/SipSession.java b/voip/java/android/net/sip/SipSession.java
index 5629b3c..5ba1626 100644
--- a/voip/java/android/net/sip/SipSession.java
+++ b/voip/java/android/net/sip/SipSession.java
@@ -160,6 +160,17 @@
         }
 
         /**
+         * Called when the call is being transferred to a new one.
+         *
+         * @hide
+         * @param newSession the new session that the call will be transferred to
+         * @param sessionDescription the new peer's session description
+         */
+        public void onCallTransferring(SipSession newSession,
+                String sessionDescription) {
+        }
+
+        /**
          * Called when an error occurs during session initialization and
          * termination.
          *
@@ -489,6 +500,16 @@
                 }
             }
 
+            public void onCallTransferring(ISipSession session,
+                    String sessionDescription) {
+                if (mListener != null) {
+                    mListener.onCallTransferring(
+                            new SipSession(session, SipSession.this.mListener),
+                            sessionDescription);
+
+                }
+            }
+
             public void onCallChangeFailed(ISipSession session, int errorCode,
                     String message) {
                 if (mListener != null) {
diff --git a/voip/java/android/net/sip/SipSessionAdapter.java b/voip/java/android/net/sip/SipSessionAdapter.java
index 86aca37..f538983 100644
--- a/voip/java/android/net/sip/SipSessionAdapter.java
+++ b/voip/java/android/net/sip/SipSessionAdapter.java
@@ -42,6 +42,10 @@
     public void onCallBusy(ISipSession session) {
     }
 
+    public void onCallTransferring(ISipSession session,
+            String sessionDescription) {
+    }
+
     public void onCallChangeFailed(ISipSession session, int errorCode,
             String message) {
     }
diff --git a/voip/java/com/android/server/sip/SipHelper.java b/voip/java/com/android/server/sip/SipHelper.java
index 018e6de..47950e3 100644
--- a/voip/java/com/android/server/sip/SipHelper.java
+++ b/voip/java/com/android/server/sip/SipHelper.java
@@ -314,7 +314,7 @@
         }
     }
 
-    private ServerTransaction getServerTransaction(RequestEvent event)
+    public ServerTransaction getServerTransaction(RequestEvent event)
             throws SipException {
         ServerTransaction transaction = event.getServerTransaction();
         if (transaction == null) {
diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java
index 2d0dd9c..481e306 100644
--- a/voip/java/com/android/server/sip/SipSessionGroup.java
+++ b/voip/java/com/android/server/sip/SipSessionGroup.java
@@ -21,6 +21,8 @@
 import gov.nist.javax.sip.header.SIPHeaderNames;
 import gov.nist.javax.sip.header.ProxyAuthenticate;
 import gov.nist.javax.sip.header.WWWAuthenticate;
+import gov.nist.javax.sip.header.extensions.ReferredByHeader;
+import gov.nist.javax.sip.header.extensions.ReplacesHeader;
 import gov.nist.javax.sip.message.SIPMessage;
 
 import android.net.sip.ISipSession;
@@ -365,24 +367,85 @@
             super(listener);
         }
 
+        private SipSessionImpl createNewSession(RequestEvent event,
+                ISipSessionListener listener, ServerTransaction transaction)
+                throws SipException {
+            SipSessionImpl newSession = new SipSessionImpl(listener);
+            newSession.mServerTransaction = transaction;
+            newSession.mState = SipSession.State.INCOMING_CALL;
+            newSession.mDialog = newSession.mServerTransaction.getDialog();
+            newSession.mInviteReceived = event;
+            newSession.mPeerProfile = createPeerProfile(event.getRequest());
+            newSession.mPeerSessionDescription =
+                    extractContent(event.getRequest());
+            return newSession;
+        }
+
+        private int processInviteWithReplaces(RequestEvent event,
+                ReplacesHeader replaces) {
+            String callId = replaces.getCallId();
+            SipSessionImpl session = mSessionMap.get(callId);
+            if (session == null) {
+                return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST;
+            }
+
+            Dialog dialog = session.mDialog;
+            if (dialog == null) return Response.DECLINE;
+
+            if (!dialog.getLocalTag().equals(replaces.getToTag()) ||
+                    !dialog.getRemoteTag().equals(replaces.getFromTag())) {
+                // No match is found, returns 481.
+                return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST;
+            }
+
+            ReferredByHeader referredBy = (ReferredByHeader) event.getRequest()
+                    .getHeader(ReferredByHeader.NAME);
+            if ((referredBy == null) ||
+                    !dialog.getRemoteParty().equals(referredBy.getAddress())) {
+                return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST;
+            }
+            return Response.OK;
+        }
+
+        private void processNewInviteRequest(RequestEvent event)
+                throws SipException {
+            ReplacesHeader replaces = (ReplacesHeader) event.getRequest()
+                    .getHeader(ReplacesHeader.NAME);
+            SipSessionImpl newSession = null;
+            if (replaces != null) {
+                int response = processInviteWithReplaces(event, replaces);
+                if (DEBUG) {
+                    Log.v(TAG, "ReplacesHeader: " + replaces
+                            + " response=" + response);
+                }
+                if (response == Response.OK) {
+                    SipSessionImpl replacedSession =
+                            mSessionMap.get(replaces.getCallId());
+                    // got INVITE w/ replaces request.
+                    newSession = createNewSession(event,
+                            replacedSession.mProxy.getListener(),
+                            mSipHelper.getServerTransaction(event));
+                    newSession.mProxy.onCallTransferring(newSession,
+                            newSession.mPeerSessionDescription);
+                } else {
+                    mSipHelper.sendResponse(event, response);
+                }
+            } else {
+                // New Incoming call.
+                newSession = createNewSession(event, mProxy,
+                        mSipHelper.sendRinging(event, generateTag()));
+                mProxy.onRinging(newSession, newSession.mPeerProfile,
+                        newSession.mPeerSessionDescription);
+            }
+            if (newSession != null) addSipSession(newSession);
+        }
+
         public boolean process(EventObject evt) throws SipException {
             if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~   " + this + ": "
                     + SipSession.State.toString(mState) + ": processing "
                     + log(evt));
             if (isRequestEvent(Request.INVITE, evt)) {
-                RequestEvent event = (RequestEvent) evt;
-                SipSessionImpl newSession = new SipSessionImpl(mProxy);
-                newSession.mState = SipSession.State.INCOMING_CALL;
-                newSession.mServerTransaction = mSipHelper.sendRinging(event,
-                        generateTag());
-                newSession.mDialog = newSession.mServerTransaction.getDialog();
-                newSession.mInviteReceived = event;
-                newSession.mPeerProfile = createPeerProfile(event.getRequest());
-                newSession.mPeerSessionDescription =
-                        extractContent(event.getRequest());
-                addSipSession(newSession);
-                mProxy.onRinging(newSession, newSession.mPeerProfile,
-                        newSession.mPeerSessionDescription);
+                processNewInviteRequest((RequestEvent) evt);
                 return true;
             } else if (isRequestEvent(Request.OPTIONS, evt)) {
                 mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
diff --git a/voip/java/com/android/server/sip/SipSessionListenerProxy.java b/voip/java/com/android/server/sip/SipSessionListenerProxy.java
index f8be0a8..8655a3a 100644
--- a/voip/java/com/android/server/sip/SipSessionListenerProxy.java
+++ b/voip/java/com/android/server/sip/SipSessionListenerProxy.java
@@ -110,6 +110,20 @@
         });
     }
 
+    public void onCallTransferring(final ISipSession newSession,
+            final String sessionDescription) {
+        if (mListener == null) return;
+        proxy(new Runnable() {
+            public void run() {
+                try {
+                    mListener.onCallTransferring(newSession, sessionDescription);
+                } catch (Throwable t) {
+                    handle(t, "onCallTransferring()");
+                }
+            }
+        });
+    }
+
     public void onCallBusy(final ISipSession session) {
         if (mListener == null) return;
         proxy(new Runnable() {