Merge "Add quick settings permission to system ui"
diff --git a/api/current.txt b/api/current.txt
index dd22a40..4bd0fc0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -728,6 +728,7 @@
     field public static final int label = 16842753; // 0x1010001
     field public static final int labelFor = 16843718; // 0x10103c6
     field public static final int labelTextSize = 16843317; // 0x1010235
+    field public static final int languageTag = 16844041; // 0x1010509
     field public static final int largeHeap = 16843610; // 0x101035a
     field public static final int largeScreens = 16843398; // 0x1010286
     field public static final int largestWidthLimitDp = 16843622; // 0x1010366
@@ -20733,6 +20734,7 @@
   public static class MediaRouter.RouteInfo {
     method public android.media.MediaRouter.RouteCategory getCategory();
     method public java.lang.CharSequence getDescription();
+    method public int getDeviceType();
     method public android.media.MediaRouter.RouteGroup getGroup();
     method public android.graphics.drawable.Drawable getIconDrawable();
     method public java.lang.CharSequence getName();
@@ -20751,6 +20753,10 @@
     method public void requestSetVolume(int);
     method public void requestUpdateVolume(int);
     method public void setTag(java.lang.Object);
+    field public static final int DEVICE_TYPE_BLUETOOTH = 3; // 0x3
+    field public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2
+    field public static final int DEVICE_TYPE_TV = 1; // 0x1
+    field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0
     field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
     field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
     field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
@@ -41832,6 +41838,7 @@
     method public abstract void setContentView(int);
     method public abstract void setContentView(android.view.View);
     method public abstract void setContentView(android.view.View, android.view.ViewGroup.LayoutParams);
+    method public abstract void setDecorCaptionShade(int);
     method protected void setDefaultWindowFormat(int);
     method public void setDimAmount(float);
     method public void setElevation(float);
@@ -41852,6 +41859,8 @@
     method public void setMediaController(android.media.session.MediaController);
     method public abstract void setNavigationBarColor(int);
     method public void setReenterTransition(android.transition.Transition);
+    method public abstract void setResizingCaptionDrawable(android.graphics.drawable.Drawable);
+    method public final void setRestrictedCaptionAreaListener(android.view.Window.RestrictedCaptionAreaListener);
     method public void setReturnTransition(android.transition.Transition);
     method public void setSharedElementEnterTransition(android.transition.Transition);
     method public void setSharedElementExitTransition(android.transition.Transition);
@@ -41880,6 +41889,9 @@
     method public abstract void takeKeyEvents(boolean);
     method public abstract void takeSurface(android.view.SurfaceHolder.Callback2);
     method public abstract void togglePanel(int, android.view.KeyEvent);
+    field public static final int DECOR_CAPTION_SHADE_AUTO = 0; // 0x0
+    field public static final int DECOR_CAPTION_SHADE_DARK = 2; // 0x2
+    field public static final int DECOR_CAPTION_SHADE_LIGHT = 1; // 0x1
     field protected static final deprecated int DEFAULT_FEATURES = 65; // 0x41
     field public static final int FEATURE_ACTION_BAR = 8; // 0x8
     field public static final int FEATURE_ACTION_BAR_OVERLAY = 9; // 0x9
@@ -41934,6 +41946,10 @@
     method public abstract android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
   }
 
+  public static abstract interface Window.RestrictedCaptionAreaListener {
+    method public abstract void onRestrictedCaptionAreaChanged(android.graphics.Rect);
+  }
+
   public final class WindowAnimationFrameStats extends android.view.FrameStats implements android.os.Parcelable {
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
@@ -43198,7 +43214,8 @@
     method public java.lang.String getExtraValue();
     method public java.lang.String getExtraValueOf(java.lang.String);
     method public int getIconResId();
-    method public java.lang.String getLocale();
+    method public java.lang.String getLanguageTag();
+    method public deprecated java.lang.String getLocale();
     method public java.lang.String getMode();
     method public int getNameResId();
     method public boolean isAsciiCapable();
@@ -43213,6 +43230,7 @@
     method public android.view.inputmethod.InputMethodSubtype build();
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean);
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean);
+    method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLanguageTag(java.lang.String);
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean);
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(java.lang.String);
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeIconResId(int);
@@ -43276,7 +43294,8 @@
     method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
     method public java.lang.String getExtraValue();
     method public java.lang.String getExtraValueOf(java.lang.String);
-    method public java.lang.String getLocale();
+    method public java.lang.String getLanguageTag();
+    method public deprecated java.lang.String getLocale();
     method public int getNameResId();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.view.textservice.SpellCheckerSubtype> CREATOR;
diff --git a/api/system-current.txt b/api/system-current.txt
index ee20e5b..18dd49fe 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -822,6 +822,7 @@
     field public static final int label = 16842753; // 0x1010001
     field public static final int labelFor = 16843718; // 0x10103c6
     field public static final int labelTextSize = 16843317; // 0x1010235
+    field public static final int languageTag = 16844041; // 0x1010509
     field public static final int largeHeap = 16843610; // 0x101035a
     field public static final int largeScreens = 16843398; // 0x1010286
     field public static final int largestWidthLimitDp = 16843622; // 0x1010366
@@ -22026,6 +22027,7 @@
   public static class MediaRouter.RouteInfo {
     method public android.media.MediaRouter.RouteCategory getCategory();
     method public java.lang.CharSequence getDescription();
+    method public int getDeviceType();
     method public android.media.MediaRouter.RouteGroup getGroup();
     method public android.graphics.drawable.Drawable getIconDrawable();
     method public java.lang.CharSequence getName();
@@ -22044,6 +22046,10 @@
     method public void requestSetVolume(int);
     method public void requestUpdateVolume(int);
     method public void setTag(java.lang.Object);
+    field public static final int DEVICE_TYPE_BLUETOOTH = 3; // 0x3
+    field public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2
+    field public static final int DEVICE_TYPE_TV = 1; // 0x1
+    field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0
     field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
     field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
     field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
@@ -44170,6 +44176,7 @@
     method public abstract void setContentView(int);
     method public abstract void setContentView(android.view.View);
     method public abstract void setContentView(android.view.View, android.view.ViewGroup.LayoutParams);
+    method public abstract void setDecorCaptionShade(int);
     method protected void setDefaultWindowFormat(int);
     method public void setDimAmount(float);
     method public void setDisableWallpaperTouchEvents(boolean);
@@ -44191,6 +44198,8 @@
     method public void setMediaController(android.media.session.MediaController);
     method public abstract void setNavigationBarColor(int);
     method public void setReenterTransition(android.transition.Transition);
+    method public abstract void setResizingCaptionDrawable(android.graphics.drawable.Drawable);
+    method public final void setRestrictedCaptionAreaListener(android.view.Window.RestrictedCaptionAreaListener);
     method public void setReturnTransition(android.transition.Transition);
     method public void setSharedElementEnterTransition(android.transition.Transition);
     method public void setSharedElementExitTransition(android.transition.Transition);
@@ -44219,6 +44228,9 @@
     method public abstract void takeKeyEvents(boolean);
     method public abstract void takeSurface(android.view.SurfaceHolder.Callback2);
     method public abstract void togglePanel(int, android.view.KeyEvent);
+    field public static final int DECOR_CAPTION_SHADE_AUTO = 0; // 0x0
+    field public static final int DECOR_CAPTION_SHADE_DARK = 2; // 0x2
+    field public static final int DECOR_CAPTION_SHADE_LIGHT = 1; // 0x1
     field protected static final deprecated int DEFAULT_FEATURES = 65; // 0x41
     field public static final int FEATURE_ACTION_BAR = 8; // 0x8
     field public static final int FEATURE_ACTION_BAR_OVERLAY = 9; // 0x9
@@ -44273,6 +44285,10 @@
     method public abstract android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
   }
 
+  public static abstract interface Window.RestrictedCaptionAreaListener {
+    method public abstract void onRestrictedCaptionAreaChanged(android.graphics.Rect);
+  }
+
   public final class WindowAnimationFrameStats extends android.view.FrameStats implements android.os.Parcelable {
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
@@ -45539,7 +45555,8 @@
     method public java.lang.String getExtraValue();
     method public java.lang.String getExtraValueOf(java.lang.String);
     method public int getIconResId();
-    method public java.lang.String getLocale();
+    method public java.lang.String getLanguageTag();
+    method public deprecated java.lang.String getLocale();
     method public java.lang.String getMode();
     method public int getNameResId();
     method public boolean isAsciiCapable();
@@ -45554,6 +45571,7 @@
     method public android.view.inputmethod.InputMethodSubtype build();
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean);
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean);
+    method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLanguageTag(java.lang.String);
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean);
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(java.lang.String);
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeIconResId(int);
@@ -45617,7 +45635,8 @@
     method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
     method public java.lang.String getExtraValue();
     method public java.lang.String getExtraValueOf(java.lang.String);
-    method public java.lang.String getLocale();
+    method public java.lang.String getLanguageTag();
+    method public deprecated java.lang.String getLocale();
     method public int getNameResId();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.view.textservice.SpellCheckerSubtype> CREATOR;
diff --git a/api/test-current.txt b/api/test-current.txt
index 032507b..205b06d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -728,6 +728,7 @@
     field public static final int label = 16842753; // 0x1010001
     field public static final int labelFor = 16843718; // 0x10103c6
     field public static final int labelTextSize = 16843317; // 0x1010235
+    field public static final int languageTag = 16844041; // 0x1010509
     field public static final int largeHeap = 16843610; // 0x101035a
     field public static final int largeScreens = 16843398; // 0x1010286
     field public static final int largestWidthLimitDp = 16843622; // 0x1010366
@@ -20733,6 +20734,7 @@
   public static class MediaRouter.RouteInfo {
     method public android.media.MediaRouter.RouteCategory getCategory();
     method public java.lang.CharSequence getDescription();
+    method public int getDeviceType();
     method public android.media.MediaRouter.RouteGroup getGroup();
     method public android.graphics.drawable.Drawable getIconDrawable();
     method public java.lang.CharSequence getName();
@@ -20751,6 +20753,10 @@
     method public void requestSetVolume(int);
     method public void requestUpdateVolume(int);
     method public void setTag(java.lang.Object);
+    field public static final int DEVICE_TYPE_BLUETOOTH = 3; // 0x3
+    field public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2
+    field public static final int DEVICE_TYPE_TV = 1; // 0x1
+    field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0
     field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
     field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
     field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
@@ -41834,6 +41840,7 @@
     method public abstract void setContentView(int);
     method public abstract void setContentView(android.view.View);
     method public abstract void setContentView(android.view.View, android.view.ViewGroup.LayoutParams);
+    method public abstract void setDecorCaptionShade(int);
     method protected void setDefaultWindowFormat(int);
     method public void setDimAmount(float);
     method public void setElevation(float);
@@ -41854,6 +41861,8 @@
     method public void setMediaController(android.media.session.MediaController);
     method public abstract void setNavigationBarColor(int);
     method public void setReenterTransition(android.transition.Transition);
+    method public abstract void setResizingCaptionDrawable(android.graphics.drawable.Drawable);
+    method public final void setRestrictedCaptionAreaListener(android.view.Window.RestrictedCaptionAreaListener);
     method public void setReturnTransition(android.transition.Transition);
     method public void setSharedElementEnterTransition(android.transition.Transition);
     method public void setSharedElementExitTransition(android.transition.Transition);
@@ -41882,6 +41891,9 @@
     method public abstract void takeKeyEvents(boolean);
     method public abstract void takeSurface(android.view.SurfaceHolder.Callback2);
     method public abstract void togglePanel(int, android.view.KeyEvent);
+    field public static final int DECOR_CAPTION_SHADE_AUTO = 0; // 0x0
+    field public static final int DECOR_CAPTION_SHADE_DARK = 2; // 0x2
+    field public static final int DECOR_CAPTION_SHADE_LIGHT = 1; // 0x1
     field protected static final deprecated int DEFAULT_FEATURES = 65; // 0x41
     field public static final int FEATURE_ACTION_BAR = 8; // 0x8
     field public static final int FEATURE_ACTION_BAR_OVERLAY = 9; // 0x9
@@ -41936,6 +41948,10 @@
     method public abstract android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
   }
 
+  public static abstract interface Window.RestrictedCaptionAreaListener {
+    method public abstract void onRestrictedCaptionAreaChanged(android.graphics.Rect);
+  }
+
   public final class WindowAnimationFrameStats extends android.view.FrameStats implements android.os.Parcelable {
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
@@ -43200,7 +43216,8 @@
     method public java.lang.String getExtraValue();
     method public java.lang.String getExtraValueOf(java.lang.String);
     method public int getIconResId();
-    method public java.lang.String getLocale();
+    method public java.lang.String getLanguageTag();
+    method public deprecated java.lang.String getLocale();
     method public java.lang.String getMode();
     method public int getNameResId();
     method public boolean isAsciiCapable();
@@ -43215,6 +43232,7 @@
     method public android.view.inputmethod.InputMethodSubtype build();
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean);
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean);
+    method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLanguageTag(java.lang.String);
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean);
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(java.lang.String);
     method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeIconResId(int);
@@ -43278,7 +43296,8 @@
     method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
     method public java.lang.String getExtraValue();
     method public java.lang.String getExtraValueOf(java.lang.String);
-    method public java.lang.String getLocale();
+    method public java.lang.String getLanguageTag();
+    method public deprecated java.lang.String getLocale();
     method public int getNameResId();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.view.textservice.SpellCheckerSubtype> CREATOR;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 099a5fe..e7dd5ff 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -46,6 +46,7 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseArray;
 import android.util.TypedValue;
 import android.view.Gravity;
 import android.view.NotificationHeaderView;
@@ -64,6 +65,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * A class that represents how a persistent notification is to be presented to
@@ -1693,11 +1695,21 @@
         bigContentView = null;
         headsUpContentView = null;
         mLargeIcon = null;
-        if (extras != null) {
-            extras.remove(Notification.EXTRA_LARGE_ICON);
-            extras.remove(Notification.EXTRA_LARGE_ICON_BIG);
-            extras.remove(Notification.EXTRA_PICTURE);
-            extras.remove(Notification.EXTRA_BIG_TEXT);
+        if (extras != null && !extras.isEmpty()) {
+            final Set<String> keyset = extras.keySet();
+            final int N = keyset.size();
+            final String[] keys = keyset.toArray(new String[N]);
+            for (int i=0; i<N; i++) {
+                final String key = keys[i];
+                final Object obj = extras.get(key);
+                if (obj != null &&
+                    (  obj instanceof Parcelable
+                    || obj instanceof Parcelable[]
+                    || obj instanceof SparseArray
+                    || obj instanceof ArrayList)) {
+                    extras.remove(key);
+                }
+            }
         }
     }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 381ca4c..74fd8cd 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3323,6 +3323,7 @@
             PUBLIC_SETTINGS.add(SOUND_EFFECTS_ENABLED);
             PUBLIC_SETTINGS.add(HAPTIC_FEEDBACK_ENABLED);
             PUBLIC_SETTINGS.add(SHOW_WEB_SUGGESTIONS);
+            PUBLIC_SETTINGS.add(VIBRATE_WHEN_RINGING);
         }
 
         /**
@@ -3344,7 +3345,6 @@
             PRIVATE_SETTINGS.add(VIBRATE_IN_SILENT);
             PRIVATE_SETTINGS.add(MEDIA_BUTTON_RECEIVER);
             PRIVATE_SETTINGS.add(HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY);
-            PRIVATE_SETTINGS.add(VIBRATE_WHEN_RINGING);
             PRIVATE_SETTINGS.add(DTMF_TONE_TYPE_WHEN_DIALING);
             PRIVATE_SETTINGS.add(HEARING_AID);
             PRIVATE_SETTINGS.add(TTY_MODE);
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index 2459cfa..57fe131 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -20,7 +20,6 @@
 import android.text.Layout;
 import android.text.Selection;
 import android.text.Spannable;
-import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
@@ -222,34 +221,26 @@
         return lineEnd(widget, buffer);
     }
 
-    private static boolean isTouchSelecting(boolean isMouse, Spannable buffer) {
-        return isMouse ? Touch.isActivelySelecting(buffer) : isSelecting(buffer);
-    }
-
     @Override
     public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
         int initialScrollX = -1;
         int initialScrollY = -1;
         final int action = event.getAction();
-        final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
 
         if (action == MotionEvent.ACTION_UP) {
             initialScrollX = Touch.getInitialScrollX(widget, buffer);
             initialScrollY = Touch.getInitialScrollY(widget, buffer);
         }
 
-        boolean wasTouchSelecting = isTouchSelecting(isMouse, buffer);
+        boolean wasTouchSelecting = isSelecting(buffer);
         boolean handled = Touch.onTouchEvent(widget, buffer, event);
 
-        if (widget.didTouchFocusSelect() && !isMouse) {
+        if (widget.didTouchFocusSelect()) {
             return handled;
         }
         if (action == MotionEvent.ACTION_DOWN) {
-            // Capture the mouse pointer down location to ensure selection starts
-            // right under the mouse (and is not influenced by cursor location).
-            // The code below needs to run for mouse events.
             // For touch events, the code should run only when selection is active.
-            if (isMouse || isTouchSelecting(isMouse, buffer)) {
+            if (isSelecting(buffer)) {
                 if (!widget.isFocused()) {
                     if (!widget.requestFocus()) {
                         return handled;
@@ -265,15 +256,8 @@
             }
         } else if (widget.isFocused()) {
             if (action == MotionEvent.ACTION_MOVE) {
-                // Cursor can be active at any location in the text while mouse pointer can start
-                // selection from a totally different location. Use LAST_TAP_DOWN span to ensure
-                // text selection will start from mouse pointer location.
-                final int startOffset = buffer.getSpanStart(LAST_TAP_DOWN);
-                if (isMouse && Touch.isSelectionStarted(buffer)) {
-                    Selection.setSelection(buffer, startOffset);
-                }
-
-                if (isTouchSelecting(isMouse, buffer) && handled) {
+                if (isSelecting(buffer) && handled) {
+                    final int startOffset = buffer.getSpanStart(LAST_TAP_DOWN);
                     // Before selecting, make sure we've moved out of the "slop".
                     // handled will be true, if we're in select mode AND we're
                     // OUT of the slop
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
index fee7377..d9068dc 100644
--- a/core/java/android/text/method/Touch.java
+++ b/core/java/android/text/method/Touch.java
@@ -119,18 +119,12 @@
             ds = buffer.getSpans(0, buffer.length(), DragState.class);
 
             if (ds.length > 0) {
-                ds[0].mIsSelectionStarted = false;
-
                 if (ds[0].mFarEnough == false) {
                     int slop = ViewConfiguration.get(widget.getContext()).getScaledTouchSlop();
 
                     if (Math.abs(event.getX() - ds[0].mX) >= slop ||
                         Math.abs(event.getY() - ds[0].mY) >= slop) {
                         ds[0].mFarEnough = true;
-                        if (event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
-                            ds[0].mIsActivelySelecting = true;
-                            ds[0].mIsSelectionStarted = true;
-                        }
                     }
                 }
 
@@ -142,13 +136,9 @@
                             || MetaKeyKeyListener.getMetaState(buffer,
                                     MetaKeyKeyListener.META_SELECTING) != 0;
 
-                    if (!event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
-                        ds[0].mIsActivelySelecting = false;
-                    }
-
                     float dx;
                     float dy;
-                    if (cap && event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
+                    if (cap) {
                         // if we're selecting, we want the scroll to go in
                         // the direction of the drag
                         dx = event.getX() - ds[0].mX;
@@ -160,7 +150,6 @@
                     ds[0].mX = event.getX();
                     ds[0].mY = event.getY();
 
-                    int nx = widget.getScrollX() + (int) dx;
                     int ny = widget.getScrollY() + (int) dy;
 
                     int padding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom();
@@ -172,10 +161,6 @@
                     int oldX = widget.getScrollX();
                     int oldY = widget.getScrollY();
 
-                    if (!event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) {
-                        scrollTo(widget, layout, nx, ny);
-                    }
-
                     // If we actually scrolled, then cancel the up action.
                     if (oldX != widget.getScrollX() || oldY != widget.getScrollY()) {
                         widget.cancelLongPress();
@@ -207,37 +192,6 @@
         return ds.length > 0 ? ds[0].mScrollY : -1;
     }
 
-    /**
-     * Checks if selection is still active.
-     * This is useful for extending Selection span on buffer.
-     * @param buffer The text buffer.
-     * @return true if buffer has been marked for selection.
-     *
-     * @hide
-     */
-    static boolean isActivelySelecting(Spannable buffer) {
-        DragState[] ds;
-        ds = buffer.getSpans(0, buffer.length(), DragState.class);
-
-        return ds.length > 0 && ds[0].mIsActivelySelecting;
-    }
-
-    /**
-     * Checks if selection has begun (are we out of slop?).
-     * Note: DragState.mIsSelectionStarted goes back to false with the very next event.
-     * This is useful for starting Selection span on buffer.
-     * @param buffer The text buffer.
-     * @return true if selection has started on the buffer.
-     *
-     * @hide
-     */
-    static boolean isSelectionStarted(Spannable buffer) {
-        DragState[] ds;
-        ds = buffer.getSpans(0, buffer.length(), DragState.class);
-
-        return ds.length > 0 && ds[0].mIsSelectionStarted;
-    }
-
     private static class DragState implements NoCopySpan {
         public float mX;
         public float mY;
@@ -245,8 +199,6 @@
         public int mScrollY;
         public boolean mFarEnough;
         public boolean mUsed;
-        public boolean mIsActivelySelecting;
-        public boolean mIsSelectionStarted;
 
         public DragState(float x, float y, int scrollX, int scrollY) {
             mX = x;
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 7a359e7..53490b4 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -29,6 +29,7 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.PixelFormat;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.media.session.MediaController;
 import android.net.Uri;
@@ -247,12 +248,30 @@
 
     private static final String PROPERTY_HARDWARE_UI = "persist.sys.ui.hw";
 
+    /**
+     * Flag for letting the theme drive the color of the window caption controls. Use with
+     * {@link #setDecorCaptionShade(int)}. This is the default value.
+     */
+    public static final int DECOR_CAPTION_SHADE_AUTO = 0;
+    /**
+     * Flag for setting light-color controls on the window caption. Use with
+     * {@link #setDecorCaptionShade(int)}.
+     */
+    public static final int DECOR_CAPTION_SHADE_LIGHT = 1;
+    /**
+     * Flag for setting dark-color controls on the window caption. Use with
+     * {@link #setDecorCaptionShade(int)}.
+     */
+    public static final int DECOR_CAPTION_SHADE_DARK = 2;
+
     private final Context mContext;
 
     private TypedArray mWindowStyle;
     private Callback mCallback;
     private OnWindowDismissedCallback mOnWindowDismissedCallback;
     private WindowControllerCallback mWindowControllerCallback;
+    private RestrictedCaptionAreaListener mRestrictedCaptionAreaListener;
+    private Rect mRestrictedCaptionAreaRect;
     private WindowManager mWindowManager;
     private IBinder mAppToken;
     private String mAppName;
@@ -565,6 +584,18 @@
         int getWindowStackId() throws RemoteException;
     }
 
+    /**
+     * Callback for clients that want to be aware of where caption draws content.
+     */
+    public interface RestrictedCaptionAreaListener {
+        /**
+         * Called when the area where caption draws content changes.
+         *
+         * @param rect The area where caption content is positioned, relative to the top view.
+         */
+        void onRestrictedCaptionAreaChanged(Rect rect);
+    }
+
     public Window(Context context) {
         mContext = context;
         mFeatures = mLocalFeatures = getDefaultFeatures(context);
@@ -778,6 +809,16 @@
     }
 
     /**
+     * Set a callback for changes of area where caption will draw its content.
+     *
+     * @param listener Callback that will be called when the area changes.
+     */
+    public final void setRestrictedCaptionAreaListener(RestrictedCaptionAreaListener listener) {
+        mRestrictedCaptionAreaListener = listener;
+        mRestrictedCaptionAreaRect = listener != null ? new Rect() : null;
+    }
+
+    /**
      * Take ownership of this window's surface.  The window's view hierarchy
      * will no longer draw into the surface, though it will otherwise continue
      * to operate (such as for receiving input events).  The given SurfaceHolder
@@ -2040,5 +2081,29 @@
         return mOverlayWithDecorCaption;
     }
 
+    /** @hide */
+    public void notifyRestrictedCaptionAreaCallback(int left, int top, int right, int bottom) {
+        if (mRestrictedCaptionAreaListener != null) {
+            mRestrictedCaptionAreaRect.set(left, top, right, bottom);
+            mRestrictedCaptionAreaListener.onRestrictedCaptionAreaChanged(
+                    mRestrictedCaptionAreaRect);
+        }
+    }
 
+    /**
+     * Set what color should the caption controls be. By default the system will try to determine
+     * the color from the theme. You can overwrite this by using {@link #DECOR_CAPTION_SHADE_DARK}
+     * or {@link #DECOR_CAPTION_SHADE_DARK}.
+     */
+    public abstract void setDecorCaptionShade(int decorCaptionShade);
+
+    /**
+     * Set the drawable that is drawn underneath the caption during the resizing.
+     *
+     * During the resizing the caption might not be drawn fast enough to match the new dimensions.
+     * There is a second caption drawn underneath it that will be fast enough. By default the
+     * caption is constructed from the theme. You can provide a drawable, that will be drawn instead
+     * to better match your application.
+     */
+    public abstract void setResizingCaptionDrawable(Drawable drawable);
 }
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index fbaf51c..a42f4d9 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -16,6 +16,7 @@
 
 package android.view.inputmethod;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -50,6 +51,7 @@
  *
  * @attr ref android.R.styleable#InputMethod_Subtype_label
  * @attr ref android.R.styleable#InputMethod_Subtype_icon
+ * @attr ref android.R.styleable#InputMethod_Subtype_languageTag
  * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeLocale
  * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeMode
  * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeExtraValue
@@ -60,6 +62,7 @@
  */
 public final class InputMethodSubtype implements Parcelable {
     private static final String TAG = InputMethodSubtype.class.getSimpleName();
+    private static final String LANGUAGE_TAG_NONE = "";
     private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
     private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
     // TODO: remove this
@@ -74,6 +77,7 @@
     private final int mSubtypeNameResId;
     private final int mSubtypeId;
     private final String mSubtypeLocale;
+    private final String mSubtypeLanguageTag;
     private final String mSubtypeMode;
     private final String mSubtypeExtraValue;
     private volatile HashMap<String, String> mExtraValueHashMapCache;
@@ -171,6 +175,15 @@
         private String mSubtypeLocale = "";
 
         /**
+         * @param languageTag is the BCP-47 Language Tag supported by this subtype.
+         */
+        public InputMethodSubtypeBuilder setLanguageTag(String languageTag) {
+            mSubtypeLanguageTag = languageTag == null ? LANGUAGE_TAG_NONE : languageTag;
+            return this;
+        }
+        private String mSubtypeLanguageTag = LANGUAGE_TAG_NONE;
+
+        /**
          * @param subtypeMode is the mode supported by this subtype.
          */
         public InputMethodSubtypeBuilder setSubtypeMode(String subtypeMode) {
@@ -271,6 +284,7 @@
         mSubtypeNameResId = builder.mSubtypeNameResId;
         mSubtypeIconResId = builder.mSubtypeIconResId;
         mSubtypeLocale = builder.mSubtypeLocale;
+        mSubtypeLanguageTag = builder.mSubtypeLanguageTag;
         mSubtypeMode = builder.mSubtypeMode;
         mSubtypeExtraValue = builder.mSubtypeExtraValue;
         mIsAuxiliary = builder.mIsAuxiliary;
@@ -291,6 +305,8 @@
         s = source.readString();
         mSubtypeLocale = s != null ? s : "";
         s = source.readString();
+        mSubtypeLanguageTag = s != null ? s : LANGUAGE_TAG_NONE;
+        s = source.readString();
         mSubtypeMode = s != null ? s : "";
         s = source.readString();
         mSubtypeExtraValue = s != null ? s : "";
@@ -318,21 +334,38 @@
     /**
      * @return The locale of the subtype. This method returns the "locale" string parameter passed
      * to the constructor.
+     *
+     * @deprecated Use {@link #getLanguageTag()} instead.
      */
+    @Deprecated
+    @NonNull
     public String getLocale() {
         return mSubtypeLocale;
     }
 
     /**
-     * @return The normalized {@link Locale} object of the subtype. The returned locale may or may
-     * not equal to "locale" string parameter passed to the constructor.
+     * @return the BCP-47 Language Tag of the subtype.  Returns an empty string when no Language Tag
+     * is specified.
      *
-     * <p>TODO: Consider to make this a public API.</p>
+     * @see Locale#forLanguageTag(String)
+     */
+    @NonNull
+    public String getLanguageTag() {
+        return mSubtypeLanguageTag;
+    }
+
+    /**
+     * @return {@link Locale} constructed from {@link #getLanguageTag()}. If the Language Tag is not
+     * specified, then try to construct from {@link #getLocale()}
+     *
+     * <p>TODO: Consider to make this a public API, or move this to support lib.</p>
      * @hide
      */
     @Nullable
     public Locale getLocaleObject() {
-        // TODO: Move the following method from InputMethodUtils to InputMethodSubtype.
+        if (!TextUtils.isEmpty(mSubtypeLanguageTag)) {
+            return Locale.forLanguageTag(mSubtypeLanguageTag);
+        }
         return InputMethodUtils.constructLocaleFromString(mSubtypeLocale);
     }
 
@@ -476,13 +509,14 @@
                 return (subtype.hashCode() == hashCode());
             }
             return (subtype.hashCode() == hashCode())
-                && (subtype.getLocale().equals(getLocale()))
-                && (subtype.getMode().equals(getMode()))
-                && (subtype.getExtraValue().equals(getExtraValue()))
-                && (subtype.isAuxiliary() == isAuxiliary())
-                && (subtype.overridesImplicitlyEnabledSubtype()
-                        == overridesImplicitlyEnabledSubtype())
-                && (subtype.isAsciiCapable() == isAsciiCapable());
+                    && (subtype.getLocale().equals(getLocale()))
+                    && (subtype.getLanguageTag().equals(getLanguageTag()))
+                    && (subtype.getMode().equals(getMode()))
+                    && (subtype.getExtraValue().equals(getExtraValue()))
+                    && (subtype.isAuxiliary() == isAuxiliary())
+                    && (subtype.overridesImplicitlyEnabledSubtype()
+                            == overridesImplicitlyEnabledSubtype())
+                    && (subtype.isAsciiCapable() == isAsciiCapable());
         }
         return false;
     }
