Merge "System server: Add Bluetooth to native processes" into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index e1569f1..e91d017 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6073,6 +6073,7 @@
     field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff
     field public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; // 0x0
     field public static final int KEYGUARD_DISABLE_FINGERPRINT = 32; // 0x20
+    field public static final int KEYGUARD_DISABLE_REMOTE_INPUT = 64; // 0x40
     field public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 2; // 0x2
     field public static final int KEYGUARD_DISABLE_SECURE_NOTIFICATIONS = 4; // 0x4
     field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10
diff --git a/api/system-current.txt b/api/system-current.txt
index 55f8b6f..8781663 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6240,6 +6240,7 @@
     field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff
     field public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; // 0x0
     field public static final int KEYGUARD_DISABLE_FINGERPRINT = 32; // 0x20
+    field public static final int KEYGUARD_DISABLE_REMOTE_INPUT = 64; // 0x40
     field public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 2; // 0x2
     field public static final int KEYGUARD_DISABLE_SECURE_NOTIFICATIONS = 4; // 0x4
     field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10
diff --git a/api/test-current.txt b/api/test-current.txt
index 1376ef1..410eb88 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6078,6 +6078,7 @@
     field public static final int KEYGUARD_DISABLE_FEATURES_ALL = 2147483647; // 0x7fffffff
     field public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; // 0x0
     field public static final int KEYGUARD_DISABLE_FINGERPRINT = 32; // 0x20
+    field public static final int KEYGUARD_DISABLE_REMOTE_INPUT = 64; // 0x40
     field public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 2; // 0x2
     field public static final int KEYGUARD_DISABLE_SECURE_NOTIFICATIONS = 4; // 0x4
     field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e54edf2..cfffe34 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4183,12 +4183,10 @@
                         // window is being added.
                         r.mPendingRemoveWindow = r.window;
                         r.mPendingRemoveWindowManager = wm;
-                        if (r.mPreserveWindow) {
-                            // We can only keep the part of the view hierarchy that we control,
-                            // everything else must be removed, because it might not be able to
-                            // behave properly when activity is relaunching.
-                            r.window.clearContentView();
-                        }
+                        // We can only keep the part of the view hierarchy that we control,
+                        // everything else must be removed, because it might not be able to
+                        // behave properly when activity is relaunching.
+                        r.window.clearContentView();
                     } else {
                         wm.removeViewImmediate(v);
                     }
@@ -4196,6 +4194,13 @@
                 if (wtoken != null && r.mPendingRemoveWindow == null) {
                     WindowManagerGlobal.getInstance().closeAll(wtoken,
                             r.activity.getClass().getName(), "Activity");
+                } else if (r.mPendingRemoveWindow != null) {
+                    // We're preserving only one window, others should be closed so app views
+                    // will be detached before the final tear down. It should be done now because
+                    // some components (e.g. WebView) rely on detach callbacks to perform receiver
+                    // unregister and other cleanup.
+                    WindowManagerGlobal.getInstance().closeAllExceptView(token, v,
+                            r.activity.getClass().getName(), "Activity");
                 }
                 r.activity.mDecor = null;
             }
@@ -5205,10 +5210,6 @@
             } else {
                 Log.e(TAG, "Unable to setupGraphicsSupport due to missing code-cache directory");
             }
-
-            // Add the lib dir path to hardware renderer so that vulkan layers
-            // can be searched for within that directory.
-            ThreadedRenderer.setLibDir(data.info.getLibDir());
         }
 
         // Install the Network Security Config Provider. This must happen before the application
diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java
index d89e186..c869944 100644
--- a/core/java/android/app/ApplicationLoaders.java
+++ b/core/java/android/app/ApplicationLoaders.java
@@ -65,6 +65,8 @@
 
                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
+                setupVulkanLayerPath(pathClassloader, librarySearchPath);
+
                 mLoaders.put(zip, pathClassloader);
                 return pathClassloader;
             }
@@ -76,6 +78,8 @@
         }
     }
 
+    private static native void setupVulkanLayerPath(ClassLoader classLoader, String librarySearchPath);
+
     /**
      * Adds a new path the classpath of the given loader.
      * @throws IllegalStateException if the provided class loader is not a {@link PathClassLoader}.
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 451acf3..f78ed1e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2571,6 +2571,11 @@
     public static final int KEYGUARD_DISABLE_FINGERPRINT = 1 << 5;
 
     /**
+     * Disable text entry into notifications on secure keyguard screens (e.g. PIN/Pattern/Password).
+     */
+    public static final int KEYGUARD_DISABLE_REMOTE_INPUT = 1 << 6;
+
+    /**
      * Disable all current and future keyguard customizations.
      */
     public static final int KEYGUARD_DISABLE_FEATURES_ALL = 0x7fffffff;
