Merge "Ensure we don't pass null values in to Theme.resolveAttributes" into lmp-mr1-dev
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index d0ba4b8..5d5d2b3 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -92,6 +92,11 @@
     void enableIpv6(String iface);
 
     /**
+     * Enables or enables IPv6 ND offload.
+     */
+    void setInterfaceIpv6NdOffload(String iface, boolean enable);
+
+    /**
      * Retrieves the network routes currently configured on the specified
      * interface
      */
diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java
index 7198e52..0a8a01f 100644
--- a/core/java/android/widget/ActionMenuView.java
+++ b/core/java/android/widget/ActionMenuView.java
@@ -429,7 +429,7 @@
         }
 
         final int childCount = getChildCount();
-        final int midVertical = (top + bottom) / 2;
+        final int midVertical = (bottom - top) / 2;
         final int dividerWidth = getDividerWidth();
         int overflowWidth = 0;
         int nonOverflowWidth = 0;
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 1ac4dd8..c68bfca 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -386,21 +386,21 @@
      */
     @android.view.RemotableViewMethod
     public void setImageResource(int resId) {
-        if (mUri != null || mResource != resId) {
-            final int oldWidth = mDrawableWidth;
-            final int oldHeight = mDrawableHeight;
+        // The resource configuration may have changed, so we should always
+        // try to load the resource even if the resId hasn't changed.
+        final int oldWidth = mDrawableWidth;
+        final int oldHeight = mDrawableHeight;
 
-            updateDrawable(null);
-            mResource = resId;
-            mUri = null;
+        updateDrawable(null);
+        mResource = resId;
+        mUri = null;
 
-            resolveUri();
+        resolveUri();
 
-            if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
-                requestLayout();
-            }
-            invalidate();
+        if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
+            requestLayout();
         }