@@ -497,6 +531,7 @@
         dest.writeInt(mSubtypeNameResId);
         dest.writeInt(mSubtypeIconResId);
         dest.writeString(mSubtypeLocale);
+        dest.writeString(mSubtypeLanguageTag);
         dest.writeString(mSubtypeMode);
         dest.writeString(mSubtypeExtraValue);
         dest.writeInt(mIsAuxiliary ? 1 : 0);
diff --git a/core/java/android/view/textservice/SpellCheckerInfo.java b/core/java/android/view/textservice/SpellCheckerInfo.java
index 491de78..471b6d4 100644
--- a/core/java/android/view/textservice/SpellCheckerInfo.java
+++ b/core/java/android/view/textservice/SpellCheckerInfo.java
@@ -117,6 +117,8 @@
                             a.getString(com.android.internal.R.styleable
                                     .SpellChecker_Subtype_subtypeLocale),
                             a.getString(com.android.internal.R.styleable
+                                    .SpellChecker_Subtype_languageTag),
+                            a.getString(com.android.internal.R.styleable
                                     .SpellChecker_Subtype_subtypeExtraValue),
                             a.getInt(com.android.internal.R.styleable
                                     .SpellChecker_Subtype_subtypeId, 0));
diff --git a/core/java/android/view/textservice/SpellCheckerSubtype.java b/core/java/android/view/textservice/SpellCheckerSubtype.java
index f2b03cc..df33698 100644
--- a/core/java/android/view/textservice/SpellCheckerSubtype.java
+++ b/core/java/android/view/textservice/SpellCheckerSubtype.java
@@ -18,6 +18,7 @@
 
 import com.android.internal.inputmethod.InputMethodUtils;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -40,6 +41,7 @@
  * @see SpellCheckerInfo
  *
  * @attr ref android.R.styleable#SpellChecker_Subtype_label
+ * @attr ref android.R.styleable#SpellChecker_Subtype_languageTag
  * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeLocale
  * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeExtraValue
  * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeId
@@ -49,11 +51,13 @@
     private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
     private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
     private static final int SUBTYPE_ID_NONE = 0;
+    private static final String SUBTYPE_LANGUAGE_TAG_NONE = "";
 
     private final int mSubtypeId;
     private final int mSubtypeHashCode;
     private final int mSubtypeNameResId;
     private final String mSubtypeLocale;
+    private final String mSubtypeLanguageTag;
     private final String mSubtypeExtraValue;
     private HashMap<String, String> mExtraValueHashMapCache;
 
@@ -66,14 +70,17 @@
      *
      * @param nameId The name of the subtype
      * @param locale The locale supported by the subtype
+     * @param languageTag The BCP-47 Language Tag associated with this subtype.
      * @param extraValue The extra value of the subtype
      * @param subtypeId The subtype ID that is supposed to be stable during package update.
      *
      * @hide
      */