diff --git a/core/java/android/content/ContentValues.java b/core/java/android/content/ContentValues.java
index 0d25f80..7ed827c 100644
--- a/core/java/android/content/ContentValues.java
+++ b/core/java/android/content/ContentValues.java
@@ -230,11 +230,12 @@
     }
 
     /**
-     * Gets a value. Valid value types are {@link String}, {@link Boolean}, and
-     * {@link Number} implementations.
+     * Gets a value. Valid value types are {@link String}, {@link Boolean},
+     * {@link Number}, and {@code byte[]} implementations.
      *
      * @param key the value to get
-     * @return the data for the value
+     * @return the data for the value, or {@code null} if the value is missing or if {@code null}
+     *         was previously added with the given {@code key}
      */
     public Object get(String key) {
         return mValues.get(key);
@@ -255,7 +256,7 @@
      * Gets a value and converts it to a Long.
      *
      * @param key the value to get
-     * @return the Long value, or null if the value is missing or cannot be converted
+     * @return the Long value, or {@code null} if the value is missing or cannot be converted
      */
     public Long getAsLong(String key) {
         Object value = mValues.get(key);
@@ -280,7 +281,7 @@
      * Gets a value and converts it to an Integer.
      *
      * @param key the value to get
-     * @return the Integer value, or null if the value is missing or cannot be converted
+     * @return the Integer value, or {@code null} if the value is missing or cannot be converted
      */
     public Integer getAsInteger(String key) {
         Object value = mValues.get(key);
@@ -305,7 +306,7 @@
      * Gets a value and converts it to a Short.
      *
      * @param key the value to get
-     * @return the Short value, or null if the value is missing or cannot be converted
+     * @return the Short value, or {@code null} if the value is missing or cannot be converted
      */
     public Short getAsShort(String key) {
         Object value = mValues.get(key);
@@ -330,7 +331,7 @@
      * Gets a value and converts it to a Byte.
      *
      * @param key the value to get
-     * @return the Byte value, or null if the value is missing or cannot be converted
+     * @return the Byte value, or {@code null} if the value is missing or cannot be converted
      */
     public Byte getAsByte(String key) {
         Object value = mValues.get(key);
@@ -355,7 +356,7 @@
      * Gets a value and converts it to a Double.
      *
      * @param key the value to get
-     * @return the Double value, or null if the value is missing or cannot be converted
+     * @return the Double value, or {@code null} if the value is missing or cannot be converted
      */
     public Double getAsDouble(String key) {
         Object value = mValues.get(key);
@@ -380,7 +381,7 @@
      * Gets a value and converts it to a Float.
      *
      * @param key the value to get
-     * @return the Float value, or null if the value is missing or cannot be converted
+     * @return the Float value, or {@code null} if the value is missing or cannot be converted
      */
     public Float getAsFloat(String key) {
         Object value = mValues.get(key);
@@ -405,7 +406,7 @@
      * Gets a value and converts it to a Boolean.
      *
      * @param key the value to get
-     * @return the Boolean value, or null if the value is missing or cannot be converted
+     * @return the Boolean value, or {@code null} if the value is missing or cannot be converted
      */
     public Boolean getAsBoolean(String key) {
         Object value = mValues.get(key);
@@ -428,7 +429,8 @@
      * any other types to byte arrays.
      *
      * @param key the value to get
-     * @return the byte[] value, or null is the value is missing or not a byte[]
+     * @return the {@code byte[]} value, or {@code null} is the value is missing or not a
+     *         {@code byte[]}
      */
     public byte[] getAsByteArray(String key) {
         Object value = mValues.get(key);
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 50e7356..0f64b92 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -1431,10 +1431,9 @@
      *            row. The keys should be the column names and the values the
      *            column values
      * @param conflictAlgorithm for insert conflict resolver
-     * @return the row ID of the newly inserted row
-     * OR the primary key of the existing row if the input param 'conflictAlgorithm' =
-     * {@link #CONFLICT_IGNORE}
-     * OR -1 if any error
+     * @return the row ID of the newly inserted row OR <code>-1</code> if either the
+     *            input parameter <code>conflictAlgorithm</code> = {@link #CONFLICT_IGNORE}
+     *            or an error occurred.
      */
     public long insertWithOnConflict(String table, String nullColumnHack,
             ContentValues initialValues, int conflictAlgorithm) {
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 1cd2fb0..e650d95 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -252,17 +252,6 @@
     }
 
     /**
-     * Sets the library directory to use as a search path for vulkan layers.
-     *
-     * @param libDir A directory that contains vulkan layers
-     *
-     * @hide
-     */
-    public static void setLibDir(String libDir) {
-        ThreadedRenderer.setupVulkanLayerPath(libDir);
-    }
-
-    /**
      * Creates a hardware renderer using OpenGL.
      *
      * @param translucent True if the surface is translucent, false otherwise
@@ -980,7 +969,6 @@
     }
 
     static native void setupShadersDiskCache(String cacheFile);
-    static native void setupVulkanLayerPath(String layerPath);
 
     private static native void nSetAtlas(long nativeProxy, GraphicBuffer buffer, long[] map);
     private static native void nSetProcessStatsBuffer(long nativeProxy, int fd);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 4e7d1914..77884f6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8740,7 +8740,7 @@
      * @param direction The direction of the focus
      */
     public void addFocusables(ArrayList<View> views, @FocusDirection int direction) {
-        addFocusables(views, direction, FOCUSABLES_TOUCH_MODE);
+        addFocusables(views, direction, isInTouchMode() ? FOCUSABLES_TOUCH_MODE : FOCUSABLES_ALL);
     }
 
     /**
@@ -8768,7 +8768,7 @@
             return;
         }
         if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE
-                && isInTouchMode() && !isFocusableInTouchMode()) {
+                && !isFocusableInTouchMode()) {
             return;
         }
         views.add(this);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 9b74fc0..fe24230 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1803,7 +1803,21 @@
          */
         public final void setSurfaceInsets(View view, boolean manual, boolean preservePrevious) {
             final int surfaceInset = (int) Math.ceil(view.getZ() * 2);
-            surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
+            // Partial workaround for b/28318973. Every inset change causes a freeform window
+            // to jump a little for a few frames. If we never allow surface insets to decrease,
+            // they will stabilize quickly (often from the very beginning, as most windows start
+            // as focused).
+            // TODO(b/22668382) to fix this properly.
+            if (surfaceInset == 0) {
+                // OK to have 0 (this is the case for non-freeform windows).
+                surfaceInsets.set(0, 0, 0, 0);
+            } else {
+                surfaceInsets.set(
+                        Math.max(surfaceInset, surfaceInsets.left),
+                        Math.max(surfaceInset, surfaceInsets.top),
+                        Math.max(surfaceInset, surfaceInsets.right),
+                        Math.max(surfaceInset, surfaceInsets.bottom));
+            }
             hasManualSurfaceInsets = manual;
             preservePreviousSurfaceInsets = preservePrevious;
         }
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 887cc3a..11734d3 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -391,17 +391,34 @@
         }
     }
 
+    /**
+     * Remove all roots with specified token.
+     *
+     * @param token app or window token.
+     * @param who name of caller, used in logs.
+     * @param what type of caller, used in logs.
+     */
     public void closeAll(IBinder token, String who, String what) {
+        closeAllExceptView(token, null /* view */, who, what);
+    }
+
+    /**
+     * Remove all roots with specified token, except maybe one view.
+     *
+     * @param token app or window token.
+     * @param view view that should be should be preserved along with it's root.
+     *             Pass null if everything should be removed.
+     * @param who name of caller, used in logs.
+     * @param what type of caller, used in logs.
+     */
+    public void closeAllExceptView(IBinder token, View view, String who, String what) {
         synchronized (mLock) {
             int count = mViews.size();
-            //Log.i("foo", "Closing all windows of " + token);
             for (int i = 0; i < count; i++) {
-                //Log.i("foo", "@ " + i + " token " + mParams[i].token
-                //        + " view " + mRoots[i].getView());
-                if (token == null || mParams.get(i).token == token) {
+                if ((view == null || mViews.get(i) != view)
+                        && (token == null || mParams.get(i).token == token)) {
                     ViewRootImpl root = mRoots.get(i);
 
-                    //Log.i("foo", "Force closing " + root);
                     if (who != null) {
                         WindowLeaked leak = new WindowLeaked(
                                 what + " " + who + " has leaked window "
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 2b7afea..5554182 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -549,6 +549,7 @@
     private static boolean startSystemServer(String abiList, String socketName)
             throws MethodAndArgsCaller, RuntimeException {
         long capabilities = posixCapabilitiesAsBits(
+            OsConstants.CAP_IPC_LOCK,
             OsConstants.CAP_KILL,
             OsConstants.CAP_NET_ADMIN,
             OsConstants.CAP_NET_BIND_SERVICE,
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 986a5fd..dd66b24 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -34,6 +34,7 @@
     com_google_android_gles_jni_EGLImpl.cpp \
     com_google_android_gles_jni_GLImpl.cpp.arm \
     android_app_Activity.cpp \
+    android_app_ApplicationLoaders.cpp \
     android_app_NativeActivity.cpp \
     android_app_admin_SecurityLog.cpp \
     android_opengl_EGL14.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index c2273d6..315887d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -181,6 +181,7 @@
 extern int register_android_app_backup_FullBackup(JNIEnv *env);
 extern int register_android_app_Activity(JNIEnv *env);
 extern int register_android_app_ActivityThread(JNIEnv *env);
+extern int register_android_app_ApplicationLoaders(JNIEnv *env);
 extern int register_android_app_NativeActivity(JNIEnv *env);
 extern int register_android_media_RemoteDisplay(JNIEnv *env);
 extern int register_android_util_jar_StrictJarFile(JNIEnv* env);
@@ -1389,6 +1390,7 @@
     REG_JNI(register_android_app_backup_FullBackup),
     REG_JNI(register_android_app_Activity),
     REG_JNI(register_android_app_ActivityThread),
+    REG_JNI(register_android_app_ApplicationLoaders),
     REG_JNI(register_android_app_NativeActivity),
     REG_JNI(register_android_util_jar_StrictJarFile),
     REG_JNI(register_android_view_InputChannel),
diff --git a/core/jni/android_app_ApplicationLoaders.cpp b/core/jni/android_app_ApplicationLoaders.cpp
new file mode 100644
index 0000000..24ee959
--- /dev/null
+++ b/core/jni/android_app_ApplicationLoaders.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <nativehelper/ScopedUtfChars.h>
+#include <nativeloader/native_loader.h>
+#include <vulkan/vulkan_loader_data.h>
+
+#include "core_jni_helpers.h"
+
+static void setupVulkanLayerPath_native(JNIEnv* env, jobject clazz,
+        jobject classLoader, jstring librarySearchPath) {
+    ScopedUtfChars layerPathChars(env, librarySearchPath);
+    vulkan::LoaderData& loader_data = vulkan::LoaderData::GetInstance();
+    loader_data.layer_path = layerPathChars.c_str();
+    loader_data.app_namespace = android::FindNamespaceByClassLoader(env, classLoader);
+}
+
+static const JNINativeMethod g_methods[] = {
+    { "setupVulkanLayerPath", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V",
+      reinterpret_cast<void*>(setupVulkanLayerPath_native) },
+};
+
+static const char* const kApplicationLoadersName = "android/app/ApplicationLoaders";
+
+namespace android
+{
+
+int register_android_app_ApplicationLoaders(JNIEnv* env) {
+    return RegisterMethodsOrDie(env, kApplicationLoadersName, g_methods, NELEM(g_methods));
+}
+
+} // namespace android
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 650a0fc..5b4bfe9 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -28,7 +28,6 @@
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
 #include <EGL/egl_cache.h>
-#include <vulkan/vulkan_loader_data.h>
 
 #include <utils/Looper.h>
 #include <utils/RefBase.h>
@@ -50,7 +49,6 @@
 #include <renderthread/RenderProxy.h>
 #include <renderthread/RenderTask.h>
 #include <renderthread/RenderThread.h>
-#include <Vector.h>
 
 namespace android {
 
@@ -718,18 +716,6 @@
 }
 
 // ----------------------------------------------------------------------------
-// Layers
-// ----------------------------------------------------------------------------
-
-static void android_view_ThreadedRenderer_setupVulkanLayerPath(JNIEnv* env, jobject clazz,
-        jstring layerPath) {
-
-    const char* layerArray = env->GetStringUTFChars(layerPath, NULL);
-    vulkan::LoaderData::GetInstance().layer_path = layerArray;
-    env->ReleaseStringUTFChars(layerPath, layerArray);
-}
-
-// ----------------------------------------------------------------------------
 // JNI Glue
 // ----------------------------------------------------------------------------
 
@@ -771,8 +757,6 @@
     { "nDumpProfileData", "([BLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileData },
     { "setupShadersDiskCache", "(Ljava/lang/String;)V",
                 (void*) android_view_ThreadedRenderer_setupShadersDiskCache },
-    { "setupVulkanLayerPath", "(Ljava/lang/String;)V",
-                (void*) android_view_ThreadedRenderer_setupVulkanLayerPath },
     { "nAddRenderNode", "(JJZ)V", (void*) android_view_ThreadedRenderer_addRenderNode},
     { "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
     { "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2a83d88..e64d905 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2476,4 +2476,9 @@
 
     <!-- True if the device supports persisting security logs across reboots. -->
     <bool name="config_supportPreRebootSecurityLogs">false</bool>
+
+    <!-- Default files to pin via Pinner Service -->
+    <string-array translatable="false" name="config_defaultPinnerServiceFiles">
+    </string-array>
+
 </resources>
diff --git a/core/res/res/values/styles_holo.xml b/core/res/res/values/styles_holo.xml
index fdf9e31..8ca12ae 100644
--- a/core/res/res/values/styles_holo.xml
+++ b/core/res/res/values/styles_holo.xml
@@ -1172,7 +1172,8 @@
 
     <style name="Widget.Holo.Light.FastScroll" parent="Widget.Holo.FastScroll" />
 
-    <style name="Widget.Holo.SuggestionItem" parent="TextAppearance.Holo.Medium">
+    <style name="Widget.Holo.SuggestionItem">
+        <item name="textAppearance">@android:style/TextAppearance.Holo.Medium</item>
         <item name="background">@color/white</item>
         <item name="drawablePadding">8dip</item>
         <item name="gravity">start|center_vertical</item>
@@ -1188,9 +1189,9 @@
         <item name="textColor">@color/black</item>
     </style>
 
-    <style name="TextAppearance.Holo.SuggestionHighlight" parent="TextAppearance.SuggestionHighlight" />
+    <style name="TextAppearance.Holo.SuggestionHighlight" parent="@android:style/TextAppearance.SuggestionHighlight" />
 
-    <style name="Widget.Holo.SuggestionButton" parent="Widget.Holo.SuggestionItem">
+    <style name="Widget.Holo.SuggestionButton" parent="@android:style/Widget.Holo.SuggestionItem">
         <item name="background">#E9E9E9</item>
         <item name="textColor">@color/black</item>
     </style>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 6752e3c..3ed8daa 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -1001,7 +1001,8 @@
         <item name="contentDescription">@string/media_route_button_content_description</item>
     </style>
 
-    <style name="Widget.Material.SuggestionItem" parent="@android:style/TextAppearance.Material.Body1">
+    <style name="Widget.Material.SuggestionItem">
+        <item name="textAppearance">@android:style/TextAppearance.Material.Body1</item>
         <item name="textColor">?attr/textColorSecondary</item>
         <item name="drawablePadding">8dip</item>
         <item name="gravity">start|center_vertical</item>
@@ -1016,11 +1017,12 @@
         <item name="textSize">14sp</item>
     </style>
 
-    <style name="TextAppearance.Material.TextSuggestionHighlight" parent="Widget.Material.SuggestionItem">
+    <style name="TextAppearance.Material.TextSuggestionHighlight">
         <item name="textColor">?attr/textColorPrimary</item>
     </style>
 
-    <style name="Widget.Material.SuggestionButton" parent="@android:style/TextAppearance.Material.Button">
+    <style name="Widget.Material.SuggestionButton">
+        <item name="textAppearance">@android:style/TextAppearance.Material.Button</item>
         <item name="textColor">?attr/colorAccent</item>
         <item name="drawablePadding">8dip</item>
         <item name="gravity">start|center_vertical</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7ed293f..ac36a2f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2587,4 +2587,8 @@
   <java-symbol type="string" name="config_tvRemoteServicePackage" />
 
   <java-symbol type="bool" name="config_supportPreRebootSecurityLogs" />
+
+  <!-- Pinner Service -->
+  <java-symbol type="array" name="config_defaultPinnerServiceFiles" />
+
 </resources>
diff --git a/docs/html/develop/index.jd b/docs/html/develop/index.jd
index de38f3d..6835beb 100644
--- a/docs/html/develop/index.jd
+++ b/docs/html/develop/index.jd
@@ -9,7 +9,7 @@
 excludeFromSuggestions=true
 @jd:body
 
-<section class="dac-expand dac-hero dac-section-light" style="background:#FFE57F">
+<section class="dac-expand dac-hero dac-section-light">
   <div class="wrap">
     <div class="cols dac-hero-content">
       <div class="col-1of2 col-push-1of2 dac-hero-figure">
@@ -19,14 +19,14 @@
             frameborder="0" allowfullscreen=""
             style="float: right;"></iframe>
         -->
-        <a href="{@docRoot}sdk/index.html">
+        <a href="{@docRoot}studio/index.html">
         <img class="dac-hero-image" src="{@docRoot}images/tools/studio/studio-feature-instant-run_2x.png" />
         </a>
       </div>
       <div class="col-1of2 col-pull-1of2">
         <h1 class="dac-hero-title">
-            <a style="color:inherit" href="{@docRoot}sdk/index.html">
-            Android Studio 2.0,<br>now available!</a></h1>
+            <a style="color:inherit" href="{@docRoot}studio/index.html">
+            Android Studio 2.1,<br>now available!</a></h1>
         <p class="dac-hero-description">
         The latest version of Android Studio is the biggest update yet.
         It includes new features like <strong>Instant Run</strong>, which
@@ -35,7 +35,7 @@
         </p>
         <div class="cols">
           <div class="col-1of2">
-            <a class="dac-hero-cta" href="{@docRoot}sdk/index.html">
+            <a class="dac-hero-cta" href="{@docRoot}studio/index.html">
               <span class="dac-sprite dac-auto-chevron"></span>
               Get Android Studio
             </a><br>
diff --git a/docs/html/distribute/essentials/quality/billions.jd b/docs/html/distribute/essentials/quality/billions.jd
index 58f15a3..7042143 100644
--- a/docs/html/distribute/essentials/quality/billions.jd
+++ b/docs/html/distribute/essentials/quality/billions.jd
@@ -585,6 +585,16 @@
    Implementing a Preferences Activity</a>.</li>
  </ul>
 
+
+
+<h3 class="rel-resources clearfloat">Related resources</h3>
+<div class="resource-widget resource-flow-layout col-13"
+  data-query="collection:distribute/essentials/billionsquality/cost"
+  data-sortOrder="-timestamp"
+  data-cardSizes="6x3"
+  data-maxResults="6"></div>
+
+
 <!-- consumption -->
 <div class="headerLine">
   <h2 id="consumption">Battery Consumption</h2>
diff --git a/docs/html/jd_collections.js b/docs/html/jd_collections.js
index cf6fb97..04e7a3d 100644
--- a/docs/html/jd_collections.js
+++ b/docs/html/jd_collections.js
@@ -375,7 +375,7 @@
       "distribute/essentials/quality/tv.html",
       "distribute/essentials/quality/wear.html",
       "distribute/essentials/quality/auto.html",
-      "https://developers.google.com/edu/guidelines"
+      "distribute/essentials/quality/billions.html"
     ]
   },
   "distribute/essentials/zhcn": {
@@ -506,7 +506,7 @@
       "distribute/essentials/quality/wear.html",
       "distribute/essentials/quality/tv.html",
       "distribute/essentials/quality/auto.html",
-      "https://developers.google.com/edu/guidelines"
+      "distribute/essentials/quality/billions.html"
     ]
   },
   "distribute/essentials/tools": {
@@ -1007,6 +1007,44 @@
       "google/play/filters.html"
     ]
   },
+ "distribute/essentials/billionsquality/connectivity": {
+    "title": "",
+    "resources": [
+      "training/basics/network-ops/managing.html",
+      "training/monitoring-device-state/connectivity-monitoring.html",
+      "guide/topics/providers/content-providers.html"
+    ]
+  },
+  "distribute/essentials/billionsquality/capability": {
+    "title": "",
+    "resources": [
+      "guide/practices/screens_support.html",
+      "training/multiscreen/screendensities.html",
+      "training/articles/memory.html"
+    ]
+  },
+  "distribute/essentials/billionsquality/cost": {
+    "title": "",
+    "resources": [
+      "https://medium.com/@wkalicinski/smallerapk-part-6-image-optimization-zopfli-webp-4c462955647d#.23hlddo3x",
+      "training/basics/network-ops/managing.html"
+    ]
+  },
+  "distribute/essentials/billionsquality/consumption": {
+    "title": "",
+    "resources": [
+      "training/efficient-downloads/efficient-network-access.html",
+      "training/monitoring-device-state/index.html"
+    ]
+  },
+  "distribute/essentials/billionsquality/content": {
+    "title": "",
+    "resources": [
+      "training/material/animations.html#Touch",
+      "training/articles/perf-anr.html",
+      "training/improving-layouts/index.html"
+    ]
+  },
   "distribute/essentials/tabletguidelines": {
     "title": "",
     "resources": [
diff --git a/docs/html/jd_extras.js b/docs/html/jd_extras.js
index d176883..685394f 100644
--- a/docs/html/jd_extras.js
+++ b/docs/html/jd_extras.js
@@ -32,6 +32,17 @@
     "type":"medium"
   },
   {
+    "title":"SmallerAPK, Part 6: Image optimization, Zopfli & WebP",
+    "category":"",
+    "summary":"Series of posts on minimizing your APK size.",
+    "url":"https://medium.com/@wkalicinski/smallerapk-part-6-image-optimization-zopfli-webp-4c462955647d#.23hlddo3x",
+    "group":"",
+    "keywords": [],
+    "tags": [],
+    "image":"https://cdn-images-1.medium.com/max/2000/1*chMiA9mGa_FBUOoesHHk3Q.png",
+    "type":"medium"
+  },
+  {
     "title":"Measure your app’s user acquisition channels",
     "titleFriendly":"",
     "summary":"Get details on how to use the Developer Console User Acquisitions reports to discover where your users come from.",
@@ -999,6 +1010,19 @@
     "lang": "en",
     "group": "",
     "tags": [],
+    "url": "training/material/animations.html#Touch",
+    "timestamp": 1194884220000,
+    "image": null,
+    "title": "Customize Touch Feedback",
+    "summary": "Provide visual confirmation when users interact with your UI.",
+    "keywords": [],
+    "type": "develop",
+    "category": "guide"
+  },
+  {
+    "lang": "en",
+    "group": "",
+    "tags": [],
     "url": "guide/topics/manifest/uses-feature-element.html#testing",
     "timestamp": 1194884220000,
     "image": null,
diff --git a/docs/html/jd_extras_en.js b/docs/html/jd_extras_en.js
index 61eecc0..b351aa5 100644
--- a/docs/html/jd_extras_en.js
+++ b/docs/html/jd_extras_en.js
@@ -657,7 +657,54 @@
     "image":"https://i1.ytimg.com/vi/K2dodTXARqc/maxresdefault.jpg",
     "type":"video"
   },
-
+  {
+    "title":"Instant Run: An Android Tool Time Deep Dive",
+    "category":"",
+    "summary":"Instant Run is an Android Studio feature that significantly reduces the time for building and deploying incremental code changes during your coding / testing / debugging lifecycle.",
+    "url":"https://www.youtube.com/watch?v=StqAZ1OQbqA&list=PLWz5rJ2EKKc_w6fodMGrA1_tsI3pqPbqa",
+    "group":"",
+    "keywords": ["studio", "tools"],
+    "tags": [
+    ],
+    "image":"https://i1.ytimg.com/vi/StqAZ1OQbqA/maxresdefault.jpg",
+    "type":"youtube"
+  },
+  {
+    "title":"What’s New in Android Studio 2.1",
+    "category":"",
+    "summary":"Android Studio 2.1 is required to try out new features and APIs of the Android N developer preview including the new Jack compiler and Java 8 language support. It also includes performance improvements to Instant Run, and a number of bug fixes and stability improvements.",
+    "url":"https://www.youtube.com/watch?v=ZOz_yr8Yxq8&list=PLWz5rJ2EKKc_w6fodMGrA1_tsI3pqPbqa",
+    "group":"",
+    "keywords": ["studio", "tools"],
+    "tags": [
+    ],
+    "image":"https://i1.ytimg.com/vi/ZOz_yr8Yxq8/maxresdefault.jpg",
+    "type":"youtube"
+  },
+  {
+    "title":"10 Things You Didn’t Know You Could Do",
+    "category":"",
+    "summary":"This Android Tool Time pro-tip roundup with Reto Meier shows off expert Android Studio tips designed to help you write less code, and make every keystroke count",
+    "url":"https://www.youtube.com/watch?v=eOV2owswDkE&list=PLWz5rJ2EKKc_w6fodMGrA1_tsI3pqPbqa",
+    "group":"",
+    "keywords": ["studio", "tools"],
+    "tags": [
+    ],
+    "image":"https://i1.ytimg.com/vi/eOV2owswDkE/maxresdefault.jpg",
+    "type":"youtube"
+  },
+  {
+    "title":"Live Templates in Android Studio",
+    "category":"",
+    "summary":"Android Tool Time Protip: Use and create your own Live Templates in Android Studio to write more code with less keystrokes using Live Templates to insert common, templatized code snippets.",
+    "url":"https://www.youtube.com/watch?v=4rI4tTd7-J8&list=PLWz5rJ2EKKc_w6fodMGrA1_tsI3pqPbqa",
+    "group":"",
+    "keywords": ["studio", "tools"],
+    "tags": [
+    ],
+    "image":"https://i1.ytimg.com/vi/4rI4tTd7-J8/maxresdefault.jpg",
+    "type":"youtube"
+  },
   {
     "title":"Google Play Services 7.5",
     "category":"",
diff --git a/docs/html/preview/api-overview.jd b/docs/html/preview/api-overview.jd
index 110418a..5d93198 100644
--- a/docs/html/preview/api-overview.jd
+++ b/docs/html/preview/api-overview.jd
@@ -24,6 +24,7 @@
         <li><a href="#number-blocking">Number-blocking</a></li>
         <li><a href="#call_screening">Call screening</a></li>
         <li><a href="#multi-locale_languages">Locales and languages</a></li>
+        <li><a href="#emoji">New Emojis</a></li>
         <li><a href="#icu4">ICU4J APIs in Android</a></li>
         <li><a href="#gles_32">OpenGL ES 3.2 API</a></li>
         <li><a href="#android_tv_recording">Android TV recording</a></li>
@@ -37,7 +38,6 @@
         <li><a href="#scoped_directory_access">Scoped directory access</a></li>
         <li><a href="#print_svc">Print service enhancements</a></li>
         <li><a href="#virtual_files">Virtual Files</a></li>
-        <li><a href="#emoji">New Emojis</a></li>
       </ol>
 </div>
 </div>
@@ -479,6 +479,49 @@
 should follow, see <a href="{@docRoot}preview/features/multilingual-support.html"
 >Multilingual Support</a>.</p>
 
+
+<h2 id="emoji">New Emojis</h2>
+
+<p>
+  Android N introduces additional emojis and emoji-related features including
+  skin tone emojis and support for variation
+  selectors. If your app supports emojis,
+  follow the guidelines below to take advantage of these emoji-related features.
+</p>
+
+<ul>
+  <li>
+    <strong>Check that a device contains an emoji before inserting it.</strong>
+    To check which emojis are present in the
+    system font, use the {@link android.graphics.Paint#hasGlyph(String)} method.
+  </li>
+  <li>
+    <strong>Check that an emoji supports variation selectors.</strong>
+    Variation selectors allow you to
+    present certain emojis in color or in black-and-white.
+    On mobile devices, apps should represent emojis in color rather than black-and-white. However,
+    if your app displays emojis inline with text, then it should use the black-and-white variation.
+    To determine whether an emoji has a variation, use the variation selector.
+    For a complete list of characters with variations, review the
+    <em>emoji variation sequences</em> section of the
+    <a class="external-link"
+    href="http://www.unicode.org/Public/9.0.0/ucd/StandardizedVariants-9.0.0d1.txt">
+      Unicode documentation on variations</a>.
+  </li>
+  <li>
+    <strong>Check that an emoji supports skin tone.</strong> Android N allows users to modify the
+    rendered skin tone of emojis to their preference. Keyboard apps should provide visual
+    indications for emojis that have multiple skin tones and should allow users to
+    select the skin tone that they prefer. To determine which system emojis have
+    skin tone modifiers, use the {@link android.graphics.Paint#hasGlyph(String)}
+    method. You can determine which emojis use skin tones by reading the
+    <a class="external-link"
+    href="http://unicode.org/emoji/charts/full-emoji-list.html">
+     Unicode documentation</a>.
+  </li>
+</ul>
+
+
 <h2 id="icu4">ICU4J APIs in Android</h2>
 
 <p>
@@ -733,19 +776,21 @@
 
 <p>
   Android N introduces APK Signature Scheme v2, a new app-signing scheme that
-  offers faster app install times and better protection against unauthorized
-  alterations to APK files. Android Studio 2.2 and Gradle provide built-in
-  support for APK Signature Scheme v2.
+  offers faster app install times and more protection against unauthorized
+  alterations to APK files. By default, Android Studio 2.2 and the Android
+  Plugin for Gradle 2.2 sign your app using both APK Signature Scheme v2 and
+  the traditional signing scheme, which uses JAR signing.
 </p>
 
 <p>
-  Although we recommend applying APK Signature Scheme v2 to your app, the new
-  scheme is not mandatory. If your app doesn't build properly when using the
-  APK Signature Scheme v2, you can use the traditional signing scheme—which
-  uses JAR signing—instead. To use the traditional scheme, open the
-  module-level <code>build.gradle</code> file and add the
-  <code>v2SigningEnabled</code> parameter to your release signing
-  configuration, setting this parameter's value to <code>false</code>:
+  Although we recommend applying APK Signature Scheme v2 to your app, this new
+  scheme is not mandatory. If your app doesn't build properly when using APK
+  Signature Scheme v2, you can disable the new scheme. The disabling process
+  causes Android Studio 2.2 and the Android Plugin for Gradle 2.2 to sign your
+  app using only the traditional signing scheme. To sign with only the
+  traditional scheme, open the module-level <code>build.gradle</code> file, then
+  add the line <code>v2SigningEnabled false</code> to your release signing
+  configuration:
 </p>
 
 <pre>
@@ -764,12 +809,18 @@
   }
 </pre>
 
+<p class="caution"><strong>Caution: </strong> If you sign your app using APK
+  Signature Scheme v2 and make further changes to the app, the app's signature
+  is invalidated. For this reason, use tools such as <code>zipalign</code>
+  before signing your app using APK Signature Scheme v2, not after.
+</p>
+
 <p>
-  For more information, see the following guides, which describe how to <a href=
-  "{@docRoot}studio/tools/publishing/app-signing.html#release-mode"> sign an app
-  in Android Studio</a> and how to <a href=
+  For more information, read the Android Studio documents that describe how to
+  <a href="{@docRoot}studio/tools/publishing/app-signing.html#release-mode">
+  sign an app</a> in Android Studio and how to <a href=
   "{@docRoot}studio/tools/building/configuring-gradle.html#signing"> configure
-  the Gradle build file for signing apps</a>.
+  the build file for signing apps</a> using the Android Plugin for Gradle.
 </p>
 
 <h2 id="scoped_directory_access">Scoped directory access</h2>
@@ -916,41 +967,3 @@
   <a href="{@docRoot}guide/topics/providers/document-provider.html">Storage
   Access Frameworks guide</a>.
 </p>
-
-<h2 id="emoji">New Emojis</h2>
-
-<p>
-  Android N introduces new emojis, including skin tone emojis, support
-  for variation
-  selectors, and other improvements. For a good user experience, observe the
-  following guidelines for using the new emojis and emoji features in your apps.
-</p>
-
-<ul>
-  <li>
-    <strong>New emojis</strong>: To check which emojis are present in the
-    system font, use the {@link android.graphics.Paint#hasGlyph(String)} method
-    and the dynamic layout in the emoji picker to place the glyphs.
-  </li>
-  <li>
-    <strong>Variation selectors</strong>: Variation selectors allow certain
-    emojis to be represented in color or in text presentation, which is used in
-    documents. For mobile devices, emojis should use their color representation.
-    To determine whether an emoji has a variation, use the variation selector.
-    You can view the complete list of characters with variations in the
-    <em>emoji variation sequences</em> section of the
-    <a href="http://www.unicode.org/Public/9.0.0/ucd/StandardizedVariants-9.0.0d1.txt">
-      Unicode documentation on variations</a>.
-  </li>
-  <li>
-    <strong>Skin tone modifiers</strong>: In Android N, users can modify the
-    rendered skin tone of emojis. This allows users to customize the presentation
-    of emojis to their preference. Keyboard apps should provide visual
-    indications for emojis that have multiple skin tones and should allow users to
-    select the skin tone that they prefer. To determine which system emojis have
-    skin tone modifiers, use the {@link android.graphics.Paint#hasGlyph(String)}
-    method. You can determine which emojis use skin tones by reading the
-    <a href="http://unicode.org/emoji/charts/full-emoji-list.html">
-     Unicode documentation</a>.
-  </li>
-</ul>
\ No newline at end of file
diff --git a/docs/html/preview/features/data-saver.jd b/docs/html/preview/features/data-saver.jd
index d5cdf27..c4cab18 100644
--- a/docs/html/preview/features/data-saver.jd
+++ b/docs/html/preview/features/data-saver.jd
@@ -14,7 +14,7 @@
         <a href="#status">Checking Data Saver Preferences</a>
         <ol>
           <li>
-            <a href="#request-whitelist">Requesting Whitelist Permissions</a>
+            <a href="#request-whitelist">Requesting whitelist permissions</a>
           </li>
         </ol>
       </li>
@@ -138,15 +138,17 @@
   If your app needs to use data in the background, it can request whitelist
   permissions by sending a
   <code>Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS</code>
-  (<code>"android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS"</code>)
-  intent with a <code>package:&lt;your-app-id&gt;</code> URI.
+  intent containing a URI of your app's package name: for example,
+  <code>package:MY_APP_ID</code>.
 </p>
 
 <p>
-  Sending the intent and URI launches the <strong>Settings</strong> app, and
-  displays your app's <strong>App Data Usage</strong> page to the user. The
-  user can then decide whether to enable background data for your app.
-  It is good practice to prompt the user before sending this intent.
+  Sending the intent and URI launches the <strong>Settings</strong> app and
+  displays data usage settings for your app. The user can then decide whether
+  to enable background data for your app. Before you send this intent, it is
+  good practice to first ask the user if they want to launch the
+  <strong>Settings</strong> app for the purpose of enabling background data
+  usage.
 </p>
 
 <h2 id="monitor-changes">
@@ -156,9 +158,8 @@
 <p>
   Apps can monitor changes to Data Saver preferences by creating a {@link
   android.content.BroadcastReceiver} to listen for {@code
-  ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED} ({@code
-  "android.net.conn.RESTRICT_BACKGROUND_CHANGED"}) and dynamically registering
-  the receiver with {@link android.content.Context#registerReceiver
+  ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED} and dynamically
+  registering the receiver with {@link android.content.Context#registerReceiver
   Context.registerReceiver()}. When an app receives this broadcast, it should
   <a href="#status">check if the new Data Saver preferences affect its
   permissions</a> by calling {@code
diff --git a/docs/html/preview/features/notification-updates.jd b/docs/html/preview/features/notification-updates.jd
index b8dfbaa..c405360 100644
--- a/docs/html/preview/features/notification-updates.jd
+++ b/docs/html/preview/features/notification-updates.jd
@@ -204,6 +204,16 @@
   android.support.v4.app.RemoteInput}, you can update the reply history
   using the {@code setRemoteInputHistory()} method.
 </p>
+
+<p>
+  The notification must be either updated or cancelled after the app has
+  received remote input. When the user replies to a remote update
+  using Direct Reply,
+  do not cancel the notification. Instead, update the notification to display the user's reply. You can update the notification using a
+  <code>MessagingStyle</code>, or you can append the user's reply to the remote
+  input history.
+</p>
+
 <h2 id="bundle">Bundled Notifications</h2>
 
 <p>Android N provides developers with a new way to represent
@@ -364,7 +374,7 @@
 
 </pre>
 
-<h2 id="style">Message Style</h2>
+<h2 id="style">Messaging Style</h2>
 <p>
   Android N introduces a new API for customizing the style of a notification.
   Using the <code>MessageStyle</code> class, you can change several of the
@@ -380,10 +390,9 @@
 <pre>
   Notification notification = new Notification.Builder()
              .setStyle(new Notification.MessagingStyle("Me")
-             .setConversationTitle("Team lunch")
-             .addMessage("Hi", timestamp1, null) // Pass in null for user.
-             .addMessage("What's up?", timestamp2, "Coworker")
-             .addMessage("Not much", timestamp3, null)
-             .addMessage("How about lunch?", timestamp4, "Coworker")
-             .setAllow());
+                 .setConversationTitle("Team lunch")
+                 .addMessage("Hi", timestamp1, null) // Pass in null for user.
+                 .addMessage("What's up?", timestamp2, "Coworker")
+                 .addMessage("Not much", timestamp3, null)
+                 .addMessage("How about lunch?", timestamp4, "Coworker"));
 </pre>
\ No newline at end of file
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 596e5a8..f49594c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -46,6 +46,7 @@
 import android.os.DropBoxManager;
 import android.os.Environment;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
@@ -199,6 +200,12 @@
     @GuardedBy("mLock")
     private SettingsRegistry mSettingsRegistry;
 
+    @GuardedBy("mLock")
+    private HandlerThread mHandlerThread;
+
+    @GuardedBy("mLock")
+    private Handler mBackgroundHandler;
+
     // We have to call in the user manager with no lock held,
     private volatile UserManager mUserManager;
 
@@ -244,6 +251,10 @@
         synchronized (mLock) {
             mUserManager = UserManager.get(getContext());
             mPackageManager = AppGlobals.getPackageManager();
+            mHandlerThread = new HandlerThread(LOG_TAG,
+                    Process.THREAD_PRIORITY_BACKGROUND);
+            mHandlerThread.start();
+            mBackgroundHandler = new Handler(mHandlerThread.getLooper());
             mSettingsRegistry = new SettingsRegistry();
         }
         registerBroadcastReceivers();
@@ -1669,7 +1680,7 @@
             if (mSettingsStates.get(key) == null) {
                 final int maxBytesPerPackage = getMaxBytesPerPackageForType(getTypeFromKey(key));
                 SettingsState settingsState = new SettingsState(mLock, getSettingsFile(key), key,
-                        maxBytesPerPackage);
+                        maxBytesPerPackage, mBackgroundHandler);
                 mSettingsStates.put(key, settingsState);
             }
         }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 2de0618c..1e02747 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -95,7 +95,7 @@
 
     private final Object mLock;
 
-    private final Handler mHandler = new MyHandler();
+    private final Handler mHandler;
 
     @GuardedBy("mLock")
     private final ArrayMap<String, Setting> mSettings = new ArrayMap<>();
@@ -134,13 +134,15 @@
     @GuardedBy("mLock")
     private long mNextId;
 
-    public SettingsState(Object lock, File file, int key, int maxBytesPerAppPackage) {
+    public SettingsState(Object lock, File file, int key, int maxBytesPerAppPackage,
+            Handler handler) {
         // It is important that we use the same lock as the settings provider
         // to ensure multiple mutations on this state are atomicaly persisted
         // as the async persistence should be blocked while we make changes.
         mLock = lock;
         mStatePersistFile = file;
         mKey = key;
+        mHandler = handler;
         if (maxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_LIMITED) {
             mMaxBytesPerAppPackage = maxBytesPerAppPackage;
             mPackageToMemoryUsage = new ArrayMap<>();
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 3f9ffa1..53c2958 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.providers.settings;
 
+import android.os.Handler;
 import android.test.AndroidTestCase;
 import android.util.Xml;
 
@@ -126,7 +127,7 @@
         final Object lock = new Object();
 
         final SettingsState ssWriter = new SettingsState(lock, file, 1,
-                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED);
+                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, new Handler());
         ssWriter.setVersionLocked(SettingsState.SETTINGS_VERSOIN_NEW_ENCODING);
 
         ssWriter.insertSettingLocked("k1", "\u0000", "package");
@@ -138,7 +139,7 @@
         }
 
         final SettingsState ssReader = new SettingsState(lock, file, 1,
-                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED);
+                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, new Handler());
         synchronized (lock) {
             assertEquals("\u0000", ssReader.getSettingLocked("k1").getValue());
             assertEquals("abc", ssReader.getSettingLocked("k2").getValue());
@@ -165,7 +166,7 @@
         os.close();
 
         final SettingsState ss = new SettingsState(lock, file, 1,
-                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED);
+                SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, new Handler());
         synchronized (lock) {
             SettingsState.Setting s;
             s = ss.getSettingLocked("k0");
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index a5f3e77..0f356e0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -107,6 +107,7 @@
     private boolean mFinishedOnStartup;
     private boolean mIgnoreAltTabRelease;
     private boolean mIsVisible;
+    private boolean mReceivedNewIntent;
 
     // Top level views
     private RecentsView mRecentsView;
@@ -120,6 +121,9 @@
     private int mFocusTimerDuration;
     private DozeTrigger mIterateTrigger;
     private final UserInteractionEvent mUserInteractionEvent = new UserInteractionEvent();
+    private final Runnable mSendEnterWindowAnimationCompleteRunnable = () -> {
+        EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
+    };
 
     /**
      * A common Runnable to finish Recents by launching Home with an animation depending on the
@@ -342,6 +346,7 @@
     @Override
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
+        mReceivedNewIntent = true;
 
         // Reload the stack view
         reloadStackView();
@@ -364,7 +369,7 @@
         RecentsActivityLaunchState launchState = config.getLaunchState();
         if (!loadPlan.hasTasks()) {
             loader.preloadTasks(loadPlan, launchState.launchedToTaskId,
-                    launchState.launchedFromHome);
+                    !launchState.launchedFromHome);
         }
 
         RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
@@ -419,7 +424,16 @@
     @Override
     public void onEnterAnimationComplete() {
         super.onEnterAnimationComplete();
-        EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent());
+
+        // Workaround for b/28705801, on first docking, we may receive the enter animation callback
+        // before the first layout, so in such cases, send the event on the next frame after all
+        // the views are laid out and attached (and registered to the EventBus).
+        mHandler.removeCallbacks(mSendEnterWindowAnimationCompleteRunnable);
+        if (!mReceivedNewIntent) {
+            mHandler.post(mSendEnterWindowAnimationCompleteRunnable);
+        } else {
+            mSendEnterWindowAnimationCompleteRunnable.run();
+        }
     }
 
     @Override
@@ -453,7 +467,8 @@
         RecentsActivityLaunchState launchState = config.getLaunchState();
         RecentsTaskLoader loader = Recents.getTaskLoader();
         RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
-        loader.preloadTasks(loadPlan, -1 /* runningTaskId */, false /* isHomeStackVisible */);
+        loader.preloadTasks(loadPlan, -1 /* runningTaskId */,
+                false /* includeFrontMostExcludedTask */);
 
         RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
         loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
@@ -477,6 +492,7 @@
 
         // Notify that recents is now hidden
         mIsVisible = false;
+        mReceivedNewIntent = false;
         EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false));
         MetricsLogger.hidden(this, MetricsEvent.OVERVIEW_ACTIVITY);
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 297dec9..611f9f2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -112,7 +112,7 @@
 
                 // Load the next task only if we aren't svelte
                 RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-                loader.preloadTasks(plan, -1, true /* isHomeStackVisible */);
+                loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
                 RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
                 // This callback is made when a new activity is launched and the old one is paused
                 // so ignore the current activity and try and preload the thumbnail for the
@@ -189,7 +189,7 @@
         // We can use a new plan since the caches will be the same.
         RecentsTaskLoader loader = Recents.getTaskLoader();
         RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-        loader.preloadTasks(plan, -1, true /* isHomeStackVisible */);
+        loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
         RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
         launchOpts.numVisibleTasks = loader.getIconCacheSize();
         launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
@@ -366,8 +366,8 @@
             ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
             RecentsTaskLoader loader = Recents.getTaskLoader();
             sInstanceLoadPlan = loader.createLoadPlan(mContext);
-            sInstanceLoadPlan.preloadRawTasks(isHomeStackVisible.value);
-            loader.preloadTasks(sInstanceLoadPlan, runningTask.id, isHomeStackVisible.value);
+            sInstanceLoadPlan.preloadRawTasks(!isHomeStackVisible.value);
+            loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible.value);
             TaskStack stack = sInstanceLoadPlan.getTaskStack();
             if (stack.getTaskCount() > 0) {
                 // Only preload the icon (but not the thumbnail since it may not have been taken for
@@ -401,7 +401,7 @@
         SystemServicesProxy ssp = Recents.getSystemServices();
         RecentsTaskLoader loader = Recents.getTaskLoader();
         RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-        loader.preloadTasks(plan, -1, true /* isHomeStackVisible */);
+        loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
         TaskStack focusedStack = plan.getTaskStack();
 
         // Return early if there are no tasks in the focused stack
@@ -453,7 +453,7 @@
         SystemServicesProxy ssp = Recents.getSystemServices();
         RecentsTaskLoader loader = Recents.getTaskLoader();
         RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-        loader.preloadTasks(plan, -1, true /* isHomeStackVisible */);
+        loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
         TaskStack focusedStack = plan.getTaskStack();
 
         // Return early if there are no tasks in the focused stack
@@ -818,7 +818,7 @@
             sInstanceLoadPlan = loader.createLoadPlan(mContext);
         }
         if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
-            loader.preloadTasks(sInstanceLoadPlan, runningTask.id, isHomeStackVisible);
+            loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible);
         }
 
         TaskStack stack = sInstanceLoadPlan.getTaskStack();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 08b52d9..62c1945 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -253,11 +253,12 @@
     /**
      * Returns a list of the recents tasks.
      *
-     * @param isHomeStackVisible whether or not the home stack is currently visible.  If it is
-     *                           visible, then we ignore all excluded tasks (even the first one).
+     * @param includeFrontMostExcludedTask if set, will ensure that the front most excluded task
+     *                                     will be visible, otherwise no excluded tasks will be
+     *                                     visible.
      */
     public List<ActivityManager.RecentTaskInfo> getRecentTasks(int numLatestTasks, int userId,
-            boolean isHomeStackVisible, ArraySet<Integer> quietProfileIds) {
+            boolean includeFrontMostExcludedTask, ArraySet<Integer> quietProfileIds) {
         if (mAm == null) return null;
 
         // If we are mocking, then create some recent tasks
@@ -295,13 +296,16 @@
         // Remove home/recents/excluded tasks
         int minNumTasksToQuery = 10;
         int numTasksToQuery = Math.max(minNumTasksToQuery, numLatestTasks);
-        List<ActivityManager.RecentTaskInfo> tasks = mAm.getRecentTasksForUser(numTasksToQuery,
-                ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS |
+        int flags = ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS |
                 ActivityManager.RECENT_INGORE_DOCKED_STACK_TOP_TASK |
                 ActivityManager.RECENT_INGORE_PINNED_STACK_TASKS |
                 ActivityManager.RECENT_IGNORE_UNAVAILABLE |
-                ActivityManager.RECENT_INCLUDE_PROFILES |
-                ActivityManager.RECENT_WITH_EXCLUDED, userId);
+                ActivityManager.RECENT_INCLUDE_PROFILES;
+        if (includeFrontMostExcludedTask) {
+            flags |= ActivityManager.RECENT_WITH_EXCLUDED;
+        }
+        List<ActivityManager.RecentTaskInfo> tasks = mAm.getRecentTasksForUser(numTasksToQuery,
+                flags, userId);
 
         // Break early if we can't get a valid set of tasks
         if (tasks == null) {
@@ -316,18 +320,20 @@
             // NOTE: The order of these checks happens in the expected order of the traversal of the
             // tasks
 
-            // Check the first non-recents task, include this task even if it is marked as excluded
-            // from recents if we are currently in the app.  In other words, only remove excluded
-            // tasks if it is not the first active task, and not in the blacklist.
+            // Remove the task if it is blacklisted
+            if (sRecentsBlacklist.contains(t.realActivity.getClassName())) {
+                iter.remove();
+            }
+
+            // Remove the task if it is marked as excluded, unless it is the first most task and we
+            // are requested to include it
             boolean isExcluded = (t.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
                     == Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
-            boolean isBlackListed = sRecentsBlacklist.contains(t.realActivity.getClassName());
-            // Filter out recent tasks from managed profiles which are in quiet mode.
             isExcluded |= quietProfileIds.contains(t.userId);
-            if (isBlackListed || (isExcluded && (isHomeStackVisible || !isFirstValidTask))) {
+            if (isExcluded && (!isFirstValidTask || !includeFrontMostExcludedTask)) {
                 iter.remove();
-                continue;
             }
+
             isFirstValidTask = false;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 251ad71..1278b73 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -100,12 +100,12 @@
      * An optimization to preload the raw list of tasks. The raw tasks are saved in least-recent
      * to most-recent order.
      */
-    public synchronized void preloadRawTasks(boolean isHomeStackVisible) {
+    public synchronized void preloadRawTasks(boolean includeFrontMostExcludedTask) {
         int currentUserId = UserHandle.USER_CURRENT;
         updateCurrentQuietProfilesCache(currentUserId);
         SystemServicesProxy ssp = Recents.getSystemServices();
         mRawTasks = ssp.getRecentTasks(ActivityManager.getMaxRecentTasksStatic(),
-                currentUserId, isHomeStackVisible, mCurrentQuietProfiles);
+                currentUserId, includeFrontMostExcludedTask, mCurrentQuietProfiles);
 
         // Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
         Collections.reverse(mRawTasks);
@@ -121,11 +121,11 @@
      * - least-recent to most-recent freeform tasks
      */
     public synchronized void preloadPlan(RecentsTaskLoader loader, int runningTaskId,
-            boolean isHomeStackVisible) {
+            boolean includeFrontMostExcludedTask) {
         Resources res = mContext.getResources();
         ArrayList<Task> allTasks = new ArrayList<>();
         if (mRawTasks == null) {
-            preloadRawTasks(isHomeStackVisible);
+            preloadRawTasks(includeFrontMostExcludedTask);
         }
 
         SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 9460b64..ca59831 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -334,8 +334,8 @@
 
     /** Preloads recents tasks using the specified plan to store the output. */
     public void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId,
-            boolean isHomeStackVisible) {
-        plan.preloadPlan(this, runningTaskId, isHomeStackVisible);
+            boolean includeFrontMostExcludedTask) {
+        plan.preloadPlan(this, runningTaskId, includeFrontMostExcludedTask);
     }
 
     /** Begins loading the heavy task data according to the specified options. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
index acebf42..3a17d22 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
@@ -180,7 +180,7 @@
         RecentsConfiguration config = Recents.getConfiguration();
         RecentsActivityLaunchState launchState = config.getLaunchState();
         if (!plan.hasTasks()) {
-            loader.preloadTasks(plan, -1, launchState.launchedFromHome);
+            loader.preloadTasks(plan, -1, !launchState.launchedFromHome);
         }
         mLaunchedFromHome = launchState.launchedFromHome;
         TaskStack stack = plan.getTaskStack();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java
index 6f7bd41..fca8d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java
@@ -63,7 +63,7 @@
             sInstanceLoadPlan = loader.createLoadPlan(mContext);
         }
         if (mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
-            loader.preloadTasks(sInstanceLoadPlan, runningTask.id, isHomeStackVisible);
+            loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible);
         }
         TaskStack stack = sInstanceLoadPlan.getTaskStack();
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 3d0de1c..1a197b6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -1391,8 +1391,9 @@
         updateLayoutAlgorithm(true /* boundScroll */);
 
         // Animate all the tasks into place
-        relayoutTaskViews(new AnimationProps(DEFAULT_SYNC_STACK_DURATION,
-                Interpolators.FAST_OUT_SLOW_IN));
+        relayoutTaskViews(mAwaitingFirstLayout
+                ? AnimationProps.IMMEDIATE
+                : new AnimationProps(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN));
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index dc5957d..593e170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -2246,9 +2246,12 @@
                 Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT,
                 0,
                 mCurrentUserId) != 0;
+        final boolean remoteInputDpm = (dpmFlags
+                & DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT) == 0;
+
 
         setShowLockscreenNotifications(show && allowedByDpm);
-        setLockScreenAllowRemoteInput(remoteInput);
+        setLockScreenAllowRemoteInput(remoteInput && remoteInputDpm);
     }
 
     protected abstract void setAreThereNotifications();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index d2326d2..270b6ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -459,7 +459,8 @@
 
         @Override
         public String toString() {
-            String result = "    summary:\n      " + summary.notification;
+            String result = "    summary:\n      "
+                    + (summary != null ? summary.notification : "null");
             result += "\n    children size: " + children.size();
             for (NotificationData.Entry child : children) {
                 result += "\n      " + child.notification;
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
new file mode 100644
index 0000000..d48aeed
--- /dev/null
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2016 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.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.util.EventLog;
+import android.util.Slog;
+import android.os.Binder;
+import android.os.Build;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructStat;
+
+import java.util.ArrayList;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * <p>PinnerService pins important files for key processes in memory.</p>
+ * <p>Files to pin are specified in the config_defaultPinnerServiceFiles
+ * overlay. </p>
+ */
+public final class PinnerService extends SystemService {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "PinnerService";
+
+    private final Context mContext;
+    private final ArrayList<String> mPinnedFiles = new ArrayList<String>();
+
+    private BinderService mBinderService;
+
+
+    public PinnerService(Context context) {
+        super(context);
+
+        mContext = context;
+
+    }
+
+    @Override
+    public void onStart() {
+        Slog.e(TAG, "Starting PinnerService");
+
+        mBinderService = new BinderService();
+        publishBinderService("pinner", mBinderService);
+
+        // Files to pin come from the overlay and can be specified per-device config
+        // Continue trying to pin remaining files even if there is a failure
+        String[] filesToPin = mContext.getResources().getStringArray(com.android.internal.R.array.config_defaultPinnerServiceFiles);
+        for (int i = 0; i < filesToPin.length; i++){
+            boolean success = pinFile(filesToPin[i], 0, 0);
+            if (success == true) {
+                mPinnedFiles.add(filesToPin[i]);
+                Slog.i(TAG, "Pinned file = " + filesToPin[i]);
+            } else {
+                Slog.e(TAG, "Failed to pin file = " + filesToPin[i]);
+            }
+        }
+    }
+
+    // mlock length bytes of fileToPin in memory, starting at offset
+    // length == 0 means pin from offset to end of file
+    private boolean pinFile(String fileToPin, long offset, long length) {
+        FileDescriptor fd = new FileDescriptor();
+        try {
+            fd = Os.open(fileToPin, OsConstants.O_RDONLY | OsConstants.O_CLOEXEC | OsConstants.O_NOFOLLOW, OsConstants.O_RDONLY);
+
+            StructStat sb = Os.fstat(fd);
+
+            if (offset + length > sb.st_size) {
+                Os.close(fd);
+                return false;
+            }
+
+            if (length == 0) {
+                length = sb.st_size - offset;
+            }
+
+            long address = Os.mmap(0, length, OsConstants.PROT_READ, OsConstants.MAP_PRIVATE, fd, offset);
+            Os.close(fd);
+
+            Os.mlock(address, length);
+
+            return true;
+        } catch (ErrnoException e) {
+            Slog.e(TAG, "Failed to pin file " + fileToPin + " with error " + e.getMessage());
+            if(fd.valid()) {
+                try { Os.close(fd); }
+                catch (ErrnoException eClose) {Slog.e(TAG, "Failed to close fd, error = " + eClose.getMessage());}
+            }
+            return false;
+        }
+    }
+
+
+    private final class BinderService extends Binder {
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+            pw.println("Pinned Files:");
+            for (int i = 0; i < mPinnedFiles.size(); i++) {
+                pw.println(mPinnedFiles.get(i));
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 29e2e44..53c5a6d 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -868,7 +868,7 @@
                 return false;
             }
             if (this.userid == UserHandle.USER_ALL) return true;
-            if (this.userid == UserHandle.USER_SYSTEM) return true;
+            if (this.isSystem) return true;
             if (nid == UserHandle.USER_ALL || nid == this.userid) return true;
             return supportsProfiles() && mUserProfiles.isCurrentProfile(nid);
         }
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index a234241..abbb5f4 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -88,6 +88,11 @@
     // case do not clear allDrawn until the animation completes.
     boolean deferClearAllDrawn;
 
+    // These are to track the app's real drawing status if there were no saved surfaces.
+    boolean allDrawnExcludingSaved;
+    int numInterestingWindowsExcludingSaved;
+    int numDrawnWindowsExclusingSaved;
+
     // Is this window's surface needed?  This is almost like hidden, except
     // it will sometimes be true a little earlier: when the token has
     // been shown, but is still waiting for its app transition to execute
@@ -411,6 +416,39 @@
         }
     }
 
+    /**
+     * Whether the app has some window that is invisible in layout, but
+     * animating with saved surface.
+     */
+    boolean isAnimatingInvisibleWithSavedSurface() {
+        for (int i = allAppWindows.size() - 1; i >= 0; i--) {
+            final WindowState w = allAppWindows.get(i);
+            if (w.isAnimatingInvisibleWithSavedSurface()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Hide all window surfaces that's still invisible in layout but animating
+     * with a saved surface, and mark them destroying.
+     */
+    void stopUsingSavedSurfaceLocked() {
+        for (int i = allAppWindows.size() - 1; i >= 0; i--) {
+            final WindowState w = allAppWindows.get(i);
+            if (w.isAnimatingInvisibleWithSavedSurface()) {
+                if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.d(TAG,
+                        "stopUsingSavedSurfaceLocked: " + w);
+                w.clearAnimatingWithSavedSurface();
+                w.mDestroying = true;
+                w.mWinAnimator.hide("stopUsingSavedSurfaceLocked");
+                w.mWinAnimator.mWallpaperControllerLocked.hideWallpapers(w);
+            }
+        }
+        destroySurfaces();
+    }
+
     void restoreSavedSurfaces() {
         if (!canRestoreSurfaces()) {
             clearVisibleBeforeClientHidden();
@@ -456,6 +494,7 @@
     void clearAllDrawn() {
         allDrawn = false;
         deferClearAllDrawn = false;
+        allDrawnExcludingSaved = false;
     }
 
     @Override
@@ -582,10 +621,7 @@
                 w.mSkipEnterAnimationForSeamlessReplacement = !candidate.mAnimateReplacingWindow;
 
                 // if we got a replacement window, reset the timeout to give drawing more time
-                service.mH.removeMessages(H.WINDOW_REPLACEMENT_TIMEOUT);
-                service.mH.sendMessageDelayed(
-                        service.mH.obtainMessage(H.WINDOW_REPLACEMENT_TIMEOUT, this),
-                            WINDOW_REPLACEMENT_TIMEOUT_DURATION);
+                service.scheduleReplacingWindowTimeouts(this);
             }
         }
         allAppWindows.add(w);
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 6e1ff06..5b2ce07 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -123,6 +123,7 @@
     private final Rect mTouchRegion = new Rect();
     private boolean mAnimatingForIme;
     private boolean mAdjustedForIme;
+    private int mImeHeight;
     private WindowState mDelayedImeWin;
     private boolean mAdjustedForDivider;
     private float mDividerAnimationStart;
@@ -296,8 +297,9 @@
 
     void setAdjustedForIme(
             boolean adjustedForIme, boolean adjustedForDivider,
-            boolean animate, WindowState imeWin) {
-        if (mAdjustedForIme != adjustedForIme || mAdjustedForDivider != adjustedForDivider) {
+            boolean animate, WindowState imeWin, int imeHeight) {
+        if (mAdjustedForIme != adjustedForIme || (adjustedForIme && mImeHeight != imeHeight)
+                || mAdjustedForDivider != adjustedForDivider) {
             if (animate) {
                 startImeAdjustAnimation(adjustedForIme, adjustedForDivider, imeWin);
             } else {
@@ -305,10 +307,15 @@
                 notifyAdjustedForImeChanged(adjustedForIme || adjustedForDivider, 0 /* duration */);
             }
             mAdjustedForIme = adjustedForIme;
+            mImeHeight = imeHeight;
             mAdjustedForDivider = adjustedForDivider;
         }
     }
 
+    int getImeHeightAdjustedFor() {
+        return mImeHeight;
+    }
+
     void positionDockedStackedDivider(Rect frame) {
         TaskStack stack = mDisplayContent.getDockedStackLocked();
         if (stack == null) {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 58468f6..7426329 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -849,10 +849,10 @@
      *
      * @param imeWin The IME window.
      */
-    void setAdjustedForIme(WindowState imeWin) {
+    void setAdjustedForIme(WindowState imeWin, boolean forceUpdate) {
         mImeWin = imeWin;
         mImeGoingAway = false;
-        if (!mAdjustedForIme) {
+        if (!mAdjustedForIme || forceUpdate) {
             mAdjustedForIme = true;
             mAdjustImeAmount = 0f;
             mAdjustDividerAmount = 0f;
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index eae7838..be060d2 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -788,6 +788,7 @@
             removeReplacedWindowsLocked();
         }
 
+        mService.stopUsingSavedSurfaceLocked();
         mService.destroyPreservedSurfaceLocked();
         mService.mWindowPlacerLocked.destroyPendingSurfaces();
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 196b38a..8fa9efb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -415,6 +415,19 @@
     final ArrayList<AppWindowToken> mFinishedStarting = new ArrayList<>();
 
     /**
+     * List of window tokens that have finished drawing their own windows and
+     * no longer need to show any saved surfaces. Windows that's still showing
+     * saved surfaces will be cleaned up after next animation pass.
+     */
+    final ArrayList<AppWindowToken> mFinishedEarlyAnim = new ArrayList<>();
+
+    /**
+     * List of app window tokens that are waiting for replacing windows. If the
+     * replacement doesn't come in time the stale windows needs to be disposed of.
+     */
+    final ArrayList<AppWindowToken> mReplacingWindowTimeouts = new ArrayList<>();
+
+    /**
      * The input consumer added to the window manager which consumes input events to windows below
      * it.
      */
@@ -2324,6 +2337,23 @@
                 Binder.restoreCallingIdentity(origId);
                 return;
             }
+
+            if (win.isAnimatingWithSavedSurface() && !appToken.allDrawnExcludingSaved) {
+                // We started enter animation early with a saved surface, now the app asks to remove
+                // this window. If we remove it now and the app is not yet drawn, we'll show a
+                // flicker. Delay the removal now until it's really drawn.
+                if (DEBUG_ADD_REMOVE) {
+                    Slog.d(TAG_WM, "removeWindowLocked: delay removal of " + win
+                            + " due to early animation");
+                }
+                // Do not set mAnimatingExit to true here, it will cause the surface to be hidden
+                // immediately after the enter animation is done. If the app is not yet drawn then
+                // it will show up as a flicker.
+                win.mRemoveOnExit = true;
+                win.mWindowRemovalAllowed = true;
+                Binder.restoreCallingIdentity(origId);
+                return;
+            }
             // If we are not currently running the exit animation, we need to see about starting one
             wasVisible = win.isWinVisibleLw();
 
@@ -7536,6 +7566,9 @@
         final boolean imeOnTop = (imeDockSide == DOCKED_TOP);
         final boolean imeOnBottom = (imeDockSide == DOCKED_BOTTOM);
         final boolean dockMinimized = displayContent.mDividerControllerLocked.isMinimizedDock();
+        final int imeHeight = mPolicy.getInputMethodWindowVisibleHeightLw();
+        final boolean imeHeightChanged = imeVisible &&
+                imeHeight != displayContent.mDividerControllerLocked.getImeHeightAdjustedFor();
 
         // The divider could be adjusted for IME position, or be thinner than usual,
         // or both. There are three possible cases:
@@ -7549,13 +7582,13 @@
                 final TaskStack stack = stacks.get(i);
                 final boolean isDockedOnBottom = stack.getDockSide() == DOCKED_BOTTOM;
                 if (stack.isVisibleLocked() && (imeOnBottom || isDockedOnBottom)) {
-                    stack.setAdjustedForIme(imeWin);
+                    stack.setAdjustedForIme(imeWin, imeOnBottom && imeHeightChanged);
                 } else {
                     stack.resetAdjustedForIme(false);
                 }
             }
             displayContent.mDividerControllerLocked.setAdjustedForIme(
-                    imeOnBottom /*ime*/, true /*divider*/, true /*animate*/, imeWin);
+                    imeOnBottom /*ime*/, true /*divider*/, true /*animate*/, imeWin, imeHeight);
         } else {
             final ArrayList<TaskStack> stacks = displayContent.getStacks();
             for (int i = stacks.size() - 1; i >= 0; --i) {
@@ -7563,7 +7596,7 @@
                 stack.resetAdjustedForIme(!dockVisible);
             }
             displayContent.mDividerControllerLocked.setAdjustedForIme(
-                    false /*ime*/, false /*divider*/, dockVisible /*animate*/, imeWin);
+                    false /*ime*/, false /*divider*/, dockVisible /*animate*/, imeWin, imeHeight);
         }
     }
 
@@ -8497,9 +8530,12 @@
                 }
                 break;
                 case WINDOW_REPLACEMENT_TIMEOUT: {
-                    final AppWindowToken token = (AppWindowToken) msg.obj;
                     synchronized (mWindowMap) {
-                        token.clearTimedoutReplacesLocked();
+                        for (int i = mReplacingWindowTimeouts.size() - 1; i >= 0; i--) {
+                            final AppWindowToken token = mReplacingWindowTimeouts.get(i);
+                            token.clearTimedoutReplacesLocked();
+                        }
+                        mReplacingWindowTimeouts.clear();
                     }
                 }
                 case NOTIFY_APP_TRANSITION_STARTING: {
@@ -8548,6 +8584,15 @@
         }
         mDestroyPreservedSurface.clear();
     }
+
+    void stopUsingSavedSurfaceLocked() {
+        for (int i = mFinishedEarlyAnim.size() - 1; i >= 0 ; i--) {
+            final AppWindowToken wtoken = mFinishedEarlyAnim.get(i);
+            wtoken.stopUsingSavedSurfaceLocked();
+        }
+        mFinishedEarlyAnim.clear();
+    }
+
     // -------------------------------------------------------------
     // IWindowManager API
     // -------------------------------------------------------------
@@ -9172,6 +9217,18 @@
     void updateResizingWindows(final WindowState w) {
         final WindowStateAnimator winAnimator = w.mWinAnimator;
         if (w.mHasSurface && w.mLayoutSeq == mLayoutSeq && !w.isGoneForLayoutLw()) {
+            final Task task = w.getTask();
+            // In the case of stack bound animations, the window frames
+            // will update (unlike other animations which just modifiy
+            // various transformation properties). We don't want to
+            // notify the client of frame changes in this case. Not only
+            // is it a lot of churn, but the frame may not correspond
+            // to the surface size or the onscreen area at various
+            // phases in the animation, and the client will become
+            // sad and confused.
+            if (task != null && task.mStack.getBoundsAnimating()) {
+                return;
+            }
             w.setInsetsChanged();
             boolean configChanged = w.isConfigChanged();
             if (DEBUG_CONFIGURATION && configChanged) {
@@ -10757,16 +10814,22 @@
                 return;
             }
             if (replacing) {
-                mH.removeMessages(H.WINDOW_REPLACEMENT_TIMEOUT);
-                mH.sendMessageDelayed(
-                        mH.obtainMessage(H.WINDOW_REPLACEMENT_TIMEOUT, appWindowToken),
-                        WINDOW_REPLACEMENT_TIMEOUT_DURATION);
+                scheduleReplacingWindowTimeouts(appWindowToken);
             } else {
                 appWindowToken.resetReplacingWindows();
             }
         }
     }
 
+    void scheduleReplacingWindowTimeouts(AppWindowToken appWindowToken) {
+        if (!mReplacingWindowTimeouts.contains(appWindowToken)) {
+            mReplacingWindowTimeouts.add(appWindowToken);
+        }
+        mH.removeMessages(H.WINDOW_REPLACEMENT_TIMEOUT);
+        mH.sendEmptyMessageDelayed(
+                H.WINDOW_REPLACEMENT_TIMEOUT, WINDOW_REPLACEMENT_TIMEOUT_DURATION);
+    }
+
     @Override
     public int getDockedStackSide() {
         synchronized (mWindowMap) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b66de89..c9d945a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -437,7 +437,9 @@
     // used to start an entering animation earlier.
     private boolean mSurfaceSaved = false;
 
-    // Whether we're performing an entering animation with a saved surface.
+    // Whether we're performing an entering animation with a saved surface. This flag is
+    // true during the time we're showing a window with a previously saved surface. It's
+    // cleared when surface is destroyed, saved, or re-drawn by the app.
     private boolean mAnimatingWithSavedSurface;
 
     // Whether the window was visible when we set the app to invisible last time. WM uses
@@ -1256,6 +1258,32 @@
     }
 
     /**
+     * Whether this window's drawn state might affect the drawn states of the app token.
+     *
+     * @param visibleOnly Whether we should consider only the windows that's currently
+     *                    visible in layout. If true, windows that has not relayout to VISIBLE
+     *                    would always return false.
+     *
+     * @return true if the window should be considered while evaluating allDrawn flags.
+     */
+    boolean mightAffectAllDrawn(boolean visibleOnly) {
+        final boolean isViewVisible = (mViewVisibility == View.VISIBLE)
+                && (mAppToken == null || !mAppToken.clientHidden);
+        return (isOnScreenIgnoringKeyguard() && (!visibleOnly || isViewVisible)
+                || mWinAnimator.mAttrType == TYPE_BASE_APPLICATION)
+                && !mAnimatingExit && !mDestroying;
+    }
+
+    /**
+     * Whether this window is "interesting" when evaluating allDrawn. If it's interesting,
+     * it must be drawn before allDrawn can become true.
+     */
+    boolean isInteresting() {
+        return mAppToken != null && !mAppDied
+                && (!mAppToken.mAppAnimator.freezingScreen || !mAppFreezing);
+    }
+
+    /**
      * Like isOnScreen(), but we don't return true if the window is part
      * of a transition that has not yet been started.
      */
@@ -1960,6 +1988,11 @@
         return mAnimatingWithSavedSurface;
     }
 
+    boolean isAnimatingInvisibleWithSavedSurface() {
+        return mAnimatingWithSavedSurface
+                && (mViewVisibility != View.VISIBLE || mWindowRemovalAllowed);
+    }
+
     public void setVisibleBeforeClientHidden() {
         mWasVisibleBeforeClientHidden |=
                 (mViewVisibility == View.VISIBLE || mAnimatingWithSavedSurface);
@@ -2042,6 +2075,7 @@
             if (mWinAnimator.mSurfaceController != null) {
                 mWinAnimator.mSurfaceController.disconnectInTransaction();
             }
+            mAnimatingWithSavedSurface = false;
         } else {
             mWinAnimator.destroySurfaceLocked();
         }
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index e20e245..7e9993d 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -792,15 +792,16 @@
                             + " isOnScreen=" + w.isOnScreen() + " allDrawn=" + atoken.allDrawn
                             + " freezingScreen=" + atoken.mAppAnimator.freezingScreen);
                 }
-                if (atoken != null && (!atoken.allDrawn || atoken.mAppAnimator.freezingScreen)) {
+                if (atoken != null && (!atoken.allDrawn || !atoken.allDrawnExcludingSaved
+                        || atoken.mAppAnimator.freezingScreen)) {
                     if (atoken.lastTransactionSequence != mService.mTransactionSequence) {
                         atoken.lastTransactionSequence = mService.mTransactionSequence;
                         atoken.numInterestingWindows = atoken.numDrawnWindows = 0;
+                        atoken.numInterestingWindowsExcludingSaved = 0;
+                        atoken.numDrawnWindowsExclusingSaved = 0;
                         atoken.startingDisplayed = false;
                     }
-                    if ((w.isOnScreenIgnoringKeyguard()
-                            || winAnimator.mAttrType == TYPE_BASE_APPLICATION)
-                            && !w.mAnimatingExit && !w.mDestroying) {
+                    if (!atoken.allDrawn && w.mightAffectAllDrawn(false /* visibleOnly */)) {
                         if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) {
                             Slog.v(TAG, "Eval win " + w + ": isDrawn="
                                     + w.isDrawnLw()
@@ -816,13 +817,14 @@
                             }
                         }
                         if (w != atoken.startingWindow) {
-                            if (!w.mAppDied &&
-                                    (!atoken.mAppAnimator.freezingScreen || !w.mAppFreezing)) {
+                            if (w.isInteresting()) {
                                 atoken.numInterestingWindows++;
                                 if (w.isDrawnLw()) {
                                     atoken.numDrawnWindows++;
                                     if (DEBUG_VISIBILITY || DEBUG_ORIENTATION)
                                         Slog.v(TAG, "tokenMayBeDrawn: " + atoken
+                                                + " w=" + w + " numInteresting="
+                                                + atoken.numInterestingWindows
                                                 + " freezingScreen="
                                                 + atoken.mAppAnimator.freezingScreen
                                                 + " mAppFreezing=" + w.mAppFreezing);
@@ -834,6 +836,23 @@
                             atoken.startingDisplayed = true;
                         }
                     }
+                    if (!atoken.allDrawnExcludingSaved
+                            && w.mightAffectAllDrawn(true /* visibleOnly */)) {
+                        if (w != atoken.startingWindow && w.isInteresting()) {
+                            atoken.numInterestingWindowsExcludingSaved++;
+                            if (w.isDrawnLw() && !w.isAnimatingWithSavedSurface()) {
+                                atoken.numDrawnWindowsExclusingSaved++;
+                                if (DEBUG_VISIBILITY || DEBUG_ORIENTATION)
+                                    Slog.v(TAG, "tokenMayBeDrawnExcludingSaved: " + atoken
+                                            + " w=" + w + " numInteresting="
+                                            + atoken.numInterestingWindowsExcludingSaved
+                                            + " freezingScreen="
+                                            + atoken.mAppAnimator.freezingScreen
+                                            + " mAppFreezing=" + w.mAppFreezing);
+                                updateAllDrawn = true;
+                            }
+                        }
+                    }
                 }
 
                 if (isDefaultDisplay && someoneLosingFocus && w == mService.mCurrentFocus
@@ -1539,6 +1558,22 @@
                                     wtoken.token).sendToTarget();
                         }
                     }
+                    if (!wtoken.allDrawnExcludingSaved) {
+                        int numInteresting = wtoken.numInterestingWindowsExcludingSaved;
+                        if (numInteresting > 0
+                                && wtoken.numDrawnWindowsExclusingSaved >= numInteresting) {
+                            if (DEBUG_VISIBILITY)
+                                Slog.v(TAG, "allDrawnExcludingSaved: " + wtoken
+                                    + " interesting=" + numInteresting
+                                    + " drawn=" + wtoken.numDrawnWindowsExclusingSaved);
+                            wtoken.allDrawnExcludingSaved = true;
+                            displayContent.layoutNeeded = true;
+                            if (wtoken.isAnimatingInvisibleWithSavedSurface()
+                                    && !mService.mFinishedEarlyAnim.contains(wtoken)) {
+                                mService.mFinishedEarlyAnim.add(wtoken);
+                            }
+                        }
+                    }
                 }
             }
         }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 00b83841..e306d89 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -643,6 +643,10 @@
             traceBeginAndSlog("ConnectivityMetricsLoggerService");
             mSystemServiceManager.startService(MetricsLoggerService.class);
             Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+
+            traceBeginAndSlog("PinnerService");
+            mSystemServiceManager.startService(PinnerService.class);
+            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
         } catch (RuntimeException e) {
             Slog.e("System", "******************************************");
             Slog.e("System", "************ Failure starting core service", e);
diff --git a/services/net/java/android/net/apf/ApfCapabilities.java b/services/net/java/android/net/apf/ApfCapabilities.java
index 0ec50c4..b0e0230 100644
--- a/services/net/java/android/net/apf/ApfCapabilities.java
+++ b/services/net/java/android/net/apf/ApfCapabilities.java
@@ -38,7 +38,8 @@
      */
     public final int apfPacketFormat;
 
-    ApfCapabilities(int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat) {
+    public ApfCapabilities(int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat)
+    {
         this.apfVersionSupported = apfVersionSupported;
         this.maximumApfProgramSize = maximumApfProgramSize;
         this.apfPacketFormat = apfPacketFormat;
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index 5a10275..4cbfd9c 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -31,6 +31,7 @@
 import android.util.Pair;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.HexDump;
 import com.android.internal.util.IndentingPrintWriter;
 
@@ -69,7 +70,8 @@
  */
 public class ApfFilter {
     // Thread to listen for RAs.
-    private class ReceiveThread extends Thread {
+    @VisibleForTesting
+    class ReceiveThread extends Thread {
         private final byte[] mPacket = new byte[1514];
         private final FileDescriptor mSocket;
         private volatile boolean mStopped;
@@ -151,8 +153,10 @@
     private final ApfCapabilities mApfCapabilities;
     private final IpManager.Callback mIpManagerCallback;
     private final NetworkInterface mNetworkInterface;
-    private byte[] mHardwareAddress;
-    private ReceiveThread mReceiveThread;
+    @VisibleForTesting
+    byte[] mHardwareAddress;
+    @VisibleForTesting
+    ReceiveThread mReceiveThread;
     @GuardedBy("this")
     private long mUniqueCounter;
     @GuardedBy("this")
@@ -161,7 +165,8 @@
     @GuardedBy("this")
     private byte[] mIPv4Address;
 
-    private ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
+    @VisibleForTesting
+    ApfFilter(ApfCapabilities apfCapabilities, NetworkInterface networkInterface,
             IpManager.Callback ipManagerCallback, boolean multicastFilter) {
         mApfCapabilities = apfCapabilities;
         mIpManagerCallback = ipManagerCallback;
@@ -184,7 +189,8 @@
      * Attempt to start listening for RAs and, if RAs are received, generating and installing
      * filters to ignore useless RAs.
      */
-    private void maybeStartFilter() {
+    @VisibleForTesting
+    void maybeStartFilter() {
         FileDescriptor socket;
         try {
             mHardwareAddress = mNetworkInterface.getHardwareAddress();
@@ -724,7 +730,8 @@
     }
 
     @GuardedBy("this")
-    private void installNewProgramLocked() {
+    @VisibleForTesting
+    void installNewProgramLocked() {
         purgeExpiredRasLocked();
         final byte[] program;
         long programMinLifetime = Long.MAX_VALUE;
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 34152cf..d8eab35 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -276,6 +276,16 @@
         public static class Builder {
             private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
 
+            public Builder withoutIPv4() {
+                mConfig.mEnableIPv4 = false;
+                return this;
+            }
+
+            public Builder withoutIPv6() {
+                mConfig.mEnableIPv6 = false;
+                return this;
+            }
+
             public Builder withoutIpReachabilityMonitor() {
                 mConfig.mUsingIpReachabilityMonitor = false;
                 return this;
@@ -311,6 +321,8 @@
             }
         }
 
+        /* package */ boolean mEnableIPv4 = true;
+        /* package */ boolean mEnableIPv6 = true;
         /* package */ boolean mUsingIpReachabilityMonitor = true;
         /* package */ int mRequestedPreDhcpActionMs;
         /* package */ StaticIpConfiguration mStaticIpConfig;
@@ -320,6 +332,8 @@
         public ProvisioningConfiguration() {}
 
         public ProvisioningConfiguration(ProvisioningConfiguration other) {
+            mEnableIPv4 = other.mEnableIPv4;
+            mEnableIPv6 = other.mEnableIPv6;
             mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
             mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
             mStaticIpConfig = other.mStaticIpConfig;
@@ -330,6 +344,8 @@
         @Override
         public String toString() {
             return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
+                    .add("mEnableIPv4: " + mEnableIPv4)
+                    .add("mEnableIPv6: " + mEnableIPv6)
                     .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
                     .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
                     .add("mStaticIpConfig: " + mStaticIpConfig)
@@ -883,6 +899,51 @@
         }
     }
 
+    private boolean startIPv4() {
+        // If we have a StaticIpConfiguration attempt to apply it and
+        // handle the result accordingly.
+        if (mConfiguration.mStaticIpConfig != null) {
+            if (setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
+                handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
+            } else {
+                if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); }
+                recordMetric(IpManagerEvent.PROVISIONING_FAIL);
+                mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
+                return false;
+            }
+        } else {
+            // Start DHCPv4.
+            mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpManager.this, mInterfaceName);
+            mDhcpClient.registerForPreDhcpNotification();
+            mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
+
+            if (mConfiguration.mProvisioningTimeoutMs > 0) {
+                final long alarmTime = SystemClock.elapsedRealtime() +
+                        mConfiguration.mProvisioningTimeoutMs;
+                mProvisioningTimeoutAlarm.schedule(alarmTime);
+            }
+        }
+
+        return true;
+    }
+
+    private boolean startIPv6() {
+        // Set privacy extensions.
+        try {
+            mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
+            mNwService.enableIpv6(mInterfaceName);
+        } catch (RemoteException re) {
+            Log.e(mTag, "Unable to change interface settings: " + re);
+            return false;
+        } catch (IllegalStateException ie) {
+            Log.e(mTag, "Unable to change interface settings: " + ie);
+            return false;
+        }
+
+        return true;
+    }
+
+
     class StoppedState extends State {
         @Override
         public void enter() {
@@ -978,15 +1039,9 @@
                 mCallback.setFallbackMulticastFilter(mMulticastFiltering);
             }
 
-            // Set privacy extensions.
-            try {
-                mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
-                mNwService.enableIpv6(mInterfaceName);
-                // TODO: Perhaps clearIPv4Address() as well.
-            } catch (RemoteException re) {
-                Log.e(mTag, "Unable to change interface settings: " + re);
-            } catch (IllegalStateException ie) {
-                Log.e(mTag, "Unable to change interface settings: " + ie);
+            if (mConfiguration.mEnableIPv6) {
+                // TODO: Consider transitionTo(mStoppingState) if this fails.
+                startIPv6();
             }
 
             if (mConfiguration.mUsingIpReachabilityMonitor) {
@@ -1001,31 +1056,10 @@
                         });
             }
 
-            // If we have a StaticIpConfiguration attempt to apply it and
-            // handle the result accordingly.
-            if (mConfiguration.mStaticIpConfig != null) {
-                if (setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
-                    handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
-                } else {
-                    if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); }
-                    recordMetric(IpManagerEvent.PROVISIONING_FAIL);
-                    mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
+            if (mConfiguration.mEnableIPv4) {
+                if (!startIPv4()) {
                     transitionTo(mStoppingState);
                 }
-            } else {
-                // Start DHCPv4.
-                mDhcpClient = DhcpClient.makeDhcpClient(
-                        mContext,
-                        IpManager.this,
-                        mInterfaceName);
-                mDhcpClient.registerForPreDhcpNotification();
-                mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
-
-                if (mConfiguration.mProvisioningTimeoutMs > 0) {
-                    final long alarmTime = SystemClock.elapsedRealtime() +
-                            mConfiguration.mProvisioningTimeoutMs;
-                    mProvisioningTimeoutAlarm.schedule(alarmTime);
-                }
             }
         }
 
diff --git a/services/tests/servicestests/jni/apf_jni.cpp b/services/tests/servicestests/jni/apf_jni.cpp
index 7d142eb..ee43dd4 100644
--- a/services/tests/servicestests/jni/apf_jni.cpp
+++ b/services/tests/servicestests/jni/apf_jni.cpp
@@ -175,7 +175,7 @@
                     (void*)com_android_server_ApfTest_compareBpfApf },
     };
 
-    jniRegisterNativeMethods(env, "com/android/server/ApfTest",
+    jniRegisterNativeMethods(env, "android/net/apf/ApfTest",
             gMethods, ARRAY_SIZE(gMethods));
 
     return JNI_VERSION_1_6;
diff --git a/services/tests/servicestests/src/android/net/apf/ApfTest.java b/services/tests/servicestests/src/android/net/apf/ApfTest.java
new file mode 100644
index 0000000..e9c5bdd
--- /dev/null
+++ b/services/tests/servicestests/src/android/net/apf/ApfTest.java
@@ -0,0 +1,952 @@
+/*
+ * Copyright (C) 2012 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.net.apf;
+
+import static android.system.OsConstants.*;
+
+import com.android.frameworks.servicestests.R;
+
+import android.net.apf.ApfCapabilities;
+import android.net.apf.ApfFilter;
+import android.net.apf.ApfGenerator;
+import android.net.apf.ApfGenerator.IllegalInstructionException;
+import android.net.apf.ApfGenerator.Register;
+import android.net.ip.IpManager;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.os.ConditionVariable;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.nio.ByteBuffer;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * Tests for APF program generator and interpreter.
+ *
+ * Build, install and run with:
+ *  runtest frameworks-services -c com.android.server.ApfTest
+ */
+public class ApfTest extends AndroidTestCase {
+    private static final int TIMEOUT_MS = 500;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        // Load up native shared library containing APF interpreter exposed via JNI.
+        System.loadLibrary("servicestestsjni");
+    }
+
+    // Expected return codes from APF interpreter.
+    private final static int PASS = 1;
+    private final static int DROP = 0;
+    // Interpreter will just accept packets without link layer headers, so pad fake packet to at
+    // least the minimum packet size.
+    private final static int MIN_PKT_SIZE = 15;
+
+    private void assertVerdict(int expected, byte[] program, byte[] packet, int filterAge) {
+        assertEquals(expected, apfSimulate(program, packet, filterAge));
+    }
+
+    private void assertPass(byte[] program, byte[] packet, int filterAge) {
+        assertVerdict(PASS, program, packet, filterAge);
+    }
+
+    private void assertDrop(byte[] program, byte[] packet, int filterAge) {
+        assertVerdict(DROP, program, packet, filterAge);
+    }
+
+    private void assertVerdict(int expected, ApfGenerator gen, byte[] packet, int filterAge)
+            throws IllegalInstructionException {
+        assertEquals(expected, apfSimulate(gen.generate(), packet, filterAge));
+    }
+
+    private void assertPass(ApfGenerator gen, byte[] packet, int filterAge)
+            throws IllegalInstructionException {
+        assertVerdict(PASS, gen, packet, filterAge);
+    }
+
+    private void assertDrop(ApfGenerator gen, byte[] packet, int filterAge)
+            throws IllegalInstructionException {
+        assertVerdict(DROP, gen, packet, filterAge);
+    }
+
+    private void assertPass(ApfGenerator gen)
+            throws IllegalInstructionException {
+        assertVerdict(PASS, gen, new byte[MIN_PKT_SIZE], 0);
+    }
+
+    private void assertDrop(ApfGenerator gen)
+            throws IllegalInstructionException {
+        assertVerdict(DROP, gen, new byte[MIN_PKT_SIZE], 0);
+    }
+
+    /**
+     * Test each instruction by generating a program containing the instruction,
+     * generating bytecode for that program and running it through the
+     * interpreter to verify it functions correctly.
+     */
+    @LargeTest
+    public void testApfInstructions() throws IllegalInstructionException {
+        // Empty program should pass because having the program counter reach the
+        // location immediately after the program indicates the packet should be
+        // passed to the AP.
+        ApfGenerator gen = new ApfGenerator();
+        assertPass(gen);
+
+        // Test jumping to pass label.
+        gen = new ApfGenerator();
+        gen.addJump(gen.PASS_LABEL);
+        byte[] program = gen.generate();
+        assertEquals(1, program.length);
+        assertEquals((14 << 3) | (0 << 1) | 0, program[0]);
+        assertPass(program, new byte[MIN_PKT_SIZE], 0);
+
+        // Test jumping to drop label.
+        gen = new ApfGenerator();
+        gen.addJump(gen.DROP_LABEL);
+        program = gen.generate();
+        assertEquals(2, program.length);
+        assertEquals((14 << 3) | (1 << 1) | 0, program[0]);
+        assertEquals(1, program[1]);
+        assertDrop(program, new byte[15], 15);
+
+        // Test jumping if equal to 0.
+        gen = new ApfGenerator();
+        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test jumping if not equal to 0.
+        gen = new ApfGenerator();
+        gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
+        assertPass(gen);
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1);
+        gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test jumping if registers equal.
+        gen = new ApfGenerator();
+        gen.addJumpIfR0EqualsR1(gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test jumping if registers not equal.
+        gen = new ApfGenerator();
+        gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
+        assertPass(gen);
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1);
+        gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test load immediate.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1234567890);
+        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test add.
+        gen = new ApfGenerator();
+        gen.addAdd(1234567890);
+        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test subtract.
+        gen = new ApfGenerator();
+        gen.addAdd(-1234567890);
+        gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test or.
+        gen = new ApfGenerator();
+        gen.addOr(1234567890);
+        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test and.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1234567890);
+        gen.addAnd(123456789);
+        gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test left shift.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1234567890);
+        gen.addLeftShift(1);
+        gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test right shift.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1234567890);
+        gen.addRightShift(1);
+        gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test multiply.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1234567890);
+        gen.addMul(2);
+        gen.addJumpIfR0Equals(1234567890 * 2, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test divide.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1234567890);
+        gen.addDiv(2);
+        gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test divide by zero.
+        gen = new ApfGenerator();
+        gen.addDiv(0);
+        gen.addJump(gen.DROP_LABEL);
+        assertPass(gen);
+
+        // Test add.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R1, 1234567890);
+        gen.addAddR1();
+        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test subtract.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R1, -1234567890);
+        gen.addAddR1();
+        gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test or.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R1, 1234567890);
+        gen.addOrR1();
+        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test and.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1234567890);
+        gen.addLoadImmediate(Register.R1, 123456789);
+        gen.addAndR1();
+        gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test left shift.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1234567890);
+        gen.addLoadImmediate(Register.R1, 1);
+        gen.addLeftShiftR1();
+        gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test right shift.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1234567890);
+        gen.addLoadImmediate(Register.R1, -1);
+        gen.addLeftShiftR1();
+        gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test multiply.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1234567890);
+        gen.addLoadImmediate(Register.R1, 2);
+        gen.addMulR1();
+        gen.addJumpIfR0Equals(1234567890 * 2, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test divide.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1234567890);
+        gen.addLoadImmediate(Register.R1, 2);
+        gen.addDivR1();
+        gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test divide by zero.
+        gen = new ApfGenerator();
+        gen.addDivR1();
+        gen.addJump(gen.DROP_LABEL);
+        assertPass(gen);
+
+        // Test byte load.
+        gen = new ApfGenerator();
+        gen.addLoad8(Register.R0, 1);
+        gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
+        assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+        // Test out of bounds load.
+        gen = new ApfGenerator();
+        gen.addLoad8(Register.R0, 16);
+        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+        assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+        // Test half-word load.
+        gen = new ApfGenerator();
+        gen.addLoad16(Register.R0, 1);
+        gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
+        assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+        // Test word load.
+        gen = new ApfGenerator();
+        gen.addLoad32(Register.R0, 1);
+        gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
+        assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
+
+        // Test byte indexed load.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R1, 1);
+        gen.addLoad8Indexed(Register.R0, 0);
+        gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
+        assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+        // Test out of bounds indexed load.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R1, 8);
+        gen.addLoad8Indexed(Register.R0, 8);
+        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+        assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+        // Test half-word indexed load.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R1, 1);
+        gen.addLoad16Indexed(Register.R0, 0);
+        gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
+        assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
+
+        // Test word indexed load.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R1, 1);
+        gen.addLoad32Indexed(Register.R0, 0);
+        gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
+        assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
+
+        // Test jumping if greater than.
+        gen = new ApfGenerator();
+        gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
+        assertPass(gen);
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1);
+        gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test jumping if less than.
+        gen = new ApfGenerator();
+        gen.addJumpIfR0LessThan(0, gen.DROP_LABEL);
+        assertPass(gen);
+        gen = new ApfGenerator();
+        gen.addJumpIfR0LessThan(1, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test jumping if any bits set.
+        gen = new ApfGenerator();
+        gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
+        assertPass(gen);
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1);
+        gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
+        assertDrop(gen);
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 3);
+        gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test jumping if register greater than.
+        gen = new ApfGenerator();
+        gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
+        assertPass(gen);
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 2);
+        gen.addLoadImmediate(Register.R1, 1);
+        gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test jumping if register less than.
+        gen = new ApfGenerator();
+        gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
+        assertPass(gen);
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R1, 1);
+        gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test jumping if any bits set in register.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R1, 3);
+        gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
+        assertPass(gen);
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R1, 3);
+        gen.addLoadImmediate(Register.R0, 1);
+        gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
+        assertDrop(gen);
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R1, 3);
+        gen.addLoadImmediate(Register.R0, 3);
+        gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test load from memory.
+        gen = new ApfGenerator();
+        gen.addLoadFromMemory(Register.R0, 0);
+        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test store to memory.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R1, 1234567890);
+        gen.addStoreToMemory(Register.R1, 12);
+        gen.addLoadFromMemory(Register.R0, 12);
+        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test filter age pre-filled memory.
+        gen = new ApfGenerator();
+        gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
+        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+        assertDrop(gen, new byte[MIN_PKT_SIZE], 1234567890);
+
+        // Test packet size pre-filled memory.
+        gen = new ApfGenerator();
+        gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
+        gen.addJumpIfR0Equals(MIN_PKT_SIZE, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test IPv4 header size pre-filled memory.
+        gen = new ApfGenerator();
+        gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+        gen.addJumpIfR0Equals(20, gen.DROP_LABEL);
+        assertDrop(gen, new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x45}, 0);
+
+        // Test not.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1234567890);
+        gen.addNot(Register.R0);
+        gen.addJumpIfR0Equals(~1234567890, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test negate.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1234567890);
+        gen.addNeg(Register.R0);
+        gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test move.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R1, 1234567890);
+        gen.addMove(Register.R0);
+        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+        assertDrop(gen);
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1234567890);
+        gen.addMove(Register.R1);
+        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test swap.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R1, 1234567890);
+        gen.addSwap();
+        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
+        assertDrop(gen);
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1234567890);
+        gen.addSwap();
+        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
+        assertDrop(gen);
+
+        // Test jump if bytes not equal.
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1);
+        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
+        program = gen.generate();
+        assertEquals(6, program.length);
+        assertEquals((13 << 3) | (1 << 1) | 0, program[0]);
+        assertEquals(1, program[1]);
+        assertEquals(((20 << 3) | (1 << 1) | 0) - 256, program[2]);
+        assertEquals(1, program[3]);
+        assertEquals(1, program[4]);
+        assertEquals(123, program[5]);
+        assertDrop(program, new byte[MIN_PKT_SIZE], 0);
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1);
+        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
+        byte[] packet123 = new byte[]{0,123,0,0,0,0,0,0,0,0,0,0,0,0,0};
+        assertPass(gen, packet123, 0);
+        gen = new ApfGenerator();
+        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
+        assertDrop(gen, packet123, 0);
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1);
+        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,30,4,5}, gen.DROP_LABEL);
+        byte[] packet12345 = new byte[]{0,1,2,3,4,5,0,0,0,0,0,0,0,0,0};
+        assertDrop(gen, packet12345, 0);
+        gen = new ApfGenerator();
+        gen.addLoadImmediate(Register.R0, 1);
+        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,3,4,5}, gen.DROP_LABEL);
+        assertPass(gen, packet12345, 0);
+    }
+
+    /**
+     * Generate some BPF programs, translate them to APF, then run APF and BPF programs
+     * over packet traces and verify both programs filter out the same packets.
+     */
+    @LargeTest
+    public void testApfAgainstBpf() throws Exception {
+        String[] tcpdump_filters = new String[]{ "udp", "tcp", "icmp", "icmp6", "udp port 53",
+                "arp", "dst 239.255.255.250", "arp or tcp or udp port 53", "net 192.168.1.0/24",
+                "arp or icmp6 or portrange 53-54", "portrange 53-54 or portrange 100-50000",
+                "tcp[tcpflags] & (tcp-ack|tcp-fin) != 0 and (ip[2:2] > 57 or icmp)" };
+        String pcap_filename = stageFile(R.raw.apf);
+        for (String tcpdump_filter : tcpdump_filters) {
+            byte[] apf_program = Bpf2Apf.convert(compileToBpf(tcpdump_filter));
+            assertTrue("Failed to match for filter: " + tcpdump_filter,
+                    compareBpfApf(tcpdump_filter, pcap_filename, apf_program));
+        }
+    }
+
+    private class MockIpManagerCallback extends IpManager.Callback {
+        private final ConditionVariable mGotApfProgram = new ConditionVariable();
+        private byte[] mLastApfProgram;
+
+        @Override
+        public void installPacketFilter(byte[] filter) {
+            mLastApfProgram = filter;
+            mGotApfProgram.open();
+        }
+
+        public void resetApfProgramWait() {
+            mGotApfProgram.close();
+        }
+
+        public byte[] getApfProgram() {
+            assertTrue(mGotApfProgram.block(TIMEOUT_MS));
+            return mLastApfProgram;
+        }
+    }
+
+    private static class TestApfFilter extends ApfFilter {
+        public final static byte[] MOCK_MAC_ADDR = new byte[]{1,2,3,4,5,6};
+        private FileDescriptor mWriteSocket;
+
+        public TestApfFilter(IpManager.Callback ipManagerCallback, boolean multicastFilter) throws
+                Exception {
+            super(new ApfCapabilities(2, 1000, ARPHRD_ETHER), NetworkInterface.getByName("lo"),
+                    ipManagerCallback, multicastFilter);
+        }
+
+        // Pretend an RA packet has been received and show it to ApfFilter.
+        public void pretendPacketReceived(byte[] packet) throws IOException, ErrnoException {
+            // ApfFilter's ReceiveThread will be waiting to read this.
+            Os.write(mWriteSocket, packet, 0, packet.length);
+        }
+
+        @Override
+        void maybeStartFilter() {
+            mHardwareAddress = MOCK_MAC_ADDR;
+            installNewProgramLocked();
+
+            // Create two sockets, "readSocket" and "mWriteSocket" and connect them together.
+            FileDescriptor readSocket = new FileDescriptor();
+            mWriteSocket = new FileDescriptor();
+            try {
+                Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket);
+            } catch (ErrnoException e) {
+                fail();
+                return;
+            }
+            // Now pass readSocket to ReceiveThread as if it was setup to read raw RAs.
+            // This allows us to pretend RA packets have been recieved via pretendPacketReceived().
+            mReceiveThread = new ReceiveThread(readSocket);
+            mReceiveThread.start();
+        }
+
+        @Override
+        public void shutdown() {
+            super.shutdown();
+            IoUtils.closeQuietly(mWriteSocket);
+        }
+    }
+
+    private static final int ETH_HEADER_LEN = 14;
+    private static final int ETH_ETHERTYPE_OFFSET = 12;
+    private static final byte[] ETH_BROADCAST_MAC_ADDRESS = new byte[]{
+        (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
+
+    private static final int IPV4_VERSION_IHL_OFFSET = ETH_HEADER_LEN + 0;
+    private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9;
+    private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
+
+    private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6;
+    private static final int IPV6_HEADER_LEN = 40;
+    private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24;
+    // The IPv6 all nodes address ff02::1
+    private static final byte[] IPV6_ALL_NODES_ADDRESS =
+            new byte[]{ (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
+
+    private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
+    private static final int ICMP6_ROUTER_ADVERTISEMENT = 134;
+    private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136;
+
+    private static final int ICMP6_RA_HEADER_LEN = 16;
+    private static final int ICMP6_RA_ROUTER_LIFETIME_OFFSET =
+            ETH_HEADER_LEN + IPV6_HEADER_LEN + 6;
+    private static final int ICMP6_RA_CHECKSUM_OFFSET =
+            ETH_HEADER_LEN + IPV6_HEADER_LEN + 2;
+    private static final int ICMP6_RA_OPTION_OFFSET =
+            ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN;
+
+    private static final int ICMP6_PREFIX_OPTION_TYPE = 3;
+    private static final int ICMP6_PREFIX_OPTION_LEN = 32;
+    private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4;
+    private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8;
+
+    // From RFC6106: Recursive DNS Server option
+    private static final int ICMP6_RDNSS_OPTION_TYPE = 25;
+    // From RFC6106: DNS Search List option
+    private static final int ICMP6_DNSSL_OPTION_TYPE = 31;
+
+    // From RFC4191: Route Information option
+    private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24;
+    // Above three options all have the same format:
+    private static final int ICMP6_4_BYTE_OPTION_LEN = 8;
+    private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4;
+    private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4;
+
+    private static final int UDP_HEADER_LEN = 8;
+    private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 22;
+
+    private static final int DHCP_CLIENT_PORT = 68;
+    private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 48;
+
+    private static int ARP_HEADER_OFFSET = ETH_HEADER_LEN;
+    private static final byte[] ARP_IPV4_REQUEST_HEADER = new byte[]{
+            0, 1, // Hardware type: Ethernet (1)
+            8, 0, // Protocol type: IP (0x0800)
+            6,    // Hardware size: 6
+            4,    // Protocol size: 4
+            0, 1  // Opcode: request (1)
+    };
+    private static int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24;
+
+    private static byte[] MOCK_IPV4_ADDR = new byte[]{10, 0, 0, 1};
+
+    @LargeTest
+    public void testApfFilterIPv4() throws Exception {
+        MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
+        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, false /* multicastFilter */);
+        byte[] program = ipManagerCallback.getApfProgram();
+
+        // Verify empty packet of 100 zero bytes is passed
+        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+        assertPass(program, packet.array(), 0);
+
+        // Verify unicast IPv4 packet is passed
+        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+        assertPass(program, packet.array(), 0);
+
+        // Verify broadcast IPv4, not DHCP to us, is dropped
+        packet.put(ETH_BROADCAST_MAC_ADDRESS);
+        assertDrop(program, packet.array(), 0);
+        packet.put(IPV4_VERSION_IHL_OFFSET, (byte)0x45);
+        assertDrop(program, packet.array(), 0);
+        packet.put(IPV4_PROTOCOL_OFFSET, (byte)IPPROTO_UDP);
+        assertDrop(program, packet.array(), 0);
+        packet.putShort(UDP_DESTINATION_PORT_OFFSET, (short)DHCP_CLIENT_PORT);
+        assertDrop(program, packet.array(), 0);
+
+        // Verify broadcast IPv4 DHCP to us is passed
+        packet.position(DHCP_CLIENT_MAC_OFFSET);
+        packet.put(TestApfFilter.MOCK_MAC_ADDR);
+        assertPass(program, packet.array(), 0);
+
+        apfFilter.shutdown();
+    }
+
+    @LargeTest
+    public void testApfFilterIPv6() throws Exception {
+        MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
+        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, false /* multicastFilter */);
+        byte[] program = ipManagerCallback.getApfProgram();
+
+        // Verify empty IPv6 packet is passed
+        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+        assertPass(program, packet.array(), 0);
+
+        // Verify empty ICMPv6 packet is passed
+        packet.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
+        assertPass(program, packet.array(), 0);
+
+        // Verify empty ICMPv6 NA packet is passed
+        packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_NEIGHBOR_ANNOUNCEMENT);
+        assertPass(program, packet.array(), 0);
+
+        // Verify ICMPv6 NA to ff02::1 is dropped
+        packet.position(IPV6_DEST_ADDR_OFFSET);
+        packet.put(IPV6_ALL_NODES_ADDRESS);
+        assertDrop(program, packet.array(), 0);
+
+        apfFilter.shutdown();
+    }
+
+    @LargeTest
+    public void testApfFilterIPv4Multicast() throws Exception {
+        MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
+        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, false /* multicastFilter */);
+        byte[] program = ipManagerCallback.getApfProgram();
+
+        // Verify initially disabled multicast filter is off
+        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IP);
+        packet.position(IPV4_DEST_ADDR_OFFSET);
+        packet.put(new byte[]{(byte)224,0,0,1});
+        assertPass(program, packet.array(), 0);
+
+        // Turn on multicast filter and verify it works
+        ipManagerCallback.resetApfProgramWait();
+        apfFilter.setMulticastFilter(true);
+        program = ipManagerCallback.getApfProgram();
+        assertDrop(program, packet.array(), 0);
+
+        // Turn off multicast filter and verify it's off
+        ipManagerCallback.resetApfProgramWait();
+        apfFilter.setMulticastFilter(false);
+        program = ipManagerCallback.getApfProgram();
+        assertPass(program, packet.array(), 0);
+
+        // Verify it can be initialized to on
+        ipManagerCallback.resetApfProgramWait();
+        apfFilter.shutdown();
+        apfFilter = new TestApfFilter(ipManagerCallback, true /* multicastFilter */);
+        program = ipManagerCallback.getApfProgram();
+        assertDrop(program, packet.array(), 0);
+
+        apfFilter.shutdown();
+    }
+
+    private void verifyArpFilter(MockIpManagerCallback ipManagerCallback, ApfFilter apfFilter,
+            LinkProperties linkProperties, int filterResult) {
+        ipManagerCallback.resetApfProgramWait();
+        apfFilter.setLinkProperties(linkProperties);
+        byte[] program = ipManagerCallback.getApfProgram();
+        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
+        assertPass(program, packet.array(), 0);
+        packet.position(ARP_HEADER_OFFSET);
+        packet.put(ARP_IPV4_REQUEST_HEADER);
+        assertVerdict(filterResult, program, packet.array(), 0);
+        packet.position(ARP_TARGET_IP_ADDRESS_OFFSET);
+        packet.put(MOCK_IPV4_ADDR);
+        assertPass(program, packet.array(), 0);
+    }
+
+    @LargeTest
+    public void testApfFilterArp() throws Exception {
+        MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
+        ApfFilter apfFilter = new TestApfFilter(ipManagerCallback, false /* multicastFilter */);
+        byte[] program = ipManagerCallback.getApfProgram();
+
+        // Verify initially ARP filter is off
+        ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+        packet.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_ARP);
+        assertPass(program, packet.array(), 0);
+        packet.position(ARP_HEADER_OFFSET);
+        packet.put(ARP_IPV4_REQUEST_HEADER);
+        assertPass(program, packet.array(), 0);
+        packet.position(ARP_TARGET_IP_ADDRESS_OFFSET);
+        packet.put(MOCK_IPV4_ADDR);
+        assertPass(program, packet.array(), 0);
+
+        // Inform ApfFilter of our address and verify ARP filtering is on
+        LinkProperties lp = new LinkProperties();
+        assertTrue(lp.addLinkAddress(
+                new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 24)));
+        verifyArpFilter(ipManagerCallback, apfFilter, lp, DROP);
+
+        // Inform ApfFilter of loss of IP and verify ARP filtering is off
+        verifyArpFilter(ipManagerCallback, apfFilter, new LinkProperties(), PASS);
+
+        apfFilter.shutdown();
+    }
+
+    // Verify that the last program pushed to the IpManager.Callback properly filters the
+    // given packet for the given lifetime.
+    private void verifyRaLifetime(MockIpManagerCallback ipManagerCallback, ByteBuffer packet,
+            int lifetime) {
+        byte[] program = ipManagerCallback.getApfProgram();
+
+        // Verify new program should drop RA for 1/6th its lifetime
+        assertDrop(program, packet.array(), 0);
+        assertDrop(program, packet.array(), lifetime/6);
+        assertPass(program, packet.array(), lifetime/6 + 1);
+        assertPass(program, packet.array(), lifetime);
+
+        // Verify RA checksum is ignored
+        packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)12345);
+        assertDrop(program, packet.array(), 0);
+        packet.putShort(ICMP6_RA_CHECKSUM_OFFSET, (short)-12345);
+        assertDrop(program, packet.array(), 0);
+
+        // Verify other changes to RA make it not match filter
+        packet.put(0, (byte)-1);
+        assertPass(program, packet.array(), 0);
+        packet.put(0, (byte)0);
+        assertDrop(program, packet.array(), 0);
+    }
+
+    // Test that when ApfFilter is shown the given packet, it generates a program to filter it
+    // for the given lifetime.
+    private void testRaLifetime(TestApfFilter apfFilter, MockIpManagerCallback ipManagerCallback,
+            ByteBuffer packet, int lifetime) throws IOException, ErrnoException {
+        // Verify new program generated if ApfFilter witnesses RA
+        ipManagerCallback.resetApfProgramWait();
+        apfFilter.pretendPacketReceived(packet.array());
+        ipManagerCallback.getApfProgram();
+
+        verifyRaLifetime(ipManagerCallback, packet, lifetime);
+    }
+
+    @LargeTest
+    public void testApfFilterRa() throws Exception {
+        MockIpManagerCallback ipManagerCallback = new MockIpManagerCallback();
+        TestApfFilter apfFilter = new TestApfFilter(ipManagerCallback, false /* multicastFilter */);
+        byte[] program = ipManagerCallback.getApfProgram();
+
+        // Verify RA is passed the first time
+        ByteBuffer basePacket = ByteBuffer.wrap(new byte[ICMP6_RA_OPTION_OFFSET]);
+        basePacket.putShort(ETH_ETHERTYPE_OFFSET, (short)ETH_P_IPV6);
+        basePacket.put(IPV6_NEXT_HEADER_OFFSET, (byte)IPPROTO_ICMPV6);
+        basePacket.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_ADVERTISEMENT);
+        basePacket.putShort(ICMP6_RA_ROUTER_LIFETIME_OFFSET, (short)1000);
+        assertPass(program, basePacket.array(), 0);
+
+        testRaLifetime(apfFilter, ipManagerCallback, basePacket, 1000);
+
+        // Generate several RAs with different options and lifetimes, and verify when
+        // ApfFilter is shown these packets, it generates programs to filter them for the
+        // appropriate lifetime.
+        ByteBuffer prefixOptionPacket = ByteBuffer.wrap(
+                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_LEN]);
+        basePacket.clear();
+        prefixOptionPacket.put(basePacket);
+        prefixOptionPacket.put((byte)ICMP6_PREFIX_OPTION_TYPE);
+        prefixOptionPacket.put((byte)(ICMP6_PREFIX_OPTION_LEN / 8));
+        prefixOptionPacket.putInt(
+                ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET, 100);
+        prefixOptionPacket.putInt(
+                ICMP6_RA_OPTION_OFFSET + ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET, 200);
+        testRaLifetime(apfFilter, ipManagerCallback, prefixOptionPacket, 100);
+
+        ByteBuffer rdnssOptionPacket = ByteBuffer.wrap(
+                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
+        basePacket.clear();
+        rdnssOptionPacket.put(basePacket);
+        rdnssOptionPacket.put((byte)ICMP6_RDNSS_OPTION_TYPE);
+        rdnssOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
+        rdnssOptionPacket.putInt(
+                ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, 300);
+        testRaLifetime(apfFilter, ipManagerCallback, rdnssOptionPacket, 300);
+
+        ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap(
+                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
+        basePacket.clear();
+        routeInfoOptionPacket.put(basePacket);
+        routeInfoOptionPacket.put((byte)ICMP6_ROUTE_INFO_OPTION_TYPE);
+        routeInfoOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
+        routeInfoOptionPacket.putInt(
+                ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, 400);
+        testRaLifetime(apfFilter, ipManagerCallback, routeInfoOptionPacket, 400);
+
+        ByteBuffer dnsslOptionPacket = ByteBuffer.wrap(
+                new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN]);
+        basePacket.clear();
+        dnsslOptionPacket.put(basePacket);
+        dnsslOptionPacket.put((byte)ICMP6_DNSSL_OPTION_TYPE);
+        dnsslOptionPacket.put((byte)(ICMP6_4_BYTE_OPTION_LEN / 8));
+        dnsslOptionPacket.putInt(
+                ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_LIFETIME_OFFSET, 2000);
+        // Note that lifetime of 2000 will be ignored in favor of shorter
+        // route lifetime of 1000.
+        testRaLifetime(apfFilter, ipManagerCallback, dnsslOptionPacket, 1000);
+
+        // Verify that current program filters all five RAs:
+        verifyRaLifetime(ipManagerCallback, basePacket, 1000);
+        verifyRaLifetime(ipManagerCallback, prefixOptionPacket, 100);
+        verifyRaLifetime(ipManagerCallback, rdnssOptionPacket, 300);
+        verifyRaLifetime(ipManagerCallback, routeInfoOptionPacket, 400);
+        verifyRaLifetime(ipManagerCallback, dnsslOptionPacket, 1000);
+
+        apfFilter.shutdown();
+    }
+
+    /**
+     * Stage a file for testing, i.e. make it native accessible. Given a resource ID,
+     * copy that resource into the app's data directory and return the path to it.
+     */
+    private String stageFile(int rawId) throws Exception {
+        File file = new File(getContext().getFilesDir(), "staged_file");
+        new File(file.getParent()).mkdirs();
+        InputStream in = null;
+        OutputStream out = null;
+        try {
+            in = getContext().getResources().openRawResource(rawId);
+            out = new FileOutputStream(file);
+            Streams.copy(in, out);
+        } finally {
+            if (in != null) in.close();
+            if (out != null) out.close();
+        }
+        return file.getAbsolutePath();
+    }
+
+    /**
+     * Call the APF interpreter the run {@code program} on {@code packet} pretending the
+     * filter was installed {@code filter_age} seconds ago.
+     */
+    private native static int apfSimulate(byte[] program, byte[] packet, int filter_age);
+
+    /**
+     * Compile a tcpdump human-readable filter (e.g. "icmp" or "tcp port 54") into a BPF
+     * prorgam and return a human-readable dump of the BPF program identical to "tcpdump -d".
+     */
+    private native static String compileToBpf(String filter);
+
+    /**
+     * Open packet capture file {@code pcap_filename} and filter the packets using tcpdump
+     * human-readable filter (e.g. "icmp" or "tcp port 54") compiled to a BPF program and
+     * at the same time using APF program {@code apf_program}.  Return {@code true} if
+     * both APF and BPF programs filter out exactly the same packets.
+     */
+    private native static boolean compareBpfApf(String filter, String pcap_filename,
+            byte[] apf_program);
+}
diff --git a/services/tests/servicestests/src/com/android/server/Bpf2Apf.java b/services/tests/servicestests/src/android/net/apf/Bpf2Apf.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/Bpf2Apf.java
rename to services/tests/servicestests/src/android/net/apf/Bpf2Apf.java
index 29594a8..220e54d 100644
--- a/services/tests/servicestests/src/com/android/server/Bpf2Apf.java
+++ b/services/tests/servicestests/src/android/net/apf/Bpf2Apf.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server;
+package android.net.apf;
 
 import android.net.apf.ApfGenerator;
 import android.net.apf.ApfGenerator.IllegalInstructionException;