+        invalidate();
     }
 
     /**
diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java
index 4410f25..34b9dcb 100644
--- a/core/java/com/android/internal/app/ToolbarActionBar.java
+++ b/core/java/com/android/internal/app/ToolbarActionBar.java
@@ -40,7 +40,6 @@
 import java.util.ArrayList;
 
 public class ToolbarActionBar extends ActionBar {
-    private Toolbar mToolbar;
     private DecorToolbar mDecorToolbar;
     private boolean mToolbarMenuPrepared;
     private Window.Callback mWindowCallback;
@@ -66,7 +65,6 @@
     };
 
     public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback windowCallback) {
-        mToolbar = toolbar;
         mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false);
         mWindowCallback = new ToolbarCallbackWrapper(windowCallback);
         mDecorToolbar.setWindowCallback(mWindowCallback);
@@ -91,8 +89,8 @@
 
     @Override
     public void setCustomView(int resId) {
-        final LayoutInflater inflater = LayoutInflater.from(mToolbar.getContext());
-        setCustomView(inflater.inflate(resId, mToolbar, false));
+        final LayoutInflater inflater = LayoutInflater.from(mDecorToolbar.getContext());
+        setCustomView(inflater.inflate(resId, mDecorToolbar.getViewGroup(), false));
     }
 
     @Override
@@ -132,17 +130,17 @@
 
     @Override
     public void setElevation(float elevation) {
-        mToolbar.setElevation(elevation);
+        mDecorToolbar.getViewGroup().setElevation(elevation);
     }
 
     @Override
     public float getElevation() {
-        return mToolbar.getElevation();
+        return mDecorToolbar.getViewGroup().getElevation();
     }
 
     @Override
     public Context getThemedContext() {
-        return mToolbar.getContext();
+        return mDecorToolbar.getContext();
     }
 
     @Override
@@ -152,12 +150,12 @@
 
     @Override
     public void setHomeAsUpIndicator(Drawable indicator) {
-        mToolbar.setNavigationIcon(indicator);
+        mDecorToolbar.setNavigationIcon(indicator);
     }
 
     @Override
     public void setHomeAsUpIndicator(int resId) {
-        mToolbar.setNavigationIcon(resId);
+        mDecorToolbar.setNavigationIcon(resId);
     }
 
     @Override
@@ -280,7 +278,7 @@
 
     @Override
     public void setBackgroundDrawable(@Nullable Drawable d) {
-        mToolbar.setBackground(d);
+        mDecorToolbar.setBackgroundDrawable(d);
     }
 
     @Override
@@ -290,12 +288,12 @@
 
     @Override
     public CharSequence getTitle() {
-        return mToolbar.getTitle();
+        return mDecorToolbar.getTitle();
     }
 
     @Override
     public CharSequence getSubtitle() {
-        return mToolbar.getSubtitle();
+        return mDecorToolbar.getSubtitle();
     }
 
     @Override
@@ -389,44 +387,44 @@
 
     @Override
     public int getHeight() {
-        return mToolbar.getHeight();
+        return mDecorToolbar.getHeight();
     }
 
     @Override
     public void show() {
         // TODO: Consider a better transition for this.
         // Right now use no automatic transition so that the app can supply one if desired.
-        mToolbar.setVisibility(View.VISIBLE);
+        mDecorToolbar.setVisibility(View.VISIBLE);
     }
 
     @Override
     public void hide() {
         // TODO: Consider a better transition for this.
         // Right now use no automatic transition so that the app can supply one if desired.
-        mToolbar.setVisibility(View.GONE);
+        mDecorToolbar.setVisibility(View.GONE);
     }
 
     @Override
     public boolean isShowing() {
-        return mToolbar.getVisibility() == View.VISIBLE;
+        return mDecorToolbar.getVisibility() == View.VISIBLE;
     }
 
     @Override
     public boolean openOptionsMenu() {
-        return mToolbar.showOverflowMenu();
+        return mDecorToolbar.showOverflowMenu();
     }
 
     @Override
     public boolean invalidateOptionsMenu() {
-        mToolbar.removeCallbacks(mMenuInvalidator);
-        mToolbar.postOnAnimation(mMenuInvalidator);
+        mDecorToolbar.getViewGroup().removeCallbacks(mMenuInvalidator);
+        mDecorToolbar.getViewGroup().postOnAnimation(mMenuInvalidator);
         return true;
     }
 
     @Override
     public boolean collapseActionView() {
-        if (mToolbar.hasExpandedActionView()) {
-            mToolbar.collapseActionView();
+        if (mDecorToolbar.hasExpandedActionView()) {
+            mDecorToolbar.collapseActionView();
             return true;
         }
         return false;
@@ -434,10 +432,10 @@
 
     void populateOptionsMenu() {
         if (!mMenuCallbackSet) {
-            mToolbar.setMenuCallbacks(new ActionMenuPresenterCallback(), new MenuBuilderCallback());
+            mDecorToolbar.setMenuCallbacks(new ActionMenuPresenterCallback(), new MenuBuilderCallback());
             mMenuCallbackSet = true;
         }
-        final Menu menu = mToolbar.getMenu();
+        final Menu menu = mDecorToolbar.getMenu();
         final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null;
         if (mb != null) {
             mb.stopDispatchingItemsChanged();
@@ -518,7 +516,7 @@
             }
 
             mClosingActionMenu = true;
-            mToolbar.dismissPopupMenus();
+            mDecorToolbar.dismissPopupMenus();
             if (mWindowCallback != null) {
                 mWindowCallback.onPanelClosed(Window.FEATURE_ACTION_BAR, menu);
             }
@@ -536,7 +534,7 @@
         @Override
         public void onMenuModeChange(MenuBuilder menu) {
             if (mWindowCallback != null) {
-                if (mToolbar.isOverflowMenuShowing()) {
+                if (mDecorToolbar.isOverflowMenuShowing()) {
                     mWindowCallback.onPanelClosed(Window.FEATURE_ACTION_BAR, menu);
                 } else if (mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL,
                         null, menu)) {
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index b9a85e5..654d08b 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -1341,6 +1341,22 @@
         updateHomeAccessibility(mUpGoerFive.isEnabled());
     }
 
+    @Override
+    public void setMenuCallbacks(MenuPresenter.Callback presenterCallback,
+            MenuBuilder.Callback menuBuilderCallback) {
+        if (mActionMenuPresenter != null) {
+            mActionMenuPresenter.setCallback(presenterCallback);
+        }
+        if (mOptionsMenu != null) {
+            mOptionsMenu.setCallback(menuBuilderCallback);
+        }
+    }
+
+    @Override
+    public Menu getMenu() {
+        return mOptionsMenu;
+    }
+
     static class SavedState extends BaseSavedState {
         int expandedMenuItemId;
         boolean isOverflowOpen;
diff --git a/core/java/com/android/internal/widget/DecorToolbar.java b/core/java/com/android/internal/widget/DecorToolbar.java
index f89f0b7..fb413b5 100644
--- a/core/java/com/android/internal/widget/DecorToolbar.java
+++ b/core/java/com/android/internal/widget/DecorToolbar.java
@@ -27,6 +27,8 @@
 import android.view.Window;
 import android.widget.AdapterView;
 import android.widget.SpinnerAdapter;
+
+import com.android.internal.view.menu.MenuBuilder;
 import com.android.internal.view.menu.MenuPresenter;
 
 /**
@@ -93,4 +95,11 @@
     void setDefaultNavigationIcon(Drawable icon);
     void saveHierarchyState(SparseArray<Parcelable> toolbarStates);
     void restoreHierarchyState(SparseArray<Parcelable> toolbarStates);
+    void setBackgroundDrawable(Drawable d);
+    int getHeight();
+    void setVisibility(int visible);
+    int getVisibility();
+    void setMenuCallbacks(MenuPresenter.Callback presenterCallback,
+            MenuBuilder.Callback menuBuilderCallback);
+    Menu getMenu();
 }
diff --git a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
index 324a6c9..054ca30 100644
--- a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
+++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
@@ -657,4 +657,36 @@
         mToolbar.restoreHierarchyState(toolbarStates);
     }
 
+    @Override
+    public void setBackgroundDrawable(Drawable d) {
+        //noinspection deprecation
+        mToolbar.setBackgroundDrawable(d);
+    }
+
+    @Override
+    public int getHeight() {
+        return mToolbar.getHeight();
+    }
+
+    @Override
+    public void setVisibility(int visible) {
+        mToolbar.setVisibility(visible);
+    }
+
+    @Override
+    public int getVisibility() {
+        return mToolbar.getVisibility();
+    }
+
+    @Override
+    public void setMenuCallbacks(MenuPresenter.Callback presenterCallback,
+            MenuBuilder.Callback menuBuilderCallback) {
+        mToolbar.setMenuCallbacks(presenterCallback, menuBuilderCallback);
+    }
+
+    @Override
+    public Menu getMenu() {
+        return mToolbar.getMenu();
+    }
+
 }
diff --git a/docs/html/training/wearables/notifications/pages.jd b/docs/html/training/wearables/notifications/pages.jd
index d74c8ea..6315037 100644
--- a/docs/html/training/wearables/notifications/pages.jd
+++ b/docs/html/training/wearables/notifications/pages.jd
@@ -57,15 +57,14 @@
         .setStyle(secondPageStyle)
         .build();
 
-// Add second page with wearable extender and extend the main notification
-Notification twoPageNotification =
-        new WearableExtender()
-                .addPage(secondPageNotification)
-                .extend(notificationBuilder)
-                .build();
+// Extend the notification builder with the second page
+Notification notification = notificationBuilder
+        .extend(new NotificationCompat.WearableExtender()
+                .addPage(secondPageNotification))
+        .build();
 
 // Issue the notification
 notificationManager =
         NotificationManagerCompat.from(this);
-notificationManager.notify(notificationId, twoPageNotification);
+notificationManager.notify(notificationId, notification);
 </pre>
\ No newline at end of file
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index b8cdc4b..1ac80c1 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -166,6 +166,7 @@
 
     @Override
     public void release() {
+        mTvInputSessionImpl.scheduleOverlayViewCleanup();
         mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
     }
 
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 5d5ea02..0ca5810 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -25,10 +25,12 @@
 import android.graphics.Rect;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
+import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.text.TextUtils;
@@ -44,10 +46,12 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.view.accessibility.CaptioningManager;
+import android.widget.FrameLayout;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -241,16 +245,25 @@
      * Base class for derived classes to implement to provide a TV input session.
      */
     public abstract static class Session implements KeyEvent.Callback {
+        private static final int DETACH_OVERLAY_VIEW_TIMEOUT = 5000;
         private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
         private final WindowManager mWindowManager;
         final Handler mHandler;
         private WindowManager.LayoutParams mWindowParams;
         private Surface mSurface;
+        private Context mContext;
+        private FrameLayout mOverlayViewContainer;
         private View mOverlayView;
+        private OverlayViewCleanUpTask mOverlayViewCleanUpTask;
         private boolean mOverlayViewEnabled;
         private IBinder mWindowToken;
         private Rect mOverlayFrame;
+
+        private Object mLock = new Object();
+        // @GuardedBy("mLock")
         private ITvInputSessionCallback mSessionCallback;
+        // @GuardedBy("mLock")
+        private List<Runnable> mPendingActions = new ArrayList<>();
 
         /**
          * Creates a new Session.
@@ -258,6 +271,7 @@
          * @param context The context of the application
          */
         public Session(Context context) {
+            mContext = context;
             mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
             mHandler = new Handler(context.getMainLooper());
         }
@@ -295,11 +309,12 @@
          * @param eventArgs Optional arguments of the event.
          * @hide
          */
+        @SystemApi
         public void notifySessionEvent(final String eventType, final Bundle eventArgs) {
             if (eventType == null) {
                 throw new IllegalArgumentException("eventType should not be null.");
             }
-            runOnMainThread(new Runnable() {
+            executeOrPostRunnable(new Runnable() {
                 @Override
                 public void run() {
                     try {
@@ -318,7 +333,7 @@
          * @param channelUri The URI of a channel.
          */
         public void notifyChannelRetuned(final Uri channelUri) {
-            runOnMainThread(new Runnable() {
+            executeOrPostRunnable(new Runnable() {
                 @Override
                 public void run() {
                     try {
@@ -355,7 +370,7 @@
             trackIdSet.clear();
 
             // TODO: Validate the track list.
-            runOnMainThread(new Runnable() {
+            executeOrPostRunnable(new Runnable() {
                 @Override
                 public void run() {
                     try {
@@ -383,7 +398,7 @@
          * @see #onSelectTrack
          */
         public void notifyTrackSelected(final int type, final String trackId) {
-            runOnMainThread(new Runnable() {
+            executeOrPostRunnable(new Runnable() {
                 @Override
                 public void run() {
                     try {
@@ -404,7 +419,7 @@
          * @see #notifyVideoUnavailable
          */
         public void notifyVideoAvailable() {
-            runOnMainThread(new Runnable() {
+            executeOrPostRunnable(new Runnable() {
                 @Override
                 public void run() {
                     try {
@@ -436,7 +451,7 @@
                     || reason > TvInputManager.VIDEO_UNAVAILABLE_REASON_END) {
                 throw new IllegalArgumentException("Unknown reason: " + reason);
             }
-            runOnMainThread(new Runnable() {
+            executeOrPostRunnable(new Runnable() {
                 @Override
                 public void run() {
                     try {
@@ -475,7 +490,7 @@
          * @see TvInputManager
          */
         public void notifyContentAllowed() {
-            runOnMainThread(new Runnable() {
+            executeOrPostRunnable(new Runnable() {
                 @Override
                 public void run() {
                     try {
@@ -515,7 +530,7 @@
          * @see TvInputManager
          */
         public void notifyContentBlocked(final TvContentRating rating) {
-            runOnMainThread(new Runnable() {
+            executeOrPostRunnable(new Runnable() {
                 @Override
                 public void run() {
                     try {
@@ -544,7 +559,7 @@
             if (left > right || top > bottm) {
                 throw new IllegalArgumentException("Invalid parameter");
             }
-            runOnMainThread(new Runnable() {
+            executeOrPostRunnable(new Runnable() {
                 @Override
                 public void run() {
                     try {
@@ -846,12 +861,18 @@
          * session.
          */
         void release() {
-            removeOverlayView(true);
             onRelease();
             if (mSurface != null) {
                 mSurface.release();
                 mSurface = null;
             }
+            synchronized(mLock) {
+                mSessionCallback = null;
+                mPendingActions.clear();
+            }
+            // Removes the overlay view lastly so that any hanging on the main thread can be handled
+            // in {@link #scheduleOverlayViewCleanup}.
+            removeOverlayView(true);
         }
 
         /**
@@ -936,9 +957,8 @@
          * @param frame A position of the overlay view.
          */
         void createOverlayView(IBinder windowToken, Rect frame) {
-            if (mOverlayView != null) {
-                mWindowManager.removeView(mOverlayView);
-                mOverlayView = null;
+            if (mOverlayViewContainer != null) {
+                removeOverlayView(false);
             }
             if (DEBUG) Log.d(TAG, "create overlay view(" + frame + ")");
             mWindowToken = windowToken;
@@ -951,6 +971,15 @@
             if (mOverlayView == null) {
                 return;
             }
+            if (mOverlayViewCleanUpTask != null) {
+                mOverlayViewCleanUpTask.cancel(true);
+                mOverlayViewCleanUpTask = null;
+            }
+            // Creates a container view to check hanging on the overlay view detaching.
+            // Adding/removing the overlay view to/from the container make the view attach/detach
+            // logic run on the main thread.
+            mOverlayViewContainer = new FrameLayout(mContext);
+            mOverlayViewContainer.addView(mOverlayView);
             // TvView's window type is TYPE_APPLICATION_MEDIA and we want to create
             // an overlay window above the media window but below the application window.
             int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
@@ -967,7 +996,7 @@
                     WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
             mWindowParams.gravity = Gravity.START | Gravity.TOP;
             mWindowParams.token = windowToken;
-            mWindowManager.addView(mOverlayView, mWindowParams);
+            mWindowManager.addView(mOverlayViewContainer, mWindowParams);
         }
 
         /**
@@ -984,33 +1013,51 @@
                 onOverlayViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
             }
             mOverlayFrame = frame;
-            if (!mOverlayViewEnabled || mOverlayView == null) {
+            if (!mOverlayViewEnabled || mOverlayViewContainer == null) {
                 return;
             }
             mWindowParams.x = frame.left;
             mWindowParams.y = frame.top;
             mWindowParams.width = frame.right - frame.left;
             mWindowParams.height = frame.bottom - frame.top;
-            mWindowManager.updateViewLayout(mOverlayView, mWindowParams);
+            mWindowManager.updateViewLayout(mOverlayViewContainer, mWindowParams);
         }
 
         /**
          * Removes the current overlay view.
          */
         void removeOverlayView(boolean clearWindowToken) {
-            if (DEBUG) Log.d(TAG, "removeOverlayView(" + mOverlayView + ")");
+            if (DEBUG) Log.d(TAG, "removeOverlayView(" + mOverlayViewContainer + ")");
             if (clearWindowToken) {
                 mWindowToken = null;
                 mOverlayFrame = null;
             }
-            if (mOverlayView != null) {
-                mWindowManager.removeView(mOverlayView);
+            if (mOverlayViewContainer != null) {
+                // Removes the overlay view from the view hierarchy in advance so that it can be
+                // cleaned up in the {@link OverlayViewCleanUpTask} if the remove process is
+                // hanging.
+                mOverlayViewContainer.removeView(mOverlayView);
                 mOverlayView = null;
+                mWindowManager.removeView(mOverlayViewContainer);
+                mOverlayViewContainer = null;
                 mWindowParams = null;
             }
         }
 
         /**
+         * Schedules a task which checks whether the overlay view is detached and kills the process
+         * if it is not. Note that this method is expected to be called in a non-main thread.
+         */
+        void scheduleOverlayViewCleanup() {
+            View overlayViewParent = mOverlayViewContainer;
+            if (overlayViewParent != null) {
+                mOverlayViewCleanUpTask = new OverlayViewCleanUpTask();
+                mOverlayViewCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
+                        overlayViewParent);
+            }
+        }
+
+        /**
          * Takes care of dispatching incoming input events and tells whether the event was handled.
          */
         int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
@@ -1039,37 +1086,70 @@
                     }
                 }
             }
-            if (mOverlayView == null || !mOverlayView.isAttachedToWindow()) {
+            if (mOverlayViewContainer == null || !mOverlayViewContainer.isAttachedToWindow()) {
                 return TvInputManager.Session.DISPATCH_NOT_HANDLED;
             }
-            if (!mOverlayView.hasWindowFocus()) {
-                mOverlayView.getViewRootImpl().windowFocusChanged(true, true);
+            if (!mOverlayViewContainer.hasWindowFocus()) {
+                mOverlayViewContainer.getViewRootImpl().windowFocusChanged(true, true);
             }
-            if (isNavigationKey && mOverlayView.hasFocusable()) {
+            if (isNavigationKey && mOverlayViewContainer.hasFocusable()) {
                 // If mOverlayView has focusable views, navigation key events should be always
                 // handled. If not, it can make the application UI navigation messed up.
                 // For example, in the case that the left-most view is focused, a left key event
                 // will not be handled in ViewRootImpl. Then, the left key event will be handled in
                 // the application during the UI navigation of the TV input.
-                mOverlayView.getViewRootImpl().dispatchInputEvent(event);
+                mOverlayViewContainer.getViewRootImpl().dispatchInputEvent(event);
                 return TvInputManager.Session.DISPATCH_HANDLED;
             } else {
-                mOverlayView.getViewRootImpl().dispatchInputEvent(event, receiver);
+                mOverlayViewContainer.getViewRootImpl().dispatchInputEvent(event, receiver);
                 return TvInputManager.Session.DISPATCH_IN_PROGRESS;
             }
         }
 
-        private void setSessionCallback(ITvInputSessionCallback callback) {
-            mSessionCallback = callback;
+        private void initialize(ITvInputSessionCallback callback) {
+            synchronized(mLock) {
+                mSessionCallback = callback;
+                for (Runnable runnable : mPendingActions) {
+                    runnable.run();
+                }
+                mPendingActions.clear();
+            }
         }
 
-        private final void runOnMainThread(Runnable action) {
-            if (mHandler.getLooper().isCurrentThread() && mSessionCallback != null) {
-                action.run();
-            } else {
-                // Posts the runnable if this is not called from the main thread or the session
-                // is not initialized yet.
-                mHandler.post(action);
+        private final void executeOrPostRunnable(Runnable action) {
+            synchronized(mLock) {
+                if (mSessionCallback == null) {
+                    // The session is not initialized yet.
+                    mPendingActions.add(action);
+                } else {
+                    if (mHandler.getLooper().isCurrentThread()) {
+                        action.run();
+                    } else {
+                        // Posts the runnable if this is not called from the main thread
+                        mHandler.post(action);
+                    }
+                }
+            }
+        }
+
+        private final class OverlayViewCleanUpTask extends AsyncTask<View, Void, Void> {
+            @Override
+            protected Void doInBackground(View... views) {
+                View overlayViewParent = views[0];
+                try {
+                    Thread.sleep(DETACH_OVERLAY_VIEW_TIMEOUT);
+                } catch (InterruptedException e) {
+                    return null;
+                }
+                if (isCancelled()) {
+                    return null;
+                }
+                if (overlayViewParent.isAttachedToWindow()) {
+                    Log.e(TAG, "Time out on releasing overlay view. Killing "
+                            + overlayViewParent.getContext().getPackageName());
+                    Process.killProcess(Process.myPid());
+                }
+                return null;
             }
         }
     }
@@ -1125,13 +1205,15 @@
                 mHardwareSession = session;
                 SomeArgs args = SomeArgs.obtain();
                 if (session != null) {
-                    args.arg1 = mProxySession;
-                    args.arg2 = mProxySessionCallback;
-                    args.arg3 = session.getToken();
+                    args.arg1 = HardwareSession.this;
+                    args.arg2 = mProxySession;
+                    args.arg3 = mProxySessionCallback;
+                    args.arg4 = session.getToken();
                 } else {
                     args.arg1 = null;
-                    args.arg2 = mProxySessionCallback;
-                    args.arg3 = null;
+                    args.arg2 = null;
+                    args.arg3 = mProxySessionCallback;
+                    args.arg4 = null;
                     onRelease();
                 }
                 mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED, args)
@@ -1269,7 +1351,6 @@
                         }
                         return;
                     }
-                    sessionImpl.setSessionCallback(cb);
                     ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
                             sessionImpl, channel);
                     if (sessionImpl instanceof HardwareSession) {
@@ -1300,9 +1381,10 @@
                                 proxySession.mHardwareSessionCallback, mServiceHandler);
                     } else {
                         SomeArgs someArgs = SomeArgs.obtain();
-                        someArgs.arg1 = stub;
-                        someArgs.arg2 = cb;
-                        someArgs.arg3 = null;
+                        someArgs.arg1 = sessionImpl;
+                        someArgs.arg2 = stub;
+                        someArgs.arg3 = cb;
+                        someArgs.arg4 = null;
                         mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED,
                                 someArgs).sendToTarget();
                     }
@@ -1310,14 +1392,18 @@
                 }
                 case DO_NOTIFY_SESSION_CREATED: {
                     SomeArgs args = (SomeArgs) msg.obj;
-                    ITvInputSession stub = (ITvInputSession) args.arg1;
-                    ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
-                    IBinder hardwareSessionToken = (IBinder) args.arg3;
+                    Session sessionImpl = (Session) args.arg1;
+                    ITvInputSession stub = (ITvInputSession) args.arg2;
+                    ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg3;
+                    IBinder hardwareSessionToken = (IBinder) args.arg4;
                     try {
                         cb.onSessionCreated(stub, hardwareSessionToken);
                     } catch (RemoteException e) {
                         Log.e(TAG, "error in onSessionCreated");
                     }
+                    if (sessionImpl != null) {
+                        sessionImpl.initialize(cb);
+                    }
                     args.recycle();
                     return;
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index a956151..6fd6758 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -48,6 +48,7 @@
         pw.print("    getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
         pw.print("    getPulseDuration(): "); pw.println(getPulseDuration());
         pw.print("    getPulseInDuration(): "); pw.println(getPulseInDuration());
+        pw.print("    getPulseInDelay(): "); pw.println(getPulseInDelay());
         pw.print("    getPulseInVisibleDuration(): "); pw.println(getPulseVisibleDuration());
         pw.print("    getPulseOutDuration(): "); pw.println(getPulseOutDuration());
         pw.print("    getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 0ddda8a..0cf2d05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -284,7 +284,7 @@
                                 || mInitialOffsetOnTouch == 0f)) {
                     mTouchSlopExceeded = true;
                     if (waitForTouchSlop && !mTracking) {
-                        if (!mJustPeeked) {
+                        if (!mJustPeeked && mInitialOffsetOnTouch != 0f) {
                             mInitialOffsetOnTouch = mExpandedHeight;
                             mInitialTouchX = x;
                             mInitialTouchY = y;
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index d03a154..0f033d71 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -940,6 +940,17 @@
     }
 
     @Override
+    public void setInterfaceIpv6NdOffload(String iface, boolean enable) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute(
+                    "interface", "ipv6ndoffload", iface, (enable ? "enable" : "disable"));
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
     public void addRoute(int netId, RouteInfo route) {
         modifyRoute("add", "" + netId, route);
     }
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index c7a2ce1..576556b 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -17,6 +17,7 @@
 package com.android.server.connectivity;
 
 import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
 
 import java.net.Inet4Address;
 
@@ -53,7 +54,7 @@
     // ConnectivityService Handler for LinkProperties updates.
     private final Handler mHandler;
 
-    // The network we're running on.
+    // The network we're running on, and its type.
     private final NetworkAgentInfo mNetwork;
 
     // Internal state variables.
@@ -90,8 +91,9 @@
         final boolean connected = nai.networkInfo.isConnected();
         final boolean hasIPv4Address =
                 (nai.linkProperties != null) ? nai.linkProperties.hasIPv4Address() : false;
-        // Only support clat on mobile for now.
-        return netType == TYPE_MOBILE && connected && !hasIPv4Address;
+        // Only support clat on mobile and wifi for now, because these are the only IPv6-only
+        // networks we can connect to.
+        return connected && !hasIPv4Address && (netType == TYPE_MOBILE || netType == TYPE_WIFI);
     }
 
     /**
@@ -211,23 +213,41 @@
         return stacked;
     }
 
+    private LinkAddress getLinkAddress(String iface) {
+        try {
+            InterfaceConfiguration config = mNMService.getInterfaceConfig(iface);
+            return config.getLinkAddress();
+        } catch(RemoteException|IllegalStateException e) {
+            Slog.e(TAG, "Error getting link properties: " + e);
+            return null;
+        }
+    }
+
+    private void maybeSetIpv6NdOffload(String iface, boolean on) {
+        if (mNetwork.networkInfo.getType() != TYPE_WIFI) {
+            return;
+        }
+        try {
+            Slog.d(TAG, (on ? "En" : "Dis") + "abling ND offload on " + iface);
+            mNMService.setInterfaceIpv6NdOffload(iface, on);
+        } catch(RemoteException|IllegalStateException e) {
+            Slog.w(TAG, "Changing IPv6 ND offload on " + iface + "failed: " + e);
+        }
+    }
+
     @Override
     public void interfaceAdded(String iface) {
         // Called by the InterfaceObserver on its own thread, so can race with stop().
         if (isStarted() && mIface.equals(iface)) {
             Slog.i(TAG, "interface " + iface + " added, mIsRunning " + mIsRunning + "->true");
 
-            LinkAddress clatAddress;
-            try {
-                InterfaceConfiguration config = mNMService.getInterfaceConfig(iface);
-                clatAddress = config.getLinkAddress();
-            } catch(RemoteException e) {
-                Slog.e(TAG, "Error getting link properties: " + e);
-                return;
-            }
-
             if (!mIsRunning) {
+                LinkAddress clatAddress = getLinkAddress(iface);
+                if (clatAddress == null) {
+                    return;
+                }
                 mIsRunning = true;
+                maybeSetIpv6NdOffload(mBaseIface, false);
                 LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
                 lp.addStackedLink(makeLinkProperties(clatAddress));
                 Slog.i(TAG, "Adding stacked link " + mIface + " on top of " + mBaseIface);
@@ -255,6 +275,7 @@
                 } catch (RemoteException|IllegalStateException e) {
                     // Well, we tried.
                 }
+                maybeSetIpv6NdOffload(mBaseIface, true);
                 LinkProperties lp = new LinkProperties(mNetwork.linkProperties);
                 lp.removeStackedLink(mIface);
                 clear();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 22ab1db..f9a85df 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3266,7 +3266,7 @@
                         if (DEBUG_PREFERRED) {
                             Slog.v(TAG, "Preferred activity bookkeeping changed; writing restrictions");
                         }
-                        mSettings.writePackageRestrictionsLPr(userId);
+                        scheduleWritePackageRestrictionsLocked(userId);
                     }
                 }
             }
@@ -11566,7 +11566,7 @@
                     + userId + ":");
             filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
             pir.addFilter(new PreferredActivity(filter, match, set, activity, always));
-            mSettings.writePackageRestrictionsLPr(userId);
+            scheduleWritePackageRestrictionsLocked(userId);
         }
     }
 
@@ -11682,8 +11682,7 @@
 
             int user = UserHandle.getCallingUserId();
             if (clearPackagePreferredActivitiesLPw(packageName, user)) {
-                mSettings.writePackageRestrictionsLPr(user);
-                scheduleWriteSettingsLocked();
+                scheduleWritePackageRestrictionsLocked(user);
             }
         }
     }
@@ -11733,8 +11732,7 @@
             int user = UserHandle.getCallingUserId();
             clearPackagePreferredActivitiesLPw(null, user);
             mSettings.readDefaultPreferredAppsLPw(this, user);
-            mSettings.writePackageRestrictionsLPr(user);
-            scheduleWriteSettingsLocked();
+            scheduleWritePackageRestrictionsLocked(user);
         }
     }
 
@@ -11786,7 +11784,7 @@
             filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
             mSettings.editPersistentPreferredActivitiesLPw(userId).addFilter(
                     new PersistentPreferredActivity(filter, activity));
-            mSettings.writePackageRestrictionsLPr(userId);
+            scheduleWritePackageRestrictionsLocked(userId);
         }
     }
 
@@ -11828,7 +11826,7 @@
             }
 
             if (changed) {
-                mSettings.writePackageRestrictionsLPr(userId);
+                scheduleWritePackageRestrictionsLocked(userId);
             }
         }
     }
@@ -11849,7 +11847,7 @@
             CrossProfileIntentFilter filter = new CrossProfileIntentFilter(intentFilter,
                     ownerPackage, UserHandle.getUserId(callingUid), targetUserId, flags);
             mSettings.editCrossProfileIntentResolverLPw(sourceUserId).addFilter(filter);
-            mSettings.writePackageRestrictionsLPr(sourceUserId);
+            scheduleWritePackageRestrictionsLocked(sourceUserId);
         }
     }
 
@@ -11873,7 +11871,7 @@
                     resolver.removeFilter(filter);
                 }
             }
-            mSettings.writePackageRestrictionsLPr(sourceUserId);
+            scheduleWritePackageRestrictionsLocked(sourceUserId);
         }
     }