-    public SpellCheckerSubtype(int nameId, String locale, String extraValue, int subtypeId) {
+    public SpellCheckerSubtype(int nameId, String locale, String languageTag, String extraValue,
+            int subtypeId) {
         mSubtypeNameResId = nameId;
         mSubtypeLocale = locale != null ? locale : "";
+        mSubtypeLanguageTag = languageTag != null ? languageTag : SUBTYPE_LANGUAGE_TAG_NONE;
         mSubtypeExtraValue = extraValue != null ? extraValue : "";
         mSubtypeId = subtypeId;
         mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ?
@@ -91,7 +98,7 @@
      * to instantiate {@link SpellCheckerSubtype} object.
      */
     public SpellCheckerSubtype(int nameId, String locale, String extraValue) {
-        this(nameId, locale, extraValue, SUBTYPE_ID_NONE);
+        this(nameId, locale, SUBTYPE_LANGUAGE_TAG_NONE, extraValue, SUBTYPE_ID_NONE);
     }
 
     SpellCheckerSubtype(Parcel source) {
@@ -100,6 +107,8 @@
         s = source.readString();
         mSubtypeLocale = s != null ? s : "";
         s = source.readString();
+        mSubtypeLanguageTag = s != null ? s : "";
+        s = source.readString();
         mSubtypeExtraValue = s != null ? s : "";
         mSubtypeId = source.readInt();
         mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ?
@@ -115,12 +124,27 @@
 
     /**
      * @return the locale of the subtype
+     *
+     * @deprecated Use {@link #getLanguageTag()} instead.
      */
+    @Deprecated
+    @NonNull
     public String getLocale() {
         return mSubtypeLocale;
     }
 
     /**
+     * @return the BCP-47 Language Tag of the subtype.  Returns an empty string when no Language Tag
+     * is specified.
+     *
+     * @see Locale#forLanguageTag(String)
+     */
+    @NonNull
+    public String getLanguageTag() {
+        return mSubtypeLanguageTag;
+    }
+
+    /**
      * @return the extra value of the subtype
      */
     public String getExtraValue() {
@@ -182,20 +206,24 @@
             return (subtype.hashCode() == hashCode())
                     && (subtype.getNameResId() == getNameResId())
                     && (subtype.getLocale().equals(getLocale()))
+                    && (subtype.getLanguageTag().equals(getLanguageTag()))
                     && (subtype.getExtraValue().equals(getExtraValue()));
         }
         return false;
     }
 
     /**
-     * @return The normalized {@link Locale} object of the subtype. The returned locale may or may
-     * not equal to "locale" string parameter passed to the constructor.
+     * @return {@link Locale} constructed from {@link #getLanguageTag()}. If the Language Tag is not
+     * specified, then try to construct from {@link #getLocale()}
      *
-     * <p>TODO: Consider to make this a public API.</p>
+     * <p>TODO: Consider to make this a public API, or move this to support lib.</p>
      * @hide
      */
     @Nullable
     public Locale getLocaleObject() {
+        if (!TextUtils.isEmpty(mSubtypeLanguageTag)) {
+            return Locale.forLanguageTag(mSubtypeLanguageTag);
+        }
         return InputMethodUtils.constructLocaleFromString(mSubtypeLocale);
     }
 
@@ -234,6 +262,7 @@
     public void writeToParcel(Parcel dest, int parcelableFlags) {
         dest.writeInt(mSubtypeNameResId);
         dest.writeString(mSubtypeLocale);
+        dest.writeString(mSubtypeLanguageTag);
         dest.writeString(mSubtypeExtraValue);
         dest.writeInt(mSubtypeId);
     }
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 5146bc6..2d1f855 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -75,6 +75,7 @@
 import android.view.DisplayListCanvas;
 import android.view.DragEvent;
 import android.view.Gravity;
+import android.view.InputDevice;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -229,7 +230,14 @@
     // Set when this TextView gained focus with some text selected. Will start selection mode.
     boolean mCreatedWithASelection;
 
-    boolean mDoubleTap = false;
+    // Indicates the current tap state (first tap, double tap, or triple click).
+    private int mTapState = TAP_STATE_INITIAL;
+    private long mLastTouchUpTime = 0;
+    private static final int TAP_STATE_INITIAL = 0;
+    private static final int TAP_STATE_FIRST_TAP = 1;
+    private static final int TAP_STATE_DOUBLE_TAP = 2;
+    // Only for mouse input.
+    private static final int TAP_STATE_TRIPLE_CLICK = 3;
 
     private Runnable mInsertionActionModeRunnable;
 
@@ -769,20 +777,12 @@
         return retOffset;
     }
 
-    /**
-     * Adjusts selection to the word under last touch offset. Return true if the operation was
-     * successfully performed.
-     */
-    private boolean selectCurrentWord() {
-        if (!mTextView.canSelectText()) {
-            return false;
-        }
-
+    private boolean needsToSelectAllToSelectWordOrParagraph() {
         if (mTextView.hasPasswordTransformationMethod()) {
             // Always select all on a password field.
             // Cut/copy menu entries are not available for passwords, but being able to select all
             // is however useful to delete or paste to replace the entire content.
-            return mTextView.selectAllText();
+            return true;
         }
 
         int inputType = mTextView.getInputType();
@@ -797,6 +797,21 @@
                 variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
                 variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS ||
                 variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Adjusts selection to the word under last touch offset. Return true if the operation was
+     * successfully performed.
+     */
+    private boolean selectCurrentWord() {
+        if (!mTextView.canSelectText()) {
+            return false;
+        }
+
+        if (needsToSelectAllToSelectWordOrParagraph()) {
             return mTextView.selectAllText();
         }
 
@@ -805,8 +820,8 @@
         final int maxOffset = TextUtils.unpackRangeEndFromLong(lastTouchOffsets);
 
         // Safety check in case standard touch event handling has been bypassed
-        if (minOffset < 0 || minOffset >= mTextView.getText().length()) return false;
-        if (maxOffset < 0 || maxOffset >= mTextView.getText().length()) return false;
+        if (minOffset < 0 || minOffset > mTextView.getText().length()) return false;
+        if (maxOffset < 0 || maxOffset > mTextView.getText().length()) return false;
 
         int selectionStart, selectionEnd;
 
@@ -839,6 +854,63 @@
         return selectionEnd > selectionStart;
     }
 
+    /**
+     * Adjusts selection to the paragraph under last touch offset. Return true if the operation was
+     * successfully performed.
+     */
+    private boolean selectCurrentParagraph() {
+        if (!mTextView.canSelectText()) {
+            return false;
+        }
+
+        if (needsToSelectAllToSelectWordOrParagraph()) {
+            return mTextView.selectAllText();
+        }
+
+        long lastTouchOffsets = getLastTouchOffsets();
+        final int minLastTouchOffset = TextUtils.unpackRangeStartFromLong(lastTouchOffsets);
+        final int maxLastTouchOffset = TextUtils.unpackRangeEndFromLong(lastTouchOffsets);
+
+        final long paragraphsRange = getParagraphsRange(minLastTouchOffset, maxLastTouchOffset);
+        final int start = TextUtils.unpackRangeStartFromLong(paragraphsRange);
+        final int end = TextUtils.unpackRangeEndFromLong(paragraphsRange);
+        if (start < end) {
+            Selection.setSelection((Spannable) mTextView.getText(), start, end);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Get the minimum range of paragraphs that contains startOffset and endOffset.
+     */
+    private long getParagraphsRange(int startOffset, int endOffset) {
+        final Layout layout = mTextView.getLayout();
+        if (layout == null) {
+            return TextUtils.packRangeInLong(-1, -1);
+        }
+        final CharSequence text = mTextView.getText();
+        int minLine = layout.getLineForOffset(startOffset);
+        // Search paragraph start.
+        while (minLine > 0) {
+            final int prevLineEndOffset = layout.getLineEnd(minLine - 1);
+            if (text.charAt(prevLineEndOffset - 1) == '\n') {
+                break;
+            }
+            minLine--;
+        }
+        int maxLine = layout.getLineForOffset(endOffset);
+        // Search paragraph end.
+        while (maxLine < layout.getLineCount() - 1) {
+            final int lineEndOffset = layout.getLineEnd(maxLine);
+            if (text.charAt(lineEndOffset - 1) == '\n') {
+                break;
+            }
+            maxLine++;
+        }
+        return TextUtils.packRangeInLong(layout.getLineStart(minLine), layout.getLineEnd(maxLine));
+    }
+
     void onLocaleChanged() {
         // Will be re-created on demand in getWordIterator with the proper new locale
         mWordIterator = null;
@@ -1219,7 +1291,31 @@
         }
     }
 
+    private void updateTapState(MotionEvent event) {
+        final int action = event.getActionMasked();
+        if (action == MotionEvent.ACTION_DOWN) {
+            final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
+            // Detect double tap and triple click.
+            if (((mTapState == TAP_STATE_FIRST_TAP)
+                    || ((mTapState == TAP_STATE_DOUBLE_TAP) && isMouse))
+                        && (SystemClock.uptimeMillis() - mLastTouchUpTime) <=
+                                ViewConfiguration.getDoubleTapTimeout()) {
+                if (mTapState == TAP_STATE_FIRST_TAP) {
+                    mTapState = TAP_STATE_DOUBLE_TAP;
+                } else {
+                    mTapState = TAP_STATE_TRIPLE_CLICK;
+                }
+            } else {
+                mTapState = TAP_STATE_FIRST_TAP;
+            }
+        }
+        if (action == MotionEvent.ACTION_UP) {
+            mLastTouchUpTime = SystemClock.uptimeMillis();
+        }
+    }
+
     void onTouchEvent(MotionEvent event) {
+        updateTapState(event);
         updateFloatingToolbarVisibility(event);
 
         if (hasSelectionController()) {
@@ -1773,7 +1869,8 @@
         stopTextActionMode();
         mPreserveDetachedSelection = false;
 
-        getSelectionController().enterDrag();
+        getSelectionController().enterDrag(
+                SelectionModifierCursorController.DRAG_ACCELERATOR_MODE_WORD);
         return true;
     }
 
@@ -3777,30 +3874,6 @@
             }
         }
 
-        public void showAtLocation(int offset) {
-            // TODO - investigate if there's a better way to show the handles
-            // after the drag accelerator has occured.
-            int[] tmpCords = new int[2];
-            mTextView.getLocationInWindow(tmpCords);
-
-            Layout layout = mTextView.getLayout();
-            int posX = tmpCords[0];
-            int posY = tmpCords[1];
-
-            final int line = layout.getLineForOffset(offset);
-
-            int startX = (int) (layout.getPrimaryHorizontal(offset) - 0.5f
-                    - mHotspotX - getHorizontalOffset() + getCursorOffset());
-            int startY = layout.getLineBottom(line);
-
-            // Take TextView's padding and scroll into account.
-            startX += mTextView.viewportToContentHorizontalOffset();
-            startY += mTextView.viewportToContentVerticalOffset();
-
-            mContainer.showAtLocation(mTextView, Gravity.NO_GRAVITY,
-                    startX + posX, startY + posY);
-        }
-
         @Override
         protected void onDraw(Canvas c) {
             final int drawWidth = mDrawable.getIntrinsicWidth();
@@ -3933,13 +4006,16 @@
 
             // Cancel the single tap delayed runnable.
             if (mInsertionActionModeRunnable != null
-                    && (mDoubleTap || isCursorInsideEasyCorrectionSpan())) {
+                    && ((mTapState == TAP_STATE_DOUBLE_TAP)
+                            || (mTapState == TAP_STATE_TRIPLE_CLICK)
+                            || isCursorInsideEasyCorrectionSpan())) {
                 mTextView.removeCallbacks(mInsertionActionModeRunnable);
             }
 
             // Prepare and schedule the single tap runnable to run exactly after the double tap
             // timeout has passed.
-            if (!mDoubleTap && !isCursorInsideEasyCorrectionSpan()
+            if ((mTapState != TAP_STATE_DOUBLE_TAP) && (mTapState != TAP_STATE_TRIPLE_CLICK)
+                    && !isCursorInsideEasyCorrectionSpan()
                     && (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION)) {
                 if (mTextActionMode == null) {
                     if (mInsertionActionModeRunnable == null) {
@@ -4223,10 +4299,15 @@
 
             boolean isExpanding;
             final float xDiff = x - mPrevX;
-            if (atRtl == isStartHandle()) {
-                isExpanding = xDiff > 0 || currLine > mPreviousLineTouched;
+            if (isStartHandle()) {
+                isExpanding = currLine < mPreviousLineTouched;
             } else {
-                isExpanding = xDiff < 0 || currLine < mPreviousLineTouched;
+                isExpanding = currLine > mPreviousLineTouched;
+            }
+            if (atRtl == isStartHandle()) {
+                isExpanding |= xDiff > 0;
+            } else {
+                isExpanding |= xDiff < 0;
             }
 
             if (mTextView.getHorizontallyScrolling()) {
@@ -4492,14 +4573,24 @@
 
         // Where the user first starts the drag motion.
         private int mStartOffset = -1;
-        // Indicates whether the user is selecting text and using the drag accelerator.
-        private boolean mDragAcceleratorActive;
+
         private boolean mHaventMovedEnoughToStartDrag;
         // The line that a selection happened most recently with the drag accelerator.
         private int mLineSelectionIsOn = -1;
         // Whether the drag accelerator has selected past the initial line.
         private boolean mSwitchedLines = false;
 
+        // Indicates the drag accelerator mode that the user is currently using.
+        private int mDragAcceleratorMode = DRAG_ACCELERATOR_MODE_INACTIVE;
+        // Drag accelerator is inactive.
+        private static final int DRAG_ACCELERATOR_MODE_INACTIVE = 0;
+        // Character based selection by dragging. Only for mouse.
+        private static final int DRAG_ACCELERATOR_MODE_CHARACTER = 1;
+        // Word based selection by dragging. Enabled after long pressing or double tapping.
+        private static final int DRAG_ACCELERATOR_MODE_WORD = 2;
+        // Paragraph based selection by dragging. Enabled after mouse triple click.
+        private static final int DRAG_ACCELERATOR_MODE_PARAGRAPH = 3;
+
         SelectionModifierCursorController() {
             resetTouchOffsets();
         }
@@ -4510,7 +4601,6 @@
             }
             initDrawables();
             initHandles();
-            hideInsertionPointCursorController();
         }
 
         private void initDrawables() {
@@ -4548,10 +4638,10 @@
             if (mEndHandle != null) mEndHandle.hide();
         }
 
-        public void enterDrag() {
+        public void enterDrag(int dragAcceleratorMode) {
             // Just need to init the handles / hide insertion cursor.
             show();
-            mDragAcceleratorActive = true;
+            mDragAcceleratorMode = dragAcceleratorMode;
             // Start location of selection.
             mStartOffset = mTextView.getOffsetForPosition(mLastDownPositionX,
                     mLastDownPositionY);
@@ -4563,6 +4653,7 @@
             // the user to continue dragging across the screen to select text; TextView will
             // scroll as necessary.
             mTextView.getParent().requestDisallowInterceptTouchEvent(true);
+            mTextView.cancelLongPress();
         }
 
         public void onTouchEvent(MotionEvent event) {
@@ -4570,6 +4661,7 @@
             // selection and tap can move cursor from this tap position.
             final float eventX = event.getX();
             final float eventY = event.getY();
+            final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
             switch (event.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN:
                     if (extractedTextModeWillBeStarted()) {
@@ -4582,7 +4674,8 @@
 
                         // Double tap detection
                         if (mGestureStayedInTapRegion) {
-                            if (mDoubleTap) {
+                            if (mTapState == TAP_STATE_DOUBLE_TAP
+                                    || mTapState == TAP_STATE_TRIPLE_CLICK) {
                                 final float deltaX = eventX - mDownPositionX;
                                 final float deltaY = eventY - mDownPositionY;
                                 final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
@@ -4593,8 +4686,12 @@
                                 boolean stayedInArea =
                                         distanceSquared < doubleTapSlop * doubleTapSlop;
 
-                                if (stayedInArea && isPositionOnText(eventX, eventY)) {
-                                    selectCurrentWordAndStartDrag();
+                                if (stayedInArea && (isMouse || isPositionOnText(eventX, eventY))) {
+                                    if (mTapState == TAP_STATE_DOUBLE_TAP) {
+                                        selectCurrentWordAndStartDrag();
+                                    } else if (mTapState == TAP_STATE_TRIPLE_CLICK) {
+                                        selectCurrentParagraphAndStartDrag();
+                                    }
                                     mDiscardNextActionUp = true;
                                 }
                             }
@@ -4639,94 +4736,168 @@
                         }
                     }
 
+                    if (isMouse && !isDragAcceleratorActive()) {
+                        final int offset = mTextView.getOffsetForPosition(eventX, eventY);
+                        if (mStartOffset != offset) {
+                            // Start character based drag accelerator.
+                            if (mTextActionMode != null) {
+                                mTextActionMode.finish();
+                            }
+                            enterDrag(DRAG_ACCELERATOR_MODE_CHARACTER);
+                            mDiscardNextActionUp = true;
+                            mHaventMovedEnoughToStartDrag = false;
+                        }
+                    }
+
                     if (mStartHandle != null && mStartHandle.isShowing()) {
                         // Don't do the drag if the handles are showing already.
                         break;
                     }
 
-                    if (mStartOffset != -1 && mTextView.getLayout() != null) {
-                        if (!mHaventMovedEnoughToStartDrag) {
-
-                            float y = eventY;
-                            if (mSwitchedLines) {
-                                // Offset the finger by the same vertical offset as the handles.
-                                // This improves visibility of the content being selected by
-                                // shifting the finger below the content, this is applied once
-                                // the user has switched lines.
-                                final float fingerOffset = (mStartHandle != null)
-                                        ? mStartHandle.getIdealVerticalOffset()
-                                        : touchSlop;
-                                y = eventY - fingerOffset;
-                            }
-
-                            final int currLine = getCurrentLineAdjustedForSlop(
-                                    mTextView.getLayout(),
-                                    mLineSelectionIsOn, y);
-                            if (!mSwitchedLines && currLine != mLineSelectionIsOn) {
-                                // Break early here, we want to offset the finger position from
-                                // the selection highlight, once the user moved their finger
-                                // to a different line we should apply the offset and *not* switch
-                                // lines until recomputing the position with the finger offset.
-                                mSwitchedLines = true;
-                                break;
-                            }
-
-                            int startOffset;
-                            int offset = mTextView.getOffsetAtCoordinate(currLine, eventX);
-                            // Snap to word boundaries.
-                            if (mStartOffset < offset) {
-                                // Expanding with end handle.
-                                offset = getWordEnd(offset);
-                                startOffset = getWordStart(mStartOffset);
-                            } else {
-                                // Expanding with start handle.
-                                offset = getWordStart(offset);
-                                startOffset = getWordEnd(mStartOffset);
-                            }
-                            mLineSelectionIsOn = currLine;
-                            Selection.setSelection((Spannable) mTextView.getText(),
-                                    startOffset, offset);
-                        }
-                    }
+                    updateSelection(event);
                     break;
 
                 case MotionEvent.ACTION_UP:
-                    if (mDragAcceleratorActive) {
-                        // No longer dragging to select text, let the parent intercept events.
-                        mTextView.getParent().requestDisallowInterceptTouchEvent(false);
-
-                        show();
-                        int startOffset = mTextView.getSelectionStart();
-                        int endOffset = mTextView.getSelectionEnd();
-
-                        // Since we don't let drag handles pass once they're visible, we need to
-                        // make sure the start / end locations are correct because the user *can*
-                        // switch directions during the initial drag.
-                        if (endOffset < startOffset) {
-                            int tmp = endOffset;
-                            endOffset = startOffset;
-                            startOffset = tmp;
-
-                            // Also update the selection with the right offsets in this case.
-                            Selection.setSelection((Spannable) mTextView.getText(),
-                                    startOffset, endOffset);
-                        }
-
-                        // Need to do this to display the handles.
-                        mStartHandle.showAtLocation(startOffset);
-                        mEndHandle.showAtLocation(endOffset);
-
-                        // No longer the first dragging motion, reset.
-                        startSelectionActionMode();
-
-                        mDragAcceleratorActive = false;
-                        mStartOffset = -1;
-                        mSwitchedLines = false;
+                    if (!isDragAcceleratorActive()) {
+                        break;
                     }
+                    updateSelection(event);
+
+                    // No longer dragging to select text, let the parent intercept events.
+                    mTextView.getParent().requestDisallowInterceptTouchEvent(false);
+
+                    int startOffset = mTextView.getSelectionStart();
+                    int endOffset = mTextView.getSelectionEnd();
+
+                    // Since we don't let drag handles pass once they're visible, we need to
+                    // make sure the start / end locations are correct because the user *can*
+                    // switch directions during the initial drag.
+                    if (endOffset < startOffset) {
+                        int tmp = endOffset;
+                        endOffset = startOffset;
+                        startOffset = tmp;
+
+                        // Also update the selection with the right offsets in this case.
+                        Selection.setSelection((Spannable) mTextView.getText(),
+                                startOffset, endOffset);
+                    }
+                    if (startOffset != endOffset) {
+                        startSelectionActionMode();
+                    }
+
+                    // No longer the first dragging motion, reset.
+                    resetDragAcceleratorState();
                     break;
             }
         }
 
+        private void updateSelection(MotionEvent event) {
+            if (mTextView.getLayout() != null) {
+                switch (mDragAcceleratorMode) {
+                    case DRAG_ACCELERATOR_MODE_CHARACTER:
+                        updateCharacterBasedSelection(event);
+                        break;
+                    case DRAG_ACCELERATOR_MODE_WORD:
+                        updateWordBasedSelection(event);
+                        break;
+                    case DRAG_ACCELERATOR_MODE_PARAGRAPH:
+                        updateParagraphBasedSelection(event);
+                        break;
+                }
+            }
+        }
+
+        /**
+         * If the TextView allows text selection, selects the current paragraph and starts a drag.
+         *
+         * @return true if the drag was started.
+         */
+        private boolean selectCurrentParagraphAndStartDrag() {
+            if (mInsertionActionModeRunnable != null) {
+                mTextView.removeCallbacks(mInsertionActionModeRunnable);
+            }
+            if (mTextActionMode != null) {
+                mTextActionMode.finish();
+            }
+            if (!selectCurrentParagraph()) {
+                return false;
+            }
+            enterDrag(SelectionModifierCursorController.DRAG_ACCELERATOR_MODE_PARAGRAPH);
+            return true;
+        }
+
+        private void updateCharacterBasedSelection(MotionEvent event) {
+            final int offset = mTextView.getOffsetForPosition(event.getX(), event.getY());
+            Selection.setSelection((Spannable) mTextView.getText(), mStartOffset, offset);
+        }
+
+        private void updateWordBasedSelection(MotionEvent event) {
+            if (mHaventMovedEnoughToStartDrag) {
+                return;
+            }
+            final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
+            final ViewConfiguration viewConfig = ViewConfiguration.get(
+                    mTextView.getContext());
+            final float eventX = event.getX();
+            final float eventY = event.getY();
+            final int currLine;
+            if (isMouse) {
+                // No need to offset the y coordinate for mouse input.
+                currLine = mTextView.getLineAtCoordinate(eventY);
+            } else {
+                float y = eventY;
+                if (mSwitchedLines) {
+                    // Offset the finger by the same vertical offset as the handles.
+                    // This improves visibility of the content being selected by
+                    // shifting the finger below the content, this is applied once
+                    // the user has switched lines.
+                    final int touchSlop = viewConfig.getScaledTouchSlop();
+                    final float fingerOffset = (mStartHandle != null)
+                            ? mStartHandle.getIdealVerticalOffset()
+                            : touchSlop;
+                    y = eventY - fingerOffset;
+                }
+
+                currLine = getCurrentLineAdjustedForSlop(mTextView.getLayout(), mLineSelectionIsOn,
+                        y);
+                if (!mSwitchedLines && currLine != mLineSelectionIsOn) {
+                    // Break early here, we want to offset the finger position from
+                    // the selection highlight, once the user moved their finger
+                    // to a different line we should apply the offset and *not* switch
+                    // lines until recomputing the position with the finger offset.
+                    mSwitchedLines = true;
+                    return;
+                }
+            }
+
+            int startOffset;
+            int offset = mTextView.getOffsetAtCoordinate(currLine, eventX);
+            // Snap to word boundaries.
+            if (mStartOffset < offset) {
+                // Expanding with end handle.
+                offset = getWordEnd(offset);
+                startOffset = getWordStart(mStartOffset);
+            } else {
+                // Expanding with start handle.
+                offset = getWordStart(offset);
+                startOffset = getWordEnd(mStartOffset);
+            }
+            mLineSelectionIsOn = currLine;
+            Selection.setSelection((Spannable) mTextView.getText(),
+                    startOffset, offset);
+        }
+
+        private void updateParagraphBasedSelection(MotionEvent event) {
+            final int offset = mTextView.getOffsetForPosition(event.getX(), event.getY());
+
+            final int start = Math.min(offset, mStartOffset);
+            final int end = Math.max(offset, mStartOffset);
+            final long paragraphsRange = getParagraphsRange(start, end);
+            final int selectionStart = TextUtils.unpackRangeStartFromLong(paragraphsRange);
+            final int selectionEnd = TextUtils.unpackRangeEndFromLong(paragraphsRange);
+            Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
+        }
+
         /**
          * @param event
          */
@@ -4749,8 +4920,12 @@
 
         public void resetTouchOffsets() {
             mMinTouchOffset = mMaxTouchOffset = -1;
+            resetDragAcceleratorState();
+        }
+
+        private void resetDragAcceleratorState() {
             mStartOffset = -1;
-            mDragAcceleratorActive = false;
+            mDragAcceleratorMode = DRAG_ACCELERATOR_MODE_INACTIVE;
             mSwitchedLines = false;
         }
 
@@ -4765,7 +4940,7 @@
          * @return true if the user is selecting text using the drag accelerator.
          */
         public boolean isDragAcceleratorActive() {
-            return mDragAcceleratorActive;
+            return mDragAcceleratorMode != DRAG_ACCELERATOR_MODE_INACTIVE;
         }
 
         public void onTouchModeChanged(boolean isInTouchMode) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 94b75b7..5574f86 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -17,7 +17,6 @@
 package android.widget;
 
 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-
 import android.R;
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
@@ -115,6 +114,7 @@
 import android.view.DragEvent;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
+import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -618,9 +618,6 @@
     private final Paint mHighlightPaint;
     private boolean mHighlightPathBogus = true;
 
-    private boolean mFirstTouch = false;
-    private long mLastTouchUpTime = 0;
-
     // Although these fields are specific to editable text, they are not added to Editor because
     // they are defined by the TextView's style and are theme-dependent.
     int mCursorDrawableRes;
@@ -8406,23 +8403,6 @@
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         final int action = event.getActionMasked();
-
-        if (mEditor != null && action == MotionEvent.ACTION_DOWN) {
-            // Detect double tap and inform the Editor.
-            if (mFirstTouch && (SystemClock.uptimeMillis() - mLastTouchUpTime) <=
-                    ViewConfiguration.getDoubleTapTimeout()) {
-                mEditor.mDoubleTap = true;
-                mFirstTouch = false;
-            } else {
-                mEditor.mDoubleTap = false;
-                mFirstTouch = true;
-            }
-        }
-
-        if (action == MotionEvent.ACTION_UP) {
-            mLastTouchUpTime = SystemClock.uptimeMillis();
-        }
-
         if (mEditor != null) {
             mEditor.onTouchEvent(event);
 
diff --git a/core/java/com/android/internal/policy/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
index 75ca639..1b44ff3 100644
--- a/core/java/com/android/internal/policy/BackdropFrameRenderer.java
+++ b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
@@ -63,17 +63,18 @@
     private boolean mReportNextDraw;
 
     private Drawable mCaptionBackgroundDrawable;
+    private Drawable mUserCaptionBackgroundDrawable;
     private Drawable mResizingBackgroundDrawable;
     private ColorDrawable mStatusBarColor;
 
     public BackdropFrameRenderer(DecorView decorView, ThreadedRenderer renderer, Rect initialBounds,
             Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawable,
-            int statusBarColor) {
+            Drawable userCaptionBackgroundDrawable, int statusBarColor) {
         setName("ResizeFrame");
 
         mRenderer = renderer;
         onResourcesLoaded(decorView, resizingBackgroundDrawable, captionBackgroundDrawable,
-                statusBarColor);
+                userCaptionBackgroundDrawable, statusBarColor);
 
         // Create a render node for the content and frame backdrop
         // which can be resized independently from the content.
@@ -92,10 +93,12 @@
     }
 
     void onResourcesLoaded(DecorView decorView, Drawable resizingBackgroundDrawable,
-            Drawable captionBackgroundDrawableDrawable, int statusBarColor) {
+            Drawable captionBackgroundDrawableDrawable, Drawable userCaptionBackgroundDrawable,
+            int statusBarColor) {
         mDecorView = decorView;
         mResizingBackgroundDrawable = resizingBackgroundDrawable;
         mCaptionBackgroundDrawable = captionBackgroundDrawableDrawable;
+        mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable;
         if (statusBarColor != 0) {
             mStatusBarColor = new ColorDrawable(statusBarColor);
             addSystemBarNodeIfNeeded();
@@ -281,8 +284,10 @@
 
         // Draw the caption and content backdrops in to our render node.
         DisplayListCanvas canvas = mFrameAndBackdropNode.start(width, height);
-        mCaptionBackgroundDrawable.setBounds(0, 0, left + width, top + mLastCaptionHeight);
-        mCaptionBackgroundDrawable.draw(canvas);
+        final Drawable drawable = mUserCaptionBackgroundDrawable != null
+                ? mUserCaptionBackgroundDrawable : mCaptionBackgroundDrawable;
+        drawable.setBounds(0, 0, left + width, top + mLastCaptionHeight);
+        drawable.draw(canvas);
 
         // The backdrop: clear everything with the background. Clipping is done elsewhere.
         mResizingBackgroundDrawable.setBounds(0, mLastCaptionHeight, left + width, top + height);
@@ -324,4 +329,8 @@
             mChoreographer.postFrameCallback(this);
         }
     }
+
+    void setUserCaptionBackgroundDrawable(Drawable userCaptionBackgroundDrawable) {
+        mUserCaptionBackgroundDrawable = userCaptionBackgroundDrawable;
+    }
 }
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 531ba2f..e405564 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -74,6 +74,8 @@
 import static android.view.View.MeasureSpec.getMode;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.Window.DECOR_CAPTION_SHADE_DARK;
+import static android.view.Window.DECOR_CAPTION_SHADE_LIGHT;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
@@ -184,9 +186,10 @@
 
     private boolean mWindowResizeCallbacksAdded = false;
 
-    BackdropFrameRenderer mBackdropFrameRenderer = null;
+    private BackdropFrameRenderer mBackdropFrameRenderer = null;
     private Drawable mResizingBackgroundDrawable;
     private Drawable mCaptionBackgroundDrawable;
+    private Drawable mUserCaptionBackgroundDrawable;
 
     DecorView(Context context, int featureId, PhoneWindow window) {
         super(context);
@@ -1580,18 +1583,20 @@
         initializeElevation();
     }
 
-    View onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
+    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
         mStackId = getStackId();
 
         mResizingBackgroundDrawable = getResizingBackgroundDrawable(
                 mWindow.mBackgroundResource, mWindow.mBackgroundFallbackResource);
-        mCaptionBackgroundDrawable =
-                getContext().getDrawable(R.drawable.decor_caption_title_focused);
+        if (mCaptionBackgroundDrawable == null) {
+            mCaptionBackgroundDrawable = getContext().getDrawable(
+                    R.drawable.decor_caption_title_focused);
+        }
 
         if (mBackdropFrameRenderer != null) {
             mBackdropFrameRenderer.onResourcesLoaded(
                     this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
-                    getCurrentColor(mStatusColorViewState));
+                    mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState));
         }
 
         mDecorCaptionView = createDecorCaptionView(inflater);
@@ -1608,17 +1613,16 @@
         }
         mContentRoot = (ViewGroup) root;
         initializeElevation();
-        return root;
     }
 
     // Free floating overlapping windows require a caption.
     private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) {
-        DecorCaptionView DecorCaptionView = null;
-        for (int i = getChildCount() - 1; i >= 0 && DecorCaptionView == null; i--) {
+        DecorCaptionView decorCaptionView = null;
+        for (int i = getChildCount() - 1; i >= 0 && decorCaptionView == null; i--) {
             View view = getChildAt(i);
             if (view instanceof DecorCaptionView) {
                 // The decor was most likely saved from a relaunch - so reuse it.
-                DecorCaptionView = (DecorCaptionView) view;
+                decorCaptionView = (DecorCaptionView) view;
                 removeViewAt(i);
             }
         }
@@ -1630,27 +1634,72 @@
                 && ActivityManager.StackId.hasWindowDecor(mStackId)) {
             // Dependent on the brightness of the used title we either use the
             // dark or the light button frame.
-            if (DecorCaptionView == null) {
-                Context context = getContext();
-                TypedValue value = new TypedValue();
-                context.getTheme().resolveAttribute(R.attr.colorPrimary, value, true);
-                inflater = inflater.from(context);
-                if (Color.luminance(value.data) < 0.5) {
-                    DecorCaptionView = (DecorCaptionView) inflater.inflate(
-                            R.layout.decor_caption_dark, null);
-                } else {
-                    DecorCaptionView = (DecorCaptionView) inflater.inflate(
-                            R.layout.decor_caption_light, null);
-                }
+            if (decorCaptionView == null) {
+                decorCaptionView = inflateDecorCaptionView(inflater);
             }
-            DecorCaptionView.setPhoneWindow(mWindow, true /*showDecor*/);
+            decorCaptionView.setPhoneWindow(mWindow, true /*showDecor*/);
         } else {
-            DecorCaptionView = null;
+            decorCaptionView = null;
         }
 
         // Tell the decor if it has a visible caption.
-        enableCaption(DecorCaptionView != null);
-        return DecorCaptionView;
+        enableCaption(decorCaptionView != null);
+        return decorCaptionView;
+    }
+
+    private DecorCaptionView inflateDecorCaptionView(LayoutInflater inflater) {
+        final Context context = getContext();
+        // We make a copy of the inflater, so it has the right context associated with it.
+        inflater = inflater.from(context);
+        final DecorCaptionView view = (DecorCaptionView) inflater.inflate(R.layout.decor_caption,
+                null);
+        setDecorCaptionShade(context, view);
+        return view;
+    }
+
+    private void setDecorCaptionShade(Context context, DecorCaptionView view) {
+        final int shade = mWindow.getDecorCaptionShade();
+        switch (shade) {
+            case DECOR_CAPTION_SHADE_LIGHT:
+                setLightDecorCaptionShade(view);
+                break;
+            case DECOR_CAPTION_SHADE_DARK:
+                setDarkDecorCaptionShade(view);
+                break;
+            default: {
+                TypedValue value = new TypedValue();
+                context.getTheme().resolveAttribute(R.attr.colorPrimary, value, true);
+                // We invert the shade depending on brightness of the theme. Dark shade for light
+                // theme and vice versa. Thanks to this the buttons should be visible on the
+                // background.
+                if (Color.luminance(value.data) < 0.5) {
+                    setLightDecorCaptionShade(view);
+                } else {
+                    setDarkDecorCaptionShade(view);
+                }
+                break;
+            }
+        }
+    }
+
+    void updateDecorCaptionShade() {
+        if (mDecorCaptionView != null) {
+            setDecorCaptionShade(getContext(), mDecorCaptionView);
+        }
+    }
+
+    private void setLightDecorCaptionShade(DecorCaptionView view) {
+        view.findViewById(R.id.maximize_window).setBackgroundResource(
+                R.drawable.decor_maximize_button_light);
+        view.findViewById(R.id.close_window).setBackgroundResource(
+                R.drawable.decor_close_button_light);
+    }
+
+    private void setDarkDecorCaptionShade(DecorCaptionView view) {
+        view.findViewById(R.id.maximize_window).setBackgroundResource(
+                R.drawable.decor_maximize_button_dark);
+        view.findViewById(R.id.close_window).setBackgroundResource(
+                R.drawable.decor_close_button_dark);
     }
 
     /**
@@ -1735,11 +1784,11 @@
         if (mBackdropFrameRenderer != null) {
             return;
         }
-        final ThreadedRenderer renderer = (ThreadedRenderer) getHardwareRenderer();
+        final ThreadedRenderer renderer = getHardwareRenderer();
         if (renderer != null) {
             mBackdropFrameRenderer = new BackdropFrameRenderer(this, renderer,
                     initialBounds, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
-                    getCurrentColor(mStatusColorViewState));
+                    mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState));
 
             // Get rid of the shadow while we are resizing. Shadow drawing takes considerable time.
             // If we want to get the shadow shown while resizing, we would need to elevate a new
@@ -1849,6 +1898,16 @@
                 getResources().getDisplayMetrics());
     }
 
+    /**
+     * Provide an override of the caption background drawable.
+     */
+    void setUserCaptionBackgroundDrawable(Drawable drawable) {
+        mUserCaptionBackgroundDrawable = drawable;
+        if (mBackdropFrameRenderer != null) {
+            mBackdropFrameRenderer.setUserCaptionBackgroundDrawable(drawable);
+        }
+    }
+
     private static class ColorViewState {
         View view = null;
         int targetVisibility = View.INVISIBLE;
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 337bb69..86bd782 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -94,7 +94,6 @@
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 import android.widget.FrameLayout;
-import android.widget.LinearLayout;
 import android.widget.ImageView;
 import android.widget.ProgressBar;
 import android.widget.TextView;
@@ -273,6 +272,8 @@
     private boolean mIsStartingWindow;
     private int mTheme = -1;
 
+    private int mDecorCaptionShade = DECOR_CAPTION_SHADE_AUTO;
+
     static class WindowManagerHolder {
         static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface(
                 ServiceManager.getService("window"));
@@ -2527,7 +2528,7 @@
         }
 
         mDecor.startChanging();
-        final View in = mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
+        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
 
         ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
         if (contentParent == null) {
@@ -3720,4 +3721,21 @@
             }
         }
     }
+
+    @Override
+    public void setResizingCaptionDrawable(Drawable drawable) {
+        mDecor.setUserCaptionBackgroundDrawable(drawable);
+    }
+
+    @Override
+    public void setDecorCaptionShade(int decorCaptionShade) {
+        mDecorCaptionShade = decorCaptionShade;
+        if (mDecor != null) {
+            mDecor.updateDecorCaptionShade();
+        }
+    }
+
+    int getDecorCaptionShade() {
+        return mDecorCaptionShade;
+    }
 }
diff --git a/core/java/com/android/internal/widget/DecorCaptionView.java b/core/java/com/android/internal/widget/DecorCaptionView.java
index d747686..c3fe9e7 100644
--- a/core/java/com/android/internal/widget/DecorCaptionView.java
+++ b/core/java/com/android/internal/widget/DecorCaptionView.java
@@ -319,6 +319,10 @@
                         captionHeight + mContent.getMeasuredHeight());
             }
         }
+
+        // This assumes that the caption bar is at the top.
+        mOwner.notifyRestrictedCaptionAreaCallback(mMaximize.getLeft(), mMaximize.getTop(),
+                mClose.getRight(), mClose.getBottom());
     }
     /**
      * Determine if the workspace is entirely covered by the window.
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 1ee7ea8..71f881e 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -595,6 +595,36 @@
     MEMINFO_COUNT
 };
 
+static long get_zram_mem_used()
+{
+#define ZRAM_SYSFS "/sys/block/zram0/"
+    FILE *f = fopen(ZRAM_SYSFS "mm_stat", "r");
+    if (f) {
+        long mem_used_total = 0;
+
+        int matched = fscanf(f, "%*d %*d %ld %*d %*d %*d %*d", &mem_used_total);
+        if (matched != 1)
+            ALOGW("failed to parse " ZRAM_SYSFS "mm_stat");
+
+        fclose(f);
+        return mem_used_total;
+    }
+
+    f = fopen(ZRAM_SYSFS "mem_used_total", "r");
+    if (f) {
+        long mem_used_total = 0;
+
+        int matched = fscanf(f, "%ld", &mem_used_total);
+        if (matched != 1)
+            ALOGW("failed to parse " ZRAM_SYSFS "mem_used_total");
+
+        fclose(f);
+        return mem_used_total;
+    }
+
+    return 0;
+}
+
 static void android_os_Debug_getMemInfo(JNIEnv *env, jobject clazz, jlongArray out)
 {
     char buffer[1024];
@@ -680,15 +710,7 @@
         if (*p) p++;
     }
 
-    fd = open("/sys/block/zram0/mem_used_total", O_RDONLY);
-    if (fd >= 0) {
-        len = read(fd, buffer, sizeof(buffer)-1);
-        close(fd);
-        if (len > 0) {
-            buffer[len] = 0;
-            mem[MEMINFO_ZRAM_TOTAL] = atoll(buffer)/1024;
-        }
-    }
+    mem[MEMINFO_ZRAM_TOTAL] = get_zram_mem_used() / 1024;
     // Recompute Vmalloc Used since the value in meminfo
     // doesn't account for I/O remapping which doesn't use RAM.
     mem[MEMINFO_VMALLOC_USED] = get_allocated_vmalloc_memory() / 1024;
diff --git a/core/res/Android.mk b/core/res/Android.mk
index cfc791d..a1bef83 100644
--- a/core/res/Android.mk
+++ b/core/res/Android.mk
@@ -23,6 +23,7 @@
 # Tell aapt to create "extending (non-application)" resource IDs,
 # since these resources will be used by many apps.
 LOCAL_AAPT_FLAGS := -x
+LOCAL_AAPT_FLAGS += --private-symbols com.android.internal
 
 LOCAL_MODULE_TAGS := optional
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5251b20..1127197 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -277,6 +277,7 @@
     <protected-broadcast android:name="android.intent.action.ADVANCED_SETTINGS" />
     <protected-broadcast android:name="android.intent.action.APPLICATION_RESTRICTIONS_CHANGED" />
     <protected-broadcast android:name="android.intent.action.BUGREPORT_FINISHED" />
+    <protected-broadcast android:name="android.intent.action.BUGREPORT_STARTED" />
 
     <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" />
     <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_END" />
diff --git a/core/res/res/drawable/ic_decor_close_button_dark_focused.xml b/core/res/res/drawable/ic_decor_close_button_dark_focused.xml
index d7b167dd..0794ed3 100644
--- a/core/res/res/drawable/ic_decor_close_button_dark_focused.xml
+++ b/core/res/res/drawable/ic_decor_close_button_dark_focused.xml
@@ -23,7 +23,7 @@
            android:translateX="8.0"
            android:translateY="8.0" >
         <path
-            android:fillColor="#FFFFFFFF"
+            android:fillColor="#ff000000"
             android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/>
     </group>
 </vector>
diff --git a/core/res/res/drawable/ic_decor_close_button_dark_unfocused.xml b/core/res/res/drawable/ic_decor_close_button_dark_unfocused.xml
index e2e81b9..bd1db51 100644
--- a/core/res/res/drawable/ic_decor_close_button_dark_unfocused.xml
+++ b/core/res/res/drawable/ic_decor_close_button_dark_unfocused.xml
@@ -23,7 +23,7 @@
            android:translateX="8.0"
            android:translateY="8.0" >
         <path
-            android:fillColor="#33FFFFFF"
+            android:fillColor="#33000000"
             android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/>
     </group>
 </vector>
diff --git a/core/res/res/drawable/ic_decor_close_button_light_focused.xml b/core/res/res/drawable/ic_decor_close_button_light_focused.xml
index 0794ed3..d7b167dd 100644
--- a/core/res/res/drawable/ic_decor_close_button_light_focused.xml
+++ b/core/res/res/drawable/ic_decor_close_button_light_focused.xml
@@ -23,7 +23,7 @@
            android:translateX="8.0"
            android:translateY="8.0" >
         <path
-            android:fillColor="#ff000000"
+            android:fillColor="#FFFFFFFF"
             android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/>
     </group>
 </vector>
diff --git a/core/res/res/drawable/ic_decor_close_button_light_unfocused.xml b/core/res/res/drawable/ic_decor_close_button_light_unfocused.xml
index bd1db51..e2e81b9 100644
--- a/core/res/res/drawable/ic_decor_close_button_light_unfocused.xml
+++ b/core/res/res/drawable/ic_decor_close_button_light_unfocused.xml
@@ -23,7 +23,7 @@
            android:translateX="8.0"
            android:translateY="8.0" >
         <path
-            android:fillColor="#33000000"
+            android:fillColor="#33FFFFFF"
             android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/>
     </group>
 </vector>
diff --git a/core/res/res/drawable/ic_decor_maximize_button_dark_focused.xml b/core/res/res/drawable/ic_decor_maximize_button_dark_focused.xml
index 73d808b..c23390e 100644
--- a/core/res/res/drawable/ic_decor_maximize_button_dark_focused.xml
+++ b/core/res/res/drawable/ic_decor_maximize_button_dark_focused.xml
@@ -23,10 +23,10 @@
            android:translateX="8.0"
            android:translateY="8.0" >
         <path
-            android:fillColor="#FFFFFFFF"
+            android:fillColor="#FF000000"
             android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/>
         <path
-            android:fillColor="#B2FFFFFF"
+            android:fillColor="#B2000000"
             android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/>
     </group>
 </vector>
diff --git a/core/res/res/drawable/ic_decor_maximize_button_dark_unfocused.xml b/core/res/res/drawable/ic_decor_maximize_button_dark_unfocused.xml
index dc79e10..a194a39 100644
--- a/core/res/res/drawable/ic_decor_maximize_button_dark_unfocused.xml
+++ b/core/res/res/drawable/ic_decor_maximize_button_dark_unfocused.xml
@@ -23,10 +23,10 @@
            android:translateX="8.0"
            android:translateY="8.0" >
         <path
-            android:fillColor="#33FFFFFF"
+            android:fillColor="#33000000"
             android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/>
         <path
-            android:fillColor="#33FFFFFF"
+            android:fillColor="#33000000"
             android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/>
     </group>
 </vector>
diff --git a/core/res/res/drawable/ic_decor_maximize_button_light_focused.xml b/core/res/res/drawable/ic_decor_maximize_button_light_focused.xml
index c23390e..73d808b 100644
--- a/core/res/res/drawable/ic_decor_maximize_button_light_focused.xml
+++ b/core/res/res/drawable/ic_decor_maximize_button_light_focused.xml
@@ -23,10 +23,10 @@
            android:translateX="8.0"
            android:translateY="8.0" >
         <path
-            android:fillColor="#FF000000"
+            android:fillColor="#FFFFFFFF"
             android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/>
         <path
-            android:fillColor="#B2000000"
+            android:fillColor="#B2FFFFFF"
             android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/>
     </group>
 </vector>
diff --git a/core/res/res/drawable/ic_decor_maximize_button_light_unfocused.xml b/core/res/res/drawable/ic_decor_maximize_button_light_unfocused.xml
index a194a39..dc79e10 100644
--- a/core/res/res/drawable/ic_decor_maximize_button_light_unfocused.xml
+++ b/core/res/res/drawable/ic_decor_maximize_button_light_unfocused.xml
@@ -23,10 +23,10 @@
            android:translateX="8.0"
            android:translateY="8.0" >
         <path
-            android:fillColor="#33000000"
+            android:fillColor="#33FFFFFF"
             android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/>
         <path
-            android:fillColor="#33000000"
+            android:fillColor="#33FFFFFF"
             android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/>
     </group>
 </vector>
diff --git a/core/res/res/layout/decor_caption.xml b/core/res/res/layout/decor_caption.xml
new file mode 100644
index 0000000..0246736
--- /dev/null
+++ b/core/res/res/layout/decor_caption.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2015, 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.
+*/
+-->
+
+<com.android.internal.widget.DecorCaptionView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:descendantFocusability="beforeDescendants" >
+    <LinearLayout
+            android:id="@+id/caption"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="end"
+            android:background="@drawable/decor_caption_title"
+            android:focusable="false"
+            android:descendantFocusability="blocksDescendants" >
+        <Button
+                android:id="@+id/maximize_window"
+                android:layout_width="32dp"
+                android:layout_height="32dp"
+                android:layout_margin="5dp"
+                android:padding="4dp"
+                android:layout_gravity="center_vertical|end"
+                android:contentDescription="@string/maximize_button_text"
+                android:background="@drawable/decor_maximize_button_dark" />
+        <Button
+                android:id="@+id/close_window"
+                android:layout_width="32dp"
+                android:layout_height="32dp"
+                android:layout_margin="5dp"
+                android:padding="4dp"
+                android:layout_gravity="center_vertical|end"
+                android:contentDescription="@string/close_button_text"
+                android:background="@drawable/decor_close_button_dark" />
+    </LinearLayout>
+</com.android.internal.widget.DecorCaptionView>
diff --git a/core/res/res/layout/decor_caption_dark.xml b/core/res/res/layout/decor_caption_dark.xml
deleted file mode 100644
index 95d2289..0000000
--- a/core/res/res/layout/decor_caption_dark.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2015, 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.
-*/
--->
-
-<com.android.internal.widget.DecorCaptionView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:descendantFocusability="beforeDescendants" >
-    <LinearLayout
-        android:id="@+id/caption"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="end"
-        android:background="@drawable/decor_caption_title"
-        android:focusable="false"
-        android:descendantFocusability="blocksDescendants" >
-        <Button
-            android:id="@+id/maximize_window"
-            android:layout_width="32dp"
-            android:layout_height="32dp"
-            android:layout_margin="5dp"
-            android:padding="4dp"
-            android:layout_gravity="center_vertical|end"
-            android:contentDescription="@string/maximize_button_text"
-            android:background="@drawable/decor_maximize_button_dark" />
-        <Button
-            android:id="@+id/close_window"
-            android:layout_width="32dp"
-            android:layout_height="32dp"
-            android:layout_margin="5dp"
-            android:padding="4dp"
-            android:layout_gravity="center_vertical|end"
-            android:contentDescription="@string/close_button_text"
-            android:background="@drawable/decor_close_button_dark" />
-    </LinearLayout>
-</com.android.internal.widget.DecorCaptionView>
diff --git a/core/res/res/layout/decor_caption_light.xml b/core/res/res/layout/decor_caption_light.xml
deleted file mode 100644
index f0f661e..0000000
--- a/core/res/res/layout/decor_caption_light.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2015, 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.
-*/
--->
-
-<com.android.internal.widget.DecorCaptionView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:descendantFocusability="beforeDescendants" >
-    <LinearLayout
-        android:id="@+id/caption"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="end"
-        android:background="@drawable/decor_caption_title"
-        android:focusable="false"
-        android:descendantFocusability="blocksDescendants" >
-        <Button
-            android:id="@+id/maximize_window"
-            android:layout_width="32dp"
-            android:layout_height="32dp"
-            android:layout_margin="5dp"
-            android:padding="4dp"
-            android:layout_gravity="center_vertical|end"
-            android:contentDescription="@string/maximize_button_text"
-            android:background="@drawable/decor_maximize_button_light" />
-        <Button
-            android:id="@+id/close_window"
-            android:layout_width="32dp"
-            android:layout_height="32dp"
-            android:layout_margin="5dp"
-            android:padding="4dp"
-            android:layout_gravity="center_vertical|end"
-            android:contentDescription="@string/close_button_text"
-            android:background="@drawable/decor_close_button_light" />
-    </LinearLayout>
-</com.android.internal.widget.DecorCaptionView>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9bca3d6..786554c 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3042,7 +3042,8 @@
         <attr name="imeSubtypeLocale" format="string" />
         <!-- The mode of the subtype. This string can be a mode (e.g. voice, keyboard...) and this
              string will be passed to the IME when the framework calls the IME with the
-             subtype.  -->
+             subtype.  {@link android.view.inputmethod.InputMethodSubtype#getLocale()} returns the
+             value specified in this attribute.  -->
         <attr name="imeSubtypeMode" format="string" />
         <!-- Set true if the subtype is auxiliary.  An auxiliary subtype won't be shown in the
              input method selection list in the settings app.
@@ -3067,6 +3068,9 @@
              this subtype. This is important because many password fields only allow
              ASCII-characters. -->
         <attr name="isAsciiCapable" format="boolean" />
+        <!-- The BCP-47 Language Tag of the subtype.  This replaces
+        {@link android.R.styleable#InputMethod_Subtype_imeSubtypeLocale}.  -->
+        <attr name="languageTag" format="string" />
     </declare-styleable>
 
     <!-- Use <code>spell-checker</code> as the root tag of the XML resource that
@@ -3090,7 +3094,8 @@
         <attr name="label" />
         <!-- The locale of the subtype. This string should be a locale (e.g. en_US, fr_FR...)
              This is also used by the framework to know the supported locales
-             of the spell checker.  -->
+             of the spell checker. {@link android.view.textservice.SpellCheckerSubtype#getLocale()}
+             returns the value specified in this attribute.  -->
         <attr name="subtypeLocale" format="string" />
         <!-- The extra value of the subtype. This string can be any string and will be passed to
              the SpellChecker.  -->
@@ -3102,6 +3107,9 @@
              {@code Arrays.hashCode(new Object[] {subtypeLocale, extraValue}) will be used instead.
               -->
         <attr name="subtypeId" />
+        <!-- The BCP-47 Language Tag of the subtype.  This replaces
+        {@link android.R.styleable#SpellChecker_Subtype_subtypeLocale}.  -->
+        <attr name="languageTag" />
     </declare-styleable>
 
     <!-- Use <code>accessibility-service</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index b6b2e20..addeb05 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2683,6 +2683,7 @@
     <public type="attr" name="encryptionAware" />
     <public type="attr" name="preferenceFragmentStyle" />
     <public type="attr" name="canControlMagnification" />
+    <public type="attr" name="languageTag" />
 
     <public type="style" name="Theme.Material.DayNight" />
     <public type="style" name="Theme.Material.DayNight.DarkActionBar" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index eadcae0..530b08d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -16,10 +16,6 @@
 */
 -->
 <resources>
-  <!-- We don't want to publish private symbols in android.R as part of the
-       SDK.  Instead, put them here. -->
-  <private-symbols package="com.android.internal" />
-
   <!-- Private symbols that we need to reference from framework code.  See
        frameworks/base/core/res/MakeJavaSymbols.sed for how to easily generate
        this.
@@ -1958,9 +1954,12 @@
   <java-symbol type="bool" name="config_built_in_sip_phone" />
   <java-symbol type="id" name="maximize_window" />
   <java-symbol type="id" name="close_window" />
-  <java-symbol type="layout" name="decor_caption_light" />
-  <java-symbol type="layout" name="decor_caption_dark" />
+  <java-symbol type="layout" name="decor_caption" />
   <java-symbol type="drawable" name="decor_caption_title_focused" />
+  <java-symbol type="drawable" name="decor_close_button_dark" />
+  <java-symbol type="drawable" name="decor_close_button_light" />
+  <java-symbol type="drawable" name="decor_maximize_button_dark" />
+  <java-symbol type="drawable" name="decor_maximize_button_light" />
 
   <!-- From TelephonyProvider -->
   <java-symbol type="xml" name="apns" />
@@ -2267,6 +2266,7 @@
 
   <!-- Floating toolbar -->
   <java-symbol type="id" name="floating_toolbar_menu_item_image_button" />
+  <java-symbol type="id" name="overflow" />
   <java-symbol type="layout" name="floating_popup_container" />
   <java-symbol type="layout" name="floating_popup_menu_button" />
   <java-symbol type="layout" name="floating_popup_open_overflow_button" />
diff --git a/core/tests/coretests/res/layout/activity_text_view.xml b/core/tests/coretests/res/layout/activity_text_view.xml
index 7ab0b13..e795c10 100644
--- a/core/tests/coretests/res/layout/activity_text_view.xml
+++ b/core/tests/coretests/res/layout/activity_text_view.xml
@@ -23,6 +23,6 @@
     <EditText
             android:id="@+id/textview"
             android:layout_width="match_parent"
-            android:layout_height="match_parent" />
+            android:layout_height="wrap_content" />
 
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java b/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java
index 73fdb10..4a1c414 100644
--- a/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java
+++ b/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java
@@ -30,11 +30,16 @@
  */
 public class SpellCheckerSubtypeTest extends InstrumentationTestCase {
     private static final int SUBTYPE_SUBTYPE_ID_NONE = 0;
+    private static final String SUBTYPE_SUBTYPE_LOCALE_STRING_NONE = "";
+    private static final String SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE = "";
+
     private static final String SUBTYPE_SUBTYPE_LOCALE_STRING_A = "en_GB";
+    private static final String SUBTYPE_SUBTYPE_LANGUAGE_TAG_A = "en-GB";
     private static final int SUBTYPE_NAME_RES_ID_A = 0x12345;
     private static final String SUBTYPE_EXTRA_VALUE_A = "Key1=Value1,Key2=Value2";
     private static final int SUBTYPE_SUBTYPE_ID_A = 42;
     private static final String SUBTYPE_SUBTYPE_LOCALE_STRING_B = "en_IN";
+    private static final String SUBTYPE_SUBTYPE_LANGUAGE_TAG_B = "en-IN";
     private static final int SUBTYPE_NAME_RES_ID_B = 0x54321;
     private static final String SUBTYPE_EXTRA_VALUE_B = "Key3=Value3,Key4=Value4";
     private static final int SUBTYPE_SUBTYPE_ID_B = -42;
@@ -60,9 +65,11 @@
     @SmallTest
     public void testSubtypeWithNoSubtypeId() throws Exception {
         final SpellCheckerSubtype subtype = new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A,
-                SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE);
+                SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_SUBTYPE_LANGUAGE_TAG_A,
+                SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE);
         assertEquals(SUBTYPE_NAME_RES_ID_A, subtype.getNameResId());
         assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, subtype.getLocale());
+        assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, subtype.getLanguageTag());
         assertEquals("Value1", subtype.getExtraValueOf("Key1"));
         assertEquals("Value2", subtype.getExtraValueOf("Key2"));
         // Historically we have used SpellCheckerSubtype#hashCode() to track which subtype is
@@ -75,6 +82,7 @@
         final SpellCheckerSubtype clonedSubtype = cloneViaParcel(subtype);
         assertEquals(SUBTYPE_NAME_RES_ID_A, clonedSubtype.getNameResId());
         assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, clonedSubtype.getLocale());
+        assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, clonedSubtype.getLanguageTag());
         assertEquals("Value1", clonedSubtype.getExtraValueOf("Key1"));
         assertEquals("Value2", clonedSubtype.getExtraValueOf("Key2"));
         assertEquals(
@@ -84,10 +92,12 @@
 
     public void testSubtypeWithSubtypeId() throws Exception {
         final SpellCheckerSubtype subtype = new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A,
-                SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A);
+                SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_SUBTYPE_LANGUAGE_TAG_A,
+                SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A);
 
         assertEquals(SUBTYPE_NAME_RES_ID_A, subtype.getNameResId());
         assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, subtype.getLocale());
+        assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, subtype.getLanguageTag());
         assertEquals("Value1", subtype.getExtraValueOf("Key1"));
         assertEquals("Value2", subtype.getExtraValueOf("Key2"));
         // Similar to "SubtypeId" in InputMethodSubtype, "SubtypeId" in SpellCheckerSubtype enables