@@ -31,9 +31,9 @@
  *
  * Example usage:
  *   javac net/java/android/net/apf/ApfGenerator.java \
- *         tests/servicestests/src/com/android/server/Bpf2Apf.java
+ *         tests/servicestests/src/android/net/apf/Bpf2Apf.java
  *   sudo tcpdump -i em1 -d icmp | java -classpath tests/servicestests/src:net/java \
- *                                      com.android.server.Bpf2Apf
+ *                                      android.net.apf.Bpf2Apf
  */
 public class Bpf2Apf {
     private static int parseImm(String line, String arg) {
diff --git a/services/tests/servicestests/src/com/android/server/ApfTest.java b/services/tests/servicestests/src/com/android/server/ApfTest.java
deleted file mode 100644
index 9ba27cb..0000000
--- a/services/tests/servicestests/src/com/android/server/ApfTest.java
+++ /dev/null
@@ -1,560 +0,0 @@
-/*
- * Copyright (C) 2012 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.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-
-import com.android.frameworks.servicestests.R;
-import android.net.apf.ApfGenerator;
-import android.net.apf.ApfGenerator.IllegalInstructionException;
-import android.net.apf.ApfGenerator.Register;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-
-/**
- * Tests for APF program generator and interpreter.
- *
- * Build, install and run with:
- *  runtest frameworks-services -c com.android.server.ApfTest
- */
-public class ApfTest extends AndroidTestCase {
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        // Load up native shared library containing APF interpreter exposed via JNI.
-        System.loadLibrary("servicestestsjni");
-    }
-
-    // Expected return codes from APF interpreter.
-    private final static int PASS = 1;
-    private final static int DROP = 0;
-    // Interpreter will just accept packets without link layer headers, so pad fake packet to at
-    // least the minimum packet size.
-    private final static int MIN_PKT_SIZE = 15;
-
-    private void assertVerdict(int expected, byte[] program, byte[] packet, int filterAge) {
-        assertEquals(expected, apfSimulate(program, packet, filterAge));
-    }
-
-    private void assertPass(byte[] program, byte[] packet, int filterAge) {
-        assertVerdict(PASS, program, packet, filterAge);
-    }
-
-    private void assertDrop(byte[] program, byte[] packet, int filterAge) {
-        assertVerdict(DROP, program, packet, filterAge);
-    }
-
-    private void assertVerdict(int expected, ApfGenerator gen, byte[] packet, int filterAge)
-            throws IllegalInstructionException {
-        assertEquals(expected, apfSimulate(gen.generate(), packet, filterAge));
-    }
-
-    private void assertPass(ApfGenerator gen, byte[] packet, int filterAge)
-            throws IllegalInstructionException {
-        assertVerdict(PASS, gen, packet, filterAge);
-    }
-
-    private void assertDrop(ApfGenerator gen, byte[] packet, int filterAge)
-            throws IllegalInstructionException {
-        assertVerdict(DROP, gen, packet, filterAge);
-    }
-
-    private void assertPass(ApfGenerator gen)
-            throws IllegalInstructionException {
-        assertVerdict(PASS, gen, new byte[MIN_PKT_SIZE], 0);
-    }
-
-    private void assertDrop(ApfGenerator gen)
-            throws IllegalInstructionException {
-        assertVerdict(DROP, gen, new byte[MIN_PKT_SIZE], 0);
-    }
-
-    /**
-     * Test each instruction by generating a program containing the instruction,
-     * generating bytecode for that program and running it through the
-     * interpreter to verify it functions correctly.
-     */
-    @LargeTest
-    public void testApfInstructions() throws IllegalInstructionException {
-        // Empty program should pass because having the program counter reach the
-        // location immediately after the program indicates the packet should be
-        // passed to the AP.
-        ApfGenerator gen = new ApfGenerator();
-        assertPass(gen);
-
-        // Test jumping to pass label.
-        gen = new ApfGenerator();
-        gen.addJump(gen.PASS_LABEL);
-        byte[] program = gen.generate();
-        assertEquals(1, program.length);
-        assertEquals((14 << 3) | (0 << 1) | 0, program[0]);
-        assertPass(program, new byte[MIN_PKT_SIZE], 0);
-
-        // Test jumping to drop label.
-        gen = new ApfGenerator();
-        gen.addJump(gen.DROP_LABEL);
-        program = gen.generate();
-        assertEquals(2, program.length);
-        assertEquals((14 << 3) | (1 << 1) | 0, program[0]);
-        assertEquals(1, program[1]);
-        assertDrop(program, new byte[15], 15);
-
-        // Test jumping if equal to 0.
-        gen = new ApfGenerator();
-        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test jumping if not equal to 0.
-        gen = new ApfGenerator();
-        gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
-        assertPass(gen);
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1);
-        gen.addJumpIfR0NotEquals(0, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test jumping if registers equal.
-        gen = new ApfGenerator();
-        gen.addJumpIfR0EqualsR1(gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test jumping if registers not equal.
-        gen = new ApfGenerator();
-        gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
-        assertPass(gen);
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1);
-        gen.addJumpIfR0NotEqualsR1(gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test load immediate.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test add.
-        gen = new ApfGenerator();
-        gen.addAdd(1234567890);
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test subtract.
-        gen = new ApfGenerator();
-        gen.addAdd(-1234567890);
-        gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test or.
-        gen = new ApfGenerator();
-        gen.addOr(1234567890);
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test and.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addAnd(123456789);
-        gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test left shift.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addLeftShift(1);
-        gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test right shift.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addRightShift(1);
-        gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test multiply.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addMul(2);
-        gen.addJumpIfR0Equals(1234567890 * 2, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test divide.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addDiv(2);
-        gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test divide by zero.
-        gen = new ApfGenerator();
-        gen.addDiv(0);
-        gen.addJump(gen.DROP_LABEL);
-        assertPass(gen);
-
-        // Test add.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R1, 1234567890);
-        gen.addAddR1();
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test subtract.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R1, -1234567890);
-        gen.addAddR1();
-        gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test or.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R1, 1234567890);
-        gen.addOrR1();
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test and.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addLoadImmediate(Register.R1, 123456789);
-        gen.addAndR1();
-        gen.addJumpIfR0Equals(1234567890 & 123456789, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test left shift.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addLoadImmediate(Register.R1, 1);
-        gen.addLeftShiftR1();
-        gen.addJumpIfR0Equals(1234567890 << 1, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test right shift.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addLoadImmediate(Register.R1, -1);
-        gen.addLeftShiftR1();
-        gen.addJumpIfR0Equals(1234567890 >> 1, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test multiply.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addLoadImmediate(Register.R1, 2);
-        gen.addMulR1();
-        gen.addJumpIfR0Equals(1234567890 * 2, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test divide.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addLoadImmediate(Register.R1, 2);
-        gen.addDivR1();
-        gen.addJumpIfR0Equals(1234567890 / 2, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test divide by zero.
-        gen = new ApfGenerator();
-        gen.addDivR1();
-        gen.addJump(gen.DROP_LABEL);
-        assertPass(gen);
-
-        // Test byte load.
-        gen = new ApfGenerator();
-        gen.addLoad8(Register.R0, 1);
-        gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
-        assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
-        // Test out of bounds load.
-        gen = new ApfGenerator();
-        gen.addLoad8(Register.R0, 16);
-        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
-        assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
-        // Test half-word load.
-        gen = new ApfGenerator();
-        gen.addLoad16(Register.R0, 1);
-        gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
-        assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
-        // Test word load.
-        gen = new ApfGenerator();
-        gen.addLoad32(Register.R0, 1);
-        gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
-        assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
-
-        // Test byte indexed load.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R1, 1);
-        gen.addLoad8Indexed(Register.R0, 0);
-        gen.addJumpIfR0Equals(45, gen.DROP_LABEL);
-        assertDrop(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
-        // Test out of bounds indexed load.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R1, 8);
-        gen.addLoad8Indexed(Register.R0, 8);
-        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
-        assertPass(gen, new byte[]{123,45,0,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
-        // Test half-word indexed load.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R1, 1);
-        gen.addLoad16Indexed(Register.R0, 0);
-        gen.addJumpIfR0Equals((45 << 8) | 67, gen.DROP_LABEL);
-        assertDrop(gen, new byte[]{123,45,67,0,0,0,0,0,0,0,0,0,0,0,0}, 0);
-
-        // Test word indexed load.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R1, 1);
-        gen.addLoad32Indexed(Register.R0, 0);
-        gen.addJumpIfR0Equals((45 << 24) | (67 << 16) | (89 << 8) | 12, gen.DROP_LABEL);
-        assertDrop(gen, new byte[]{123,45,67,89,12,0,0,0,0,0,0,0,0,0,0}, 0);
-
-        // Test jumping if greater than.
-        gen = new ApfGenerator();
-        gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
-        assertPass(gen);
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1);
-        gen.addJumpIfR0GreaterThan(0, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test jumping if less than.
-        gen = new ApfGenerator();
-        gen.addJumpIfR0LessThan(0, gen.DROP_LABEL);
-        assertPass(gen);
-        gen = new ApfGenerator();
-        gen.addJumpIfR0LessThan(1, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test jumping if any bits set.
-        gen = new ApfGenerator();
-        gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
-        assertPass(gen);
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1);
-        gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
-        assertDrop(gen);
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 3);
-        gen.addJumpIfR0AnyBitsSet(3, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test jumping if register greater than.
-        gen = new ApfGenerator();
-        gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
-        assertPass(gen);
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 2);
-        gen.addLoadImmediate(Register.R1, 1);
-        gen.addJumpIfR0GreaterThanR1(gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test jumping if register less than.
-        gen = new ApfGenerator();
-        gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
-        assertPass(gen);
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R1, 1);
-        gen.addJumpIfR0LessThanR1(gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test jumping if any bits set in register.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R1, 3);
-        gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
-        assertPass(gen);
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R1, 3);
-        gen.addLoadImmediate(Register.R0, 1);
-        gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
-        assertDrop(gen);
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R1, 3);
-        gen.addLoadImmediate(Register.R0, 3);
-        gen.addJumpIfR0AnyBitsSetR1(gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test load from memory.
-        gen = new ApfGenerator();
-        gen.addLoadFromMemory(Register.R0, 0);
-        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test store to memory.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R1, 1234567890);
-        gen.addStoreToMemory(Register.R1, 12);
-        gen.addLoadFromMemory(Register.R0, 12);
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test filter age pre-filled memory.
-        gen = new ApfGenerator();
-        gen.addLoadFromMemory(Register.R0, gen.FILTER_AGE_MEMORY_SLOT);
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen, new byte[MIN_PKT_SIZE], 1234567890);
-
-        // Test packet size pre-filled memory.
-        gen = new ApfGenerator();
-        gen.addLoadFromMemory(Register.R0, gen.PACKET_SIZE_MEMORY_SLOT);
-        gen.addJumpIfR0Equals(MIN_PKT_SIZE, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test IPv4 header size pre-filled memory.
-        gen = new ApfGenerator();
-        gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
-        gen.addJumpIfR0Equals(20, gen.DROP_LABEL);
-        assertDrop(gen, new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x45}, 0);
-
-        // Test not.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addNot(Register.R0);
-        gen.addJumpIfR0Equals(~1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test negate.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addNeg(Register.R0);
-        gen.addJumpIfR0Equals(-1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test move.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R1, 1234567890);
-        gen.addMove(Register.R0);
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addMove(Register.R1);
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test swap.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R1, 1234567890);
-        gen.addSwap();
-        gen.addJumpIfR0Equals(1234567890, gen.DROP_LABEL);
-        assertDrop(gen);
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1234567890);
-        gen.addSwap();
-        gen.addJumpIfR0Equals(0, gen.DROP_LABEL);
-        assertDrop(gen);
-
-        // Test jump if bytes not equal.
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1);
-        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
-        program = gen.generate();
-        assertEquals(6, program.length);
-        assertEquals((13 << 3) | (1 << 1) | 0, program[0]);
-        assertEquals(1, program[1]);
-        assertEquals(((20 << 3) | (1 << 1) | 0) - 256, program[2]);
-        assertEquals(1, program[3]);
-        assertEquals(1, program[4]);
-        assertEquals(123, program[5]);
-        assertDrop(program, new byte[MIN_PKT_SIZE], 0);
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1);
-        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
-        byte[] packet123 = new byte[]{0,123,0,0,0,0,0,0,0,0,0,0,0,0,0};
-        assertPass(gen, packet123, 0);
-        gen = new ApfGenerator();
-        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{123}, gen.DROP_LABEL);
-        assertDrop(gen, packet123, 0);
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1);
-        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,30,4,5}, gen.DROP_LABEL);
-        byte[] packet12345 = new byte[]{0,1,2,3,4,5,0,0,0,0,0,0,0,0,0};
-        assertDrop(gen, packet12345, 0);
-        gen = new ApfGenerator();
-        gen.addLoadImmediate(Register.R0, 1);
-        gen.addJumpIfBytesNotEqual(Register.R0, new byte[]{1,2,3,4,5}, gen.DROP_LABEL);
-        assertPass(gen, packet12345, 0);
-    }
-
-    /**
-     * Generate some BPF programs, translate them to APF, then run APF and BPF programs
-     * over packet traces and verify both programs filter out the same packets.
-     */
-    @LargeTest
-    public void testApfAgainstBpf() throws Exception {
-        String[] tcpdump_filters = new String[]{ "udp", "tcp", "icmp", "icmp6", "udp port 53",
-                "arp", "dst 239.255.255.250", "arp or tcp or udp port 53", "net 192.168.1.0/24",
-                "arp or icmp6 or portrange 53-54", "portrange 53-54 or portrange 100-50000",
-                "tcp[tcpflags] & (tcp-ack|tcp-fin) != 0 and (ip[2:2] > 57 or icmp)" };
-        String pcap_filename = stageFile(R.raw.apf);
-        for (String tcpdump_filter : tcpdump_filters) {
-            byte[] apf_program = Bpf2Apf.convert(compileToBpf(tcpdump_filter));
-            assertTrue("Failed to match for filter: " + tcpdump_filter,
-                    compareBpfApf(tcpdump_filter, pcap_filename, apf_program));
-        }
-    }
-
-    /**
-     * Stage a file for testing, i.e. make it native accessible. Given a resource ID,
-     * copy that resource into the app's data directory and return the path to it.
-     */
-    private String stageFile(int rawId) throws Exception {
-        File file = new File(getContext().getFilesDir(), "staged_file");
-        new File(file.getParent()).mkdirs();
-        InputStream in = null;
-        OutputStream out = null;
-        try {
-            in = getContext().getResources().openRawResource(rawId);
-            out = new FileOutputStream(file);
-            Streams.copy(in, out);
-        } finally {
-            if (in != null) in.close();
-            if (out != null) out.close();
-        }
-        return file.getAbsolutePath();
-    }
-
-    /**
-     * Call the APF interpreter the run {@code program} on {@code packet} pretending the
-     * filter was installed {@code filter_age} seconds ago.
-     */
-    private native static int apfSimulate(byte[] program, byte[] packet, int filter_age);
-
-    /**
-     * Compile a tcpdump human-readable filter (e.g. "icmp" or "tcp port 54") into a BPF
-     * prorgam and return a human-readable dump of the BPF program identical to "tcpdump -d".
-     */
-    private native static String compileToBpf(String filter);
-
-    /**
-     * Open packet capture file {@code pcap_filename} and filter the packets using tcpdump
-     * human-readable filter (e.g. "icmp" or "tcp port 54") compiled to a BPF program and
-     * at the same time using APF program {@code apf_program}.  Return {@code true} if
-     * both APF and BPF programs filter out exactly the same packets.
-     */
-    private native static boolean compareBpfApf(String filter, String pcap_filename,
-            byte[] apf_program);
-}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 8f09bda..33e6cea 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -699,7 +699,7 @@
         sDefaults.putBoolean(KEY_CARRIER_VOLTE_AVAILABLE_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_VT_AVAILABLE_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL, false);
-        sDefaults.putBoolean(KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, true);
+        sDefaults.putBoolean(KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL, false);
         sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT, 2);