@@ -97,6 +107,7 @@
         final SpellCheckerSubtype clonedSubtype = cloneViaParcel(subtype);
         assertEquals(SUBTYPE_NAME_RES_ID_A, clonedSubtype.getNameResId());
         assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, clonedSubtype.getLocale());
+        assertEquals(SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, clonedSubtype.getLanguageTag());
         assertEquals("Value1", clonedSubtype.getExtraValueOf("Key1"));
         assertEquals("Value2", clonedSubtype.getExtraValueOf("Key2"));
         assertEquals(SUBTYPE_SUBTYPE_ID_A, clonedSubtype.hashCode());
@@ -104,30 +115,42 @@
 
     @SmallTest
     public void testGetLocaleObject() throws Exception {
-        assertEquals(new Locale("en"), new SpellCheckerSubtype(
-                SUBTYPE_NAME_RES_ID_A, "en", SUBTYPE_EXTRA_VALUE_A).getLocaleObject());
-        assertEquals(new Locale("en", "US"), new SpellCheckerSubtype(
-                SUBTYPE_NAME_RES_ID_A, "en_US", SUBTYPE_EXTRA_VALUE_A).getLocaleObject());
-        assertEquals(new Locale("en", "US", "POSIX"), new SpellCheckerSubtype(
-                SUBTYPE_NAME_RES_ID_A, "en_US_POSIX", SUBTYPE_EXTRA_VALUE_A).getLocaleObject());
+        assertEquals(new Locale("en", "GB"),
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, "en_GB",
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject());
+        assertEquals(new Locale("en", "GB"),
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_NONE,
+                        "en-GB", SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject());
 
-        // Special rewrite rule for "tl" for versions of Android earlier than Lollipop that did not
-        // support three letter language codes, and used "tl" (Tagalog) as the language string for
-        // "fil" (Filipino).
-        assertEquals(new Locale("fil"), new SpellCheckerSubtype(
-                SUBTYPE_NAME_RES_ID_A, "tl", SUBTYPE_EXTRA_VALUE_A).getLocaleObject());
-        assertEquals(new Locale("fil", "PH"), new SpellCheckerSubtype(
-                SUBTYPE_NAME_RES_ID_A, "tl_PH", SUBTYPE_EXTRA_VALUE_A).getLocaleObject());
-        assertEquals(new Locale("fil", "PH", "POSIX"), new SpellCheckerSubtype(
-                SUBTYPE_NAME_RES_ID_A, "tl_PH_POSIX", SUBTYPE_EXTRA_VALUE_A).getLocaleObject());
+        // If neither locale string nor language tag is specified,
+        // {@link SpellCheckerSubtype#getLocaleObject} returns null.
+        assertNull(
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_NONE,
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject());
 
-        // So far rejecting invalid/unexpected locale strings is out of the scope.
-        assertEquals(new Locale("a"), new SpellCheckerSubtype(
-                SUBTYPE_NAME_RES_ID_A, "a", SUBTYPE_EXTRA_VALUE_A).getLocaleObject());
-        assertEquals(new Locale("a b c"), new SpellCheckerSubtype(
-                SUBTYPE_NAME_RES_ID_A, "a b c", SUBTYPE_EXTRA_VALUE_A).getLocaleObject());
-        assertEquals(new Locale("en-US"), new SpellCheckerSubtype(
-                SUBTYPE_NAME_RES_ID_A, "en-US", SUBTYPE_EXTRA_VALUE_A).getLocaleObject());
+        // If both locale string and language tag are specified,
+        // {@link SpellCheckerSubtype#getLocaleObject} uses language tag.
+        assertEquals(new Locale("en", "GB"),
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, "en_US", "en-GB",
+                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject());
+
+        // Make sure that "tl_PH" is rewritten to "fil_PH" for spell checkers that need to support
+        // Android KitKat and prior, which do not support 3-letter language codes.
+        assertEquals(new Locale("fil", "PH"),
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, "tl_PH",
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_NONE, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_NONE).getLocaleObject());
+
+        // "languageTag" attribute is available in Android N and later, where 3-letter country codes
+        // are guaranteed to be available.  It's developers' responsibility for specifying a valid
+        // country subtags here and we do not rewrite "tl" to "fil" for simplicity.
+        assertEquals("tl",
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_NONE,
+                        "tl-PH", SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE)
+                        .getLocaleObject().getLanguage());
     }
 
     @SmallTest
@@ -156,50 +179,83 @@
         // If subtype ID is 0 (== SUBTYPE_SUBTYPE_ID_NONE), we keep the same behavior.
         assertEquals(
                 new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
-                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE),
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_NONE),
                 new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
-                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE));
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_NONE));
         assertNotEqual(
                 new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
-                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE),
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_NONE),
                 new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_B, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
-                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE));
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_NONE));
         assertNotEqual(
                 new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
-                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE),
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_NONE),
                 new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
-                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE));
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_NONE));
         assertNotEqual(
                 new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
-                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE),
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_NONE),
                 new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
-                        SUBTYPE_EXTRA_VALUE_B, SUBTYPE_SUBTYPE_ID_NONE));
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_B, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_NONE));
+        assertNotEqual(
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_NONE),
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_B,
+                        SUBTYPE_SUBTYPE_ID_NONE));
 
         // If subtype ID is not 0, we test the equality based only on the subtype ID.
         assertEquals(
                 new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
-                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_A),
                 new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
-                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A));
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_A));
         assertEquals(
                 new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
-                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_A),
                 new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_B, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
-                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A));
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_A));
         assertEquals(
                 new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
-                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_A),
                 new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
-                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A));
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_A));
         assertEquals(
                 new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
-                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_A),
                 new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
-                        SUBTYPE_EXTRA_VALUE_B, SUBTYPE_SUBTYPE_ID_A));
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_B, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_A));
+        assertEquals(
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_A),
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_B,
+                        SUBTYPE_SUBTYPE_ID_A));
         assertNotEqual(
                 new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
-                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_A),
                 new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
-                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_B));
+                        SUBTYPE_SUBTYPE_LANGUAGE_TAG_A, SUBTYPE_EXTRA_VALUE_A,
+                        SUBTYPE_SUBTYPE_ID_B));
     }
+
 }
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
index ddbdc87..83a9e01 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
@@ -16,15 +16,23 @@
 
 package android.widget;
 
+import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles;
+import static android.widget.espresso.DragHandleUtils.onHandleView;
+import static android.widget.espresso.TextViewActions.mouseClickOnTextAtIndex;
 import static android.widget.espresso.TextViewActions.mouseDoubleClickOnTextAtIndex;
 import static android.widget.espresso.TextViewActions.mouseLongClickOnTextAtIndex;
 import static android.widget.espresso.TextViewActions.mouseDoubleClickAndDragOnText;
 import static android.widget.espresso.TextViewActions.mouseDragOnText;
 import static android.widget.espresso.TextViewActions.mouseLongClickAndDragOnText;
+import static android.widget.espresso.TextViewActions.mouseTripleClickAndDragOnText;
+import static android.widget.espresso.TextViewActions.mouseTripleClickOnTextAtIndex;
 import static android.widget.espresso.TextViewAssertions.hasSelection;
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.action.ViewActions.replaceText;
 import static android.support.test.espresso.action.ViewActions.typeTextIntoFocusedView;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 
 import com.android.frameworks.coretests.R;
@@ -48,10 +56,23 @@
         final String helloWorld = "Hello world!";
         onView(withId(R.id.textview)).perform(click());
         onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
+
+        assertNoSelectionHandles();
+
         onView(withId(R.id.textview)).perform(
                 mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!")));
 
         onView(withId(R.id.textview)).check(hasSelection("llo wor"));
+
+        onHandleView(com.android.internal.R.id.selection_start_handle)
+                .check(matches(isDisplayed()));
+        onHandleView(com.android.internal.R.id.selection_end_handle)
+                .check(matches(isDisplayed()));
+
+        onView(withId(R.id.textview)).perform(mouseClickOnTextAtIndex(helloWorld.indexOf("w")));
+        onView(withId(R.id.textview)).check(hasSelection(""));
+
+        assertNoSelectionHandles();
     }
 
     @SmallTest
@@ -89,6 +110,9 @@
         onView(withId(R.id.textview)).perform(mouseLongClickOnTextAtIndex(
                 helloWorld.indexOf("rld")));
         onView(withId(R.id.textview)).check(hasSelection("world"));
+
+        onView(withId(R.id.textview)).perform(mouseLongClickOnTextAtIndex(helloWorld.length()));
+        onView(withId(R.id.textview)).check(hasSelection("!"));
     }
 
     @SmallTest
@@ -113,6 +137,9 @@
         onView(withId(R.id.textview)).perform(mouseDoubleClickOnTextAtIndex(
                 helloWorld.indexOf("rld")));
         onView(withId(R.id.textview)).check(hasSelection("world"));
+
+        onView(withId(R.id.textview)).perform(mouseDoubleClickOnTextAtIndex(helloWorld.length()));
+        onView(withId(R.id.textview)).check(hasSelection("!"));
     }
 
     @SmallTest
@@ -166,4 +193,102 @@
                 mouseLongClickAndDragOnText(text.indexOf("j"), text.indexOf("f")));
         onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
     }
+
+    @SmallTest
+    public void testSelectTextByTripleClick() throws Exception {
+        getActivity();
+
+        final StringBuilder builder = new StringBuilder();
+        builder.append("First paragraph.\n");
+        builder.append("Second paragraph.");
+        for (int i = 0; i < 10; i++) {
+            builder.append(" This paragraph is very long.");
+        }
+        builder.append('\n');
+        builder.append("Third paragraph.");
+        final String text = builder.toString();
+
+        onView(withId(R.id.textview)).perform(click());
+        onView(withId(R.id.textview)).perform(replaceText(text));
+
+        onView(withId(R.id.textview)).perform(
+                mouseTripleClickOnTextAtIndex(text.indexOf("rst")));
+        onView(withId(R.id.textview)).check(hasSelection("First paragraph.\n"));
+
+        onView(withId(R.id.textview)).perform(
+                mouseTripleClickOnTextAtIndex(text.indexOf("cond")));
+        onView(withId(R.id.textview)).check(hasSelection(
+                text.substring(text.indexOf("Second"), text.indexOf("Third"))));
+
+        onView(withId(R.id.textview)).perform(
+                mouseTripleClickOnTextAtIndex(text.indexOf("ird")));
+        onView(withId(R.id.textview)).check(hasSelection("Third paragraph."));
+
+        onView(withId(R.id.textview)).perform(
+                mouseTripleClickOnTextAtIndex(text.indexOf("very long")));
+        onView(withId(R.id.textview)).check(hasSelection(
+                text.substring(text.indexOf("Second"), text.indexOf("Third"))));
+    }
+
+    @SmallTest
+    public void testSelectTextByTripleClickAndDrag() throws Exception {
+        getActivity();
+
+        final StringBuilder builder = new StringBuilder();
+        builder.append("First paragraph.\n");
+        builder.append("Second paragraph.");
+        for (int i = 0; i < 10; i++) {
+            builder.append(" This paragraph is very long.");
+        }
+        builder.append('\n');
+        builder.append("Third paragraph.");
+        final String text = builder.toString();
+
+        onView(withId(R.id.textview)).perform(click());
+        onView(withId(R.id.textview)).perform(replaceText(text));
+
+        onView(withId(R.id.textview)).perform(
+                mouseTripleClickAndDragOnText(text.indexOf("irst"), text.indexOf("st")));
+        onView(withId(R.id.textview)).check(hasSelection("First paragraph.\n"));
+
+        onView(withId(R.id.textview)).perform(
+                mouseTripleClickAndDragOnText(text.indexOf("cond"), text.indexOf("Third") - 2));
+        onView(withId(R.id.textview)).check(hasSelection(
+                text.substring(text.indexOf("Second"), text.indexOf("Third"))));
+
+        onView(withId(R.id.textview)).perform(
+                mouseTripleClickAndDragOnText(text.indexOf("First"), text.indexOf("ird")));
+        onView(withId(R.id.textview)).check(hasSelection(text));
+    }
+
+    @SmallTest
+    public void testSelectTextByTripleClickAndDrag_reverse() throws Exception {
+        getActivity();
+
+        final StringBuilder builder = new StringBuilder();
+        builder.append("First paragraph.\n");
+        builder.append("Second paragraph.");
+        for (int i = 0; i < 10; i++) {
+            builder.append(" This paragraph is very long.");
+        }
+        builder.append('\n');
+        builder.append("Third paragraph.");
+        final String text = builder.toString();
+
+        onView(withId(R.id.textview)).perform(click());
+        onView(withId(R.id.textview)).perform(replaceText(text));
+
+        onView(withId(R.id.textview)).perform(
+                mouseTripleClickAndDragOnText(text.indexOf("st"), text.indexOf("irst")));
+        onView(withId(R.id.textview)).check(hasSelection("First paragraph.\n"));
+
+        onView(withId(R.id.textview)).perform(
+                mouseTripleClickAndDragOnText(text.indexOf("Third") - 2, text.indexOf("cond")));
+        onView(withId(R.id.textview)).check(hasSelection(
+                text.substring(text.indexOf("Second"), text.indexOf("Third"))));
+
+        onView(withId(R.id.textview)).perform(
+                mouseTripleClickAndDragOnText(text.indexOf("ird"), text.indexOf("First")));
+        onView(withId(R.id.textview)).check(hasSelection(text));
+    }
 }
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 4614505..e54723e 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -16,6 +16,8 @@
 
 package android.widget;
 
+import static android.widget.espresso.DragHandleUtils.assertNoSelectionHandles;
+import static android.widget.espresso.DragHandleUtils.onHandleView;
 import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
 import static android.widget.espresso.TextViewActions.doubleTapAndDragOnText;
 import static android.widget.espresso.TextViewActions.doubleClickOnTextAtIndex;
@@ -26,24 +28,22 @@
 import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex;
 import static android.widget.espresso.TextViewAssertions.hasSelection;
 import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsDisplayed;
+import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsNotDisplayed;
+import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup;
+import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarContainsItem;
+import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarDoesNotContainItem;
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.action.ViewActions.click;
 import static android.support.test.espresso.action.ViewActions.pressKey;
+import static android.support.test.espresso.action.ViewActions.replaceText;
 import static android.support.test.espresso.action.ViewActions.typeTextIntoFocusedView;
 import static android.support.test.espresso.assertion.ViewAssertions.matches;
-import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
-import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
-import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
 import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 import static android.support.test.espresso.matcher.ViewMatchers.withText;
-import static org.hamcrest.Matchers.allOf;
 
 import com.android.frameworks.coretests.R;
 
-import android.support.test.espresso.NoMatchingRootException;
-import android.support.test.espresso.NoMatchingViewException;
-import android.support.test.espresso.ViewInteraction;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.KeyEvent;
@@ -153,19 +153,115 @@
 
     @SmallTest
     public void testToolbarAppearsAfterSelection() throws Exception {
-        // It'll be nice to check that the toolbar is not visible (or does not exist) here
-        // I can't currently find a way to do this. I'll get to it later.
-
         final String text = "Toolbar appears after selection.";
         onView(withId(R.id.textview)).perform(click());
+        assertFloatingToolbarIsNotDisplayed();
         onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
         onView(withId(R.id.textview)).perform(
                 longPressOnTextAtIndex(text.indexOf("appears")));
 
-        // It takes the toolbar less than 100ms to start to animate into screen.
-        // Ideally, we'll wait using the UiController, but I guess this works for now.
-        Thread.sleep(100);
-        assertFloatingToolbarIsDisplayed(getActivity());
+        sleepForFloatingToolbarPopup();
+        assertFloatingToolbarIsDisplayed();
+
+        final String text2 = "Toolbar disappears after typing text.";
+        onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text2));
+        assertFloatingToolbarIsNotDisplayed();
+    }
+
+    @SmallTest
+    public void testToolbarAndInsertionHandle() throws Exception {
+        final String text = "text";
+        onView(withId(R.id.textview)).perform(click());
+        onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
+        assertFloatingToolbarIsNotDisplayed();
+
+        onHandleView(com.android.internal.R.id.insertion_handle).perform(click());
+        sleepForFloatingToolbarPopup();
+        assertFloatingToolbarIsDisplayed();
+
+        assertFloatingToolbarContainsItem(
+                getActivity().getString(com.android.internal.R.string.selectAll));
+        assertFloatingToolbarDoesNotContainItem(
+                getActivity().getString(com.android.internal.R.string.copy));
+        assertFloatingToolbarDoesNotContainItem(
+                getActivity().getString(com.android.internal.R.string.cut));
+    }
+
+    @SmallTest
+    public void testToolbarAndSelectionHandle() throws Exception {
+        final String text = "abcd efg hijk";
+        onView(withId(R.id.textview)).perform(click());
+        onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("f")));
+        sleepForFloatingToolbarPopup();
+        assertFloatingToolbarIsDisplayed();
+
+        assertFloatingToolbarContainsItem(
+                getActivity().getString(com.android.internal.R.string.selectAll));
+        assertFloatingToolbarContainsItem(
+                getActivity().getString(com.android.internal.R.string.copy));
+        assertFloatingToolbarContainsItem(
+                getActivity().getString(com.android.internal.R.string.cut));
+
+        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
+        onHandleView(com.android.internal.R.id.selection_start_handle)
+                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
+        sleepForFloatingToolbarPopup();
+        assertFloatingToolbarIsDisplayed();
+
+        onHandleView(com.android.internal.R.id.selection_end_handle)
+                .perform(dragHandle(textView, Handle.SELECTION_END, text.length()));
+        sleepForFloatingToolbarPopup();
+        assertFloatingToolbarIsDisplayed();
+
+        assertFloatingToolbarDoesNotContainItem(
+                getActivity().getString(com.android.internal.R.string.selectAll));
+        assertFloatingToolbarContainsItem(
+                getActivity().getString(com.android.internal.R.string.copy));
+        assertFloatingToolbarContainsItem(
+                getActivity().getString(com.android.internal.R.string.cut));
+    }
+
+    @SmallTest
+    public void testInsertionHandle() throws Exception {
+        final String text = "abcd efg hijk ";
+        onView(withId(R.id.textview)).perform(click());
+        onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
+
+        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
+
+        onHandleView(com.android.internal.R.id.insertion_handle)
+                .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('a')));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("a")));
+
+        onHandleView(com.android.internal.R.id.insertion_handle)
+                .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('f')));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f")));
+    }
+
+    @SmallTest
+    public void testInsertionHandle_multiLine() throws Exception {
+        final String text = "abcd\n" + "efg\n" + "hijk\n";
+        onView(withId(R.id.textview)).perform(click());
+        onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
+
+        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
+
+        onHandleView(com.android.internal.R.id.insertion_handle)
+                .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('a')));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("a")));
+
+        onHandleView(com.android.internal.R.id.insertion_handle)
+                .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('f')));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f")));
     }
 
     @SmallTest
@@ -183,7 +279,7 @@
         onHandleView(com.android.internal.R.id.selection_end_handle)
                 .check(matches(isDisplayed()));
 
-        final TextView textView = (TextView)getActivity().findViewById(R.id.textview);
+        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
         onHandleView(com.android.internal.R.id.selection_start_handle)
                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
         onView(withId(R.id.textview)).check(hasSelection("abcd efg"));
@@ -200,7 +296,7 @@
         onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
         onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('i')));
 
-        final TextView textView = (TextView)getActivity().findViewById(R.id.textview);
+        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
         onHandleView(com.android.internal.R.id.selection_start_handle)
                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('e')));
         onView(withId(R.id.textview)).check(hasSelection("efg\nhijk"));
@@ -219,13 +315,46 @@
     }
 
     @SmallTest
+    public void testSelectionHandles_multiLine_rtl() throws Exception {
+        // Arabic text.
+        final String text = "\u062A\u062B\u062C\n" + "\u062D\u062E\u062F\n"
+                + "\u0630\u0631\u0632\n" + "\u0633\u0634\u0635\n" + "\u0636\u0637\u0638\n"
+                + "\u0639\u063A\u063B";
+        onView(withId(R.id.textview)).perform(click());
+        onView(withId(R.id.textview)).perform(replaceText(text));
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
+        onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('\u0634')));
+
+        final TextView textView = (TextView)getActivity().findViewById(R.id.textview);
+        onHandleView(com.android.internal.R.id.selection_start_handle)
+                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u062E')));
+        onView(withId(R.id.textview)).check(hasSelection(
+                text.substring(text.indexOf('\u062D'), text.indexOf('\u0635') + 1)));
+
+        onHandleView(com.android.internal.R.id.selection_start_handle)
+                .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u062A')));
+        onView(withId(R.id.textview)).check(hasSelection(
+                text.substring(text.indexOf('\u062A'), text.indexOf('\u0635') + 1)));
+
+        onHandleView(com.android.internal.R.id.selection_end_handle)
+                .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('\u0638')));
+        onView(withId(R.id.textview)).check(hasSelection(
+                text.substring(text.indexOf('\u062A'), text.indexOf('\u0638') + 1)));
+
+        onHandleView(com.android.internal.R.id.selection_end_handle)
+                .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('\u063B')));
+        onView(withId(R.id.textview)).check(hasSelection(text));
+    }
+
+
+    @SmallTest
     public void testSelectionHandles_doesNotPassAnotherHandle() throws Exception {
         final String text = "abcd efg hijk lmn";
         onView(withId(R.id.textview)).perform(click());
         onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
         onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('f')));
 
-        final TextView textView = (TextView)getActivity().findViewById(R.id.textview);
+        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
         onHandleView(com.android.internal.R.id.selection_start_handle)
                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('l')));
         onView(withId(R.id.textview)).check(hasSelection("g"));
@@ -243,7 +372,7 @@
         onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
         onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('i')));
 
-        final TextView textView = (TextView)getActivity().findViewById(R.id.textview);
+        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
         onHandleView(com.android.internal.R.id.selection_start_handle)
                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('r') + 1));
         onView(withId(R.id.textview)).check(hasSelection("k"));
@@ -261,7 +390,7 @@
         onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
         onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('i')));
 
-        final TextView textView = (TextView)getActivity().findViewById(R.id.textview);
+        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
 
         onHandleView(com.android.internal.R.id.selection_start_handle)
                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('f')));
@@ -314,7 +443,7 @@
         onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
         onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('m')));
 
-        final TextView textView = (TextView)getActivity().findViewById(R.id.textview);
+        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
 
         onHandleView(com.android.internal.R.id.selection_start_handle)
                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('c')));
@@ -342,25 +471,4 @@
                 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('i')));
         onView(withId(R.id.textview)).check(hasSelection("hijk"));
     }
-
-    private static void assertNoSelectionHandles() {
-        try {
-            onHandleView(com.android.internal.R.id.selection_start_handle)
-                    .check(matches(isDisplayed()));
-        } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) {
-            try {
-                onHandleView(com.android.internal.R.id.selection_end_handle)
-                        .check(matches(isDisplayed()));
-            } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e1) {
-                return;
-            }
-        }
-        throw new AssertionError("Selection handle found");
-    }
-
-    private static ViewInteraction onHandleView(int id)
-            throws NoMatchingRootException, NoMatchingViewException, AssertionError {
-        return onView(allOf(withId(id), isAssignableFrom(Editor.HandleView.class)))
-                .inRoot(withDecorView(hasDescendant(withId(id))));
-    }
 }
diff --git a/core/tests/coretests/src/android/widget/espresso/DragAction.java b/core/tests/coretests/src/android/widget/espresso/DragAction.java
index ce97568..b2c8e38 100644
--- a/core/tests/coretests/src/android/widget/espresso/DragAction.java
+++ b/core/tests/coretests/src/android/widget/espresso/DragAction.java
@@ -157,6 +157,59 @@
         },
 
         /**
+         * Starts a drag with a mouse triple click.
+         */
+        MOUSE_TRIPLE_CLICK {
+            private DownMotionPerformer downMotion = new DownMotionPerformer() {
+                @Override
+                @Nullable
+                public MotionEvent perform(
+                        UiController uiController, float[] coordinates, float[] precision) {
+                    MotionEvent downEvent = MotionEvents.sendDown(
+                            uiController, coordinates, precision)
+                            .down;
+                    for (int i = 0; i < 2; ++i) {
+                        try {
+                            if (!MotionEvents.sendUp(uiController, downEvent)) {
+                                String logMessage = "Injection of up event as part of the triple "
+                                        + "click failed. Sending cancel event.";
+                                Log.d(TAG, logMessage);
+                                MotionEvents.sendCancel(uiController, downEvent);
+                                return null;
+                            }
+
+                            long doubleTapMinimumTimeout = ViewConfiguration.getDoubleTapMinTime();
+                            uiController.loopMainThreadForAtLeast(doubleTapMinimumTimeout);
+                        } finally {
+                            downEvent.recycle();
+                        }
+                        downEvent = MotionEvents.sendDown(
+                                uiController, coordinates, precision).down;
+                    }
+                    return downEvent;
+                }
+            };
+
+            @Override
+            public Status sendSwipe(
+                    UiController uiController,
+                    float[] startCoordinates, float[] endCoordinates, float[] precision) {
+                return sendLinearDrag(
+                        uiController, downMotion, startCoordinates, endCoordinates, precision);
+            }
+
+            @Override
+            public String toString() {
+                return "mouse triple click and drag to select";
+            }
+
+            @Override
+            public UiController wrapUiController(UiController uiController) {
+                return new MouseUiController(uiController);
+            }
+        },
+
+        /**
          * Starts a drag with a tap.
          */
         TAP {
diff --git a/core/tests/coretests/src/android/widget/espresso/DragHandleUtils.java b/core/tests/coretests/src/android/widget/espresso/DragHandleUtils.java
new file mode 100644
index 0000000..f744cae
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/espresso/DragHandleUtils.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 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.widget.espresso;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
+import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static org.hamcrest.Matchers.allOf;
+
+import android.support.test.espresso.NoMatchingRootException;
+import android.support.test.espresso.NoMatchingViewException;
+import android.support.test.espresso.ViewInteraction;
+import android.widget.Editor;
+
+public class DragHandleUtils {
+    private DragHandleUtils() {
+
+    }
+
+    public static void assertNoSelectionHandles() {
+        try {
+            onHandleView(com.android.internal.R.id.selection_start_handle)
+                    .check(matches(isDisplayed()));
+        } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) {
+            try {
+                onHandleView(com.android.internal.R.id.selection_end_handle)
+                        .check(matches(isDisplayed()));
+            } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e1) {
+                return;
+            }
+        }
+        throw new AssertionError("Selection handle found");
+    }
+
+    public static ViewInteraction onHandleView(int id)
+            throws NoMatchingRootException, NoMatchingViewException, AssertionError {
+        return onView(allOf(withId(id), isAssignableFrom(Editor.HandleView.class)))
+                .inRoot(withDecorView(hasDescendant(withId(id))));
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java b/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
index fc01d84..f02fe00 100644
--- a/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
+++ b/core/tests/coretests/src/android/widget/espresso/FloatingToolbarEspressoUtils.java
@@ -19,31 +19,133 @@
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.assertion.ViewAssertions.matches;
 import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
+import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
 import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
 import static android.support.test.espresso.matcher.ViewMatchers.withTagValue;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static org.hamcrest.Matchers.allOf;
 import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
 
-import android.app.Activity;
+import org.hamcrest.Matcher;
+
+import android.support.test.espresso.NoMatchingRootException;
+import android.support.test.espresso.NoMatchingViewException;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.ViewInteraction;
+import android.support.test.espresso.action.ViewActions;
+import android.support.test.espresso.matcher.ViewMatchers;
+import android.view.View;
+
 import com.android.internal.widget.FloatingToolbar;
 
 /**
  * Espresso utility methods for the floating toolbar.
  */
 public class FloatingToolbarEspressoUtils {
-
+    private final static Object TAG = FloatingToolbar.FLOATING_TOOLBAR_TAG;
 
     private FloatingToolbarEspressoUtils() {}
 
+    private static ViewInteraction onFloatingToolBar() {
+        return onView(withTagValue(is(TAG)))
+                .inRoot(withDecorView(hasDescendant(withTagValue(is(TAG)))));
+    }
+
     /**
      * Asserts that the floating toolbar is displayed on screen.
      *
      * @throws AssertionError if the assertion fails
      */
-    public static void assertFloatingToolbarIsDisplayed(Activity activity) {
-        onView(withTagValue(is((Object) FloatingToolbar.FLOATING_TOOLBAR_TAG)))
-                .inRoot(withDecorView(not(is(activity.getWindow().getDecorView()))))
-                .check(matches(isDisplayed()));
+    public static void assertFloatingToolbarIsDisplayed() {
+        onFloatingToolBar().check(matches(isDisplayed()));
     }
 
+    /**
+     * Asserts that the floating toolbar is not displayed on screen.
+     *
+     * @throws AssertionError if the assertion fails
+     */
+    public static void assertFloatingToolbarIsNotDisplayed() {
+        try {
+            onFloatingToolBar().check(matches(isDisplayed()));
+        } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) {
+            return;
+        }
+        throw new AssertionError("Floating toolbar is displayed");
+    }
+
+    private static void toggleOverflow() {
+        final int id = com.android.internal.R.id.overflow;
+        onView(allOf(withId(id), isDisplayed()))
+                .inRoot(withDecorView(hasDescendant(withId(id))))
+                .perform(ViewActions.click());
+        onView(isRoot()).perform(SLEEP);
+    }
+
+    public static void sleepForFloatingToolbarPopup() {
+        onView(isRoot()).perform(SLEEP);
+    }
+
+    /**
+     * Asserts that the floating toolbar contains the specified item.
+     *
+     * @param itemLabel label of the item.
+     * @throws AssertionError if the assertion fails
+     */
+    public static void assertFloatingToolbarContainsItem(String itemLabel) {
+        try{
+            onFloatingToolBar().check(matches(hasDescendant(ViewMatchers.withText(itemLabel))));
+        } catch (AssertionError e) {
+            try{
+                toggleOverflow();
+            } catch (NoMatchingViewException | NoMatchingRootException e2) {
+                // No overflow items.
+                throw e;
+            }
+            try{
+                onFloatingToolBar().check(matches(hasDescendant(ViewMatchers.withText(itemLabel))));
+            } finally {
+                toggleOverflow();
+            }
+        }
+    }
+
+    /**
+     * Asserts that the floating toolbar doesn't contain the specified item.
+     *
+     * @param itemLabel label of the item.
+     * @throws AssertionError if the assertion fails
+     */
+    public static void assertFloatingToolbarDoesNotContainItem(String itemLabel) {
+        try{
+            assertFloatingToolbarContainsItem(itemLabel);
+        } catch (AssertionError e) {
+            return;
+        }
+        throw new AssertionError("Floating toolbar contains " + itemLabel);
+    }
+
+    /**
+     * ViewAction to sleep to wait floating toolbar's animation.
+     */
+    private static final ViewAction SLEEP = new ViewAction() {
+        private static final long SLEEP_DURATION = 400;
+
+        @Override
+        public Matcher<View> getConstraints() {
+            return isDisplayed();
+        }
+
+        @Override
+        public String getDescription() {
+            return "Sleep " + SLEEP_DURATION + " ms.";
+        }
+
+        @Override
+        public void perform(UiController uiController, View view) {
+            uiController.loopMainThreadForAtLeast(SLEEP_DURATION);
+        }
+    };
 }
diff --git a/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java b/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java
index de640ca..e51f2785 100644
--- a/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java
+++ b/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java
@@ -22,9 +22,13 @@
 import android.support.test.espresso.ViewAction;
 import android.support.test.espresso.action.CoordinatesProvider;
 import android.support.test.espresso.action.GeneralClickAction;
+import android.support.test.espresso.action.MotionEvents;
+import android.support.test.espresso.action.MotionEvents.DownResultHolder;
 import android.support.test.espresso.action.PrecisionDescriber;
+import android.support.test.espresso.action.Press;
 import android.support.test.espresso.action.Tapper;
 import android.view.View;
+import android.view.ViewConfiguration;
 
 /**
  * ViewAction for performing an click on View by a mouse.
@@ -32,10 +36,58 @@
 public final class MouseClickAction implements ViewAction {
     private final GeneralClickAction mGeneralClickAction;
 
-    public MouseClickAction(Tapper tapper, CoordinatesProvider coordinatesProvider,
-            PrecisionDescriber precisionDescriber) {
+    public enum CLICK implements Tapper {
+        TRIPLE {
+            @Override
+            public Tapper.Status sendTap(UiController uiController, float[] coordinates,
+                    float[] precision) {
+                Tapper.Status stat = sendSingleTap(uiController, coordinates, precision);
+                boolean warning = false;
+                if (stat == Tapper.Status.FAILURE) {
+                    return Tapper.Status.FAILURE;
+                } else if (stat == Tapper.Status.WARNING) {
+                    warning = true;
+                }
+
+                long doubleTapMinimumTimeout = ViewConfiguration.getDoubleTapMinTime();
+                for (int i = 0; i < 2; i++) {
+                    if (0 < doubleTapMinimumTimeout) {
+                        uiController.loopMainThreadForAtLeast(doubleTapMinimumTimeout);
+                    }
+                    stat = sendSingleTap(uiController, coordinates, precision);
+                    if (stat == Tapper.Status.FAILURE) {
+                        return Tapper.Status.FAILURE;
+                    } else if (stat == Tapper.Status.WARNING) {
+                        warning = true;
+                    }
+                }
+
+                if (warning) {
+                    return Tapper.Status.WARNING;
+                } else {
+                    return Tapper.Status.SUCCESS;
+                }
+            }
+        };
+
+        private static Tapper.Status sendSingleTap(UiController uiController,
+                float[] coordinates, float[] precision) {
+            DownResultHolder res = MotionEvents.sendDown(uiController, coordinates, precision);
+            try {
+                if (!MotionEvents.sendUp(uiController, res.down)) {
+                    MotionEvents.sendCancel(uiController, res.down);
+                    return Tapper.Status.FAILURE;
+                }
+            } finally {
+                res.down.recycle();
+            }
+            return res.longPress ? Tapper.Status.WARNING : Tapper.Status.SUCCESS;
+        }
+    };
+
+    public MouseClickAction(Tapper tapper, CoordinatesProvider coordinatesProvider) {
         mGeneralClickAction = new GeneralClickAction(tapper, coordinatesProvider,
-                precisionDescriber);
+                Press.PINPOINT);
     }
 
     @Override
@@ -51,5 +103,13 @@
     @Override
     public void perform(UiController uiController, View view) {
         mGeneralClickAction.perform(new MouseUiController(uiController), view);
+        long doubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
+        if (0 < doubleTapTimeout) {
+            // Wait to avoid false gesture detection. Without this wait, consecutive clicks can be
+            // detected as a triple click. e.g. 2 double clicks are detected as a triple click and
+            // a single click because espresso isn't aware of triple click detection logic, which
+            // is TextView specific gesture.
+            uiController.loopMainThreadForAtLeast(doubleTapTimeout);
+        }
     }
 }
diff --git a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
index 32cc6d6..54d5823 100644
--- a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
+++ b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
@@ -64,7 +64,7 @@
      */
     public static ViewAction mouseClickOnTextAtIndex(int index) {
         return actionWithAssertions(
-                new MouseClickAction(Tap.SINGLE, new TextCoordinates(index), Press.PINPOINT));
+                new MouseClickAction(Tap.SINGLE, new TextCoordinates(index)));
     }
 
     /**
@@ -94,7 +94,7 @@
      */
     public static ViewAction mouseDoubleClickOnTextAtIndex(int index) {
         return actionWithAssertions(
-                new MouseClickAction(Tap.DOUBLE, new TextCoordinates(index), Press.PINPOINT));
+                new MouseClickAction(Tap.DOUBLE, new TextCoordinates(index)));
     }
 
     /**
@@ -124,7 +124,22 @@
      */
     public static ViewAction mouseLongClickOnTextAtIndex(int index) {
         return actionWithAssertions(
-                new MouseClickAction(Tap.LONG, new TextCoordinates(index), Press.PINPOINT));
+                new MouseClickAction(Tap.LONG, new TextCoordinates(index)));
+    }
+
+    /**
+     * Returns an action that triple-clicks by mouse on text at an index on the TextView.<br>
+     * <br>
+     * View constraints:
+     * <ul>
+     * <li>must be a TextView displayed on screen
+     * <ul>
+     *
+     * @param index The index of the TextView's text to triple-click on.
+     */
+    public static ViewAction mouseTripleClickOnTextAtIndex(int index) {
+        return actionWithAssertions(
+                new MouseClickAction(MouseClickAction.CLICK.TRIPLE, new TextCoordinates(index)));
     }
 
     /**
@@ -237,6 +252,28 @@
                         TextView.class));
     }
 
+    /**
+    * Returns an action that triple click then drags by mouse on text from startIndex to endIndex
+    * on the TextView.<br>
+    * <br>
+    * View constraints:
+    * <ul>
+    * <li>must be a TextView displayed on screen
+    * <ul>
+    *
+    * @param startIndex The index of the TextView's text to start a drag from
+    * @param endIndex The index of the TextView's text to end the drag at
+    */
+   public static ViewAction mouseTripleClickAndDragOnText(int startIndex, int endIndex) {
+       return actionWithAssertions(
+               new DragAction(
+                       DragAction.Drag.MOUSE_TRIPLE_CLICK,
+                       new TextCoordinates(startIndex),
+                       new TextCoordinates(endIndex),
+                       Press.PINPOINT,
+                       TextView.class));
+   }
+
     public enum Handle {
         SELECTION_START,
         SELECTION_END,
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index 3d35dd5..f1c89b895 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -20,6 +20,7 @@
 #include "Caches.h"
 #include "Glop.h"
 #include "GlopBuilder.h"
+#include "Patch.h"
 #include "PathTessellator.h"
 #include "renderstate/OffscreenBufferPool.h"
 #include "renderstate/RenderState.h"
@@ -56,8 +57,6 @@
     if (entry) {
         entry->uvMapper.map(texCoords);
     }
-    // init to non-empty, so we can safely expandtoCoverRect
-    Rect totalBounds = firstState.computedState.clippedBounds;
     for (size_t i = 0; i < opList.count; i++) {
         const BakedOpState& state = *(opList.states[i]);
         TextureVertex* rectVerts = &vertices[i * 4];
@@ -68,8 +67,6 @@
         }
         storeTexturedRect(rectVerts, opBounds, texCoords);
         renderer.dirtyRenderTarget(opBounds);
-
-        totalBounds.expandToCover(opBounds);
     }
 
     const int textureFillFlags = (bitmap->colorType() == kAlpha_8_SkColorType)
@@ -80,7 +77,111 @@
             .setMeshTexturedIndexedQuads(vertices, opList.count * 6)
             .setFillTexturePaint(*texture, textureFillFlags, firstState.op->paint, firstState.alpha)
             .setTransform(Matrix4::identity(), TransformFlags::None)
-            .setModelViewOffsetRect(0, 0, totalBounds) // don't snap here, we snap per-quad above
+            .setModelViewIdentityEmptyBounds()
+            .build();
+    renderer.renderGlop(nullptr, opList.clipSideFlags ? &opList.clip : nullptr, glop);
+}
+
+void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer,
+        const MergedBakedOpList& opList) {
+    const PatchOp& firstOp = *(static_cast<const PatchOp*>(opList.states[0]->op));
+    const BakedOpState& firstState = *(opList.states[0]);
+    AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(
+            firstOp.bitmap->pixelRef());
+
+    // Batches will usually contain a small number of items so it's
+    // worth performing a first iteration to count the exact number
+    // of vertices we need in the new mesh
+    uint32_t totalVertices = 0;
+
+    for (size_t i = 0; i < opList.count; i++) {
+        const PatchOp& op = *(static_cast<const PatchOp*>(opList.states[i]->op));
+
+        // TODO: cache mesh lookups
+        const Patch* opMesh = renderer.caches().patchCache.get(
+                entry, op.bitmap->width(), op.bitmap->height(),
+                op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch);
+        totalVertices += opMesh->verticesCount;
+    }
+
+    const bool dirtyRenderTarget = renderer.offscreenRenderTarget();
+
+    uint32_t indexCount = 0;
+
+    TextureVertex vertices[totalVertices];
+    TextureVertex* vertex = &vertices[0];
+    // Create a mesh that contains the transformed vertices for all the
+    // 9-patch objects that are part of the batch. Note that onDefer()
+    // enforces ops drawn by this function to have a pure translate or
+    // identity matrix
+    for (size_t i = 0; i < opList.count; i++) {
+        const PatchOp& op = *(static_cast<const PatchOp*>(opList.states[i]->op));
+        const BakedOpState& state = *opList.states[i];
+
+        // TODO: cache mesh lookups
+        const Patch* opMesh = renderer.caches().patchCache.get(
+                entry, op.bitmap->width(), op.bitmap->height(),
+                op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch);
+
+
+        uint32_t vertexCount = opMesh->verticesCount;
+        if (vertexCount == 0) continue;
+
+        // We use the bounds to know where to translate our vertices
+        // Using patchOp->state.mBounds wouldn't work because these
+        // bounds are clipped
+        const float tx = floorf(state.computedState.transform.getTranslateX()
+                + op.unmappedBounds.left + 0.5f);
+        const float ty = floorf(state.computedState.transform.getTranslateY()
+                + op.unmappedBounds.top + 0.5f);
+
+        // Copy & transform all the vertices for the current operation
+        TextureVertex* opVertices = opMesh->vertices.get();
+        for (uint32_t j = 0; j < vertexCount; j++, opVertices++) {
+            TextureVertex::set(vertex++,
+                    opVertices->x + tx, opVertices->y + ty,
+                    opVertices->u, opVertices->v);
+        }
+
+        // Dirty the current layer if possible. When the 9-patch does not
+        // contain empty quads we can take a shortcut and simply set the
+        // dirty rect to the object's bounds.
+        if (dirtyRenderTarget) {
+            if (!opMesh->hasEmptyQuads) {
+                renderer.dirtyRenderTarget(Rect(tx, ty,
+                        tx + op.unmappedBounds.getWidth(), ty + op.unmappedBounds.getHeight()));
+            } else {
+                const size_t count = opMesh->quads.size();
+                for (size_t i = 0; i < count; i++) {
+                    const Rect& quadBounds = opMesh->quads[i];
+                    const float x = tx + quadBounds.left;
+                    const float y = ty + quadBounds.top;
+                    renderer.dirtyRenderTarget(Rect(x, y,
+                            x + quadBounds.getWidth(), y + quadBounds.getHeight()));
+                }
+            }
+        }
+
+        indexCount += opMesh->indexCount;
+    }
+
+
+    Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(firstOp.bitmap);
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
+
+    // 9 patches are built for stretching - always filter
+    int textureFillFlags = TextureFillFlags::ForceFilter;
+    if (firstOp.bitmap->colorType() == kAlpha_8_SkColorType) {
+        textureFillFlags |= TextureFillFlags::IsAlphaMaskTexture;
+    }
+    Glop glop;
+    GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+            .setRoundRectClipState(firstState.roundRectClipState)
+            .setMeshTexturedIndexedQuads(vertices, indexCount)
+            .setFillTexturePaint(*texture, textureFillFlags, firstOp.paint, firstState.alpha)
+            .setTransform(Matrix4::identity(), TransformFlags::None)
+            .setModelViewIdentityEmptyBounds()
             .build();
     renderer.renderGlop(nullptr, opList.clipSideFlags ? &opList.clip : nullptr, glop);
 }
@@ -310,6 +411,105 @@
     renderer.renderGlop(state, glop);
 }
 
+void BakedOpDispatcher::onBitmapMeshOp(BakedOpRenderer& renderer, const BitmapMeshOp& op, const BakedOpState& state) {
+    const static UvMapper defaultUvMapper;
+    const uint32_t elementCount = op.meshWidth * op.meshHeight * 6;
+
+    std::unique_ptr<ColorTextureVertex[]> mesh(new ColorTextureVertex[elementCount]);
+    ColorTextureVertex* vertex = &mesh[0];
+
+    const int* colors = op.colors;
+    std::unique_ptr<int[]> tempColors;
+    if (!colors) {
+        uint32_t colorsCount = (op.meshWidth + 1) * (op.meshHeight + 1);
+        tempColors.reset(new int[colorsCount]);
+        memset(tempColors.get(), 0xff, colorsCount * sizeof(int));
+        colors = tempColors.get();
+    }
+
+    Texture* texture = renderer.renderState().assetAtlas().getEntryTexture(op.bitmap->pixelRef());
+    const UvMapper& mapper(texture && texture->uvMapper ? *texture->uvMapper : defaultUvMapper);
+
+    for (int32_t y = 0; y < op.meshHeight; y++) {
+        for (int32_t x = 0; x < op.meshWidth; x++) {
+            uint32_t i = (y * (op.meshWidth + 1) + x) * 2;
+
+            float u1 = float(x) / op.meshWidth;
+            float u2 = float(x + 1) / op.meshWidth;
+            float v1 = float(y) / op.meshHeight;
+            float v2 = float(y + 1) / op.meshHeight;
+
+            mapper.map(u1, v1, u2, v2);
+
+            int ax = i + (op.meshWidth + 1) * 2;
+            int ay = ax + 1;
+            int bx = i;
+            int by = bx + 1;
+            int cx = i + 2;
+            int cy = cx + 1;
+            int dx = i + (op.meshWidth + 1) * 2 + 2;
+            int dy = dx + 1;
+
+            const float* vertices = op.vertices;
+            ColorTextureVertex::set(vertex++, vertices[dx], vertices[dy], u2, v2, colors[dx / 2]);
+            ColorTextureVertex::set(vertex++, vertices[ax], vertices[ay], u1, v2, colors[ax / 2]);
+            ColorTextureVertex::set(vertex++, vertices[bx], vertices[by], u1, v1, colors[bx / 2]);
+
+            ColorTextureVertex::set(vertex++, vertices[dx], vertices[dy], u2, v2, colors[dx / 2]);
+            ColorTextureVertex::set(vertex++, vertices[bx], vertices[by], u1, v1, colors[bx / 2]);
+            ColorTextureVertex::set(vertex++, vertices[cx], vertices[cy], u2, v1, colors[cx / 2]);
+        }
+    }
+
+    if (!texture) {
+        texture = renderer.caches().textureCache.get(op.bitmap);
+        if (!texture) {
+            return;
+        }
+    }
+    const AutoTexture autoCleanup(texture);
+
+    /*
+     * TODO: handle alpha_8 textures correctly by applying paint color, but *not*
+     * shader in that case to mimic the behavior in SkiaCanvas::drawBitmapMesh.
+     */
+    const int textureFillFlags = TextureFillFlags::None;
+    Glop glop;
+    GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+            .setRoundRectClipState(state.roundRectClipState)
+            .setMeshColoredTexturedMesh(mesh.get(), elementCount)
+            .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
+            .setTransform(state.computedState.transform,  TransformFlags::None)
+            .setModelViewOffsetRect(0, 0, op.unmappedBounds)
+            .build();
+    renderer.renderGlop(state, glop);
+}
+
+void BakedOpDispatcher::onBitmapRectOp(BakedOpRenderer& renderer, const BitmapRectOp& op, const BakedOpState& state) {
+    Texture* texture = renderer.getTexture(op.bitmap);
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
+
+    Rect uv(std::max(0.0f, op.src.left / texture->width),
+            std::max(0.0f, op.src.top / texture->height),
+            std::min(1.0f, op.src.right / texture->width),
+            std::min(1.0f, op.src.bottom / texture->height));
+
+    const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
+            ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
+    const bool tryToSnap = MathUtils::areEqual(op.src.getWidth(), op.unmappedBounds.getWidth())
+            && MathUtils::areEqual(op.src.getHeight(), op.unmappedBounds.getHeight());
+    Glop glop;
+    GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+            .setRoundRectClipState(state.roundRectClipState)
+            .setMeshTexturedUvQuad(texture->uvMapper, uv)
+            .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
+            .setTransform(state.computedState.transform, TransformFlags::None)
+            .setModelViewMapUnitToRectOptionalSnap(tryToSnap, op.unmappedBounds)
+            .build();
+    renderer.renderGlop(state, glop);
+}
+
 void BakedOpDispatcher::onLinesOp(BakedOpRenderer& renderer, const LinesOp& op, const BakedOpState& state) {
     VertexBuffer buffer;
     PathTessellator::tessellateLines(op.points, op.floatCount, op.paint,
@@ -334,6 +534,35 @@
     }
 }
 
+void BakedOpDispatcher::onPatchOp(BakedOpRenderer& renderer, const PatchOp& op, const BakedOpState& state) {
+    // 9 patches are built for stretching - always filter
+    int textureFillFlags = TextureFillFlags::ForceFilter;
+    if (op.bitmap->colorType() == kAlpha_8_SkColorType) {
+        textureFillFlags |= TextureFillFlags::IsAlphaMaskTexture;
+    }
+
+    // TODO: avoid redoing the below work each frame:
+    AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(op.bitmap->pixelRef());
+    const Patch* mesh = renderer.caches().patchCache.get(
+            entry, op.bitmap->width(), op.bitmap->height(),
+            op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch);
+
+    Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(op.bitmap);
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
+    Glop glop;
+    GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+            .setRoundRectClipState(state.roundRectClipState)
+            .setMeshPatchQuads(*mesh)
+            .setMeshTexturedUnitQuad(texture->uvMapper)
+            .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
+            .setTransform(state.computedState.transform, TransformFlags::None)
+            .setModelViewOffsetRectSnap(op.unmappedBounds.left, op.unmappedBounds.top,
+                    Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight()))
+            .build();
+    renderer.renderGlop(state, glop);
+}
+
 void BakedOpDispatcher::onPathOp(BakedOpRenderer& renderer, const PathOp& op, const BakedOpState& state) {
     PathTexture* texture = renderer.caches().pathCache.get(op.path, op.paint);
     const AutoTexture holder(texture);
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 9c8649f..8acdb62 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -73,7 +73,7 @@
             .setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount())
             .setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, bakedState->alpha)
             .setTransform(bakedState->computedState.transform, transformFlags)
-            .setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0))
+            .setModelViewIdentityEmptyBounds()
             .build();
     // Note: don't pass dirty bounds here, so user must manage passing dirty bounds to renderer
     renderer->renderGlop(nullptr, clip, glop);
diff --git a/libs/hwui/Glop.h b/libs/hwui/Glop.h
index 4785ea4..bcf819e 100644
--- a/libs/hwui/Glop.h
+++ b/libs/hwui/Glop.h
@@ -64,7 +64,7 @@
 
         // Canvas transform isn't applied to the mesh at draw time,
         //since it's already built in.
-        MeshIgnoresCanvasTransform = 1 << 1,
+        MeshIgnoresCanvasTransform = 1 << 1, // TODO: remove
     };
 };
 
diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h
index b647b90..6e5797d 100644
--- a/libs/hwui/GlopBuilder.h
+++ b/libs/hwui/GlopBuilder.h
@@ -95,6 +95,10 @@
             return setModelViewOffsetRect(offsetX, offsetY, source);
         }
     }
+    GlopBuilder& setModelViewIdentityEmptyBounds() {
+        // pass empty rect since not needed for damage / snap
+        return setModelViewOffsetRect(0, 0, Rect());
+    }
 
     GlopBuilder& setRoundRectClipState(const RoundRectClipState* roundRectClipState);
 
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index f948f18..b936e6d5 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -757,6 +757,18 @@
     }
 }
 
+void OpReorderer::onBitmapMeshOp(const BitmapMeshOp& op) {
+    BakedOpState* bakedState = tryBakeOpState(op);
+    if (!bakedState) return; // quick rejected
+    currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
+}
+
+void OpReorderer::onBitmapRectOp(const BitmapRectOp& op) {
+    BakedOpState* bakedState = tryBakeOpState(op);
+    if (!bakedState) return; // quick rejected
+    currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
+}
+
 void OpReorderer::onLinesOp(const LinesOp& op) {
     batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices;
     onStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced);
@@ -766,6 +778,23 @@
     onStrokeableOp(op, tessBatchId(op));
 }
 
+void OpReorderer::onPatchOp(const PatchOp& op) {
+    BakedOpState* bakedState = tryBakeOpState(op);
+    if (!bakedState) return; // quick rejected
+
+    if (bakedState->computedState.transform.isPureTranslate()
+            && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode) {
+        mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID();
+        // TODO: AssetAtlas in mergeId
+
+        // Only use the MergedPatch batchId when merged, so Bitmap+Patch don't try to merge together
+        currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::MergedPatch, mergeId);
+    } else {
+        // Use Bitmap batchId since Bitmap+Patch use same shader
+        currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
+    }
+}
+
 void OpReorderer::onPathOp(const PathOp& op) {
     onStrokeableOp(op, OpBatchType::Bitmap);
 }
diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h
index 58b607c..0b88f04 100644
--- a/libs/hwui/OpReorderer.h
+++ b/libs/hwui/OpReorderer.h
@@ -45,7 +45,7 @@
     enum {
         None = 0, // Don't batch
         Bitmap,
-        Patch,
+        MergedPatch,
         AlphaVertices,
         Vertices,
         AlphaMaskTexture,
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index 8ce5473..75ecdae 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -24,7 +24,8 @@
 #include "utils/LinearAllocator.h"
 #include "Vector.h"
 
-#include "SkXfermode.h"
+#include <androidfw/ResourceTypes.h>
+#include <SkXfermode.h>
 
 class SkBitmap;
 class SkPaint;
@@ -45,8 +46,11 @@
 #define MAP_OPS_BASED_ON_MERGEABILITY(U_OP_FN, M_OP_FN) \
         U_OP_FN(ArcOp) \
         M_OP_FN(BitmapOp) \
+        U_OP_FN(BitmapMeshOp) \
+        U_OP_FN(BitmapRectOp) \
         U_OP_FN(LinesOp) \
         U_OP_FN(OvalOp) \
+        M_OP_FN(PatchOp) \
         U_OP_FN(PathOp) \
         U_OP_FN(PointsOp) \
         U_OP_FN(RectOp) \
@@ -152,6 +156,31 @@
     // TODO: asset atlas/texture id lookup?
 };
 
+struct BitmapMeshOp : RecordedOp {
+    BitmapMeshOp(BASE_PARAMS, const SkBitmap* bitmap, int meshWidth, int meshHeight,
+            const float* vertices, const int* colors)
+            : SUPER(BitmapMeshOp)
+            , bitmap(bitmap)
+            , meshWidth(meshWidth)
+            , meshHeight(meshHeight)
+            , vertices(vertices)
+            , colors(colors) {}
+    const SkBitmap* bitmap;
+    const int meshWidth;
+    const int meshHeight;
+    const float* vertices;
+    const int* colors;
+};
+
+struct BitmapRectOp : RecordedOp {
+    BitmapRectOp(BASE_PARAMS, const SkBitmap* bitmap, const Rect& src)
+            : SUPER(BitmapRectOp)
+            , bitmap(bitmap)
+            , src(src) {}
+    const SkBitmap* bitmap;
+    const Rect src;
+};
+
 struct LinesOp : RecordedOp {
     LinesOp(BASE_PARAMS, const float* points, const int floatCount)
             : SUPER(LinesOp)
@@ -166,6 +195,16 @@
             : SUPER(OvalOp) {}
 };
 
+
+struct PatchOp : RecordedOp {
+    PatchOp(BASE_PARAMS, const SkBitmap* bitmap, const Res_png_9patch* patch)
+            : SUPER(PatchOp)
+            , bitmap(bitmap)
+            , patch(patch) {}
+    const SkBitmap* bitmap;
+    const Res_png_9patch* patch;
+};
+
 struct PathOp : RecordedOp {
     PathOp(BASE_PARAMS, const SkPath* path)
             : SUPER(PathOp)
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 148c940..57f0d34 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -418,19 +418,35 @@
         drawBitmap(&bitmap, paint);
         restore();
     } else {
-        LOG_ALWAYS_FATAL("TODO!");
+        addOp(new (alloc()) BitmapRectOp(
+                Rect(dstLeft, dstTop, dstRight, dstBottom),
+                *(mState.currentSnapshot()->transform),
+                mState.getRenderTargetClipBounds(),
+                refPaint(paint), refBitmap(bitmap),
+                Rect(srcLeft, srcTop, srcRight, srcBottom)));
     }
 }
 
 void RecordingCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight,
             const float* vertices, const int* colors, const SkPaint* paint) {
-    LOG_ALWAYS_FATAL("TODO!");
+    int vertexCount = (meshWidth + 1) * (meshHeight + 1);
+    addOp(new (alloc()) BitmapMeshOp(
+            calcBoundsOfPoints(vertices, vertexCount * 2),
+            *(mState.currentSnapshot()->transform),
+            mState.getRenderTargetClipBounds(),
+            refPaint(paint), refBitmap(bitmap), meshWidth, meshHeight,
+            refBuffer<float>(vertices, vertexCount * 2), // 2 floats per vertex
+            refBuffer<int>(colors, vertexCount))); // 1 color per vertex
 }
 
-void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
+void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& patch,
             float dstLeft, float dstTop, float dstRight, float dstBottom,
             const SkPaint* paint) {
-    LOG_ALWAYS_FATAL("TODO!");
+    addOp(new (alloc()) PatchOp(
+            Rect(dstLeft, dstTop, dstRight, dstBottom),
+            *(mState.currentSnapshot()->transform),
+            mState.getRenderTargetClipBounds(),
+            refPaint(paint), refBitmap(bitmap), refPatch(&patch)));
 }
 
 // Text
@@ -452,7 +468,6 @@
 
 void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
             float hOffset, float vOffset, const SkPaint& paint) {
-    // NOTE: can't use refPaint() directly, since it forces left alignment
     LOG_ALWAYS_FATAL("TODO!");
 }
 
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index a046512..bcc2b406 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.DrawableRes;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.app.ActivityThread;
 import android.content.BroadcastReceiver;
@@ -41,6 +42,8 @@
 import android.util.Log;
 import android.view.Display;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -201,6 +204,7 @@
                         info.mDescription = sStatic.mResources.getText(
                                 com.android.internal.R.string.bluetooth_a2dp_audio_route_name);
                         info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
+                        info.mDeviceType = RouteInfo.DEVICE_TYPE_BLUETOOTH;
                         sStatic.mBluetoothA2dpRoute = info;
                         addRouteStatic(sStatic.mBluetoothA2dpRoute);
                     } else {
@@ -480,6 +484,7 @@
             route.mName = globalRoute.name;
             route.mDescription = globalRoute.description;
             route.mSupportedTypes = globalRoute.supportedTypes;
+            route.mDeviceType = globalRoute.deviceType;
             route.mEnabled = globalRoute.enabled;
             route.setRealStatusCode(globalRoute.statusCode);
             route.mPlaybackType = globalRoute.playbackType;
@@ -1411,6 +1416,7 @@
         newRoute.mDescription = sStatic.mResources.getText(
                 com.android.internal.R.string.wireless_display_route_description);
         newRoute.updatePresentationDisplay();
+        newRoute.mDeviceType = RouteInfo.DEVICE_TYPE_TV;
         return newRoute;
     }
 
@@ -1470,6 +1476,7 @@
         CharSequence mDescription;
         private CharSequence mStatus;
         int mSupportedTypes;
+        int mDeviceType;
         RouteGroup mGroup;
         final RouteCategory mCategory;
         Drawable mIcon;
@@ -1502,6 +1509,42 @@
         /** @hide */ public static final int STATUS_IN_USE = 5;
         /** @hide */ public static final int STATUS_CONNECTED = 6;
 
+        /** @hide */
+        @IntDef({DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_TV, DEVICE_TYPE_SPEAKER, DEVICE_TYPE_BLUETOOTH})
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface DeviceType {}
+
+        /**
+         * The default receiver device type of the route indicating the type is unknown.
+         *
+         * @see #getDeviceType
+         */
+        public static final int DEVICE_TYPE_UNKNOWN = 0;
+
+        /**
+         * A receiver device type of the route indicating the presentation of the media is happening
+         * on a TV.
+         *
+         * @see #getDeviceType
+         */
+        public static final int DEVICE_TYPE_TV = 1;
+
+        /**
+         * A receiver device type of the route indicating the presentation of the media is happening
+         * on a speaker.
+         *
+         * @see #getDeviceType
+         */
+        public static final int DEVICE_TYPE_SPEAKER = 2;
+
+        /**
+         * A receiver device type of the route indicating the presentation of the media is happening
+         * on a bluetooth device such as a bluetooth speaker.
+         *
+         * @see #getDeviceType
+         */
+        public static final int DEVICE_TYPE_BLUETOOTH = 3;
+
         private Object mTag;
 
         /**
@@ -1533,6 +1576,7 @@
 
         RouteInfo(RouteCategory category) {
             mCategory = category;
+            mDeviceType = DEVICE_TYPE_UNKNOWN;
         }
 
         /**
@@ -1670,6 +1714,18 @@
             return mSupportedTypes;
         }
 
+        /**
+         * Gets the type of the receiver device associated with this route.
+         *
+         * @return The type of the receiver device associated with this route:
+         * {@link #DEVICE_TYPE_BLUETOOTH}, {@link #DEVICE_TYPE_TV}, {@link #DEVICE_TYPE_SPEAKER},
+         * or {@link #DEVICE_TYPE_UNKNOWN}.
+         */
+        @DeviceType
+        public int getDeviceType() {
+            return mDeviceType;
+        }
+
         /** @hide */
         public boolean matchesTypes(int types) {
             return (mSupportedTypes & types) != 0;
diff --git a/media/java/android/media/MediaRouterClientState.java b/media/java/android/media/MediaRouterClientState.java
index 54b8276..34e18f6 100644
--- a/media/java/android/media/MediaRouterClientState.java
+++ b/media/java/android/media/MediaRouterClientState.java
@@ -104,6 +104,7 @@
         public int volumeMax;
         public int volumeHandling;
         public int presentationDisplayId;
+        public @MediaRouter.RouteInfo.DeviceType int deviceType;
 
         public RouteInfo(String id) {
             this.id = id;
@@ -113,6 +114,7 @@
             playbackStream = -1;
             volumeHandling = MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
             presentationDisplayId = -1;
+            deviceType = MediaRouter.RouteInfo.DEVICE_TYPE_UNKNOWN;
         }
 
         public RouteInfo(RouteInfo other) {
@@ -128,6 +130,7 @@
             volumeMax = other.volumeMax;
             volumeHandling = other.volumeHandling;
             presentationDisplayId = other.presentationDisplayId;
+            deviceType = other.deviceType;
         }
 
         RouteInfo(Parcel in) {
@@ -143,6 +146,7 @@
             volumeMax = in.readInt();
             volumeHandling = in.readInt();
             presentationDisplayId = in.readInt();
+            deviceType = in.readInt();
         }
 
         @Override
@@ -164,6 +168,7 @@
             dest.writeInt(volumeMax);
             dest.writeInt(volumeHandling);
             dest.writeInt(presentationDisplayId);
+            dest.writeInt(deviceType);
         }
 
         @Override
@@ -180,6 +185,7 @@
                     + ", volumeMax=" + volumeMax
                     + ", volumeHandling=" + volumeHandling
                     + ", presentationDisplayId=" + presentationDisplayId
+                    + ", deviceType=" + deviceType
                     + " }";
         }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 2d77c6a..770d011 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -316,12 +316,8 @@
         mAdapter = new DocumentsAdapter(context);
         mRecView.setAdapter(mAdapter);
 
-        mDefaultItemColor = context.getResources().getColor(android.R.color.transparent);
-        // Get the accent color.
-        TypedValue selColor = new TypedValue();
-        context.getTheme().resolveAttribute(android.R.attr.colorAccent, selColor, true);
-        // Set the opacity to 10%.
-        mSelectedItemColor = (selColor.data & 0x00ffffff) | 0x16000000;
+        mDefaultItemColor = context.getResources().getColor(R.color.item_doc_background);
+        mSelectedItemColor = context.getResources().getColor(R.color.item_doc_background_selected);
 
         GestureDetector.SimpleOnGestureListener listener =
                 new GestureDetector.SimpleOnGestureListener() {
@@ -943,6 +939,7 @@
 
         public void setSelected(boolean selected) {
             itemView.setActivated(selected);
+            itemView.setBackgroundColor(selected ? mSelectedItemColor : mDefaultItemColor);
         }
 
         @Override
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/NetworkPolicySerializer.java b/packages/SettingsProvider/src/com/android/providers/settings/NetworkPolicySerializer.java
new file mode 100644
index 0000000..4b87da4
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/NetworkPolicySerializer.java
@@ -0,0 +1,186 @@
+package com.android.providers.settings;
+
+import android.net.NetworkPolicy;
+import android.net.NetworkTemplate;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * Backup/Restore Serializer Class for android.net.NetworkPolicy
+ */
+public class NetworkPolicySerializer {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "NetworkPolicySerializer";
+
+    private static final int NULL = 0;
+    private static final int NOT_NULL = 1;
+    /**
+     * Current Version of the Serializer.
+     */
+    private static int STATE_VERSION = 1;
+
+    /**
+     * Marshals an array of NetworkPolicy objects into a byte-array.
+     *
+     * @param policies - NetworkPolicies to be Marshaled
+     * @return byte array
+     */
+
+    public static byte[] marshalNetworkPolicies(NetworkPolicy policies[]) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        if (policies != null && policies.length != 0) {
+            DataOutputStream out = new DataOutputStream(baos);
+            try {
+                out.writeInt(STATE_VERSION);
+                out.writeInt(policies.length);
+                for (NetworkPolicy policy : policies) {
+                    byte[] marshaledPolicy = marshalNetworkPolicy(policy);
+                    if (marshaledPolicy != null) {
+                        out.writeByte(NOT_NULL);
+                        out.writeInt(marshaledPolicy.length);
+                        out.write(marshaledPolicy);
+                    } else {
+                        out.writeByte(NULL);
+                    }
+                }
+            } catch (IOException ioe) {
+                Log.e(TAG, "Failed to Convert NetworkPolicies to byte array", ioe);
+                baos.reset();
+            }
+        }
+        return baos.toByteArray();
+    }
+
+    /**
+     * Unmarshals a byte array into an array of NetworkPolicy Objects
+     *
+     * @param data - marshaled NetworkPolicies Array
+     * @return NetworkPolicy[] array
+     */
+    public static NetworkPolicy[] unmarshalNetworkPolicies(byte[] data) {
+        if (data == null || data.length == 0) {
+            return new NetworkPolicy[0];
+        }
+        DataInputStream in = new DataInputStream(new ByteArrayInputStream(data));
+        try {
+            int version = in.readInt();
+            int length = in.readInt();
+            NetworkPolicy[] policies = new NetworkPolicy[length];
+            for (int i = 0; i < length; i++) {
+                byte isNull = in.readByte();
+                if (isNull == NULL) continue;
+                int byteLength = in.readInt();
+                byte[] policyData = new byte[byteLength];
+                in.read(policyData, 0, byteLength);
+                policies[i] = unmarshalNetworkPolicy(policyData);
+            }
+            return policies;
+        } catch (IOException ioe) {
+            Log.e(TAG, "Failed to Convert byte array to NetworkPolicies", ioe);
+            return new NetworkPolicy[0];
+        }
+    }
+
+    /**
+     * Marshals a NetworkPolicy object into a byte-array.
+     *
+     * @param networkPolicy - NetworkPolicy to be Marshaled
+     * @return byte array
+     */
+    public static byte[] marshalNetworkPolicy(NetworkPolicy networkPolicy) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        if (networkPolicy != null) {
+            DataOutputStream out = new DataOutputStream(baos);
+            try {
+                out.writeInt(STATE_VERSION);
+                writeNetworkTemplate(out, networkPolicy.template);
+                out.writeInt(networkPolicy.cycleDay);
+                writeString(out, networkPolicy.cycleTimezone);
+                out.writeLong(networkPolicy.warningBytes);
+                out.writeLong(networkPolicy.limitBytes);
+                out.writeLong(networkPolicy.lastWarningSnooze);
+                out.writeLong(networkPolicy.lastLimitSnooze);
+                out.writeInt(networkPolicy.metered ? 1 : 0);
+                out.writeInt(networkPolicy.inferred ? 1 : 0);
+            } catch (IOException ioe) {
+                Log.e(TAG, "Failed to Convert NetworkPolicy to byte array", ioe);
+                baos.reset();
+            }
+        }
+        return baos.toByteArray();
+    }
+
+    /**
+     * Unmarshals a byte array into a NetworkPolicy Object
+     *
+     * @param data - marshaled NetworkPolicy Object
+     * @return NetworkPolicy Object
+     */
+    public static NetworkPolicy unmarshalNetworkPolicy(byte[] data) {
+        if (data == null || data.length == 0) {
+            return null;
+        }
+        DataInputStream in = new DataInputStream(new ByteArrayInputStream(data));
+        try {
+            int version = in.readInt();
+            NetworkTemplate template = readNetworkTemplate(in, version);
+            int cycleDay = in.readInt();
+            String cycleTimeZone = readString(in, version);
+            long warningBytes = in.readLong();
+            long limitBytes = in.readLong();
+            long lastWarningSnooze = in.readLong();
+            long lastLimitSnooze = in.readLong();
+            boolean metered = in.readInt() == 1;
+            boolean inferred = in.readInt() == 1;
+            return new NetworkPolicy(template, cycleDay, cycleTimeZone, warningBytes, limitBytes,
+                    lastWarningSnooze, lastLimitSnooze, metered, inferred);
+        } catch (IOException ioe) {
+            Log.e(TAG, "Failed to Convert byte array to NetworkPolicy", ioe);
+            return null;
+        }
+    }
+
+    private static NetworkTemplate readNetworkTemplate(DataInputStream in, int version)
+            throws IOException {
+        byte isNull = in.readByte();
+        if (isNull == NULL) return null;
+        int matchRule = in.readInt();
+        String subscriberId = readString(in, version);
+        String networkId = readString(in, version);
+        return new NetworkTemplate(matchRule, subscriberId, networkId);
+    }
+
+    private static void writeNetworkTemplate(DataOutputStream out, NetworkTemplate template)
+            throws IOException {
+        if (template != null) {
+            out.writeByte(NOT_NULL);
+            out.writeInt(template.getMatchRule());
+            writeString(out, template.getSubscriberId());
+            writeString(out, template.getNetworkId());
+        } else {
+            out.writeByte(NULL);
+        }
+    }
+
+    private static String readString(DataInputStream in, int version) throws IOException {
+        byte isNull = in.readByte();
+        if (isNull == NOT_NULL) {
+            return in.readUTF();
+        }
+        return null;
+    }
+
+    private static void writeString(DataOutputStream out, String val) throws IOException {
+        if (val != null) {
+            out.writeByte(NOT_NULL);
+            out.writeUTF(val);
+        } else {
+            out.writeByte(NULL);
+        }
+    }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 2e96f18..185a03f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -24,6 +24,7 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
+import android.net.NetworkPolicyManager;
 import android.net.Uri;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
@@ -81,10 +82,13 @@
     private static final String KEY_GLOBAL = "global";
     private static final String KEY_LOCALE = "locale";
     private static final String KEY_LOCK_SETTINGS = "lock_settings";
+    private static final String KEY_SOFTAP_CONFIG = "softap_config";
+    private static final String KEY_NET_POLICIES = "network_policies";
+
 
     // Versioning of the state file.  Increment this version
     // number any time the set of state items is altered.
-    private static final int STATE_VERSION = 4;
+    private static final int STATE_VERSION = 6;
 
     // Slots in the checksum array.  Never insert new items in the middle
     // of this array; new slots must be appended.
@@ -95,22 +99,28 @@
     private static final int STATE_WIFI_CONFIG     = 4;
     private static final int STATE_GLOBAL          = 5;
     private static final int STATE_LOCK_SETTINGS   = 6;
+    private static final int STATE_SOFTAP_CONFIG   = 7;
+    private static final int STATE_NET_POLICIES    = 8;
 
-    private static final int STATE_SIZE            = 7; // The current number of state items
+    private static final int STATE_SIZE            = 9; // The current number of state items
 
     // Number of entries in the checksum array at various version numbers
     private static final int STATE_SIZES[] = {
-        0,
-        4,              // version 1
-        5,              // version 2 added STATE_WIFI_CONFIG
-        6,              // version 3 added STATE_GLOBAL
-        STATE_SIZE      // version 4 added STATE_LOCK_SETTINGS
+            0,
+            4,              // version 1
+            5,              // version 2 added STATE_WIFI_CONFIG
+            6,              // version 3 added STATE_GLOBAL
+            7,              // version 4 added STATE_LOCK_SETTINGS
+            8,              // version 5 added STATE_SOFTAP_CONFIG
+            STATE_SIZE      // version 6 added STATE_NET_POLICIES
     };
 
     // Versioning of the 'full backup' format
     private static final int FULL_BACKUP_VERSION = 3;
     private static final int FULL_BACKUP_ADDED_GLOBAL = 2;  // added the "global" entry
     private static final int FULL_BACKUP_ADDED_LOCK_SETTINGS = 3; // added the "lock_settings" entry
+    private static final int FULL_BACKUP_ADDED_SOFTAP_CONF = 4; //added the "softap_config" entry
+    private static final int FULL_BACKUP_ADDED_NET_POLICIES = 5; //added the "network_policies" entry
 
     private static final int INTEGER_BYTE_COUNT = Integer.SIZE / Byte.SIZE;
 
@@ -119,8 +129,8 @@
     private static final String TAG = "SettingsBackupAgent";
 
     private static final String[] PROJECTION = {
-        Settings.NameValueTable.NAME,
-        Settings.NameValueTable.VALUE
+            Settings.NameValueTable.NAME,
+            Settings.NameValueTable.VALUE
     };
 
     private static final String FILE_WIFI_SUPPLICANT = "/data/misc/wifi/wpa_supplicant.conf";
@@ -396,7 +406,7 @@
 
     @Override
     public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
-            ParcelFileDescriptor newState) throws IOException {
+                         ParcelFileDescriptor newState) throws IOException {
 
         byte[] systemSettingsData = getSystemSettings();
         byte[] secureSettingsData = getSecureSettings();
@@ -405,26 +415,34 @@
         byte[] locale = mSettingsHelper.getLocaleData();
         byte[] wifiSupplicantData = getWifiSupplicant(FILE_WIFI_SUPPLICANT);
         byte[] wifiConfigData = getFileData(mWifiConfigFile);
+        byte[] softApConfigData = getSoftAPConfiguration();
+        byte[] netPoliciesData = getNetworkPolicies();
 
         long[] stateChecksums = readOldChecksums(oldState);
 
         stateChecksums[STATE_SYSTEM] =
-            writeIfChanged(stateChecksums[STATE_SYSTEM], KEY_SYSTEM, systemSettingsData, data);
+                writeIfChanged(stateChecksums[STATE_SYSTEM], KEY_SYSTEM, systemSettingsData, data);
         stateChecksums[STATE_SECURE] =
-            writeIfChanged(stateChecksums[STATE_SECURE], KEY_SECURE, secureSettingsData, data);
+                writeIfChanged(stateChecksums[STATE_SECURE], KEY_SECURE, secureSettingsData, data);
         stateChecksums[STATE_GLOBAL] =
-            writeIfChanged(stateChecksums[STATE_GLOBAL], KEY_GLOBAL, globalSettingsData, data);
+                writeIfChanged(stateChecksums[STATE_GLOBAL], KEY_GLOBAL, globalSettingsData, data);
         stateChecksums[STATE_LOCALE] =
-            writeIfChanged(stateChecksums[STATE_LOCALE], KEY_LOCALE, locale, data);
+                writeIfChanged(stateChecksums[STATE_LOCALE], KEY_LOCALE, locale, data);
         stateChecksums[STATE_WIFI_SUPPLICANT] =
-            writeIfChanged(stateChecksums[STATE_WIFI_SUPPLICANT], KEY_WIFI_SUPPLICANT,
-                    wifiSupplicantData, data);
+                writeIfChanged(stateChecksums[STATE_WIFI_SUPPLICANT], KEY_WIFI_SUPPLICANT,
+                        wifiSupplicantData, data);
         stateChecksums[STATE_WIFI_CONFIG] =
-            writeIfChanged(stateChecksums[STATE_WIFI_CONFIG], KEY_WIFI_CONFIG, wifiConfigData,
-                    data);
+                writeIfChanged(stateChecksums[STATE_WIFI_CONFIG], KEY_WIFI_CONFIG, wifiConfigData,
+                        data);
         stateChecksums[STATE_LOCK_SETTINGS] =
-            writeIfChanged(stateChecksums[STATE_LOCK_SETTINGS], KEY_LOCK_SETTINGS,
-                    lockSettingsData, data);
+                writeIfChanged(stateChecksums[STATE_LOCK_SETTINGS], KEY_LOCK_SETTINGS,
+                        lockSettingsData, data);
+        stateChecksums[STATE_SOFTAP_CONFIG] =
+                writeIfChanged(stateChecksums[STATE_SOFTAP_CONFIG], KEY_SOFTAP_CONFIG,
+                        softApConfigData, data);
+        stateChecksums[STATE_NET_POLICIES] =
+                writeIfChanged(stateChecksums[STATE_NET_POLICIES], KEY_NET_POLICIES,
+                        netPoliciesData, data);
 
         writeNewChecksums(stateChecksums, newState);
     }
@@ -504,7 +522,7 @@
                             restoredSupplicantData, restoredSupplicantData.length);
                     FileUtils.setPermissions(FILE_WIFI_SUPPLICANT,
                             FileUtils.S_IRUSR | FileUtils.S_IWUSR |
-                            FileUtils.S_IRGRP | FileUtils.S_IWGRP,
+                                    FileUtils.S_IRGRP | FileUtils.S_IWGRP,
                             Process.myUid(), Process.WIFI_UID);
                 }
                 if (restoredWifiConfigFile != null) {
@@ -533,7 +551,7 @@
 
     @Override
     public void onRestore(BackupDataInput data, int appVersionCode,
-            ParcelFileDescriptor newState) throws IOException {
+                          ParcelFileDescriptor newState) throws IOException {
 
         HashSet<String> movedToGlobal = new HashSet<String>();
         Settings.System.getMovedToGlobalSettings(movedToGlobal);
@@ -561,7 +579,15 @@
                 mWifiRestore.incorporateWifiConfigFile(data);
             } else if (KEY_LOCK_SETTINGS.equals(key)) {
                 restoreLockSettings(data);
-             } else {
+            } else if (KEY_SOFTAP_CONFIG.equals(key)){
+                byte[] softapData = new byte[size];
+                data.readEntityData(softapData, 0, size);
+                restoreSoftApConfiguration(softapData);
+            } else if (KEY_NET_POLICIES.equals(key)) {
+                byte[] netPoliciesData = new byte[size];
+                data.readEntityData(netPoliciesData, 0, size);
+                restoreNetworkPolicies(netPoliciesData);
+            } else {
                 data.skipEntityData();
             }
         }
@@ -589,6 +615,8 @@
         byte[] locale = mSettingsHelper.getLocaleData();
         byte[] wifiSupplicantData = getWifiSupplicant(FILE_WIFI_SUPPLICANT);
         byte[] wifiConfigData = getFileData(mWifiConfigFile);
+        byte[] softApConfigData = getSoftAPConfiguration();
+        byte[] netPoliciesData = getNetworkPolicies();
 
         // Write the data to the staging file, then emit that as our tarfile
         // representation of the backed-up settings.
@@ -623,6 +651,12 @@
             if (DEBUG_BACKUP) Log.d(TAG, lockSettingsData.length + " bytes of lock settings data");
             out.writeInt(lockSettingsData.length);
             out.write(lockSettingsData);
+            if (DEBUG_BACKUP) Log.d(TAG, softApConfigData.length + " bytes of softap config data");
+            out.writeInt(softApConfigData.length);
+            out.write(softApConfigData);
+            if (DEBUG_BACKUP) Log.d(TAG, netPoliciesData.length + " bytes of network policies data");
+            out.writeInt(netPoliciesData.length);
+            out.write(netPoliciesData);
 
             out.flush();    // also flushes downstream
 
@@ -635,7 +669,7 @@
 
     @Override
     public void onRestoreFile(ParcelFileDescriptor data, long size,
-            int type, String domain, String relpath, long mode, long mtime)
+                              int type, String domain, String relpath, long mode, long mtime)
             throws IOException {
         if (DEBUG_BACKUP) Log.d(TAG, "onRestoreFile() invoked");
         // Our data is actually a blob of flattened settings data identical to that
@@ -692,7 +726,7 @@
             restoreWifiSupplicant(FILE_WIFI_SUPPLICANT, buffer, nBytes);
             FileUtils.setPermissions(FILE_WIFI_SUPPLICANT,
                     FileUtils.S_IRUSR | FileUtils.S_IWUSR |
-                    FileUtils.S_IRGRP | FileUtils.S_IWGRP,
+                            FileUtils.S_IRGRP | FileUtils.S_IWGRP,
                     Process.myUid(), Process.WIFI_UID);
             // retain the previous WIFI state.
             enableWifi(retainedWifiState == WifiManager.WIFI_STATE_ENABLED ||
@@ -715,6 +749,26 @@
                 }
             }
 
+            if (version >= FULL_BACKUP_ADDED_SOFTAP_CONF){
+                nBytes = in.readInt();
+                if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of softap config data");
+                if (nBytes > buffer.length) buffer = new byte[nBytes];
+                if (nBytes > 0) {
+                    in.readFully(buffer, 0, nBytes);
+                    restoreSoftApConfiguration(buffer);
+                }
+            }
+
+            if (version >= FULL_BACKUP_ADDED_NET_POLICIES){
+                nBytes = in.readInt();
+                if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of network policies data");
+                if (nBytes > buffer.length) buffer = new byte[nBytes];
+                if (nBytes > 0) {
+                    in.readFully(buffer, 0, nBytes);
+                    restoreNetworkPolicies(buffer);
+                }
+            }
+
             if (DEBUG_BACKUP) Log.d(TAG, "Full restore complete.");
         } else {
             data.close();
@@ -754,7 +808,7 @@
     }
 
     private long writeIfChanged(long oldChecksum, String key, byte[] data,
-            BackupDataOutput output) {
+                                BackupDataOutput output) {
         CRC32 checkSummer = new CRC32();
         checkSummer.update(data);
         long newChecksum = checkSummer.getValue();
@@ -829,7 +883,7 @@
     }
 
     private void restoreSettings(BackupDataInput data, Uri contentUri,
-            HashSet<String> movedToGlobal) {
+                                 HashSet<String> movedToGlobal) {
         byte[] settings = new byte[data.getDataSize()];
         try {
             data.readEntityData(settings, 0, settings.length);
@@ -841,7 +895,7 @@
     }
 
     private void restoreSettings(byte[] settings, int bytes, Uri contentUri,
-            HashSet<String> movedToGlobal) {
+                                 HashSet<String> movedToGlobal) {
         if (DEBUG) {
             Log.i(TAG, "restoreSettings: " + contentUri);
         }
@@ -1160,6 +1214,30 @@
         }
     }
 
+    private byte[] getSoftAPConfiguration(){
+        WifiManager wifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE);
+        return WiFiConfigurationSerializer.marshalWifiConfig(wifiManager.getWifiApConfiguration());
+    }
+
+    private void restoreSoftApConfiguration(byte[] data){
+        WifiManager wifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE);
+        wifiManager.setWifiApConfiguration(WiFiConfigurationSerializer.unmarshalWifiConfig(data));
+    }
+
+    private byte[] getNetworkPolicies(){
+        NetworkPolicyManager networkPolicyManager =
+                (NetworkPolicyManager)getSystemService(NETWORK_POLICY_SERVICE);
+        return NetworkPolicySerializer
+                .marshalNetworkPolicies(networkPolicyManager.getNetworkPolicies());
+    }
+
+    private void restoreNetworkPolicies(byte[] data){
+        NetworkPolicyManager networkPolicyManager =
+                (NetworkPolicyManager)getSystemService(NETWORK_POLICY_SERVICE);
+        networkPolicyManager
+                .setNetworkPolicies(NetworkPolicySerializer.unmarshalNetworkPolicies(data));
+    }
+
     /**
      * Write an int in BigEndian into the byte array.
      * @param out byte array
@@ -1181,8 +1259,7 @@
     }
 
     private int readInt(byte[] in, int pos) {
-        int result =
-                ((in[pos    ] & 0xFF) << 24) |
+        int result =    ((in[pos    ] & 0xFF) << 24) |
                 ((in[pos + 1] & 0xFF) << 16) |
                 ((in[pos + 2] & 0xFF) <<  8) |
                 ((in[pos + 3] & 0xFF) <<  0);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WiFiConfigurationSerializer.java b/packages/SettingsProvider/src/com/android/providers/settings/WiFiConfigurationSerializer.java
new file mode 100644
index 0000000..f9f1d3f
--- /dev/null
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WiFiConfigurationSerializer.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2015 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.providers.settings;
+
+import android.net.IpConfiguration;
+import android.net.LinkAddress;
+import android.net.ProxyInfo;
+import android.net.StaticIpConfiguration;
+import android.net.wifi.WifiConfiguration;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.BitSet;
+
+
+/**
+ * Backup/Restore Serializer Class for com.android.net.wifi.WifiConfiguration
+ */
+public class WiFiConfigurationSerializer {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "WiFiConfigSerializer";
+
+    private static final int NULL = 0;
+    private static final int NOT_NULL = 1;
+    /**
+     * Current Version of the Serializer.
+     */
+    private static int STATE_VERSION = 1;
+
+
+    /**
+     * Marshals a WifiConfig object into a byte-array.
+     *
+     * @param wifiConfig - WifiConfiguration to be Marshalled
+     * @return byte array
+     */
+
+    public static byte[] marshalWifiConfig(WifiConfiguration wifiConfig) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        if(wifiConfig != null) {
+            DataOutputStream out = new DataOutputStream(baos);
+            try {
+                out.writeInt(STATE_VERSION);
+                out.writeInt(wifiConfig.networkId);
+                out.writeInt(wifiConfig.status);
+                out.writeInt(wifiConfig.disableReason);
+                writeString(out, wifiConfig.SSID);
+                writeString(out, wifiConfig.BSSID);
+                out.writeInt(wifiConfig.apBand);
+                out.writeInt(wifiConfig.apChannel);
+                writeString(out, wifiConfig.autoJoinBSSID);
+                writeString(out, wifiConfig.FQDN);
+                writeString(out, wifiConfig.providerFriendlyName);
+                out.writeInt(wifiConfig.roamingConsortiumIds.length);
+                for (long id : wifiConfig.roamingConsortiumIds) {
+                    out.writeLong(id);
+                }
+                writeString(out, wifiConfig.preSharedKey);
+                for (String wepKey : wifiConfig.wepKeys) {
+                    writeString(out, wepKey);
+                }
+                out.writeInt(wifiConfig.wepTxKeyIndex);
+                out.writeInt(wifiConfig.priority);
+                out.writeInt(wifiConfig.hiddenSSID ? 1 : 0);
+                out.writeInt(wifiConfig.requirePMF ? 1 : 0);
+                writeString(out, wifiConfig.updateIdentifier);
+
+                writeBitSet(out, wifiConfig.allowedKeyManagement);
+                writeBitSet(out, wifiConfig.allowedProtocols);
+                writeBitSet(out, wifiConfig.allowedAuthAlgorithms);
+                writeBitSet(out, wifiConfig.allowedPairwiseCiphers);
+                writeBitSet(out, wifiConfig.allowedGroupCiphers);
+
+
+                //IpConfiguration
+                writeIpConfiguration(out, wifiConfig.getIpConfiguration());
+
+                writeString(out, wifiConfig.dhcpServer);
+                writeString(out, wifiConfig.defaultGwMacAddress);
+                out.writeInt(wifiConfig.autoJoinStatus);
+                out.writeInt(wifiConfig.selfAdded ? 1 : 0);
+                out.writeInt(wifiConfig.didSelfAdd ? 1 : 0);
+                out.writeInt(wifiConfig.validatedInternetAccess ? 1 : 0);
+                out.writeInt(wifiConfig.ephemeral ? 1 : 0);
+                out.writeInt(wifiConfig.creatorUid);
+                out.writeInt(wifiConfig.lastConnectUid);
+                out.writeInt(wifiConfig.lastUpdateUid);
+                writeString(out, wifiConfig.creatorName);
+                writeString(out, wifiConfig.lastUpdateName);
+                out.writeLong(wifiConfig.blackListTimestamp);
+                out.writeLong(wifiConfig.lastConnectionFailure);
+                out.writeLong(wifiConfig.lastRoamingFailure);
+                out.writeInt(wifiConfig.lastRoamingFailureReason);
+                out.writeLong(wifiConfig.roamingFailureBlackListTimeMilli);
+                out.writeLong(wifiConfig.numConnectionFailures);
+                out.writeLong(wifiConfig.numIpConfigFailures);
+                out.writeInt(wifiConfig.numAuthFailures);
+                out.writeInt(wifiConfig.numScorerOverride);
+                out.writeInt(wifiConfig.numScorerOverrideAndSwitchedNetwork);
+                out.writeInt(wifiConfig.numAssociation);
+                out.writeInt(wifiConfig.numUserTriggeredWifiDisableLowRSSI);
+                out.writeInt(wifiConfig.numUserTriggeredWifiDisableBadRSSI);
+                out.writeInt(wifiConfig.numUserTriggeredWifiDisableNotHighRSSI);
+                out.writeInt(wifiConfig.numTicksAtLowRSSI);
+                out.writeInt(wifiConfig.numTicksAtBadRSSI);
+                out.writeInt(wifiConfig.numTicksAtNotHighRSSI);
+                out.writeInt(wifiConfig.numUserTriggeredJoinAttempts);
+                out.writeInt(wifiConfig.autoJoinUseAggressiveJoinAttemptThreshold);
+                out.writeInt(wifiConfig.autoJoinBailedDueToLowRssi ? 1 : 0);
+                out.writeInt(wifiConfig.userApproved);
+                out.writeInt(wifiConfig.numNoInternetAccessReports);
+                out.writeInt(wifiConfig.noInternetAccessExpected ? 1 : 0);
+            } catch (IOException ioe) {
+                Log.e(TAG, "Failed to Convert WifiConfiguration to byte array", ioe);
+                baos.reset();
+            }
+        }
+        return baos.toByteArray();
+    }
+
+    /**
+     * Unmarshals a byte array into a WifiConfig Object
+     *
+     * @param data - marshalled WifiConfig Object
+     * @return WifiConfiguration Object
+     */
+
+    public static WifiConfiguration unmarshalWifiConfig(byte[] data) {
+        if (data == null ||  data.length == 0) {
+            return null;
+        }
+        DataInputStream in = new DataInputStream(new ByteArrayInputStream(data));
+        WifiConfiguration config = new WifiConfiguration();
+        try {
+            int version = in.readInt();
+
+            config.networkId = in.readInt();
+            config.status = in.readInt();
+            config.disableReason = in.readInt();
+            config.SSID = readString(in, version);
+            config.BSSID = readString(in, version);
+            config.apBand = in.readInt();
+            config.apChannel = in.readInt();
+            config.autoJoinBSSID = readString(in, version);
+            config.FQDN = readString(in, version);
+            config.providerFriendlyName = readString(in, version);
+            int numRoamingConsortiumIds = in.readInt();
+            config.roamingConsortiumIds = new long[numRoamingConsortiumIds];
+            for (int i = 0; i < numRoamingConsortiumIds; i++) {
+                config.roamingConsortiumIds[i] = in.readLong();
+            }
+            config.preSharedKey = readString(in, version);
+            for (int i = 0; i < config.wepKeys.length; i++) {
+                config.wepKeys[i] = readString(in, version);
+            }
+            config.wepTxKeyIndex = in.readInt();
+            config.priority = in.readInt();
+            config.hiddenSSID = in.readInt() != 0;
+            config.requirePMF = in.readInt() != 0;
+            config.updateIdentifier = readString(in, version);
+
+            config.allowedKeyManagement = readBitSet(in, version);
+            config.allowedProtocols = readBitSet(in, version);
+            config.allowedAuthAlgorithms = readBitSet(in, version);
+            config.allowedPairwiseCiphers = readBitSet(in, version);
+            config.allowedGroupCiphers = readBitSet(in, version);
+
+            //Not backed-up because EnterpriseConfig involves
+            //Certificates which are device specific.
+            //config.enterpriseConfig = new WifiEnterpriseConfig();
+
+            config.setIpConfiguration(readIpConfiguration(in, version));
+
+
+            config.dhcpServer = readString(in, version);
+            config.defaultGwMacAddress = readString(in, version);
+            config.autoJoinStatus = in.readInt();
+            config.selfAdded = in.readInt() != 0;
+            config.didSelfAdd = in.readInt() != 0;
+            config.validatedInternetAccess = in.readInt() != 0;
+            config.ephemeral = in.readInt() != 0;
+            config.creatorUid = in.readInt();
+            config.lastConnectUid = in.readInt();
+            config.lastUpdateUid = in.readInt();
+            config.creatorName = readString(in, version);
+            config.lastUpdateName = readString(in, version);
+            config.blackListTimestamp = in.readLong();
+            config.lastConnectionFailure = in.readLong();
+            config.lastRoamingFailure = in.readLong();
+            config.lastRoamingFailureReason = in.readInt();
+            config.roamingFailureBlackListTimeMilli = in.readLong();
+            config.numConnectionFailures = in.readInt();
+            config.numIpConfigFailures = in.readInt();
+            config.numAuthFailures = in.readInt();
+            config.numScorerOverride = in.readInt();
+            config.numScorerOverrideAndSwitchedNetwork = in.readInt();
+            config.numAssociation = in.readInt();
+            config.numUserTriggeredWifiDisableLowRSSI = in.readInt();
+            config.numUserTriggeredWifiDisableBadRSSI = in.readInt();
+            config.numUserTriggeredWifiDisableNotHighRSSI = in.readInt();
+            config.numTicksAtLowRSSI = in.readInt();
+            config.numTicksAtBadRSSI = in.readInt();
+            config.numTicksAtNotHighRSSI = in.readInt();
+            config.numUserTriggeredJoinAttempts = in.readInt();
+            config.autoJoinUseAggressiveJoinAttemptThreshold = in.readInt();
+            config.autoJoinBailedDueToLowRssi = in.readInt() != 0;
+            config.userApproved = in.readInt();
+            config.numNoInternetAccessReports = in.readInt();
+            config.noInternetAccessExpected = in.readInt() != 0;
+        } catch (IOException ioe) {
+            Log.e(TAG, "Failed to convert byte array to WifiConfiguration object", ioe);
+            return null;
+        }
+        return config;
+    }
+
+    private static ProxyInfo readProxyInfo(DataInputStream in, int version) throws IOException {
+        int isNull = in.readByte();
+        if (isNull == NULL) return null;
+        String host = readString(in, version);
+        int port = in.readInt();
+        String exclusionList = readString(in, version);
+        return new ProxyInfo(host, port, exclusionList);
+    }
+
+    private static void writeProxyInfo(DataOutputStream out, ProxyInfo proxyInfo) throws IOException {
+        if (proxyInfo != null) {
+            out.writeByte(NOT_NULL);
+            writeString(out, proxyInfo.getHost());
+            out.writeInt(proxyInfo.getPort());
+            writeString(out, proxyInfo.getExclusionListAsString());
+        } else {
+            out.writeByte(NULL);
+        }
+    }
+
+    private static InetAddress readInetAddress(DataInputStream in, int version) throws IOException {
+        int isNull = in.readByte();
+        if (isNull == NULL) return null;
+        InetAddress address = null;
+        int addressLength = in.readInt();
+        if (addressLength < 1) return address;
+        byte[] addressBytes = new byte[addressLength];
+        in.read(addressBytes, 0, addressLength);
+        try {
+            address = InetAddress.getByAddress(addressBytes);
+        } catch (UnknownHostException unknownHostException) {
+            return null;
+        }
+        return address;
+    }
+
+    private static void writeInetAddress(DataOutputStream out, InetAddress address) throws IOException {
+        if (address.getAddress() != null) {
+            out.writeByte(NOT_NULL);
+            out.writeInt(address.getAddress().length);
+            out.write(address.getAddress(), 0, address.getAddress().length);
+        } else {
+            out.writeByte(NULL);
+        }
+    }
+
+    private static LinkAddress readLinkAddress(DataInputStream in, int version) throws IOException {
+        int isNull = in.readByte();
+        if (isNull == NULL) return null;
+        InetAddress address = readInetAddress(in, version);
+        int prefixLength = in.readInt();
+        int flags = in.readInt();
+        int scope = in.readInt();
+        return new LinkAddress(address, prefixLength, flags, scope);
+    }
+
+    private static void writeLinkAddress(DataOutputStream out, LinkAddress address) throws IOException {
+        if (address != null) {
+            out.writeByte(NOT_NULL);
+            writeInetAddress(out, address.getAddress());
+            out.writeInt(address.getPrefixLength());
+            out.writeInt(address.getFlags());
+            out.writeInt(address.getScope());
+        } else {
+            out.writeByte(NULL);
+        }
+    }
+
+    private static StaticIpConfiguration readStaticIpConfiguration(DataInputStream in, int version) throws IOException {
+        int isNull = in.readByte();
+        if (isNull == NULL) return null;
+        StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration();
+        staticIpConfiguration.ipAddress = readLinkAddress(in, version);
+        staticIpConfiguration.gateway = readInetAddress(in, version);
+        int dnsServersLength = in.readInt();
+        for (int i = 0; i < dnsServersLength; i++) {
+            staticIpConfiguration.dnsServers.add(readInetAddress(in, version));
+        }
+        staticIpConfiguration.domains = readString(in, version);
+        return staticIpConfiguration;
+    }
+
+    private static void writeStaticIpConfiguration(DataOutputStream out, StaticIpConfiguration staticIpConfiguration) throws IOException {
+        if (staticIpConfiguration != null) {
+            out.writeByte(NOT_NULL);
+            writeLinkAddress(out, staticIpConfiguration.ipAddress);
+            writeInetAddress(out, staticIpConfiguration.gateway);
+            out.writeInt(staticIpConfiguration.dnsServers.size());
+            for (InetAddress inetAddress : staticIpConfiguration.dnsServers) {
+                writeInetAddress(out, inetAddress);
+            }
+            writeString(out, staticIpConfiguration.domains);
+        } else {
+            out.writeByte(NULL);
+        }
+    }
+
+    private static IpConfiguration readIpConfiguration(DataInputStream in, int version) throws IOException {
+        int isNull = in.readByte();
+        if (isNull == NULL) return null;
+        IpConfiguration ipConfiguration = new IpConfiguration();
+        String tmp = readString(in, version);
+        ipConfiguration.ipAssignment = tmp == null ? null : IpConfiguration.IpAssignment.valueOf(tmp);
+        tmp = readString(in, version);
+        ipConfiguration.proxySettings = tmp == null ? null : IpConfiguration.ProxySettings.valueOf(tmp);
+        ipConfiguration.staticIpConfiguration = readStaticIpConfiguration(in, version);
+        ipConfiguration.httpProxy = readProxyInfo(in, version);
+        return ipConfiguration;
+    }
+
+
+    private static void writeIpConfiguration(DataOutputStream out, IpConfiguration ipConfiguration) throws IOException {
+        if (ipConfiguration != null) {
+            out.writeByte(NOT_NULL);
+            writeString(out, ipConfiguration.ipAssignment != null ? ipConfiguration.ipAssignment.name() : null);
+            writeString(out, ipConfiguration.proxySettings != null ? ipConfiguration.proxySettings.name() : null);
+            writeStaticIpConfiguration(out, ipConfiguration.staticIpConfiguration);
+            writeProxyInfo(out, ipConfiguration.httpProxy);
+        } else {
+            out.writeByte(NULL);
+        }
+
+    }
+
+    private static String readString(DataInputStream in, int version) throws IOException {
+        byte isNull = in.readByte();
+        if (isNull == NOT_NULL) {
+            return in.readUTF();
+        }
+        return null;
+    }
+
+    private static void writeString(DataOutputStream out, String val) throws IOException {
+        if (val != null) {
+            out.writeByte(NOT_NULL);
+            out.writeUTF(val);
+        } else {
+            out.writeByte(NULL);
+        }
+    }
+
+    private static BitSet readBitSet(DataInputStream in, int version) throws IOException {
+        byte isNull = in.readByte();
+        if (isNull == NOT_NULL) {
+            int length = in.readInt();
+            byte[] bytes = new byte[length];
+            in.read(bytes, 0, length);
+            return BitSet.valueOf(bytes);
+        }
+        return new BitSet();
+    }
+
+    private static void writeBitSet(DataOutputStream out, BitSet val) throws IOException {
+        if (val != null) {
+            out.writeByte(NOT_NULL);
+            byte[] byteArray = val.toByteArray();
+            out.writeInt(byteArray.length);
+            out.write(byteArray);
+        } else {
+            out.writeByte(NULL);
+        }
+    }
+}
diff --git a/packages/SettingsProvider/test/Android.mk b/packages/SettingsProvider/test/Android.mk
index ef863e7..f278967 100644
--- a/packages/SettingsProvider/test/Android.mk
+++ b/packages/SettingsProvider/test/Android.mk
@@ -5,7 +5,10 @@
 # Note we statically link SettingsState to do some unit tests.  It's not accessible otherwise
 # because this test is not an instrumentation test. (because the target runs in the system process.)
 LOCAL_SRC_FILES := $(call all-subdir-java-files) \
-    ../src/com/android/providers/settings/SettingsState.java
+    ../src/com/android/providers/settings/SettingsState.java \
+    ../src/com/android/providers/settings/WiFiConfigurationSerializer.java \
+    ../src/com/android/providers/settings/NetworkPolicySerializer.java
+
 
 LOCAL_PACKAGE_NAME := SettingsProviderTest
 
@@ -13,4 +16,4 @@
 
 LOCAL_CERTIFICATE := platform
 
-include $(BUILD_PACKAGE)
\ No newline at end of file
+include $(BUILD_PACKAGE)
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/NetworkPolicySerializerTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/NetworkPolicySerializerTest.java
new file mode 100644
index 0000000..1986596
--- /dev/null
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/NetworkPolicySerializerTest.java
@@ -0,0 +1,117 @@
+package com.android.providers.settings;
+
+import android.net.NetworkPolicy;
+import android.net.NetworkTemplate;
+import android.test.AndroidTestCase;
+
+import java.util.Random;
+
+/**
+ * Tests for NetworkPolicySerializer
+ */
+public class NetworkPolicySerializerTest extends AndroidTestCase {
+    static Random sRandom = new Random();
+
+    public void testMarshallAndUnmarshalNetworkPolicy() {
+        NetworkPolicy policy = getDummyNetworkPolicy();
+        byte[] data = NetworkPolicySerializer.marshalNetworkPolicy(policy);
+        assertNotNull("Got Null data from marshal", data);
+        assertFalse("Got back an empty byte[] from marshal", data.length == 0);
+
+        NetworkPolicy unmarshaled = NetworkPolicySerializer.unmarshalNetworkPolicy(data);
+        assertNotNull("Got Null data from unmarshaled", unmarshaled);
+        assertTrue("NetworkPolicy Marshall and Unmarshal Failed!", policy.equals(unmarshaled));
+    }
+
+    public void testMarshallNetworkPolicyEdgeCases() {
+        byte[] data = NetworkPolicySerializer.marshalNetworkPolicy(null);
+        assertNotNull("NetworkPolicy marshal returned null. Expected: byte[0]", data);
+        assertEquals("NetworkPolicy marshal returned incomplete byte array. Expected: byte[0]",
+                data.length, 0);
+    }
+
+    public void testUnmarshallNetworkPolicyEdgeCases() {
+        NetworkPolicy policy = NetworkPolicySerializer.unmarshalNetworkPolicy(null);
+        assertNull("Non null NetworkPolicy returned for null byte[] input", policy);
+
+        policy = NetworkPolicySerializer.unmarshalNetworkPolicy(new byte[0]);
+        assertNull("Non null NetworkPolicy returned for empty byte[] input", policy);
+
+        policy = NetworkPolicySerializer.unmarshalNetworkPolicy(new byte[]{10, 20, 30, 40, 50, 60});
+        assertNull("Non null NetworkPolicy returned for incomplete byte[] input", policy);
+    }
+
+    public void testMarshallAndUnmarshalNetworkPolicies() {
+        NetworkPolicy[] policies = getDummyNetworkPolicies(5);
+        byte[] data = NetworkPolicySerializer.marshalNetworkPolicies(policies);
+        assertNotNull("Got Null data from marshal", data);
+        assertFalse("Got back an empty byte[] from marshal", data.length == 0);
+
+        NetworkPolicy[] unmarshaled = NetworkPolicySerializer.unmarshalNetworkPolicies(data);
+        assertNotNull("Got Null data from unmarshaled", unmarshaled);
+        try {
+            for (int i = 0; i < policies.length; i++) {
+                assertTrue("NetworkPolicies Marshall and Unmarshal Failed!",
+                        policies[i].equals(unmarshaled[i]));
+            }
+        } catch (NullPointerException npe) {
+            assertTrue("Some policies were not marshaled/unmarshaled correctly", false);
+        }
+    }
+
+    public void testMarshallNetworkPoliciesEdgeCases() {
+        byte[] data = NetworkPolicySerializer.marshalNetworkPolicies(null);
+        assertNotNull("NetworkPolicies marshal returned null!", data);
+        assertEquals("NetworkPolicies marshal returned incomplete byte array", data.length, 0);
+
+        data = NetworkPolicySerializer.marshalNetworkPolicies(new NetworkPolicy[0]);
+        assertNotNull("NetworkPolicies marshal returned null for empty NetworkPolicy[]", data);
+        assertEquals("NetworkPolicies marshal returned incomplete byte array for empty NetworkPolicy[]"
+                , data.length, 0);
+    }
+
+    public void testUnmarshalNetworkPoliciesEdgeCases() {
+        NetworkPolicy[] policies = NetworkPolicySerializer.unmarshalNetworkPolicies(null);
+        assertNotNull("NetworkPolicies unmarshal returned null for null input. Expected: byte[0] ",
+                policies);
+        assertEquals("Non Empty NetworkPolicy[] returned for null input Expected: byte[0]",
+                policies.length, 0);
+
+        policies = NetworkPolicySerializer.unmarshalNetworkPolicies(new byte[0]);
+        assertNotNull("NetworkPolicies unmarshal returned null for empty byte[] input. Expected: byte[0]",
+                policies);
+        assertEquals("Non Empty NetworkPolicy[] returned for empty byte[] input. Expected: byte[0]",
+                policies.length, 0);
+
+        policies = NetworkPolicySerializer.unmarshalNetworkPolicies(new byte[]{10, 20, 30, 40, 50, 60});
+        assertNotNull("NetworkPolicies unmarshal returned null for incomplete byte[] input. " +
+                "Expected: byte[0] ", policies);
+        assertEquals("Non Empty NetworkPolicy[] returned for incomplete byte[] input Expected: byte[0]",
+                policies.length, 0);
+
+    }
+
+    private NetworkPolicy[] getDummyNetworkPolicies(int num) {
+        NetworkPolicy[] policies = new NetworkPolicy[num];
+        for (int i = 0; i < num; i++) {
+            policies[i] = getDummyNetworkPolicy();
+        }
+        return policies;
+    }
+
+    private NetworkPolicy getDummyNetworkPolicy() {
+        NetworkTemplate template = new NetworkTemplate(NetworkTemplate.MATCH_MOBILE_ALL, "subId",
+                "GoogleGuest");
+        int cycleDay = sRandom.nextInt();
+        String cycleTimezone = "timezone";
+        long warningBytes = sRandom.nextLong();
+        long limitBytes = sRandom.nextLong();
+        long lastWarningSnooze = sRandom.nextLong();
+        long lastLimitSnooze = sRandom.nextLong();
+        boolean metered = sRandom.nextInt() % 2 == 0;
+        boolean inferred = sRandom.nextInt() % 2 == 0;
+        return new NetworkPolicy(template, cycleDay, cycleTimezone, warningBytes, limitBytes,
+                lastWarningSnooze, lastLimitSnooze, metered, inferred);
+    }
+
+}
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 0e31cdf..44018a0 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -40,6 +40,8 @@
 import java.util.zip.ZipOutputStream;
 
 import libcore.io.Streams;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningServiceInfo;
 import android.app.Instrumentation;
 import android.app.NotificationManager;
 import android.content.Context;
@@ -130,7 +132,8 @@
         Bundle extras = sendBugreportFinishedIntent(42, PLAIN_TEXT_PATH, SCREENSHOT_PATH);
         assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT);
 
-        // TODO: assert service is down
+        String service = BugreportProgressService.class.getName();
+        assertFalse("Service '" + service + "' is still running", isServiceRunning(service));
     }
 
     public void testBugreportFinished_withWarning() throws Exception {
@@ -306,6 +309,17 @@
         fail("Did not find entry '" + entryName + "' on file '" + uri + "'");
     }
 
+    private boolean isServiceRunning(String name) {
+        ActivityManager manager = (ActivityManager) mContext
+                .getSystemService(Context.ACTIVITY_SERVICE);
+        for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
+            if (service.service.getClassName().equals(name)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private static void createTextFile(String path, String content) throws IOException {
         Log.v(TAG, "createFile(" + path + ")");
         try (Writer writer = new BufferedWriter(new OutputStreamWriter(
diff --git a/packages/Shell/tests/src/com/android/shell/UiBot.java b/packages/Shell/tests/src/com/android/shell/UiBot.java
index 5e8bab1..7d37137 100644
--- a/packages/Shell/tests/src/com/android/shell/UiBot.java
+++ b/packages/Shell/tests/src/com/android/shell/UiBot.java
@@ -118,7 +118,8 @@
     // TODO: UI Automator should provide such logic.
     public void chooseActivity(String name) {
         // First check if the activity is the default option.
-        String shareText = String.format("Share with %s", name);
+        String shareText = "Share with " + name;
+        Log.v(TAG, "Waiting for ActivityChooser text: '" + shareText + "'");
         boolean gotIt = mDevice.wait(Until.hasObject(By.text(shareText)), mTimeout);
 
         if (gotIt) {
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index e949adc..a995ec7 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -78,47 +78,64 @@
             android:tint="@android:color/white" />
     </LinearLayout>
 
-    <include layout="@layout/split_clock_view"
-        android:layout_width="wrap_content"
+    <TextView
+        android:id="@+id/header_emergency_calls_only"
         android:layout_height="wrap_content"
-        android:layout_marginStart="16dp"
-        android:layout_marginTop="2dp"
+        android:layout_width="wrap_content"
         android:layout_alignParentStart="true"
         android:layout_alignParentTop="true"
-        android:id="@+id/clock"
-        />
-
-    <com.android.systemui.statusbar.policy.DateView
-        android:id="@+id/date"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="6dp"
-        android:layout_marginTop="8dp"
-        android:layout_toEndOf="@id/clock"
-        android:layout_alignParentTop="true"
-        android:drawableStart="@drawable/header_dot"
-        android:drawablePadding="6dp"
-        android:singleLine="true"
-        android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
-        android:textSize="@dimen/qs_time_collapsed_size"
-        systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm"
-        />
-
-    <com.android.systemui.statusbar.AlphaOptimizedButton
-        android:id="@+id/alarm_status"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentTop="true"
-        android:layout_toEndOf="@id/date"
-        android:drawablePadding="6dp"
-        android:drawableStart="@drawable/ic_access_alarms_small"
-        android:textColor="#64ffffff"
-        android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date"
-        android:minHeight="36dp"
-        android:paddingStart="6dp"
-        android:background="?android:attr/selectableItemBackground"
+        android:paddingStart="16dp"
+        android:paddingEnd="16dp"
+        android:paddingTop="8dp"
         android:visibility="gone"
-        />
+        android:textAppearance="@style/TextAppearance.StatusBar.Expanded.EmergencyCallsOnly"
+        android:text="@*android:string/emergency_calls_only"
+        android:singleLine="true"
+        android:gravity="center_vertical" />
+
+    <LinearLayout
+        android:id="@+id/date_time_group"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentTop="true"
+        android:orientation="horizontal">
+
+        <include layout="@layout/split_clock_view"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:layout_marginTop="2dp"
+            android:id="@+id/clock" />
+
+        <com.android.systemui.statusbar.policy.DateView
+            android:id="@+id/date"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="6dp"
+            android:layout_marginTop="8dp"
+            android:layout_alignParentTop="true"
+            android:drawableStart="@drawable/header_dot"
+            android:drawablePadding="6dp"
+            android:singleLine="true"
+            android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
+            android:textSize="@dimen/qs_time_collapsed_size"
+            systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" />
+
+        <com.android.systemui.statusbar.AlphaOptimizedButton
+            android:id="@+id/alarm_status"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:drawablePadding="6dp"
+            android:drawableStart="@drawable/ic_access_alarms_small"
+            android:textColor="#64ffffff"
+            android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Date"
+            android:minHeight="36dp"
+            android:paddingStart="6dp"
+            android:background="?android:attr/selectableItemBackground"
+            android:visibility="gone" />
+    </LinearLayout>
 
     <com.android.systemui.qs.QuickQSPanel
         android:id="@+id/quick_qs_panel"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 388da17..40e8b50 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -103,7 +103,7 @@
 
     <!-- The default tiles to display in QuickSettings -->
     <string name="quick_settings_tiles_default" translatable="false">
-        wifi,bt,inversion,dnd,cell,airplane,rotation,flashlight,location,cast,hotspot
+        wifi,bt,flashlight,dnd,cell,battery,rotation,airplane,location,cast
     </string>
 
     <!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 4e812aa..4f070d6 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -45,8 +45,11 @@
     <!-- Height of a large notification in the status bar -->
     <dimen name="notification_max_height">276dp</dimen>
 
-    <!-- Height of a medium notification in the status bar -->
-    <dimen name="notification_mid_height">128dp</dimen>
+    <!-- Height of a heads up notification in the status bar for legacy custom views -->
+    <dimen name="notification_max_heads_up_height_legacy">128dp</dimen>
+
+    <!-- Height of a heads up notification in the status bar -->
+    <dimen name="notification_max_heads_up_height">140dp</dimen>
 
     <!-- Height of a the summary ("more card") notification on keyguard. -->
     <dimen name="notification_summary_height">44dp</dimen>
@@ -133,6 +136,7 @@
     <dimen name="qs_quick_actions_padding">25dp</dimen>
     <dimen name="qs_quick_tile_size">48dp</dimen>
     <dimen name="qs_quick_tile_padding">12dp</dimen>
+    <dimen name="qs_date_anim_translation">44.5dp</dimen>
     <dimen name="qs_page_indicator_size">12dp</dimen>
     <dimen name="qs_tile_icon_size">24dp</dimen>
     <dimen name="qs_tile_text_size">12sp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
index 5b8d3d6..c9ba885 100644
--- a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.content.Context;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Paint;
@@ -35,13 +36,19 @@
 
     private final Paint mDarkPaint = new Paint();
     private final Interpolator mLinearOutSlowInInterpolator;
-    private final ArrayList<View> mTargets;
     private final ColorMatrix mMatrix = new ColorMatrix();
     private final ColorMatrix mGrayscaleMatrix = new ColorMatrix();
     private final long mFadeDuration;
+    private final ArrayList<View> mTargets = new ArrayList<>();
 
-    public ViewInvertHelper(View target, long fadeDuration) {
-        this(constructArray(target), fadeDuration);
+    public ViewInvertHelper(View v, long fadeDuration) {
+        this(v.getContext(), fadeDuration);
+        addTarget(v);
+    }
+    public ViewInvertHelper(Context context, long fadeDuration) {
+        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+                android.R.interpolator.linear_out_slow_in);
+        mFadeDuration = fadeDuration;
     }
 
     private static ArrayList<View> constructArray(View target) {
@@ -50,11 +57,12 @@
         return views;
     }
 
-    public ViewInvertHelper(ArrayList<View> targets, long fadeDuration) {
-        mTargets = targets;
-        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(mTargets.get(0).getContext(),
-                android.R.interpolator.linear_out_slow_in);
-        mFadeDuration = fadeDuration;
+    public void clearTargets() {
+        mTargets.clear();
+    }
+
+    public void addTarget(View target) {
+        mTargets.add(target);
     }
 
     public void fade(final boolean invert, long delay) {
@@ -112,4 +120,12 @@
         mMatrix.preConcat(mGrayscaleMatrix);
         mDarkPaint.setColorFilter(new ColorMatrixColorFilter(mMatrix));
     }
+
+    public void setInverted(boolean invert, boolean fade, long delay) {
+        if (fade) {
+            fade(invert, delay);
+        } else {
+            update(invert);
+        }
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 879624e..b1847e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -127,8 +127,8 @@
 
     public static final boolean ENABLE_REMOTE_INPUT =
             SystemProperties.getBoolean("debug.enable_remote_input", true);
-    public static final boolean ENABLE_CHILD_NOTIFICATIONS = Build.IS_DEBUGGABLE
-                    && SystemProperties.getBoolean("debug.child_notifs", false);
+    public static final boolean ENABLE_CHILD_NOTIFICATIONS
+            = SystemProperties.getBoolean("debug.child_notifs", true);
 
     protected static final int MSG_SHOW_RECENT_APPS = 1019;
     protected static final int MSG_HIDE_RECENT_APPS = 1020;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 5c79c7d..2b93554 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -51,6 +51,8 @@
     private static final int COLORED_DIVIDER_ALPHA = 0x7B;
     private final LinearInterpolator mLinearInterpolator = new LinearInterpolator();
     private final int mNotificationMinHeightLegacy;
+    private final int mMaxHeadsUpHeightLegacy;
+    private final int mMaxHeadsUpHeight;
     private final int mNotificationMinHeight;
     private final int mNotificationMaxHeight;
     private int mRowMinHeight;
@@ -95,6 +97,7 @@
     private boolean mIsHeadsUp;
     private boolean mLastChronometerRunning = true;
     private NotificationHeaderView mNotificationHeader;
+    private NotificationViewWrapper mNotificationHeaderWrapper;
     private ViewStub mChildrenContainerStub;
     private NotificationGroupManager mGroupManager;
     private boolean mChildrenExpanded;
@@ -218,10 +221,15 @@
         boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
         int minHeight = customView && beforeN && !mIsSummaryWithChildren ?
                 mNotificationMinHeightLegacy : mNotificationMinHeight;
+        boolean headsUpCustom = getPrivateLayout().getHeadsUpChild() != null &&
+                getPrivateLayout().getHeadsUpChild().getId()
+                != com.android.internal.R.id.status_bar_latest_event_content;
+        int headsUpheight = headsUpCustom && beforeN ? mMaxHeadsUpHeightLegacy
+                : mMaxHeadsUpHeight;
         mRowMinHeight = minHeight;
         mMaxViewHeight = mNotificationMaxHeight;
-        mPrivateLayout.setSmallHeight(mRowMinHeight);
-        mPublicLayout.setSmallHeight(mRowMinHeight);
+        mPrivateLayout.setHeights(mRowMinHeight, headsUpheight);
+        mPublicLayout.setHeights(mRowMinHeight, headsUpheight);
     }
 
     public StatusBarNotification getStatusBarNotification() {
@@ -385,6 +393,9 @@
     }
 
     public int getHeadsUpHeight() {
+        if (mIsSummaryWithChildren) {
+            return mChildrenContainer.getIntrinsicHeight();
+        }
         return mHeadsUpHeight;
     }
 
@@ -462,6 +473,10 @@
                 R.dimen.notification_min_height);
         mNotificationMaxHeight =  getResources().getDimensionPixelSize(
                 R.dimen.notification_max_height);
+        mMaxHeadsUpHeightLegacy =  getResources().getDimensionPixelSize(
+                R.dimen.notification_max_heads_up_height_legacy);
+        mMaxHeadsUpHeight =  getResources().getDimensionPixelSize(
+                R.dimen.notification_max_heads_up_height);
     }
 
     /**
@@ -570,6 +585,10 @@
         if (showing != null) {
             showing.setDark(dark, fade, delay);
         }
+        if (mIsSummaryWithChildren) {
+            mChildrenContainer.setDark(dark, fade, delay);
+            mNotificationHeaderWrapper.setDark(dark, fade, delay);
+        }
     }
 
     public boolean isExpandable() {
@@ -967,9 +986,12 @@
                     com.android.internal.R.id.expand_button);
             expandButton.setVisibility(VISIBLE);
             mNotificationHeader.setOnClickListener(mExpandClickListener);
+            mNotificationHeaderWrapper = NotificationViewWrapper.wrap(getContext(),
+                    mNotificationHeader);
             addView(mNotificationHeader);
         } else {
             header.reapply(getContext(), mNotificationHeader);
+            mNotificationHeaderWrapper.notifyContentUpdated();
         }
         updateHeaderExpandButton();
         updateChildrenHeaderAppearance();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index da01d54..2944c4f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -52,7 +52,6 @@
     private static final int VISIBLE_TYPE_SINGLELINE = 3;
 
     private final Rect mClipBounds = new Rect();
-    private final int mHeadsUpHeight;
     private final int mRoundRectRadius;
     private final Interpolator mLinearInterpolator = new LinearInterpolator();
     private final boolean mRoundRectClippingEnabled;
@@ -77,6 +76,7 @@
     private boolean mShowingLegacyBackground;
     private boolean mIsChildInGroup;
     private int mSmallHeight;
+    private int mHeadsUpHeight;
     private StatusBarNotification mStatusBarNotification;
     private NotificationGroupManager mGroupManager;
 
@@ -103,7 +103,6 @@
         super(context, attrs);
         mHybridViewManager = new HybridNotificationViewManager(getContext(), this);
         mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
-        mHeadsUpHeight = getResources().getDimensionPixelSize(R.dimen.notification_mid_height);
         mRoundRectRadius = getResources().getDimensionPixelSize(
                 R.dimen.notification_material_rounded_rect_radius);
         mRoundRectClippingEnabled = getResources().getBoolean(
@@ -112,8 +111,9 @@
         setOutlineProvider(mOutlineProvider);
     }
 
-    public void setSmallHeight(int smallHeight) {
+    public void setHeights(int smallHeight, int headsUpMaxHeight) {
         mSmallHeight = smallHeight;
+        mHeadsUpHeight = headsUpMaxHeight;
     }
 
     @Override
@@ -150,7 +150,7 @@
             ViewGroup.LayoutParams layoutParams = mHeadsUpChild.getLayoutParams();
             if (layoutParams.height >= 0) {
                 // An actual height is set
-                size = Math.min(maxSize, layoutParams.height);
+                size = Math.min(size, layoutParams.height);
             }
             mHeadsUpChild.measure(widthMeasureSpec,
                     MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST));
@@ -283,10 +283,10 @@
     }
 
     public int getMaxHeight() {
-        if (mIsHeadsUp && mHeadsUpChild != null) {
-            return mHeadsUpChild.getHeight();
-        } else if (mExpandedChild != null) {
+        if (mExpandedChild != null) {
             return mExpandedChild.getHeight();
+        } else if (mIsHeadsUp && mHeadsUpChild != null) {
+            return mHeadsUpChild.getHeight();
         }
         return mSmallHeight;
     }
@@ -457,6 +457,9 @@
         if (mDark == dark || mContractedChild == null) return;
         mDark = dark;
         mContractedWrapper.setDark(dark && !mShowingLegacyBackground, fade, delay);
+        if (mSingleLineView != null) {
+            mSingleLineView.setDark(dark, fade, delay);
+        }
     }
 
     public void setHeadsUp(boolean headsUp) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderViewWrapper.java
new file mode 100644
index 0000000..ddad2e0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderViewWrapper.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2015 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.systemui.statusbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
+import android.view.NotificationHeaderView;
+import android.view.View;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+import com.android.systemui.ViewInvertHelper;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
+
+import java.util.ArrayList;
+
+/**
+ * Wraps a notification header view.
+ */
+public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
+
+    private final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix();
+    private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter(
+            0, PorterDuff.Mode.SRC_ATOP);
+    private final int mIconDarkAlpha;
+    private final int mIconDarkColor = 0xffffffff;
+    protected final Interpolator mLinearOutSlowInInterpolator;
+    protected final ViewInvertHelper mInvertHelper;
+
+    protected int mColor;
+    private ImageView mIcon;
+
+    private ImageView mExpandButton;
+    private NotificationHeaderView mNotificationHeader;
+
+    protected NotificationHeaderViewWrapper(Context ctx, View view) {
+        super(view);
+        mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
+        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(ctx,
+                android.R.interpolator.linear_out_slow_in);
+        mInvertHelper = new ViewInvertHelper(ctx, NotificationPanelView.DOZE_ANIMATION_DURATION);
+        resolveHeaderViews();
+    }
+
+    protected void resolveHeaderViews() {
+        mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
+        mExpandButton = (ImageView) mView.findViewById(com.android.internal.R.id.expand_button);
+        mColor = resolveColor(mExpandButton);
+        mNotificationHeader = (NotificationHeaderView) mView.findViewById(
+                com.android.internal.R.id.notification_header);
+        for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
+            View child = mNotificationHeader.getChildAt(i);
+            if (child != mIcon) {
+                mInvertHelper.addTarget(child);
+            }
+        }
+    }
+
+    private int resolveColor(ImageView icon) {
+        if (icon != null && icon.getDrawable() != null) {
+            ColorFilter filter = icon.getDrawable().getColorFilter();
+            if (filter instanceof PorterDuffColorFilter) {
+                return ((PorterDuffColorFilter) filter).getColor();
+            }
+        }
+        return 0;
+    }
+
+    @Override
+    public void notifyContentUpdated() {
+        mInvertHelper.clearTargets();
+        // Reinspect the notification.
+        resolveHeaderViews();
+    }
+
+    @Override
+    public void setDark(boolean dark, boolean fade, long delay) {
+        if (fade) {
+            mInvertHelper.fade(dark, delay);
+        } else {
+            mInvertHelper.update(dark);
+        }
+        if (mIcon != null) {
+            boolean hadColorFilter = mNotificationHeader.getOriginalIconColor()
+                    != NotificationHeaderView.NO_COLOR;
+            if (fade) {
+                if (hadColorFilter) {
+                    fadeIconColorFilter(mIcon, dark, delay);
+                    fadeIconAlpha(mIcon, dark, delay);
+                } else {
+                    fadeGrayscale(mIcon, dark, delay);
+                }
+            } else {
+                if (hadColorFilter) {
+                    updateIconColorFilter(mIcon, dark);
+                    updateIconAlpha(mIcon, dark);
+                } else {
+                    updateGrayscale(mIcon, dark);
+                }
+            }
+        }
+    }
+
+    protected void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener,
+            boolean dark, long delay, Animator.AnimatorListener listener) {
+        float startIntensity = dark ? 0f : 1f;
+        float endIntensity = dark ? 1f : 0f;
+        ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity);
+        animator.addUpdateListener(updateListener);
+        animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
+        animator.setInterpolator(mLinearOutSlowInInterpolator);
+        animator.setStartDelay(delay);
+        if (listener != null) {
+            animator.addListener(listener);
+        }
+        animator.start();
+    }
+
+    private void fadeIconColorFilter(final ImageView target, boolean dark, long delay) {
+        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                updateIconColorFilter(target, (Float) animation.getAnimatedValue());
+            }
+        }, dark, delay, null /* listener */);
+    }
+
+    private void fadeIconAlpha(final ImageView target, boolean dark, long delay) {
+        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                float t = (float) animation.getAnimatedValue();
+                target.setImageAlpha((int) (255 * (1f - t) + mIconDarkAlpha * t));
+            }
+        }, dark, delay, null /* listener */);
+    }
+
+    protected void fadeGrayscale(final ImageView target, final boolean dark, long delay) {
+        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                updateGrayscaleMatrix((float) animation.getAnimatedValue());
+                target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
+            }
+        }, dark, delay, new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (!dark) {
+                    target.setColorFilter(null);
+                }
+            }
+        });
+    }
+
+    private void updateIconColorFilter(ImageView target, boolean dark) {
+        updateIconColorFilter(target, dark ? 1f : 0f);
+    }
+
+    private void updateIconColorFilter(ImageView target, float intensity) {
+        int color = interpolateColor(mColor, mIconDarkColor, intensity);
+        mIconColorFilter.setColor(color);
+        Drawable iconDrawable = target.getDrawable();
+
+        // Also, the notification might have been modified during the animation, so background
+        // might be null here.
+        if (iconDrawable != null) {
+            iconDrawable.mutate().setColorFilter(mIconColorFilter);
+        }
+    }
+
+    private void updateIconAlpha(ImageView target, boolean dark) {
+        target.setImageAlpha(dark ? mIconDarkAlpha : 255);
+    }
+
+    protected void updateGrayscale(ImageView target, boolean dark) {
+        if (dark) {
+            updateGrayscaleMatrix(1f);
+            target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
+        } else {
+            target.setColorFilter(null);
+        }
+    }
+
+    @Override
+    public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {
+        mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
+        mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
+    }
+
+    private void updateGrayscaleMatrix(float intensity) {
+        mGrayscaleColorMatrix.setSaturation(1 - intensity);
+    }
+
+    private static int interpolateColor(int source, int target, float t) {
+        int aSource = Color.alpha(source);
+        int rSource = Color.red(source);
+        int gSource = Color.green(source);
+        int bSource = Color.blue(source);
+        int aTarget = Color.alpha(target);
+        int rTarget = Color.red(target);
+        int gTarget = Color.green(target);
+        int bTarget = Color.blue(target);
+        return Color.argb(
+                (int) (aSource * (1f - t) + aTarget * t),
+                (int) (rSource * (1f - t) + rTarget * t),
+                (int) (gSource * (1f - t) + gTarget * t),
+                (int) (bSource * (1f - t) + bTarget * t));
+    }
+
+    @Override
+    public NotificationHeaderView getNotificationHeader() {
+        return mNotificationHeader;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
index fb0a419..77e8c55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
@@ -16,74 +16,31 @@
 
 package com.android.systemui.statusbar;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
-import android.view.MotionEvent;
-import android.view.NotificationHeaderView;
 import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
 import android.widget.ImageView;
 import android.widget.ProgressBar;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-import com.android.systemui.ViewInvertHelper;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
-
-import java.util.ArrayList;
 
 /**
  * Wraps a notification view inflated from a template.
  */
-public class NotificationTemplateViewWrapper extends NotificationViewWrapper {
+public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapper {
 
-    private final ColorMatrix mGrayscaleColorMatrix = new ColorMatrix();
-    private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter(
-            0, PorterDuff.Mode.SRC_ATOP);
-    private final int mIconDarkAlpha;
-    private final int mIconDarkColor = 0xffffffff;
-    private final int mDarkProgressTint = 0xffffffff;
-    private final Interpolator mLinearOutSlowInInterpolator;
+    private static final int mDarkProgressTint = 0xffffffff;
 
-    private int mColor;
-    private ViewInvertHelper mInvertHelper;
-    private ImageView mIcon;
     protected ImageView mPicture;
-
-    private ImageView mExpandButton;
-    private NotificationHeaderView mNotificationHeader;
     private ProgressBar mProgressBar;
 
     protected NotificationTemplateViewWrapper(Context ctx, View view) {
-        super(view);
-        mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
-        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(ctx,
-                android.R.interpolator.linear_out_slow_in);
-
-        resolveViews();
+        super(ctx, view);
+        resolveTemplateViews();
     }
 
-    private void resolveViews() {
+    private void resolveTemplateViews() {
         View mainColumn = mView.findViewById(com.android.internal.R.id.notification_main_column);
-        mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
         mPicture = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
-        mExpandButton = (ImageView) mView.findViewById(com.android.internal.R.id.expand_button);
-        mColor = resolveColor(mExpandButton);
         final View progress = mView.findViewById(com.android.internal.R.id.progress);
         if (progress instanceof ProgressBar) {
             mProgressBar = (ProgressBar) progress;
@@ -91,30 +48,9 @@
             // It's still a viewstub
             mProgressBar = null;
         }
-        mNotificationHeader = (NotificationHeaderView) mView.findViewById(
-                com.android.internal.R.id.notification_header);
-        ArrayList<View> viewsToInvert = new ArrayList<>();
         if (mainColumn != null) {
-            viewsToInvert.add(mainColumn);
+            mInvertHelper.addTarget(mainColumn);
         }
-        for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
-            View child = mNotificationHeader.getChildAt(i);
-            if (child != mIcon) {
-                viewsToInvert.add(child);
-            }
-        }
-        mInvertHelper = new ViewInvertHelper(viewsToInvert,
-                NotificationPanelView.DOZE_ANIMATION_DURATION);
-    }
-
-    private int resolveColor(ImageView icon) {
-        if (icon != null && icon.getDrawable() != null) {
-            ColorFilter filter = icon.getDrawable().getColorFilter();
-            if (filter instanceof PorterDuffColorFilter) {
-                return ((PorterDuffColorFilter) filter).getColor();
-            }
-        }
-        return 0;
     }
 
     @Override
@@ -122,37 +58,12 @@
         super.notifyContentUpdated();
 
         // Reinspect the notification.
-        resolveViews();
+        resolveTemplateViews();
     }
 
     @Override
     public void setDark(boolean dark, boolean fade, long delay) {
-        if (mInvertHelper != null) {
-            if (fade) {
-                mInvertHelper.fade(dark, delay);
-            } else {
-                mInvertHelper.update(dark);
-            }
-        }
-        if (mIcon != null) {
-            boolean hadColorFilter = mNotificationHeader.getOriginalIconColor()
-                    != NotificationHeaderView.NO_COLOR;
-            if (fade) {
-                if (hadColorFilter) {
-                    fadeIconColorFilter(mIcon, dark, delay);
-                    fadeIconAlpha(mIcon, dark, delay);
-                } else {
-                    fadeGrayscale(mIcon, dark, delay);
-                }
-            } else {
-                if (hadColorFilter) {
-                    updateIconColorFilter(mIcon, dark);
-                    updateIconAlpha(mIcon, dark);
-                } else {
-                    updateGrayscale(mIcon, dark);
-                }
-            }
-        }
+        super.setDark(dark, fade, delay);
         setPictureGrayscale(dark, fade, delay);
         setProgressBarDark(dark, fade, delay);
     }
@@ -197,96 +108,6 @@
         }
     }
 
-    private void startIntensityAnimation(ValueAnimator.AnimatorUpdateListener updateListener,
-            boolean dark, long delay, Animator.AnimatorListener listener) {
-        float startIntensity = dark ? 0f : 1f;
-        float endIntensity = dark ? 1f : 0f;
-        ValueAnimator animator = ValueAnimator.ofFloat(startIntensity, endIntensity);
-        animator.addUpdateListener(updateListener);
-        animator.setDuration(NotificationPanelView.DOZE_ANIMATION_DURATION);
-        animator.setInterpolator(mLinearOutSlowInInterpolator);
-        animator.setStartDelay(delay);
-        if (listener != null) {
-            animator.addListener(listener);
-        }
-        animator.start();
-    }
-
-    private void fadeIconColorFilter(final ImageView target, boolean dark, long delay) {
-        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                updateIconColorFilter(target, (Float) animation.getAnimatedValue());
-            }
-        }, dark, delay, null /* listener */);
-    }
-
-    private void fadeIconAlpha(final ImageView target, boolean dark, long delay) {
-        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                float t = (float) animation.getAnimatedValue();
-                target.setImageAlpha((int) (255 * (1f - t) + mIconDarkAlpha * t));
-            }
-        }, dark, delay, null /* listener */);
-    }
-
-    protected void fadeGrayscale(final ImageView target, final boolean dark, long delay) {
-        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                updateGrayscaleMatrix((float) animation.getAnimatedValue());
-                target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
-            }
-        }, dark, delay, new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (!dark) {
-                    target.setColorFilter(null);
-                }
-            }
-        });
-    }
-
-    private void updateIconColorFilter(ImageView target, boolean dark) {
-        updateIconColorFilter(target, dark ? 1f : 0f);
-    }
-
-    private void updateIconColorFilter(ImageView target, float intensity) {
-        int color = interpolateColor(mColor, mIconDarkColor, intensity);
-        mIconColorFilter.setColor(color);
-        Drawable iconDrawable = target.getDrawable();
-
-        // Also, the notification might have been modified during the animation, so background
-        // might be null here.
-        if (iconDrawable != null) {
-            iconDrawable.mutate().setColorFilter(mIconColorFilter);
-        }
-    }
-
-    private void updateIconAlpha(ImageView target, boolean dark) {
-        target.setImageAlpha(dark ? mIconDarkAlpha : 255);
-    }
-
-    protected void updateGrayscale(ImageView target, boolean dark) {
-        if (dark) {
-            updateGrayscaleMatrix(1f);
-            target.setColorFilter(new ColorMatrixColorFilter(mGrayscaleColorMatrix));
-        } else {
-            target.setColorFilter(null);
-        }
-    }
-
-    @Override
-    public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {
-        mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
-        mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
-    }
-
-    private void updateGrayscaleMatrix(float intensity) {
-        mGrayscaleColorMatrix.setSaturation(1 - intensity);
-    }
-
     private static int interpolateColor(int source, int target, float t) {
         int aSource = Color.alpha(source);
         int rSource = Color.red(source);
@@ -302,9 +123,4 @@
                 (int) (gSource * (1f - t) + gTarget * t),
                 (int) (bSource * (1f - t) + bTarget * t));
     }
-
-    @Override
-    public NotificationHeaderView getNotificationHeader() {
-        return mNotificationHeader;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
index 119d57b..61499de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
@@ -26,16 +26,13 @@
  */
 public abstract class NotificationViewWrapper {
 
-    private static final String TAG_BIG_MEDIA_NARROW = "bigMediaNarrow";
-    private static final String TAG_MEDIA = "media";
-    private static final String TAG_BIG_PICTURE = "bigPicture";
-
     protected final View mView;
-    private boolean mSubTextVisible = true;
 
     public static NotificationViewWrapper wrap(Context ctx, View v) {
         if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
             return new NotificationTemplateViewWrapper(ctx, v);
+        } else if (v instanceof NotificationHeaderView) {
+            return new NotificationHeaderViewWrapper(ctx, v);
         } else {
             return new NotificationCustomViewWrapper(v);
         }
@@ -57,9 +54,7 @@
     /**
      * Notifies this wrapper that the content of the view might have changed.
      */
-    public void notifyContentUpdated() {
-        setSubTextVisible(mSubTextVisible);
-    }
+    public void notifyContentUpdated() {};
 
     /**
      * @return true if this template might need to be clipped with a round rect to make it look
@@ -70,14 +65,6 @@
     }
 
     /**
-     * Change the subTextVisibility
-     * @param visible Should the subtext be visible
-     */
-    public void setSubTextVisible(boolean visible) {
-        mSubTextVisible = visible;
-    }
-
-    /**
      * Update the appearance of the expand button.
      *
      * @param expandable should this view be expandable
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
index fafea98..5fb6fec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridNotificationView.java
@@ -23,6 +23,8 @@
 
 import com.android.keyguard.AlphaOptimizedLinearLayout;
 import com.android.systemui.R;
+import com.android.systemui.ViewInvertHelper;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
 
 /**
  * A hybrid view which may contain information about one ore more notifications.
@@ -31,6 +33,7 @@
 
     protected TextView mTitleView;
     protected TextView mTextView;
+    private ViewInvertHelper mInvertHelper;
 
     public HybridNotificationView(Context context) {
         this(context, null);
@@ -54,6 +57,7 @@
         super.onFinishInflate();
         mTitleView = (TextView) findViewById(R.id.notification_title);
         mTextView = (TextView) findViewById(R.id.notification_text);
+        mInvertHelper = new ViewInvertHelper(this, NotificationPanelView.DOZE_ANIMATION_DURATION);
     }
 
     public void bind(CharSequence title) {
@@ -65,4 +69,8 @@
         mTextView.setText(text);
         requestLayout();
     }
+
+    public void setDark(boolean dark, boolean fade, long delay) {
+        mInvertHelper.setInverted(dark, fade, delay);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index c4930a9..1372cca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -62,11 +62,15 @@
 
     private boolean mDetailTransitioning;
     private ViewGroup mExpandedGroup;
+    private ViewGroup mDateTimeGroup;
+    private View mEmergencyOnly;
     private TextView mQsDetailHeaderTitle;
     private boolean mListening;
     private AlarmManager.AlarmClockInfo mNextAlarm;
 
     private QuickQSPanel mHeaderQsPanel;
+    private boolean mShowEmergencyCallsOnly;
+    private float mDateTimeTranslation;
 
     public QuickStatusBarHeader(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -76,6 +80,10 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
+        mEmergencyOnly = findViewById(R.id.header_emergency_calls_only);
+        mDateTimeTranslation = mContext.getResources().getDimension(
+                R.dimen.qs_date_anim_translation);
+        mDateTimeGroup = (ViewGroup) findViewById(R.id.date_time_group);
         mExpandedGroup = (ViewGroup) findViewById(R.id.expanded_group);
 
         mHeaderQsPanel = (QuickQSPanel) findViewById(R.id.quick_qs_panel);
@@ -141,6 +149,9 @@
         mExpandedGroup.setVisibility(headerExpansionFraction > 0 ? View.VISIBLE : View.INVISIBLE);
         mHeaderQsPanel.setAlpha(1 - headerExpansionFraction);
         mHeaderQsPanel.setVisibility(headerExpansionFraction < 1 ? View.VISIBLE : View.INVISIBLE);
+
+        mDateTimeGroup.setTranslationY(headerExpansionFraction * mDateTimeTranslation);
+        mEmergencyOnly.setAlpha(headerExpansionFraction);
     }
 
     public void setListening(boolean listening) {
@@ -160,6 +171,8 @@
     private void updateVisibilities() {
         mAlarmStatus.setVisibility(mAlarmShowing ? View.VISIBLE : View.GONE);
         mQsDetailHeader.setVisibility(mExpanded && mShowingDetail ? View.VISIBLE : View.INVISIBLE);
+        mEmergencyOnly.setVisibility(mExpanded && mShowEmergencyCallsOnly
+                ? View.VISIBLE : View.INVISIBLE);
         mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
                 TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
     }
@@ -256,8 +269,14 @@
     }
 
     @Override
-    public void setEmergencyCallsOnly(boolean emergencyOnly) {
-        // Don't care.
+    public void setEmergencyCallsOnly(boolean show) {
+        boolean changed = show != mShowEmergencyCallsOnly;
+        if (changed) {
+            mShowEmergencyCallsOnly = show;
+            if (mExpanded) {
+                updateEverything();
+            }
+        }
     }
 
     private final QSPanel.Callback mQsPanelCallback = new QSPanel.Callback() {
@@ -314,6 +333,7 @@
 
         private void handleShowingDetail(final QSTile.DetailAdapter detail) {
             final boolean showingDetail = detail != null;
+            transition(mDateTimeGroup, !showingDetail);
             transition(mExpandedGroup, !showingDetail);
             if (mAlarmShowing) {
                 transition(mAlarmStatus, !showingDetail);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index 9015a0e..2b71ce9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -23,9 +23,11 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.R;
+import com.android.systemui.ViewInvertHelper;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.HybridNotificationViewManager;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -49,6 +51,7 @@
     private final int mNotificatonTopPadding;
     private final HybridNotificationViewManager mHybridViewManager;
     private final float mCollapsedBottompadding;
+    private ViewInvertHelper mOverflowInvertHelper;
     private boolean mChildrenExpanded;
     private ExpandableNotificationRow mNotificationParent;
     private HybridNotificationView mGroupOverflowContainer;
@@ -172,12 +175,17 @@
         if (hasOverflow) {
             mGroupOverflowContainer = mHybridViewManager.bindFromNotificationGroup(
                     mGroupOverflowContainer, mChildren, lastVisibleIndex + 1);
+            if (mOverflowInvertHelper == null) {
+                mOverflowInvertHelper= new ViewInvertHelper(mGroupOverflowContainer,
+                        NotificationPanelView.DOZE_ANIMATION_DURATION);
+            }
             if (mGroupOverFlowState == null) {
                 mGroupOverFlowState = new ViewState();
             }
         } else if (mGroupOverflowContainer != null) {
             removeView(mGroupOverflowContainer);
             mGroupOverflowContainer = null;
+            mOverflowInvertHelper = null;
             mGroupOverFlowState = null;
         }
     }
@@ -324,7 +332,7 @@
         if (!likeCollapsed && (mChildrenExpanded || mNotificationParent.isUserLocked())) {
             return NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
         }
-        if (mNotificationParent.isExpanded()) {
+        if (mNotificationParent.isExpanded() || mNotificationParent.isHeadsUp()) {
             return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED;
         }
         return NUMBER_OF_CHILDREN_WHEN_COLLAPSED;
@@ -387,16 +395,17 @@
             boolean withDelays, long baseDelay, long duration) {
         int childCount = mChildren.size();
         ViewState tmpState = new ViewState();
-        int notGoneIndex = 0;
-        for (int i = 0; i < childCount; i++) {
+        int delayIndex = 0;
+        int maxAllowChildCount = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
+        for (int i = childCount - 1; i >= 0; i--) {
             ExpandableNotificationRow child = mChildren.get(i);
             StackViewState viewState = state.getViewStateForView(child);
             int difference = Math.min(StackStateAnimator.DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN,
-                    notGoneIndex + 1);
+                    delayIndex);
             long delay = withDelays
                     ? difference * StackStateAnimator.ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN
                     : 0;
-            delay += baseDelay;
+            delay = (long) (delay * (mChildrenExpanded ? 1.0f : 0.5f) + baseDelay);
             stateAnimator.startStackAnimations(child, viewState, state, -1, delay);
 
             // layout the divider
@@ -405,11 +414,13 @@
             tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
             tmpState.alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
             stateAnimator.startViewAnimations(divider, tmpState, delay, duration);
-
-            notGoneIndex++;
+            if (i < maxAllowChildCount) {
+                delayIndex++;
+            }
         }
         if (mGroupOverflowContainer != null) {
-            stateAnimator.startViewAnimations(mGroupOverflowContainer, mGroupOverFlowState, -1, 0);
+            stateAnimator.startViewAnimations(mGroupOverflowContainer, mGroupOverFlowState,
+                    baseDelay, duration);
         }
     }
 
@@ -443,4 +454,10 @@
     public int getMinHeight() {
         return getIntrinsicHeight(getMaxAllowedVisibleChildren(true /* forceCollapsed */));
     }
+
+    public void setDark(boolean dark, boolean fade, long delay) {
+        if (mGroupOverflowContainer != null) {
+            mOverflowInvertHelper.setInverted(dark, fade, delay);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 960fb4b..5f57a76 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -1233,14 +1233,6 @@
                 }
             }
 
-            // direct-callback alarms must be wakeup alarms (otherwise they should just be
-            // posting work to a Handler)
-            if (directReceiver != null) {
-                if (type != RTC_WAKEUP && type != ELAPSED_REALTIME_WAKEUP) {
-                    throw new IllegalArgumentException("Only wakeup alarms can use AlarmReceivers");
-                }
-            }
-
             if (workSource != null) {
                 getContext().enforcePermission(
                         android.Manifest.permission.UPDATE_DEVICE_STATS,
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index ede92fb..7fcedc6 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -788,6 +788,9 @@
 
     @Override
     public void startWatchingMode(int op, String packageName, IAppOpsCallback callback) {
+        if (callback == null) {
+            return;
+        }
         synchronized (this) {
             op = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
             Callback cb = mModeWatchers.get(callback.asBinder());
@@ -816,6 +819,9 @@
 
     @Override
     public void stopWatchingMode(IAppOpsCallback callback) {
+        if (callback == null) {
+            return;
+        }
         synchronized (this) {
             Callback cb = mModeWatchers.remove(callback.asBinder());
             if (cb != null) {
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 45c1ed2..0282a72 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -3536,6 +3536,7 @@
         private static final String ATTR_LABEL = "label";
         private static final String ATTR_ICON = "icon";
         private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
+        private static final String ATTR_IME_SUBTYPE_LANGUAGE_TAG = "languageTag";
         private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode";
         private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue";
         private static final String ATTR_IS_AUXILIARY = "isAuxiliary";
@@ -3629,6 +3630,8 @@
                         out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId()));
                         out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId()));
                         out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
+                        out.attribute(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG,
+                                subtype.getLanguageTag());
                         out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode());
                         out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue());
                         out.attribute(null, ATTR_IS_AUXILIARY,
@@ -3690,6 +3693,8 @@
                                 parser.getAttributeValue(null, ATTR_LABEL));
                         final String imeSubtypeLocale =
                                 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
+                        final String languageTag =
+                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG);
                         final String imeSubtypeMode =
                                 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE);
                         final String imeSubtypeExtraValue =
@@ -3700,6 +3705,7 @@
                                 .setSubtypeNameResId(label)
                                 .setSubtypeIconResId(icon)
                                 .setSubtypeLocale(imeSubtypeLocale)
+                                .setLanguageTag(languageTag)
                                 .setSubtypeMode(imeSubtypeMode)
                                 .setSubtypeExtraValue(imeSubtypeExtraValue)
                                 .setIsAuxiliary(isAuxiliary)
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 39e25ee..1bab7b9 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -1075,9 +1075,18 @@
                 }
             }
 
+            // This is safe to do even if we are skipping the broadcast, and we need
+            // this information now to evaluate whether it is going to be allowed to run.
+            final int receiverUid = info.activityInfo.applicationInfo.uid;
+            // If it's a singleton, it needs to be the same app or a special app
+            if (r.callingUid != Process.SYSTEM_UID && isSingleton
+                    && mService.isValidSingletonCall(r.callingUid, receiverUid)) {
+                info.activityInfo = mService.getActivityInfoForUser(info.activityInfo, 0);
+            }
             String targetProcess = info.activityInfo.processName;
             ProcessRecord app = mService.getProcessRecordLocked(targetProcess,
                     info.activityInfo.applicationInfo.uid, false);
+
             if (!skip) {
                 final int allowed = mService.checkAllowBackgroundLocked(
                         info.activityInfo.applicationInfo.uid, info.activityInfo.packageName, -1);
@@ -1118,12 +1127,6 @@
 
             r.state = BroadcastRecord.APP_RECEIVE;
             r.curComponent = component;
-            final int receiverUid = info.activityInfo.applicationInfo.uid;
-            // If it's a singleton, it needs to be the same app or a special app
-            if (r.callingUid != Process.SYSTEM_UID && isSingleton
-                    && mService.isValidSingletonCall(r.callingUid, receiverUid)) {
-                info.activityInfo = mService.getActivityInfoForUser(info.activityInfo, 0);
-            }
             r.curReceiver = info.activityInfo;
             if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) {
                 Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, "
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 33f39bc..c83012c 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -159,8 +159,8 @@
 public class NotificationManagerService extends SystemService {
     static final String TAG = "NotificationService";
     static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
-    public static final boolean ENABLE_CHILD_NOTIFICATIONS = Build.IS_DEBUGGABLE
-            && SystemProperties.getBoolean("debug.child_notifs", false);
+    public static final boolean ENABLE_CHILD_NOTIFICATIONS
+            = SystemProperties.getBoolean("debug.child_notifs", true);
 
     static final int MAX_PACKAGE_NOTIFICATIONS = 50;
 
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 812d9b6..2329b42 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -389,7 +389,6 @@
     }
 
     private void scheduleRenew() {
-        mRenewAlarm.cancel();
         if (mDhcpLeaseExpiry != 0) {
             long now = SystemClock.elapsedRealtime();
             long alarmTime = (now + mDhcpLeaseExpiry) / 2;
@@ -822,6 +821,11 @@
                     return NOT_HANDLED;
             }
         }
+
+        @Override
+        public void exit() {
+            mRenewAlarm.cancel();
+        }
     }
 
     class DhcpRenewingState extends PacketRetransmittingState {