DO NOT MERGE Revert "Add support lib version meta-data to AndroidManifest.xml"
am: c1706b062a  -s ours

Change-Id: I7a029b45d902d263a04efb0c6754bfb0a5798cde
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 4caef90..2811ea9 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,5 +1,5 @@
 [Hook Scripts]
-checkstyle_hook = ../../development/tools/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
+checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
 
 [Builtin Hooks]
 commit_msg_changeid_field = true
diff --git a/annotations/src/android/support/annotation/IntDef.java b/annotations/src/android/support/annotation/IntDef.java
index be2e2b8..f621b7f 100644
--- a/annotations/src/android/support/annotation/IntDef.java
+++ b/annotations/src/android/support/annotation/IntDef.java
@@ -15,16 +15,12 @@
  */
 package android.support.annotation;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
 import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
 /**
  * Denotes that the annotated element of integer type, represents
  * a logical type and that its value should be one of the explicitly
@@ -47,7 +43,7 @@
  * For a flag, set the flag attribute:
  * <pre><code>
  *  &#64;IntDef(
- *      flag = true
+ *      flag = true,
  *      value = {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
  * </code></pre>
  */
diff --git a/api/25.1.0.txt b/api/25.1.0.txt
index 38141fe..0b2129f 100644
--- a/api/25.1.0.txt
+++ b/api/25.1.0.txt
@@ -858,6 +858,173 @@
 
 }
 
+package android.support.media {
+
+  public class ExifInterface {
+    ctor public ExifInterface(java.lang.String) throws java.io.IOException;
+    ctor public ExifInterface(java.io.InputStream) throws java.io.IOException;
+    method public double getAltitude(double);
+    method public java.lang.String getAttribute(java.lang.String);
+    method public double getAttributeDouble(java.lang.String, double);
+    method public int getAttributeInt(java.lang.String, int);
+    method public boolean getLatLong(float[]);
+    method public byte[] getThumbnail();
+    method public android.graphics.Bitmap getThumbnailBitmap();
+    method public byte[] getThumbnailBytes();
+    method public long[] getThumbnailRange();
+    method public boolean hasThumbnail();
+    method public boolean isThumbnailCompressed();
+    method public void saveAttributes() throws java.io.IOException;
+    method public void setAttribute(java.lang.String, java.lang.String);
+    field public static final int ORIENTATION_FLIP_HORIZONTAL = 2; // 0x2
+    field public static final int ORIENTATION_FLIP_VERTICAL = 4; // 0x4
+    field public static final int ORIENTATION_NORMAL = 1; // 0x1
+    field public static final int ORIENTATION_ROTATE_180 = 3; // 0x3
+    field public static final int ORIENTATION_ROTATE_270 = 8; // 0x8
+    field public static final int ORIENTATION_ROTATE_90 = 6; // 0x6
+    field public static final int ORIENTATION_TRANSPOSE = 5; // 0x5
+    field public static final int ORIENTATION_TRANSVERSE = 7; // 0x7
+    field public static final int ORIENTATION_UNDEFINED = 0; // 0x0
+    field public static final java.lang.String TAG_APERTURE_VALUE = "ApertureValue";
+    field public static final java.lang.String TAG_ARTIST = "Artist";
+    field public static final java.lang.String TAG_BITS_PER_SAMPLE = "BitsPerSample";
+    field public static final java.lang.String TAG_BRIGHTNESS_VALUE = "BrightnessValue";
+    field public static final java.lang.String TAG_CFA_PATTERN = "CFAPattern";
+    field public static final java.lang.String TAG_COLOR_SPACE = "ColorSpace";
+    field public static final java.lang.String TAG_COMPONENTS_CONFIGURATION = "ComponentsConfiguration";
+    field public static final java.lang.String TAG_COMPRESSED_BITS_PER_PIXEL = "CompressedBitsPerPixel";
+    field public static final java.lang.String TAG_COMPRESSION = "Compression";
+    field public static final java.lang.String TAG_CONTRAST = "Contrast";
+    field public static final java.lang.String TAG_COPYRIGHT = "Copyright";
+    field public static final java.lang.String TAG_CUSTOM_RENDERED = "CustomRendered";
+    field public static final java.lang.String TAG_DATETIME = "DateTime";
+    field public static final java.lang.String TAG_DATETIME_DIGITIZED = "DateTimeDigitized";
+    field public static final java.lang.String TAG_DATETIME_ORIGINAL = "DateTimeOriginal";
+    field public static final java.lang.String TAG_DEFAULT_CROP_SIZE = "DefaultCropSize";
+    field public static final java.lang.String TAG_DEVICE_SETTING_DESCRIPTION = "DeviceSettingDescription";
+    field public static final java.lang.String TAG_DIGITAL_ZOOM_RATIO = "DigitalZoomRatio";
+    field public static final java.lang.String TAG_DNG_VERSION = "DNGVersion";
+    field public static final java.lang.String TAG_EXIF_VERSION = "ExifVersion";
+    field public static final java.lang.String TAG_EXPOSURE_BIAS_VALUE = "ExposureBiasValue";
+    field public static final java.lang.String TAG_EXPOSURE_INDEX = "ExposureIndex";
+    field public static final java.lang.String TAG_EXPOSURE_MODE = "ExposureMode";
+    field public static final java.lang.String TAG_EXPOSURE_PROGRAM = "ExposureProgram";
+    field public static final java.lang.String TAG_EXPOSURE_TIME = "ExposureTime";
+    field public static final java.lang.String TAG_FILE_SOURCE = "FileSource";
+    field public static final java.lang.String TAG_FLASH = "Flash";
+    field public static final java.lang.String TAG_FLASHPIX_VERSION = "FlashpixVersion";
+    field public static final java.lang.String TAG_FLASH_ENERGY = "FlashEnergy";
+    field public static final java.lang.String TAG_FOCAL_LENGTH = "FocalLength";
+    field public static final java.lang.String TAG_FOCAL_LENGTH_IN_35MM_FILM = "FocalLengthIn35mmFilm";
+    field public static final java.lang.String TAG_FOCAL_PLANE_RESOLUTION_UNIT = "FocalPlaneResolutionUnit";
+    field public static final java.lang.String TAG_FOCAL_PLANE_X_RESOLUTION = "FocalPlaneXResolution";
+    field public static final java.lang.String TAG_FOCAL_PLANE_Y_RESOLUTION = "FocalPlaneYResolution";
+    field public static final java.lang.String TAG_F_NUMBER = "FNumber";
+    field public static final java.lang.String TAG_GAIN_CONTROL = "GainControl";
+    field public static final java.lang.String TAG_GPS_ALTITUDE = "GPSAltitude";
+    field public static final java.lang.String TAG_GPS_ALTITUDE_REF = "GPSAltitudeRef";
+    field public static final java.lang.String TAG_GPS_AREA_INFORMATION = "GPSAreaInformation";
+    field public static final java.lang.String TAG_GPS_DATESTAMP = "GPSDateStamp";
+    field public static final java.lang.String TAG_GPS_DEST_BEARING = "GPSDestBearing";
+    field public static final java.lang.String TAG_GPS_DEST_BEARING_REF = "GPSDestBearingRef";
+    field public static final java.lang.String TAG_GPS_DEST_DISTANCE = "GPSDestDistance";
+    field public static final java.lang.String TAG_GPS_DEST_DISTANCE_REF = "GPSDestDistanceRef";
+    field public static final java.lang.String TAG_GPS_DEST_LATITUDE = "GPSDestLatitude";
+    field public static final java.lang.String TAG_GPS_DEST_LATITUDE_REF = "GPSDestLatitudeRef";
+    field public static final java.lang.String TAG_GPS_DEST_LONGITUDE = "GPSDestLongitude";
+    field public static final java.lang.String TAG_GPS_DEST_LONGITUDE_REF = "GPSDestLongitudeRef";
+    field public static final java.lang.String TAG_GPS_DIFFERENTIAL = "GPSDifferential";
+    field public static final java.lang.String TAG_GPS_DOP = "GPSDOP";
+    field public static final java.lang.String TAG_GPS_IMG_DIRECTION = "GPSImgDirection";
+    field public static final java.lang.String TAG_GPS_IMG_DIRECTION_REF = "GPSImgDirectionRef";
+    field public static final java.lang.String TAG_GPS_LATITUDE = "GPSLatitude";
+    field public static final java.lang.String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef";
+    field public static final java.lang.String TAG_GPS_LONGITUDE = "GPSLongitude";
+    field public static final java.lang.String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef";
+    field public static final java.lang.String TAG_GPS_MAP_DATUM = "GPSMapDatum";
+    field public static final java.lang.String TAG_GPS_MEASURE_MODE = "GPSMeasureMode";
+    field public static final java.lang.String TAG_GPS_PROCESSING_METHOD = "GPSProcessingMethod";
+    field public static final java.lang.String TAG_GPS_SATELLITES = "GPSSatellites";
+    field public static final java.lang.String TAG_GPS_SPEED = "GPSSpeed";
+    field public static final java.lang.String TAG_GPS_SPEED_REF = "GPSSpeedRef";
+    field public static final java.lang.String TAG_GPS_STATUS = "GPSStatus";
+    field public static final java.lang.String TAG_GPS_TIMESTAMP = "GPSTimeStamp";
+    field public static final java.lang.String TAG_GPS_TRACK = "GPSTrack";
+    field public static final java.lang.String TAG_GPS_TRACK_REF = "GPSTrackRef";
+    field public static final java.lang.String TAG_GPS_VERSION_ID = "GPSVersionID";
+    field public static final java.lang.String TAG_IMAGE_DESCRIPTION = "ImageDescription";
+    field public static final java.lang.String TAG_IMAGE_LENGTH = "ImageLength";
+    field public static final java.lang.String TAG_IMAGE_UNIQUE_ID = "ImageUniqueID";
+    field public static final java.lang.String TAG_IMAGE_WIDTH = "ImageWidth";
+    field public static final java.lang.String TAG_INTEROPERABILITY_INDEX = "InteroperabilityIndex";
+    field public static final java.lang.String TAG_ISO_SPEED_RATINGS = "ISOSpeedRatings";
+    field public static final java.lang.String TAG_JPEG_INTERCHANGE_FORMAT = "JPEGInterchangeFormat";
+    field public static final java.lang.String TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = "JPEGInterchangeFormatLength";
+    field public static final java.lang.String TAG_LIGHT_SOURCE = "LightSource";
+    field public static final java.lang.String TAG_MAKE = "Make";
+    field public static final java.lang.String TAG_MAKER_NOTE = "MakerNote";
+    field public static final java.lang.String TAG_MAX_APERTURE_VALUE = "MaxApertureValue";
+    field public static final java.lang.String TAG_METERING_MODE = "MeteringMode";
+    field public static final java.lang.String TAG_MODEL = "Model";
+    field public static final java.lang.String TAG_NEW_SUBFILE_TYPE = "NewSubfileType";
+    field public static final java.lang.String TAG_OECF = "OECF";
+    field public static final java.lang.String TAG_ORF_ASPECT_FRAME = "AspectFrame";
+    field public static final java.lang.String TAG_ORF_PREVIEW_IMAGE_LENGTH = "PreviewImageLength";
+    field public static final java.lang.String TAG_ORF_PREVIEW_IMAGE_START = "PreviewImageStart";
+    field public static final java.lang.String TAG_ORF_THUMBNAIL_IMAGE = "ThumbnailImage";
+    field public static final java.lang.String TAG_ORIENTATION = "Orientation";
+    field public static final java.lang.String TAG_PHOTOMETRIC_INTERPRETATION = "PhotometricInterpretation";
+    field public static final java.lang.String TAG_PIXEL_X_DIMENSION = "PixelXDimension";
+    field public static final java.lang.String TAG_PIXEL_Y_DIMENSION = "PixelYDimension";
+    field public static final java.lang.String TAG_PLANAR_CONFIGURATION = "PlanarConfiguration";
+    field public static final java.lang.String TAG_PRIMARY_CHROMATICITIES = "PrimaryChromaticities";
+    field public static final java.lang.String TAG_REFERENCE_BLACK_WHITE = "ReferenceBlackWhite";
+    field public static final java.lang.String TAG_RELATED_SOUND_FILE = "RelatedSoundFile";
+    field public static final java.lang.String TAG_RESOLUTION_UNIT = "ResolutionUnit";
+    field public static final java.lang.String TAG_ROWS_PER_STRIP = "RowsPerStrip";
+    field public static final java.lang.String TAG_RW2_ISO = "ISO";
+    field public static final java.lang.String TAG_RW2_JPG_FROM_RAW = "JpgFromRaw";
+    field public static final java.lang.String TAG_RW2_SENSOR_BOTTOM_BORDER = "SensorBottomBorder";
+    field public static final java.lang.String TAG_RW2_SENSOR_LEFT_BORDER = "SensorLeftBorder";
+    field public static final java.lang.String TAG_RW2_SENSOR_RIGHT_BORDER = "SensorRightBorder";
+    field public static final java.lang.String TAG_RW2_SENSOR_TOP_BORDER = "SensorTopBorder";
+    field public static final java.lang.String TAG_SAMPLES_PER_PIXEL = "SamplesPerPixel";
+    field public static final java.lang.String TAG_SATURATION = "Saturation";
+    field public static final java.lang.String TAG_SCENE_CAPTURE_TYPE = "SceneCaptureType";
+    field public static final java.lang.String TAG_SCENE_TYPE = "SceneType";
+    field public static final java.lang.String TAG_SENSING_METHOD = "SensingMethod";
+    field public static final java.lang.String TAG_SHARPNESS = "Sharpness";
+    field public static final java.lang.String TAG_SHUTTER_SPEED_VALUE = "ShutterSpeedValue";
+    field public static final java.lang.String TAG_SOFTWARE = "Software";
+    field public static final java.lang.String TAG_SPATIAL_FREQUENCY_RESPONSE = "SpatialFrequencyResponse";
+    field public static final java.lang.String TAG_SPECTRAL_SENSITIVITY = "SpectralSensitivity";
+    field public static final java.lang.String TAG_STRIP_BYTE_COUNTS = "StripByteCounts";
+    field public static final java.lang.String TAG_STRIP_OFFSETS = "StripOffsets";
+    field public static final java.lang.String TAG_SUBFILE_TYPE = "SubfileType";
+    field public static final java.lang.String TAG_SUBJECT_AREA = "SubjectArea";
+    field public static final java.lang.String TAG_SUBJECT_DISTANCE = "SubjectDistance";
+    field public static final java.lang.String TAG_SUBJECT_DISTANCE_RANGE = "SubjectDistanceRange";
+    field public static final java.lang.String TAG_SUBJECT_LOCATION = "SubjectLocation";
+    field public static final java.lang.String TAG_SUBSEC_TIME = "SubSecTime";
+    field public static final java.lang.String TAG_SUBSEC_TIME_DIGITIZED = "SubSecTimeDigitized";
+    field public static final java.lang.String TAG_SUBSEC_TIME_ORIGINAL = "SubSecTimeOriginal";
+    field public static final java.lang.String TAG_THUMBNAIL_IMAGE_LENGTH = "ThumbnailImageLength";
+    field public static final java.lang.String TAG_THUMBNAIL_IMAGE_WIDTH = "ThumbnailImageWidth";
+    field public static final java.lang.String TAG_TRANSFER_FUNCTION = "TransferFunction";
+    field public static final java.lang.String TAG_USER_COMMENT = "UserComment";
+    field public static final java.lang.String TAG_WHITE_BALANCE = "WhiteBalance";
+    field public static final java.lang.String TAG_WHITE_POINT = "WhitePoint";
+    field public static final java.lang.String TAG_X_RESOLUTION = "XResolution";
+    field public static final java.lang.String TAG_Y_CB_CR_COEFFICIENTS = "YCbCrCoefficients";
+    field public static final java.lang.String TAG_Y_CB_CR_POSITIONING = "YCbCrPositioning";
+    field public static final java.lang.String TAG_Y_CB_CR_SUB_SAMPLING = "YCbCrSubSampling";
+    field public static final java.lang.String TAG_Y_RESOLUTION = "YResolution";
+    field public static final int WHITEBALANCE_AUTO = 0; // 0x0
+    field public static final int WHITEBALANCE_MANUAL = 1; // 0x1
+  }
+
+}
+
 package android.support.percent {
 
   public class PercentFrameLayout extends android.widget.FrameLayout {
@@ -1761,10 +1928,9 @@
     method public abstract void onHeaderSelected(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
   }
 
-  public abstract class MediaControllerGlue extends android.support.v17.leanback.app.PlaybackControlGlue {
-    ctor public deprecated MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[]);
-    ctor public deprecated MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[], int[]);
-    ctor public MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost, int[], int[]);
+  public abstract deprecated class MediaControllerGlue extends android.support.v17.leanback.app.PlaybackControlGlue {
+    ctor public MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[]);
+    ctor public MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[], int[]);
     method public void attachToMediaController(android.support.v4.media.session.MediaControllerCompat);
     method public void detach();
     method public int getCurrentPosition();
@@ -1815,59 +1981,25 @@
     method public final void setLogoResourceId(int);
   }
 
-  public abstract class PlaybackControlGlue extends android.support.v17.leanback.app.PlaybackGlue implements android.support.v17.leanback.widget.OnActionClickedListener android.view.View.OnKeyListener {
+  public abstract deprecated class PlaybackControlGlue extends android.support.v17.leanback.media.PlaybackControlGlue {
     ctor public PlaybackControlGlue(android.content.Context, int[]);
     ctor public PlaybackControlGlue(android.content.Context, int[], int[]);
-    ctor public deprecated PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[]);
-    ctor public deprecated PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[], int[]);
-    ctor public PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost, int[], int[]);
+    ctor public PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[]);
+    ctor public PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[], int[]);
     method public android.support.v17.leanback.widget.PlaybackControlsRowPresenter createControlsRowAndPresenter();
     method protected android.support.v17.leanback.widget.SparseArrayObjectAdapter createPrimaryActionsAdapter(android.support.v17.leanback.widget.PresenterSelector);
-    method public void enableProgressUpdating(boolean);
-    method public android.support.v17.leanback.widget.PlaybackControlsRow getControlsRow();
-    method public abstract int getCurrentPosition();
-    method public abstract int getCurrentSpeedId();
-    method public int[] getFastForwardSpeeds();
-    method public deprecated android.support.v17.leanback.app.PlaybackOverlayFragment getFragment();
-    method public abstract android.graphics.drawable.Drawable getMediaArt();
-    method public abstract int getMediaDuration();
-    method public abstract java.lang.CharSequence getMediaSubtitle();
-    method public abstract java.lang.CharSequence getMediaTitle();
+    method public android.support.v17.leanback.app.PlaybackOverlayFragment getFragment();
     method public deprecated android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
-    method public int[] getRewindSpeeds();
-    method public abstract long getSupportedActions();
-    method public int getUpdatePeriod();
-    method public abstract boolean hasValidMedia();
-    method public boolean isFadingEnabled();
-    method public abstract boolean isMediaPlaying();
-    method public void onActionClicked(android.support.v17.leanback.widget.Action);
-    method public boolean onKey(android.view.View, int, android.view.KeyEvent);
-    method protected void onMetadataChanged();
-    method protected deprecated void onRowChanged(android.support.v17.leanback.widget.PlaybackControlsRow);
-    method protected void onStateChanged();
+    method public final void next();
+    method protected void onRowChanged(android.support.v17.leanback.widget.PlaybackControlsRow);
+    method public final void pause();
     method protected deprecated void pausePlayback();
-    method public void setControlsRow(android.support.v17.leanback.widget.PlaybackControlsRow);
-    method public void setFadingEnabled(boolean);
+    method public final void play(int);
+    method public final void previous();
     method public deprecated void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
     method protected deprecated void skipToNext();
     method protected deprecated void skipToPrevious();
     method protected deprecated void startPlayback(int);
-    method public void updateProgress();
-    field public static final int ACTION_CUSTOM_LEFT_FIRST = 1; // 0x1
-    field public static final int ACTION_CUSTOM_RIGHT_FIRST = 4096; // 0x1000
-    field public static final int ACTION_FAST_FORWARD = 128; // 0x80
-    field public static final int ACTION_PLAY_PAUSE = 64; // 0x40
-    field public static final int ACTION_REWIND = 32; // 0x20
-    field public static final int ACTION_SKIP_TO_NEXT = 256; // 0x100
-    field public static final int ACTION_SKIP_TO_PREVIOUS = 16; // 0x10
-    field public static final int PLAYBACK_SPEED_FAST_L0 = 10; // 0xa
-    field public static final int PLAYBACK_SPEED_FAST_L1 = 11; // 0xb
-    field public static final int PLAYBACK_SPEED_FAST_L2 = 12; // 0xc
-    field public static final int PLAYBACK_SPEED_FAST_L3 = 13; // 0xd
-    field public static final int PLAYBACK_SPEED_FAST_L4 = 14; // 0xe
-    field public static final int PLAYBACK_SPEED_INVALID = -1; // 0xffffffff
-    field public static final int PLAYBACK_SPEED_NORMAL = 1; // 0x1
-    field public static final int PLAYBACK_SPEED_PAUSED = 0; // 0x0
   }
 
   public static abstract deprecated interface PlaybackControlGlue.InputEventHandler {
@@ -1901,16 +2033,12 @@
     method public void fadeOut();
     method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
     method public int getBackgroundType();
-    method public final android.view.View.OnKeyListener getEventHandler();
-    method public android.support.v17.leanback.app.PlaybackFragment.OnFadeCompleteListener getFadeCompleteListener();
     method public boolean isFadingEnabled();
     method public void notifyPlaybackRowChanged();
-    method public void resetFocus();
     method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
     method public void setBackgroundType(int);
-    method public void setFadeCompleteListener(android.support.v17.leanback.app.PlaybackFragment.OnFadeCompleteListener);
     method public void setFadingEnabled(boolean);
-    method public void setHostLifecycleCallback(android.support.v17.leanback.app.PlaybackGlue.HostLifecycleCallback);
+    method public void setHostCallback(android.support.v17.leanback.media.PlaybackGlueHost.HostCallback);
     method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
     method public final void setOnKeyInterceptListener(android.view.View.OnKeyListener);
     method public void setOnPlaybackItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
@@ -1924,52 +2052,10 @@
     field public static final int BG_NONE = 0; // 0x0
   }
 
-  public static class PlaybackFragment.OnFadeCompleteListener {
-    ctor public PlaybackFragment.OnFadeCompleteListener();
-    method public void onFadeInComplete();
-    method public void onFadeOutComplete();
-  }
-
-  public class PlaybackFragmentGlueHost extends android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost {
+  public class PlaybackFragmentGlueHost extends android.support.v17.leanback.media.PlaybackGlueHost {
     ctor public PlaybackFragmentGlueHost(android.support.v17.leanback.app.PlaybackFragment);
   }
 
-  public abstract class PlaybackGlue {
-    ctor public PlaybackGlue(android.content.Context);
-    method public android.content.Context getContext();
-    method public android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost getHost();
-    method public boolean isReadyForPlayback();
-    method public void next();
-    method public void onDetachedFromHost();
-    method public void pause();
-    method public void play();
-    method public void previous();
-    method public void setHost(android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost);
-    method public void setPlayerCallback(android.support.v17.leanback.app.PlaybackGlue.PlayerCallback);
-  }
-
-  public static abstract class PlaybackGlue.HostLifecycleCallback {
-    ctor public PlaybackGlue.HostLifecycleCallback();
-    method public abstract void onHostStart();
-    method public abstract void onHostStop();
-  }
-
-  public static class PlaybackGlue.PlaybackGlueHost {
-    ctor public PlaybackGlue.PlaybackGlueHost();
-    method public void notifyPlaybackRowChanged();
-    method public void setFadingEnabled(boolean);
-    method public void setHostLifeCycleCallback(android.support.v17.leanback.app.PlaybackGlue.HostLifecycleCallback);
-    method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
-    method public void setOnKeyInterceptListener(android.view.View.OnKeyListener);
-    method public void setPlaybackRow(android.support.v17.leanback.widget.Row);
-    method public void setPlaybackRowPresenter(android.support.v17.leanback.widget.PlaybackRowPresenter);
-  }
-
-  public static abstract class PlaybackGlue.PlayerCallback {
-    ctor public PlaybackGlue.PlayerCallback();
-    method public abstract void onReadyForPlayback();
-  }
-
   public deprecated class PlaybackOverlayFragment extends android.support.v17.leanback.app.DetailsFragment {
     ctor public PlaybackOverlayFragment();
     method public void fadeOut();
@@ -2031,16 +2117,12 @@
     method public void fadeOut();
     method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
     method public int getBackgroundType();
-    method public final android.view.View.OnKeyListener getEventHandler();
-    method public android.support.v17.leanback.app.PlaybackSupportFragment.OnFadeCompleteListener getFadeCompleteListener();
     method public boolean isFadingEnabled();
     method public void notifyPlaybackRowChanged();
-    method public void resetFocus();
     method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
     method public void setBackgroundType(int);
-    method public void setFadeCompleteListener(android.support.v17.leanback.app.PlaybackSupportFragment.OnFadeCompleteListener);
     method public void setFadingEnabled(boolean);
-    method public void setHostLifecycleCallback(android.support.v17.leanback.app.PlaybackGlue.HostLifecycleCallback);
+    method public void setHostCallback(android.support.v17.leanback.media.PlaybackGlueHost.HostCallback);
     method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
     method public final void setOnKeyInterceptListener(android.view.View.OnKeyListener);
     method public void setOnPlaybackItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
@@ -2054,13 +2136,7 @@
     field public static final int BG_NONE = 0; // 0x0
   }
 
-  public static class PlaybackSupportFragment.OnFadeCompleteListener {
-    ctor public PlaybackSupportFragment.OnFadeCompleteListener();
-    method public void onFadeInComplete();
-    method public void onFadeOutComplete();
-  }
-
-  public class PlaybackSupportFragmentGlueHost extends android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost {
+  public class PlaybackSupportFragmentGlueHost extends android.support.v17.leanback.media.PlaybackGlueHost {
     ctor public PlaybackSupportFragmentGlueHost(android.support.v17.leanback.app.PlaybackSupportFragment);
   }
 
@@ -2186,10 +2262,6 @@
     method public abstract boolean onQueryTextSubmit(java.lang.String);
   }
 
-  public abstract interface SurfaceHolderGlueHost {
-    method public abstract void setSurfaceHolderCallback(android.view.SurfaceHolder.Callback);
-  }
-
   public class VerticalGridFragment extends android.support.v17.leanback.app.BrandedFragment {
     ctor public VerticalGridFragment();
     method protected java.lang.Object createEntranceTransition();
@@ -2261,6 +2333,123 @@
 
 }
 
+package android.support.v17.leanback.media {
+
+  public abstract class MediaControllerGlue extends android.support.v17.leanback.media.PlaybackControlGlue {
+    ctor public MediaControllerGlue(android.content.Context, int[], int[]);
+    method public void attachToMediaController(android.support.v4.media.session.MediaControllerCompat);
+    method public void detach();
+    method public int getCurrentPosition();
+    method public int getCurrentSpeedId();
+    method public android.graphics.drawable.Drawable getMediaArt();
+    method public final android.support.v4.media.session.MediaControllerCompat getMediaController();
+    method public int getMediaDuration();
+    method public java.lang.CharSequence getMediaSubtitle();
+    method public java.lang.CharSequence getMediaTitle();
+    method public long getSupportedActions();
+    method public boolean hasValidMedia();
+    method public boolean isMediaPlaying();
+  }
+
+  public abstract class PlaybackControlGlue extends android.support.v17.leanback.media.PlaybackGlue implements android.support.v17.leanback.widget.OnActionClickedListener android.view.View.OnKeyListener {
+    ctor public PlaybackControlGlue(android.content.Context, int[]);
+    ctor public PlaybackControlGlue(android.content.Context, int[], int[]);
+    method public void enableProgressUpdating(boolean);
+    method public android.support.v17.leanback.widget.PlaybackControlsRow getControlsRow();
+    method public android.support.v17.leanback.widget.PlaybackControlsRowPresenter getControlsRowPresenter();
+    method public abstract int getCurrentPosition();
+    method public abstract int getCurrentSpeedId();
+    method public int[] getFastForwardSpeeds();
+    method public abstract android.graphics.drawable.Drawable getMediaArt();
+    method public abstract int getMediaDuration();
+    method public abstract java.lang.CharSequence getMediaSubtitle();
+    method public abstract java.lang.CharSequence getMediaTitle();
+    method public int[] getRewindSpeeds();
+    method public abstract long getSupportedActions();
+    method public int getUpdatePeriod();
+    method public abstract boolean hasValidMedia();
+    method public boolean isFadingEnabled();
+    method public abstract boolean isMediaPlaying();
+    method public void onActionClicked(android.support.v17.leanback.widget.Action);
+    method protected void onCreateControlsRowAndPresenter();
+    method protected void onCreatePrimaryActions(android.support.v17.leanback.widget.SparseArrayObjectAdapter);
+    method protected void onCreateSecondaryActions(android.support.v17.leanback.widget.ArrayObjectAdapter);
+    method public boolean onKey(android.view.View, int, android.view.KeyEvent);
+    method protected void onMetadataChanged();
+    method protected void onStateChanged();
+    method public void play(int);
+    method public final void play();
+    method public void setControlsRow(android.support.v17.leanback.widget.PlaybackControlsRow);
+    method public void setControlsRowPresenter(android.support.v17.leanback.widget.PlaybackControlsRowPresenter);
+    method public void setFadingEnabled(boolean);
+    method public void updateProgress();
+    field public static final int ACTION_CUSTOM_LEFT_FIRST = 1; // 0x1
+    field public static final int ACTION_CUSTOM_RIGHT_FIRST = 4096; // 0x1000
+    field public static final int ACTION_FAST_FORWARD = 128; // 0x80
+    field public static final int ACTION_PLAY_PAUSE = 64; // 0x40
+    field public static final int ACTION_REWIND = 32; // 0x20
+    field public static final int ACTION_SKIP_TO_NEXT = 256; // 0x100
+    field public static final int ACTION_SKIP_TO_PREVIOUS = 16; // 0x10
+    field public static final int PLAYBACK_SPEED_FAST_L0 = 10; // 0xa
+    field public static final int PLAYBACK_SPEED_FAST_L1 = 11; // 0xb
+    field public static final int PLAYBACK_SPEED_FAST_L2 = 12; // 0xc
+    field public static final int PLAYBACK_SPEED_FAST_L3 = 13; // 0xd
+    field public static final int PLAYBACK_SPEED_FAST_L4 = 14; // 0xe
+    field public static final int PLAYBACK_SPEED_INVALID = -1; // 0xffffffff
+    field public static final int PLAYBACK_SPEED_NORMAL = 1; // 0x1
+    field public static final int PLAYBACK_SPEED_PAUSED = 0; // 0x0
+  }
+
+  public abstract class PlaybackGlue {
+    ctor public PlaybackGlue(android.content.Context);
+    method public android.content.Context getContext();
+    method public android.support.v17.leanback.media.PlaybackGlueHost getHost();
+    method public boolean isReadyForPlayback();
+    method public void next();
+    method protected void onAttachedToHost(android.support.v17.leanback.media.PlaybackGlueHost);
+    method protected void onDetachedFromHost();
+    method protected void onHostPause();
+    method protected void onHostResume();
+    method protected void onHostStart();
+    method protected void onHostStop();
+    method public void pause();
+    method public void play();
+    method public void previous();
+    method public final void setHost(android.support.v17.leanback.media.PlaybackGlueHost);
+    method public void setPlayerCallback(android.support.v17.leanback.media.PlaybackGlue.PlayerCallback);
+  }
+
+  public static abstract class PlaybackGlue.PlayerCallback {
+    ctor public PlaybackGlue.PlayerCallback();
+    method public abstract void onReadyForPlayback();
+  }
+
+  public abstract class PlaybackGlueHost {
+    ctor public PlaybackGlueHost();
+    method public void fadeOut();
+    method public void notifyPlaybackRowChanged();
+    method public void setFadingEnabled(boolean);
+    method public void setHostCallback(android.support.v17.leanback.media.PlaybackGlueHost.HostCallback);
+    method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
+    method public void setOnKeyInterceptListener(android.view.View.OnKeyListener);
+    method public void setPlaybackRow(android.support.v17.leanback.widget.Row);
+    method public void setPlaybackRowPresenter(android.support.v17.leanback.widget.PlaybackRowPresenter);
+  }
+
+  public static abstract class PlaybackGlueHost.HostCallback {
+    ctor public PlaybackGlueHost.HostCallback();
+    method public void onHostPause();
+    method public void onHostResume();
+    method public void onHostStart();
+    method public void onHostStop();
+  }
+
+  public abstract interface SurfaceHolderGlueHost {
+    method public abstract void setSurfaceHolderCallback(android.view.SurfaceHolder.Callback);
+  }
+
+}
+
 package android.support.v17.leanback.system {
 
   public class Settings {
@@ -9574,6 +9763,7 @@
     method public int findLastVisibleItemPosition();
     method public android.support.v7.widget.RecyclerView.LayoutParams generateDefaultLayoutParams();
     method protected int getExtraLayoutSpace(android.support.v7.widget.RecyclerView.State);
+    method public int getInitialItemPrefetchCount();
     method public int getOrientation();
     method public boolean getRecycleChildrenOnDetach();
     method public boolean getReverseLayout();
@@ -9581,6 +9771,7 @@
     method protected boolean isLayoutRTL();
     method public boolean isSmoothScrollbarEnabled();
     method public void scrollToPositionWithOffset(int, int);
+    method public void setInitialPrefetchItemCount(int);
     method public void setOrientation(int);
     method public void setRecycleChildrenOnDetach(boolean);
     method public void setReverseLayout(boolean);
@@ -9971,6 +10162,8 @@
     method public boolean canScrollVertically();
     method public boolean checkLayoutParams(android.support.v7.widget.RecyclerView.LayoutParams);
     method public static int chooseSize(int, int, int);
+    method public void collectAdjacentPrefetchPositions(int, int, android.support.v7.widget.RecyclerView.State, android.support.v7.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry);
+    method public void collectInitialPrefetchPositions(int, android.support.v7.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry);
     method public int computeHorizontalScrollExtent(android.support.v7.widget.RecyclerView.State);
     method public int computeHorizontalScrollOffset(android.support.v7.widget.RecyclerView.State);
     method public int computeHorizontalScrollRange(android.support.v7.widget.RecyclerView.State);
@@ -10096,6 +10289,10 @@
     method public boolean supportsPredictiveItemAnimations();
   }
 
+  public static abstract interface RecyclerView.LayoutManager.LayoutPrefetchRegistry {
+    method public abstract void addPosition(int, int);
+  }
+
   public static class RecyclerView.LayoutManager.Properties {
     ctor public RecyclerView.LayoutManager.Properties();
     field public int orientation;
diff --git a/api/current.txt b/api/current.txt
index fb6b8b8..0b2129f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1928,10 +1928,9 @@
     method public abstract void onHeaderSelected(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
   }
 
-  public abstract class MediaControllerGlue extends android.support.v17.leanback.app.PlaybackControlGlue {
-    ctor public deprecated MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[]);
-    ctor public deprecated MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[], int[]);
-    ctor public MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost, int[], int[]);
+  public abstract deprecated class MediaControllerGlue extends android.support.v17.leanback.app.PlaybackControlGlue {
+    ctor public MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[]);
+    ctor public MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[], int[]);
     method public void attachToMediaController(android.support.v4.media.session.MediaControllerCompat);
     method public void detach();
     method public int getCurrentPosition();
@@ -1982,59 +1981,25 @@
     method public final void setLogoResourceId(int);
   }
 
-  public abstract class PlaybackControlGlue extends android.support.v17.leanback.app.PlaybackGlue implements android.support.v17.leanback.widget.OnActionClickedListener android.view.View.OnKeyListener {
+  public abstract deprecated class PlaybackControlGlue extends android.support.v17.leanback.media.PlaybackControlGlue {
     ctor public PlaybackControlGlue(android.content.Context, int[]);
     ctor public PlaybackControlGlue(android.content.Context, int[], int[]);
-    ctor public deprecated PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[]);
-    ctor public deprecated PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[], int[]);
-    ctor public PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost, int[], int[]);
+    ctor public PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[]);
+    ctor public PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[], int[]);
     method public android.support.v17.leanback.widget.PlaybackControlsRowPresenter createControlsRowAndPresenter();
     method protected android.support.v17.leanback.widget.SparseArrayObjectAdapter createPrimaryActionsAdapter(android.support.v17.leanback.widget.PresenterSelector);
-    method public void enableProgressUpdating(boolean);
-    method public android.support.v17.leanback.widget.PlaybackControlsRow getControlsRow();
-    method public abstract int getCurrentPosition();
-    method public abstract int getCurrentSpeedId();
-    method public int[] getFastForwardSpeeds();
-    method public deprecated android.support.v17.leanback.app.PlaybackOverlayFragment getFragment();
-    method public abstract android.graphics.drawable.Drawable getMediaArt();
-    method public abstract int getMediaDuration();
-    method public abstract java.lang.CharSequence getMediaSubtitle();
-    method public abstract java.lang.CharSequence getMediaTitle();
+    method public android.support.v17.leanback.app.PlaybackOverlayFragment getFragment();
     method public deprecated android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
-    method public int[] getRewindSpeeds();
-    method public abstract long getSupportedActions();
-    method public int getUpdatePeriod();
-    method public abstract boolean hasValidMedia();
-    method public boolean isFadingEnabled();
-    method public abstract boolean isMediaPlaying();
-    method public void onActionClicked(android.support.v17.leanback.widget.Action);
-    method public boolean onKey(android.view.View, int, android.view.KeyEvent);
-    method protected void onMetadataChanged();
-    method protected deprecated void onRowChanged(android.support.v17.leanback.widget.PlaybackControlsRow);
-    method protected void onStateChanged();
+    method public final void next();
+    method protected void onRowChanged(android.support.v17.leanback.widget.PlaybackControlsRow);
+    method public final void pause();
     method protected deprecated void pausePlayback();
-    method public void setControlsRow(android.support.v17.leanback.widget.PlaybackControlsRow);
-    method public void setFadingEnabled(boolean);
+    method public final void play(int);
+    method public final void previous();
     method public deprecated void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
     method protected deprecated void skipToNext();
     method protected deprecated void skipToPrevious();
     method protected deprecated void startPlayback(int);
-    method public void updateProgress();
-    field public static final int ACTION_CUSTOM_LEFT_FIRST = 1; // 0x1
-    field public static final int ACTION_CUSTOM_RIGHT_FIRST = 4096; // 0x1000
-    field public static final int ACTION_FAST_FORWARD = 128; // 0x80
-    field public static final int ACTION_PLAY_PAUSE = 64; // 0x40
-    field public static final int ACTION_REWIND = 32; // 0x20
-    field public static final int ACTION_SKIP_TO_NEXT = 256; // 0x100
-    field public static final int ACTION_SKIP_TO_PREVIOUS = 16; // 0x10
-    field public static final int PLAYBACK_SPEED_FAST_L0 = 10; // 0xa
-    field public static final int PLAYBACK_SPEED_FAST_L1 = 11; // 0xb
-    field public static final int PLAYBACK_SPEED_FAST_L2 = 12; // 0xc
-    field public static final int PLAYBACK_SPEED_FAST_L3 = 13; // 0xd
-    field public static final int PLAYBACK_SPEED_FAST_L4 = 14; // 0xe
-    field public static final int PLAYBACK_SPEED_INVALID = -1; // 0xffffffff
-    field public static final int PLAYBACK_SPEED_NORMAL = 1; // 0x1
-    field public static final int PLAYBACK_SPEED_PAUSED = 0; // 0x0
   }
 
   public static abstract deprecated interface PlaybackControlGlue.InputEventHandler {
@@ -2068,16 +2033,12 @@
     method public void fadeOut();
     method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
     method public int getBackgroundType();
-    method public final android.view.View.OnKeyListener getEventHandler();
-    method public android.support.v17.leanback.app.PlaybackFragment.OnFadeCompleteListener getFadeCompleteListener();
     method public boolean isFadingEnabled();
     method public void notifyPlaybackRowChanged();
-    method public void resetFocus();
     method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
     method public void setBackgroundType(int);
-    method public void setFadeCompleteListener(android.support.v17.leanback.app.PlaybackFragment.OnFadeCompleteListener);
     method public void setFadingEnabled(boolean);
-    method public void setHostLifecycleCallback(android.support.v17.leanback.app.PlaybackGlue.HostLifecycleCallback);
+    method public void setHostCallback(android.support.v17.leanback.media.PlaybackGlueHost.HostCallback);
     method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
     method public final void setOnKeyInterceptListener(android.view.View.OnKeyListener);
     method public void setOnPlaybackItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
@@ -2091,52 +2052,10 @@
     field public static final int BG_NONE = 0; // 0x0
   }
 
-  public static class PlaybackFragment.OnFadeCompleteListener {
-    ctor public PlaybackFragment.OnFadeCompleteListener();
-    method public void onFadeInComplete();
-    method public void onFadeOutComplete();
-  }
-
-  public class PlaybackFragmentGlueHost extends android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost {
+  public class PlaybackFragmentGlueHost extends android.support.v17.leanback.media.PlaybackGlueHost {
     ctor public PlaybackFragmentGlueHost(android.support.v17.leanback.app.PlaybackFragment);
   }
 
-  public abstract class PlaybackGlue {
-    ctor public PlaybackGlue(android.content.Context);
-    method public android.content.Context getContext();
-    method public android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost getHost();
-    method public boolean isReadyForPlayback();
-    method public void next();
-    method public void onDetachedFromHost();
-    method public void pause();
-    method public void play();
-    method public void previous();
-    method public void setHost(android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost);
-    method public void setPlayerCallback(android.support.v17.leanback.app.PlaybackGlue.PlayerCallback);
-  }
-
-  public static abstract class PlaybackGlue.HostLifecycleCallback {
-    ctor public PlaybackGlue.HostLifecycleCallback();
-    method public abstract void onHostStart();
-    method public abstract void onHostStop();
-  }
-
-  public static class PlaybackGlue.PlaybackGlueHost {
-    ctor public PlaybackGlue.PlaybackGlueHost();
-    method public void notifyPlaybackRowChanged();
-    method public void setFadingEnabled(boolean);
-    method public void setHostLifeCycleCallback(android.support.v17.leanback.app.PlaybackGlue.HostLifecycleCallback);
-    method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
-    method public void setOnKeyInterceptListener(android.view.View.OnKeyListener);
-    method public void setPlaybackRow(android.support.v17.leanback.widget.Row);
-    method public void setPlaybackRowPresenter(android.support.v17.leanback.widget.PlaybackRowPresenter);
-  }
-
-  public static abstract class PlaybackGlue.PlayerCallback {
-    ctor public PlaybackGlue.PlayerCallback();
-    method public abstract void onReadyForPlayback();
-  }
-
   public deprecated class PlaybackOverlayFragment extends android.support.v17.leanback.app.DetailsFragment {
     ctor public PlaybackOverlayFragment();
     method public void fadeOut();
@@ -2198,16 +2117,12 @@
     method public void fadeOut();
     method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
     method public int getBackgroundType();
-    method public final android.view.View.OnKeyListener getEventHandler();
-    method public android.support.v17.leanback.app.PlaybackSupportFragment.OnFadeCompleteListener getFadeCompleteListener();
     method public boolean isFadingEnabled();
     method public void notifyPlaybackRowChanged();
-    method public void resetFocus();
     method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
     method public void setBackgroundType(int);
-    method public void setFadeCompleteListener(android.support.v17.leanback.app.PlaybackSupportFragment.OnFadeCompleteListener);
     method public void setFadingEnabled(boolean);
-    method public void setHostLifecycleCallback(android.support.v17.leanback.app.PlaybackGlue.HostLifecycleCallback);
+    method public void setHostCallback(android.support.v17.leanback.media.PlaybackGlueHost.HostCallback);
     method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
     method public final void setOnKeyInterceptListener(android.view.View.OnKeyListener);
     method public void setOnPlaybackItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
@@ -2221,13 +2136,7 @@
     field public static final int BG_NONE = 0; // 0x0
   }
 
-  public static class PlaybackSupportFragment.OnFadeCompleteListener {
-    ctor public PlaybackSupportFragment.OnFadeCompleteListener();
-    method public void onFadeInComplete();
-    method public void onFadeOutComplete();
-  }
-
-  public class PlaybackSupportFragmentGlueHost extends android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost {
+  public class PlaybackSupportFragmentGlueHost extends android.support.v17.leanback.media.PlaybackGlueHost {
     ctor public PlaybackSupportFragmentGlueHost(android.support.v17.leanback.app.PlaybackSupportFragment);
   }
 
@@ -2353,10 +2262,6 @@
     method public abstract boolean onQueryTextSubmit(java.lang.String);
   }
 
-  public abstract interface SurfaceHolderGlueHost {
-    method public abstract void setSurfaceHolderCallback(android.view.SurfaceHolder.Callback);
-  }
-
   public class VerticalGridFragment extends android.support.v17.leanback.app.BrandedFragment {
     ctor public VerticalGridFragment();
     method protected java.lang.Object createEntranceTransition();
@@ -2428,6 +2333,123 @@
 
 }
 
+package android.support.v17.leanback.media {
+
+  public abstract class MediaControllerGlue extends android.support.v17.leanback.media.PlaybackControlGlue {
+    ctor public MediaControllerGlue(android.content.Context, int[], int[]);
+    method public void attachToMediaController(android.support.v4.media.session.MediaControllerCompat);
+    method public void detach();
+    method public int getCurrentPosition();
+    method public int getCurrentSpeedId();
+    method public android.graphics.drawable.Drawable getMediaArt();
+    method public final android.support.v4.media.session.MediaControllerCompat getMediaController();
+    method public int getMediaDuration();
+    method public java.lang.CharSequence getMediaSubtitle();
+    method public java.lang.CharSequence getMediaTitle();
+    method public long getSupportedActions();
+    method public boolean hasValidMedia();
+    method public boolean isMediaPlaying();
+  }
+
+  public abstract class PlaybackControlGlue extends android.support.v17.leanback.media.PlaybackGlue implements android.support.v17.leanback.widget.OnActionClickedListener android.view.View.OnKeyListener {
+    ctor public PlaybackControlGlue(android.content.Context, int[]);
+    ctor public PlaybackControlGlue(android.content.Context, int[], int[]);
+    method public void enableProgressUpdating(boolean);
+    method public android.support.v17.leanback.widget.PlaybackControlsRow getControlsRow();
+    method public android.support.v17.leanback.widget.PlaybackControlsRowPresenter getControlsRowPresenter();
+    method public abstract int getCurrentPosition();
+    method public abstract int getCurrentSpeedId();
+    method public int[] getFastForwardSpeeds();
+    method public abstract android.graphics.drawable.Drawable getMediaArt();
+    method public abstract int getMediaDuration();
+    method public abstract java.lang.CharSequence getMediaSubtitle();
+    method public abstract java.lang.CharSequence getMediaTitle();
+    method public int[] getRewindSpeeds();
+    method public abstract long getSupportedActions();
+    method public int getUpdatePeriod();
+    method public abstract boolean hasValidMedia();
+    method public boolean isFadingEnabled();
+    method public abstract boolean isMediaPlaying();
+    method public void onActionClicked(android.support.v17.leanback.widget.Action);
+    method protected void onCreateControlsRowAndPresenter();
+    method protected void onCreatePrimaryActions(android.support.v17.leanback.widget.SparseArrayObjectAdapter);
+    method protected void onCreateSecondaryActions(android.support.v17.leanback.widget.ArrayObjectAdapter);
+    method public boolean onKey(android.view.View, int, android.view.KeyEvent);
+    method protected void onMetadataChanged();
+    method protected void onStateChanged();
+    method public void play(int);
+    method public final void play();
+    method public void setControlsRow(android.support.v17.leanback.widget.PlaybackControlsRow);
+    method public void setControlsRowPresenter(android.support.v17.leanback.widget.PlaybackControlsRowPresenter);
+    method public void setFadingEnabled(boolean);
+    method public void updateProgress();
+    field public static final int ACTION_CUSTOM_LEFT_FIRST = 1; // 0x1
+    field public static final int ACTION_CUSTOM_RIGHT_FIRST = 4096; // 0x1000
+    field public static final int ACTION_FAST_FORWARD = 128; // 0x80
+    field public static final int ACTION_PLAY_PAUSE = 64; // 0x40
+    field public static final int ACTION_REWIND = 32; // 0x20
+    field public static final int ACTION_SKIP_TO_NEXT = 256; // 0x100
+    field public static final int ACTION_SKIP_TO_PREVIOUS = 16; // 0x10
+    field public static final int PLAYBACK_SPEED_FAST_L0 = 10; // 0xa
+    field public static final int PLAYBACK_SPEED_FAST_L1 = 11; // 0xb
+    field public static final int PLAYBACK_SPEED_FAST_L2 = 12; // 0xc
+    field public static final int PLAYBACK_SPEED_FAST_L3 = 13; // 0xd
+    field public static final int PLAYBACK_SPEED_FAST_L4 = 14; // 0xe
+    field public static final int PLAYBACK_SPEED_INVALID = -1; // 0xffffffff
+    field public static final int PLAYBACK_SPEED_NORMAL = 1; // 0x1
+    field public static final int PLAYBACK_SPEED_PAUSED = 0; // 0x0
+  }
+
+  public abstract class PlaybackGlue {
+    ctor public PlaybackGlue(android.content.Context);
+    method public android.content.Context getContext();
+    method public android.support.v17.leanback.media.PlaybackGlueHost getHost();
+    method public boolean isReadyForPlayback();
+    method public void next();
+    method protected void onAttachedToHost(android.support.v17.leanback.media.PlaybackGlueHost);
+    method protected void onDetachedFromHost();
+    method protected void onHostPause();
+    method protected void onHostResume();
+    method protected void onHostStart();
+    method protected void onHostStop();
+    method public void pause();
+    method public void play();
+    method public void previous();
+    method public final void setHost(android.support.v17.leanback.media.PlaybackGlueHost);
+    method public void setPlayerCallback(android.support.v17.leanback.media.PlaybackGlue.PlayerCallback);
+  }
+
+  public static abstract class PlaybackGlue.PlayerCallback {
+    ctor public PlaybackGlue.PlayerCallback();
+    method public abstract void onReadyForPlayback();
+  }
+
+  public abstract class PlaybackGlueHost {
+    ctor public PlaybackGlueHost();
+    method public void fadeOut();
+    method public void notifyPlaybackRowChanged();
+    method public void setFadingEnabled(boolean);
+    method public void setHostCallback(android.support.v17.leanback.media.PlaybackGlueHost.HostCallback);
+    method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
+    method public void setOnKeyInterceptListener(android.view.View.OnKeyListener);
+    method public void setPlaybackRow(android.support.v17.leanback.widget.Row);
+    method public void setPlaybackRowPresenter(android.support.v17.leanback.widget.PlaybackRowPresenter);
+  }
+
+  public static abstract class PlaybackGlueHost.HostCallback {
+    ctor public PlaybackGlueHost.HostCallback();
+    method public void onHostPause();
+    method public void onHostResume();
+    method public void onHostStart();
+    method public void onHostStop();
+  }
+
+  public abstract interface SurfaceHolderGlueHost {
+    method public abstract void setSurfaceHolderCallback(android.view.SurfaceHolder.Callback);
+  }
+
+}
+
 package android.support.v17.leanback.system {
 
   public class Settings {
diff --git a/build.gradle b/build.gradle
index 96adfd8..d67d932 100644
--- a/build.gradle
+++ b/build.gradle
@@ -24,7 +24,7 @@
     }
     dependencies {
         // Keep gradle plugin version in sync with ub_supportlib-master manifest.
-        classpath 'com.android.tools.build:gradle:2.2.1'
+        classpath 'com.android.tools.build:gradle:2.2.4'
     }
 }
 
@@ -41,8 +41,8 @@
 }
 
 gradle.ext.currentSdk = 25
-ext.supportVersion = '25.1.0-SNAPSHOT'
-ext.extraVersion = 40
+ext.supportVersion = '25.2.0-SNAPSHOT'
+ext.extraVersion = 41
 ext.supportRepoOut = ''
 ext.buildToolsVersion = '24.0.1'
 ext.buildNumber = Integer.toString(ext.extraVersion)
@@ -57,7 +57,8 @@
 // Use the embedded SDK by default, which can be overridden by setting the 'sdk.dir' property
 // (e.g. local.properties) or the 'ANDROID_HOME' environment variable.
 final String platform = OperatingSystem.current().isMacOsX() ? 'darwin' : 'linux'
-System.setProperty('android.home', "${rootDir}/../../prebuilts/fullsdk-${platform}")
+ext.sdkDirectory = "${rootDir}/../../prebuilts/fullsdk-${platform}";
+System.setProperty('android.home', sdkDirectory)
 
 /*
  * With the build server you are given two env variables.
@@ -190,7 +191,7 @@
  * @return a file collection containing the Android prebuilt JAR
  */
 FileCollection getAndroidPrebuilt(apiLevel) {
-    files("${project.rootDir}/../../prebuilts/sdk/$apiLevel/android.jar")
+    files("${ext.sdkDirectory}/platforms/android-${apiLevel}/android.jar")
 }
 
 void registerForDocsTask(Task task, Project subProject, releaseVariant) {
@@ -230,8 +231,6 @@
         addStringOption "templatedir",
                 "${project.rootDir}/../../build/tools/droiddoc/templates-sdk"
         addStringOption "federate Android", "http://developer.android.com"
-        addStringOption "federationapi Android",
-                "${project.rootDir}/../../prebuilts/sdk/api/24.txt"
         addStringOption "stubpackages", "android.support.*"
         addStringOption "samplesdir", "${project.rootDir}/samples"
         addOption hdfOption
@@ -256,8 +255,6 @@
         addStringOption "templatedir",
                 "${project.rootDir}/../../build/tools/droiddoc/templates-sdk"
         addStringOption "federate Android", "http://developer.android.com"
-        addStringOption "federationapi Android",
-                "${project.rootDir}/../../prebuilts/sdk/api/24.txt"
         addStringOption "stubpackages", "android.support.*"
     }
     exclude '**/BuildConfig.java'
@@ -321,10 +318,10 @@
 checkApi.dependsOn checkApiStable
 
 subprojects {
-    // Only modify android projects.
+    // Only modify Android projects.
     if (project.name.equals('doclava')) return;
 
-    // current SDK is set in studioCompat.gradle
+    // Current SDK is set in studioCompat.gradle.
     project.ext.currentSdk = gradle.ext.currentSdk
     apply plugin: 'maven'
 
@@ -338,23 +335,29 @@
     }
 
     project.plugins.whenPluginAdded { plugin ->
-        if ("com.android.build.gradle.LibraryPlugin".equals(plugin.class.name)
-                || "com.android.build.gradle.AppPlugin".equals(plugin.class.name)) {
+        def isAndroidLibrary = "com.android.build.gradle.LibraryPlugin".equals(plugin.class.name)
+        def isAndroidApp = "com.android.build.gradle.AppPlugin".equals(plugin.class.name)
+        def isJavaLibrary = "org.gradle.api.plugins.JavaPlugin".equals(plugin.class.name)
+
+        if (isAndroidLibrary || isAndroidApp) {
             project.android.buildToolsVersion = rootProject.buildToolsVersion
-            // enable code coverage for debug builds only if we are not running inside the IDE
-            // enabling coverage reports breaks the method parameter resolution in the IDE debugger
+
+            // Enable code coverage for debug builds only if we are not running inside the IDE,
+            // since enabling coverage reports breaks the method parameter resolution in the IDE
+            // debugger.
             project.android.buildTypes.debug.testCoverageEnabled = !hasProperty('android.injected.invoked.from.ide')
 
-            // Ensure we run API lint checks.
+            // Enforce NewApi lint check as fatal.
             project.android.lintOptions.check 'NewApi'
             project.android.lintOptions.fatal 'NewApi'
             project.parent.lint.dependsOn project.lint
         }
 
-        // Create release and separate zip task for Android libraries (and android-annotations,
-        // which is just a Java library).
-        if ("com.android.build.gradle.LibraryPlugin".equals(plugin.class.name)
-                || "org.gradle.api.plugins.JavaPlugin".equals(plugin.class.name)) {
+        if (isAndroidLibrary || isJavaLibrary) {
+            // Add library to the aggregate dependency report.
+            task allDeps(type: DependencyReportTask) {}
+
+            // Create release and separate zip task for library.
             task release(type: Upload) {
                 configuration = configurations.archives
                 repositories {
@@ -421,9 +424,10 @@
             }
             project.parent.createArchive.dependsOn createSeparateZip
 
-            // before the upload, make sure the repo is ready.
+            // Before the upload, make sure the repo is ready.
             release.dependsOn rootProject.tasks.prepareRepo
-            // make the mainupload depend on this one.
+
+            // Make the mainupload depend on this one.
             mainUpload.dependsOn release
         }
     }
@@ -484,6 +488,14 @@
             }
         }
     }
+
+    // Update the version meta-data in each Manifest
+    project.afterEvaluate { p ->
+        if (p.hasProperty('android')) {
+            p.android.defaultConfig.manifestPlaceholders =
+                    ["support-version": rootProject.ext.supportVersion]
+        }
+    }
 }
 
 project.gradle.buildFinished { buildResult ->
diff --git a/compat/AndroidManifest.xml b/compat/AndroidManifest.xml
index b2bd5bb..55ddcff 100644
--- a/compat/AndroidManifest.xml
+++ b/compat/AndroidManifest.xml
@@ -17,5 +17,6 @@
           xmlns:tools="http://schemas.android.com/tools"
           package="android.support.compat">
     <uses-sdk android:minSdkVersion="9" tools:overrideLibrary="android.support.compat"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application />
 </manifest>
diff --git a/compat/build.gradle b/compat/build.gradle
index 5722998..e87db0e 100644
--- a/compat/build.gradle
+++ b/compat/build.gradle
@@ -15,9 +15,6 @@
     testCompile 'junit:junit:4.12'
 }
 
-sourceCompatibility = JavaVersion.VERSION_1_7
-targetCompatibility = JavaVersion.VERSION_1_7
-
 android {
     compileSdkVersion project.ext.currentSdk
 
@@ -58,11 +55,7 @@
         sourceCompatibility JavaVersion.VERSION_1_7
         targetCompatibility JavaVersion.VERSION_1_7
     }
-
-    testOptions {
-        unitTests.returnDefaultValues = true
         compileSdkVersion project.ext.currentSdk
-    }
 }
 
 android.libraryVariants.all { variant ->
@@ -73,22 +66,6 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
@@ -96,7 +73,6 @@
         exclude('android/service/media/**')
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/core-ui/AndroidManifest.xml b/core-ui/AndroidManifest.xml
index 9bcc44e..5357112 100644
--- a/core-ui/AndroidManifest.xml
+++ b/core-ui/AndroidManifest.xml
@@ -17,5 +17,6 @@
           xmlns:tools="http://schemas.android.com/tools"
           package="android.support.coreui">
     <uses-sdk android:minSdkVersion="9" tools:overrideLibrary="android.support.coreui"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application />
 </manifest>
diff --git a/core-ui/build.gradle b/core-ui/build.gradle
index 89796ea..0b94a96 100644
--- a/core-ui/build.gradle
+++ b/core-ui/build.gradle
@@ -16,9 +16,6 @@
     testCompile 'junit:junit:4.12'
 }
 
-sourceCompatibility = JavaVersion.VERSION_1_7
-targetCompatibility = JavaVersion.VERSION_1_7
-
 android {
     compileSdkVersion project.ext.currentSdk
 
@@ -54,7 +51,6 @@
 
     testOptions {
         unitTests.returnDefaultValues = true
-        compileSdkVersion project.ext.currentSdk
     }
 }
 
@@ -66,22 +62,6 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
@@ -89,7 +69,6 @@
         exclude('android/service/media/**')
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/core-ui/java/android/support/v4/widget/DrawerLayout.java b/core-ui/java/android/support/v4/widget/DrawerLayout.java
index 7a564bb..f48b036 100644
--- a/core-ui/java/android/support/v4/widget/DrawerLayout.java
+++ b/core-ui/java/android/support/v4/widget/DrawerLayout.java
@@ -453,7 +453,7 @@
     /**
      * Set a simple drawable used for the left or right shadow. The drawable provided must have a
      * nonzero intrinsic width. For API 21 and above, an elevation will be set on the drawer
-     * instead of the drawable provided.
+     * instead of using the provided shadow drawable.
      *
      * <p>Note that for better support for both left-to-right and right-to-left layout
      * directions, a drawable for RTL layout (in additional to the one in LTR layout) can be
@@ -492,7 +492,7 @@
     /**
      * Set a simple drawable used for the left or right shadow. The drawable provided must have a
      * nonzero intrinsic width. For API 21 and above, an elevation will be set on the drawer
-     * instead of the drawable provided.
+     * instead of using the provided shadow drawable.
      *
      * <p>Note that for better support for both left-to-right and right-to-left layout
      * directions, a drawable for RTL layout (in additional to the one in LTR layout) can be
diff --git a/core-utils/AndroidManifest.xml b/core-utils/AndroidManifest.xml
index 586a28e..03ff3b4 100644
--- a/core-utils/AndroidManifest.xml
+++ b/core-utils/AndroidManifest.xml
@@ -17,5 +17,6 @@
           xmlns:tools="http://schemas.android.com/tools"
           package="android.support.coreutils">
     <uses-sdk android:minSdkVersion="9" tools:overrideLibrary="android.support.coreutils"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application />
 </manifest>
diff --git a/core-utils/build.gradle b/core-utils/build.gradle
index 750cf49..d40781d 100644
--- a/core-utils/build.gradle
+++ b/core-utils/build.gradle
@@ -48,11 +48,6 @@
         sourceCompatibility JavaVersion.VERSION_1_7
         targetCompatibility JavaVersion.VERSION_1_7
     }
-
-    testOptions {
-        unitTests.returnDefaultValues = true
-        compileSdkVersion project.ext.currentSdk
-    }
 }
 
 android.libraryVariants.all { variant ->
@@ -63,22 +58,6 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
@@ -86,7 +65,6 @@
         exclude('android/service/media/**')
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/customtabs/AndroidManifest.xml b/customtabs/AndroidManifest.xml
index 212fab9..19a1d54 100644
--- a/customtabs/AndroidManifest.xml
+++ b/customtabs/AndroidManifest.xml
@@ -16,5 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.customtabs">
     <uses-sdk android:minSdkVersion="15"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application />
 </manifest>
diff --git a/customtabs/build.gradle b/customtabs/build.gradle
index a409dba..e427d1e 100644
--- a/customtabs/build.gradle
+++ b/customtabs/build.gradle
@@ -1,5 +1,4 @@
 apply plugin: 'com.android.library'
-
 archivesBaseName = 'customtabs'
 
 dependencies {
@@ -48,27 +47,10 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
diff --git a/design/AndroidManifest.xml b/design/AndroidManifest.xml
index d51186d..2d5fe0b 100644
--- a/design/AndroidManifest.xml
+++ b/design/AndroidManifest.xml
@@ -18,5 +18,6 @@
           package="android.support.design">
     <uses-sdk android:minSdkVersion="9"
               tools:overrideLibrary="android.support.transition"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application />
 </manifest>
diff --git a/design/build.gradle b/design/build.gradle
index abd99c0..37e6625 100644
--- a/design/build.gradle
+++ b/design/build.gradle
@@ -83,28 +83,11 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/design/res/layout/design_bottom_navigation_item.xml b/design/res/layout/design_bottom_navigation_item.xml
index 67df838..f6212cf 100644
--- a/design/res/layout/design_bottom_navigation_item.xml
+++ b/design/res/layout/design_bottom_navigation_item.xml
@@ -26,8 +26,9 @@
     <android.support.design.internal.BaselineLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginBottom="@dimen/design_bottom_navigation_margin"
         android:layout_gravity="bottom|center_horizontal"
+        android:clipToPadding="false"
+        android:paddingBottom="10dp"
         android:duplicateParentState="true">
         <TextView
             android:id="@+id/smallLabel"
diff --git a/design/src/android/support/design/internal/BaselineLayout.java b/design/src/android/support/design/internal/BaselineLayout.java
index eac3542..23a04cd 100644
--- a/design/src/android/support/design/internal/BaselineLayout.java
+++ b/design/src/android/support/design/internal/BaselineLayout.java
@@ -24,7 +24,8 @@
 import android.view.ViewGroup;
 
 /**
- * A simple ViewGroup that aligns all the views inside on a baseline.
+ * A simple ViewGroup that aligns all the views inside on a baseline. Note: bottom padding for this
+ * view will be measured starting from the baseline.
  *
  * @hide
  */
@@ -69,6 +70,7 @@
                     ViewCompat.getMeasuredState(child));
         }
         if (maxChildBaseline != -1) {
+            maxChildDescent = Math.max(maxChildDescent, getPaddingBottom());
             maxHeight = Math.max(maxHeight, maxChildBaseline + maxChildDescent);
             mBaseline = maxChildBaseline;
         }
diff --git a/design/src/android/support/design/internal/BottomNavigationMenuView.java b/design/src/android/support/design/internal/BottomNavigationMenuView.java
index bc73970..82d983e 100644
--- a/design/src/android/support/design/internal/BottomNavigationMenuView.java
+++ b/design/src/android/support/design/internal/BottomNavigationMenuView.java
@@ -255,6 +255,7 @@
         }
         removeAllViews();
         if (mMenu.size() == 0) {
+            mButtons = null;
             return;
         }
         mButtons = new BottomNavigationItemView[mMenu.size()];
diff --git a/design/src/android/support/design/widget/AppBarLayout.java b/design/src/android/support/design/widget/AppBarLayout.java
index 4c7ef0a..8a34239 100644
--- a/design/src/android/support/design/widget/AppBarLayout.java
+++ b/design/src/android/support/design/widget/AppBarLayout.java
@@ -111,6 +111,7 @@
     static final int PENDING_ACTION_EXPANDED = 0x1;
     static final int PENDING_ACTION_COLLAPSED = 0x2;
     static final int PENDING_ACTION_ANIMATE_ENABLED = 0x4;
+    static final int PENDING_ACTION_FORCE = 0x8;
 
     /**
      * Interface definition for a callback to be invoked when an {@link AppBarLayout}'s vertical
@@ -172,7 +173,7 @@
                 0, R.style.Widget_Design_AppBarLayout);
         ViewCompat.setBackground(this, a.getDrawable(R.styleable.AppBarLayout_android_background));
         if (a.hasValue(R.styleable.AppBarLayout_expanded)) {
-            setExpanded(a.getBoolean(R.styleable.AppBarLayout_expanded, false));
+            setExpanded(a.getBoolean(R.styleable.AppBarLayout_expanded, false), false, false);
         }
         if (Build.VERSION.SDK_INT >= 21 && a.hasValue(R.styleable.AppBarLayout_elevation)) {
             ViewUtilsLollipop.setDefaultAppBarLayoutStateListAnimator(
@@ -299,8 +300,13 @@
      * @attr ref android.support.design.R.styleable#AppBarLayout_expanded
      */
     public void setExpanded(boolean expanded, boolean animate) {
+        setExpanded(expanded, animate, true);
+    }
+
+    private void setExpanded(boolean expanded, boolean animate, boolean force) {
         mPendingAction = (expanded ? PENDING_ACTION_EXPANDED : PENDING_ACTION_COLLAPSED)
-                | (animate ? PENDING_ACTION_ANIMATE_ENABLED : 0);
+                | (animate ? PENDING_ACTION_ANIMATE_ENABLED : 0)
+                | (force ? PENDING_ACTION_FORCE : 0);
         requestLayout();
     }
 
@@ -1048,8 +1054,21 @@
                 int layoutDirection) {
             boolean handled = super.onLayoutChild(parent, abl, layoutDirection);
 
+            // The priority for for actions here is (first which is true wins):
+            // 1. forced pending actions
+            // 2. offsets for restorations
+            // 3. non-forced pending actions
             final int pendingAction = abl.getPendingAction();
-            if (pendingAction != PENDING_ACTION_NONE) {
+            if (mOffsetToChildIndexOnLayout >= 0 && (pendingAction & PENDING_ACTION_FORCE) == 0) {
+                View child = abl.getChildAt(mOffsetToChildIndexOnLayout);
+                int offset = -child.getBottom();
+                if (mOffsetToChildIndexOnLayoutIsMinHeight) {
+                    offset += ViewCompat.getMinimumHeight(child) + abl.getTopInset();
+                } else {
+                    offset += Math.round(child.getHeight() * mOffsetToChildIndexOnLayoutPerc);
+                }
+                setHeaderTopBottomOffset(parent, abl, offset);
+            } else if (pendingAction != PENDING_ACTION_NONE) {
                 final boolean animate = (pendingAction & PENDING_ACTION_ANIMATE_ENABLED) != 0;
                 if ((pendingAction & PENDING_ACTION_COLLAPSED) != 0) {
                     final int offset = -abl.getUpNestedPreScrollRange();
@@ -1065,15 +1084,6 @@
                         setHeaderTopBottomOffset(parent, abl, 0);
                     }
                 }
-            } else if (mOffsetToChildIndexOnLayout >= 0) {
-                View child = abl.getChildAt(mOffsetToChildIndexOnLayout);
-                int offset = -child.getBottom();
-                if (mOffsetToChildIndexOnLayoutIsMinHeight) {
-                    offset += ViewCompat.getMinimumHeight(child);
-                } else {
-                    offset += Math.round(child.getHeight() * mOffsetToChildIndexOnLayoutPerc);
-                }
-                setTopAndBottomOffset(offset);
             }
 
             // Finally reset any pending states
@@ -1085,6 +1095,11 @@
             setTopAndBottomOffset(
                     MathUtils.constrain(getTopAndBottomOffset(), -abl.getTotalScrollRange(), 0));
 
+            // Update the AppBarLayout's drawable state for any elevation changes.
+            // This is needed so that the elevation is set in the first layout, so that
+            // we don't get a visual elevation jump pre-N (due to the draw dispatch skip)
+            updateAppBarLayoutDrawableState(parent, abl, getTopAndBottomOffset(), 0, true);
+
             // Make sure we dispatch the offset update
             abl.dispatchOffsetUpdates(getTopAndBottomOffset());
 
@@ -1161,7 +1176,7 @@
 
                     // Update the AppBarLayout's drawable state (for any elevation changes)
                     updateAppBarLayoutDrawableState(coordinatorLayout, appBarLayout, newOffset,
-                            newOffset < curOffset ? -1 : 1);
+                            newOffset < curOffset ? -1 : 1, false);
                 }
             } else {
                 // Reset the offset delta
@@ -1224,7 +1239,8 @@
         }
 
         private void updateAppBarLayoutDrawableState(final CoordinatorLayout parent,
-                final AppBarLayout layout, final int offset, final int direction) {
+                final AppBarLayout layout, final int offset, final int direction,
+                final boolean forceJump) {
             final View child = getAppBarChildOnOffset(layout, offset);
             if (child != null) {
                 final AppBarLayout.LayoutParams childLp = (LayoutParams) child.getLayoutParams();
@@ -1248,8 +1264,8 @@
 
                 final boolean changed = layout.setCollapsedState(collapsed);
 
-                if (changed && Build.VERSION.SDK_INT >= 11
-                        && shouldJumpElevationState(parent, layout)) {
+                if (Build.VERSION.SDK_INT >= 11 && (forceJump
+                        || (changed && shouldJumpElevationState(parent, layout)))) {
                     // If the collapsed state changed, we may need to
                     // jump to the current state if we have an overlapping view
                     layout.jumpDrawablesToCurrentState();
diff --git a/design/src/android/support/design/widget/CollapsingToolbarLayout.java b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
index 8fcf796..5f8de36 100644
--- a/design/src/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
@@ -110,7 +110,6 @@
     private Toolbar mToolbar;
     private View mToolbarDirectChild;
     private View mDummyView;
-    private int mToolbarDrawIndex;
 
     private int mExpandedMarginStart;
     private int mExpandedMarginTop;
@@ -314,16 +313,14 @@
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
         // This is a little weird. Our scrim needs to be behind the Toolbar (if it is present),
         // but in front of any other children which are behind it. To do this we intercept the
-        // drawChild() call, and draw our scrim after the preceding view is drawn
-        boolean invalidate = super.drawChild(canvas, child, drawingTime);
-
-        if (mContentScrim != null && mScrimAlpha > 0 && isToolbarChildDrawnNext(child)) {
+        // drawChild() call, and draw our scrim just before the Toolbar is drawn
+        boolean invalidated = false;
+        if (mContentScrim != null && mScrimAlpha > 0 && isToolbarChild(child)) {
             mContentScrim.mutate().setAlpha(mScrimAlpha);
             mContentScrim.draw(canvas);
-            invalidate = true;
+            invalidated = true;
         }
-
-        return invalidate;
+        return super.drawChild(canvas, child, drawingTime) || invalidated;
     }
 
     @Override
@@ -369,8 +366,10 @@
         mRefreshToolbar = false;
     }
 
-    private boolean isToolbarChildDrawnNext(View child) {
-        return mToolbarDrawIndex >= 0 && mToolbarDrawIndex == indexOfChild(child) + 1;
+    private boolean isToolbarChild(View child) {
+        return (mToolbarDirectChild == null || mToolbarDirectChild == this)
+                ? child == mToolbar
+                : child == mToolbarDirectChild;
     }
 
     /**
@@ -480,13 +479,9 @@
             }
             if (mToolbarDirectChild == null || mToolbarDirectChild == this) {
                 setMinimumHeight(getHeightWithMargins(mToolbar));
-                mToolbarDrawIndex = indexOfChild(mToolbar);
             } else {
                 setMinimumHeight(getHeightWithMargins(mToolbarDirectChild));
-                mToolbarDrawIndex = indexOfChild(mToolbarDirectChild);
             }
-        } else {
-            mToolbarDrawIndex = -1;
         }
 
         updateScrimVisibility();
@@ -631,6 +626,10 @@
         }
     }
 
+    int getScrimAlpha() {
+        return mScrimAlpha;
+    }
+
     /**
      * Set the drawable to use for the content scrim from resources. Providing null will disable
      * the scrim functionality.
diff --git a/design/src/android/support/design/widget/CoordinatorLayout.java b/design/src/android/support/design/widget/CoordinatorLayout.java
index 262fd9f..1f570b6 100644
--- a/design/src/android/support/design/widget/CoordinatorLayout.java
+++ b/design/src/android/support/design/widget/CoordinatorLayout.java
@@ -47,6 +47,7 @@
 import android.support.v4.graphics.drawable.DrawableCompat;
 import android.support.v4.os.ParcelableCompat;
 import android.support.v4.os.ParcelableCompatCreatorCallbacks;
+import android.support.v4.util.Pools;
 import android.support.v4.view.AbsSavedState;
 import android.support.v4.view.GravityCompat;
 import android.support.v4.view.MotionEventCompat;
@@ -133,7 +134,6 @@
     static final ThreadLocal<Map<String, Constructor<Behavior>>> sConstructors =
             new ThreadLocal<>();
 
-
     static final int EVENT_PRE_DRAW = 0;
     static final int EVENT_NESTED_SCROLL = 1;
     static final int EVENT_VIEW_REMOVED = 2;
@@ -145,17 +145,27 @@
     public @interface DispatchChangeEvent {}
 
     static final Comparator<View> TOP_SORTED_CHILDREN_COMPARATOR;
+    private static final Pools.Pool<Rect> sRectPool = new Pools.SynchronizedPool<>(12);
+
+    @NonNull
+    private static Rect acquireTempRect() {
+        Rect rect = sRectPool.acquire();
+        if (rect == null) {
+            rect = new Rect();
+        }
+        return rect;
+    }
+
+    private static void releaseTempRect(@NonNull Rect rect) {
+        rect.setEmpty();
+        sRectPool.release(rect);
+    }
 
     private final List<View> mDependencySortedChildren = new ArrayList<>();
     private final DirectedAcyclicGraph<View> mChildDag = new DirectedAcyclicGraph<>();
 
     private final List<View> mTempList1 = new ArrayList<>();
     private final List<View> mTempDependenciesList = new ArrayList<>();
-    private final Rect mTempRect1 = new Rect();
-    private final Rect mTempRect2 = new Rect();
-    private final Rect mTempRect3 = new Rect();
-    private final Rect mTempRect4 = new Rect();
-    private final Rect mTempRect5 = new Rect();
     private final int[] mTempIntPair = new int[2];
     private Paint mScrimPaint;
 
@@ -1047,12 +1057,16 @@
     private void layoutChildWithAnchor(View child, View anchor, int layoutDirection) {
         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
 
-        final Rect anchorRect = mTempRect1;
-        final Rect childRect = mTempRect2;
-        getDescendantRect(anchor, anchorRect);
-        getDesiredAnchoredChildRect(child, layoutDirection, anchorRect, childRect);
-
-        child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
+        final Rect anchorRect = acquireTempRect();
+        final Rect childRect = acquireTempRect();
+        try {
+            getDescendantRect(anchor, anchorRect);
+            getDesiredAnchoredChildRect(child, layoutDirection, anchorRect, childRect);
+            child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom);
+        } finally {
+            releaseTempRect(anchorRect);
+            releaseTempRect(childRect);
+        }
     }
 
     /**
@@ -1131,7 +1145,7 @@
      */
     private void layoutChild(View child, int layoutDirection) {
         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-        final Rect parent = mTempRect1;
+        final Rect parent = acquireTempRect();
         parent.set(getPaddingLeft() + lp.leftMargin,
                 getPaddingTop() + lp.topMargin,
                 getWidth() - getPaddingRight() - lp.rightMargin,
@@ -1147,10 +1161,13 @@
             parent.bottom -= mLastInsets.getSystemWindowInsetBottom();
         }
 
-        final Rect out = mTempRect2;
+        final Rect out = acquireTempRect();
         GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(),
                 child.getMeasuredHeight(), parent, out, layoutDirection);
         child.layout(out.left, out.top, out.right, out.bottom);
+
+        releaseTempRect(parent);
+        releaseTempRect(out);
     }
 
     /**
@@ -1225,8 +1242,10 @@
     final void onChildViewsChanged(@DispatchChangeEvent final int type) {
         final int layoutDirection = ViewCompat.getLayoutDirection(this);
         final int childCount = mDependencySortedChildren.size();
-        final Rect inset = mTempRect4;
-        inset.setEmpty();
+        final Rect inset = acquireTempRect();
+        final Rect drawRect = acquireTempRect();
+        final Rect lastDrawRect = acquireTempRect();
+
         for (int i = 0; i < childCount; i++) {
             final View child = mDependencySortedChildren.get(i);
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
@@ -1245,7 +1264,6 @@
             }
 
             // Get the current draw rect of the view
-            final Rect drawRect = mTempRect1;
             getChildRect(child, true, drawRect);
 
             // Accumulate inset sizes
@@ -1275,9 +1293,8 @@
                 offsetChildByInset(child, inset, layoutDirection);
             }
 
-            if (type == EVENT_PRE_DRAW) {
+            if (type != EVENT_VIEW_REMOVED) {
                 // Did it change? if not continue
-                final Rect lastDrawRect = mTempRect2;
                 getLastChildRect(child, lastDrawRect);
                 if (lastDrawRect.equals(drawRect)) {
                     continue;
@@ -1321,6 +1338,10 @@
                 }
             }
         }
+
+        releaseTempRect(inset);
+        releaseTempRect(drawRect);
+        releaseTempRect(lastDrawRect);
     }
 
     private void offsetChildByInset(final View child, final Rect inset, final int layoutDirection) {
@@ -1329,31 +1350,34 @@
             return;
         }
 
-        final Rect bounds = mTempRect5;
-        bounds.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
-        if (bounds.isEmpty()) {
+        if (child.getWidth() <= 0 || child.getHeight() <= 0) {
             // Bounds are empty so there is nothing to dodge against, skip...
             return;
         }
 
         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
         final Behavior behavior = lp.getBehavior();
-        final Rect dodgeRect = mTempRect3;
-        dodgeRect.setEmpty();
+        final Rect dodgeRect = acquireTempRect();
+        final Rect bounds = acquireTempRect();
+        bounds.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
 
         if (behavior != null && behavior.getInsetDodgeRect(this, child, dodgeRect)) {
             // Make sure that the rect is within the view's bounds
             if (!bounds.contains(dodgeRect)) {
-                throw new IllegalArgumentException("Rect should be within the child's bounds."
-                        + " Rect:" + dodgeRect.toShortString()
-                        + " | Bounds:" + bounds.toShortString());
+                    throw new IllegalArgumentException("Rect should be within the child's bounds."
+                            + " Rect:" + dodgeRect.toShortString()
+                            + " | Bounds:" + bounds.toShortString());
             }
         } else {
             dodgeRect.set(bounds);
         }
 
+        // We can release the bounds rect now
+        releaseTempRect(bounds);
+
         if (dodgeRect.isEmpty()) {
             // Rect is empty so there is nothing to dodge against, skip...
+            releaseTempRect(dodgeRect);
             return;
         }
 
@@ -1397,6 +1421,8 @@
         if (!offsetX) {
             setInsetOffsetX(child, 0);
         }
+
+        releaseTempRect(dodgeRect);
     }
 
     private void setInsetOffsetX(View child, int offsetX) {
@@ -1558,9 +1584,9 @@
     void offsetChildToAnchor(View child, int layoutDirection) {
         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
         if (lp.mAnchorView != null) {
-            final Rect anchorRect = mTempRect1;
-            final Rect childRect = mTempRect2;
-            final Rect desiredChildRect = mTempRect3;
+            final Rect anchorRect = acquireTempRect();
+            final Rect childRect = acquireTempRect();
+            final Rect desiredChildRect = acquireTempRect();
 
             getDescendantRect(lp.mAnchorView, anchorRect);
             getChildRect(child, false, childRect);
@@ -1590,6 +1616,10 @@
                     b.onDependentViewChanged(this, child, lp.mAnchorView);
                 }
             }
+
+            releaseTempRect(anchorRect);
+            releaseTempRect(childRect);
+            releaseTempRect(desiredChildRect);
         }
     }
 
@@ -1603,9 +1633,13 @@
      * @return true if the point is within the child view's bounds, false otherwise
      */
     public boolean isPointInChildBounds(View child, int x, int y) {
-        final Rect r = mTempRect1;
+        final Rect r = acquireTempRect();
         getDescendantRect(child, r);
-        return r.contains(x, y);
+        try {
+            return r.contains(x, y);
+        } finally {
+            releaseTempRect(r);
+        }
     }
 
     /**
@@ -1618,13 +1652,17 @@
      */
     public boolean doViewsOverlap(View first, View second) {
         if (first.getVisibility() == VISIBLE && second.getVisibility() == VISIBLE) {
-            final Rect firstRect = mTempRect1;
+            final Rect firstRect = acquireTempRect();
             getChildRect(first, first.getParent() != this, firstRect);
-            final Rect secondRect = mTempRect2;
+            final Rect secondRect = acquireTempRect();
             getChildRect(second, second.getParent() != this, secondRect);
-
-            return !(firstRect.left > secondRect.right || firstRect.top > secondRect.bottom
-                    || firstRect.right < secondRect.left || firstRect.bottom < secondRect.top);
+            try {
+                return !(firstRect.left > secondRect.right || firstRect.top > secondRect.bottom
+                        || firstRect.right < secondRect.left || firstRect.bottom < secondRect.top);
+            } finally {
+                releaseTempRect(firstRect);
+                releaseTempRect(secondRect);
+            }
         }
         return false;
     }
@@ -1954,6 +1992,10 @@
          * <p>Once a Behavior intercepts touch events, the rest of the event stream will
          * be sent to the {@link #onTouchEvent} method.</p>
          *
+         * <p>This method will be called regardless of the visibility of the associated child
+         * of the behavior. If you only wish to handle touch events when the child is visible, you
+         * should add a check to {@link View#isShown()} on the given child.</p>
+         *
          * <p>The default implementation of this method always returns false.</p>
          *
          * @param parent the parent view currently receiving this touch event
@@ -1975,6 +2017,10 @@
          * UI pane open or closed. This method should perform actual mutations of view
          * layout state.</p>
          *
+         * <p>This method will be called regardless of the visibility of the associated child
+         * of the behavior. If you only wish to handle touch events when the child is visible, you
+         * should add a check to {@link View#isShown()} on the given child.</p>
+         *
          * @param parent the parent view currently receiving this touch event
          * @param child the child view associated with this Behavior
          * @param ev the MotionEvent describing the touch event being processed
diff --git a/design/src/android/support/design/widget/TextInputLayout.java b/design/src/android/support/design/widget/TextInputLayout.java
index 92c1506..10da563 100644
--- a/design/src/android/support/design/widget/TextInputLayout.java
+++ b/design/src/android/support/design/widget/TextInputLayout.java
@@ -201,8 +201,6 @@
         mCollapsingTextHelper.setPositionInterpolator(new AccelerateInterpolator());
         mCollapsingTextHelper.setCollapsedTextGravity(Gravity.TOP | GravityCompat.START);
 
-        mHintExpanded = mCollapsingTextHelper.getExpansionFraction() == 1f;
-
         final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
                 R.styleable.TextInputLayout, defStyleAttr, R.style.Widget_Design_TextInputLayout);
         mHintEnabled = a.getBoolean(R.styleable.TextInputLayout_hintEnabled, true);
@@ -380,8 +378,8 @@
 
         updatePasswordToggleView();
 
-        // Update the label visibility with no animation
-        updateLabelState(false);
+        // Update the label visibility with no animation, but force a state change
+        updateLabelState(false, true);
     }
 
     private void updateInputLayoutMargins() {
@@ -408,6 +406,10 @@
     }
 
     void updateLabelState(boolean animate) {
+        updateLabelState(animate, false);
+    }
+
+    void updateLabelState(final boolean animate, final boolean force) {
         final boolean isEnabled = isEnabled();
         final boolean hasText = mEditText != null && !TextUtils.isEmpty(mEditText.getText());
         final boolean isFocused = arrayContains(getDrawableState(), android.R.attr.state_focused);
@@ -427,12 +429,12 @@
 
         if (hasText || (isEnabled() && (isFocused || isErrorShowing))) {
             // We should be showing the label so do so if it isn't already
-            if (mHintExpanded) {
+            if (force || mHintExpanded) {
                 collapseHint(animate);
             }
         } else {
             // We should not be showing the label so hide it
-            if (!mHintExpanded) {
+            if (force || !mHintExpanded) {
                 expandHint(animate);
             }
         }
@@ -1076,6 +1078,13 @@
                 });
             }
 
+            if (mEditText != null && ViewCompat.getMinimumHeight(mEditText) <= 0) {
+                // We should make sure that the EditText has the same min-height as the password
+                // toggle view. This ensure focus works properly, and there is no visual jump
+                // if the password toggle is enabled/disabled.
+                mEditText.setMinimumHeight(ViewCompat.getMinimumHeight(mPasswordToggleView));
+            }
+
             mPasswordToggleView.setVisibility(VISIBLE);
             mPasswordToggleView.setChecked(mPasswordToggledVisible);
 
diff --git a/design/tests/AndroidManifest.xml b/design/tests/AndroidManifest.xml
index 29d46e0..886540d 100755
--- a/design/tests/AndroidManifest.xml
+++ b/design/tests/AndroidManifest.xml
@@ -88,6 +88,10 @@
         <activity
                 android:name="android.support.v7.app.AppCompatActivity"/>
 
+        <activity
+            android:name="android.support.design.widget.AppBarLayoutCollapsePinTestActivity"
+            android:theme="@style/Theme.TranslucentStatus"/>
+
     </application>
 
     <instrumentation
diff --git a/design/tests/res/layout/design_appbar_toolbar_collapse_pin_restore_test.xml b/design/tests/res/layout/design_appbar_toolbar_collapse_pin_restore_test.xml
new file mode 100644
index 0000000..fbe031b
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_toolbar_collapse_pin_restore_test.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/coordinator_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true">
+
+    <android.support.design.widget.AppBarLayout
+        android:id="@+id/app_bar"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/appbar_height"
+        android:fitsSystemWindows="true"
+        app:expanded="true">
+
+        <android.support.design.widget.CollapsingToolbarLayout
+            android:id="@+id/collapsing_app_bar"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            app:layout_scrollFlags="scroll|exitUntilCollapsed">
+
+            <android.support.v7.widget.Toolbar
+                android:id="@+id/toolbar"
+                android:layout_height="?attr/actionBarSize"
+                android:layout_width="match_parent"
+                app:layout_collapseMode="pin"/>
+
+        </android.support.design.widget.CollapsingToolbarLayout>
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/include_appbar_scrollview" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/layout/design_navigation_view.xml b/design/tests/res/layout/design_navigation_view.xml
index 1789843..4f7147d 100644
--- a/design/tests/res/layout/design_navigation_view.xml
+++ b/design/tests/res/layout/design_navigation_view.xml
@@ -37,7 +37,7 @@
          android:fitsSystemWindows="true" tells the system to have
          DrawerLayout span the full height of the screen, including the
          system status bar on Lollipop+ versions of the plaform. -->
-    <android.support.design.widget.NavigationView
+    <android.support.design.widget.NavigationTestView
         android:id="@+id/start_drawer"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
diff --git a/design/tests/res/layout/design_text_input.xml b/design/tests/res/layout/design_text_input.xml
index f4bd1d1..4dba825 100644
--- a/design/tests/res/layout/design_text_input.xml
+++ b/design/tests/res/layout/design_text_input.xml
@@ -58,4 +58,18 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"/>
 
+    <android.support.design.widget.TextInputLayout
+        android:id="@+id/textinput_with_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <android.support.design.widget.TextInputEditText
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:imeOptions="flagNoExtractUi"
+            android:hint="@string/textinput_hint"
+            android:text="@string/snackbar_text"/>
+
+    </android.support.design.widget.TextInputLayout>
+
 </LinearLayout>
\ No newline at end of file
diff --git a/design/tests/src/android/support/design/testutils/AppBarLayoutMatchers.java b/design/tests/src/android/support/design/testutils/AppBarLayoutMatchers.java
new file mode 100755
index 0000000..4d6fc63
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/AppBarLayoutMatchers.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.testutils;
+
+import android.support.design.widget.AppBarLayout;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public class AppBarLayoutMatchers {
+
+    /**
+     * Returns a matcher that matches AppBarLayouts which are collapsed.
+     */
+    public static Matcher isCollapsed() {
+        return new TypeSafeMatcher<AppBarLayout>() {
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("AppBarLayout is collapsed");
+            }
+
+            @Override
+            protected boolean matchesSafely(AppBarLayout item) {
+                return item.getBottom() == (item.getHeight() - item.getTotalScrollRange());
+            }
+        };
+    }
+
+}
diff --git a/design/tests/src/android/support/design/testutils/CollapsingToolbarLayoutActions.java b/design/tests/src/android/support/design/testutils/CollapsingToolbarLayoutActions.java
new file mode 100644
index 0000000..71dd150
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/CollapsingToolbarLayoutActions.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.testutils;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+
+import android.support.annotation.ColorInt;
+import android.support.design.widget.CollapsingToolbarLayout;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.view.View;
+
+import org.hamcrest.Matcher;
+
+public class CollapsingToolbarLayoutActions {
+
+    public static ViewAction setContentScrimColor(@ColorInt final int color) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isDisplayed();
+            }
+
+            @Override
+            public String getDescription() {
+                return "Set the content scrim to a color";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+                CollapsingToolbarLayout ctl = (CollapsingToolbarLayout) view;
+                ctl.setContentScrimColor(color);
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+}
diff --git a/design/tests/src/android/support/design/testutils/SwipeUtils.java b/design/tests/src/android/support/design/testutils/SwipeUtils.java
new file mode 100644
index 0000000..cf92883
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/SwipeUtils.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.testutils;
+
+import android.support.test.espresso.action.CoordinatesProvider;
+import android.support.test.espresso.action.GeneralSwipeAction;
+import android.support.test.espresso.action.Press;
+import android.support.test.espresso.action.Swipe;
+import android.view.View;
+
+public class SwipeUtils {
+
+    public static GeneralSwipeAction swipeUp(final int swipeX,
+            final int swipeStartY, final int swipeAmountY) {
+        return new GeneralSwipeAction(
+                Swipe.SLOW,
+                new CoordinatesProvider() {
+                    @Override
+                    public float[] calculateCoordinates(View view) {
+                        return new float[] { swipeX, swipeStartY };
+                    }
+                },
+                new CoordinatesProvider() {
+                    @Override
+                    public float[] calculateCoordinates(View view) {
+                        return new float[] { swipeX, swipeStartY - swipeAmountY };
+                    }
+                },
+                Press.FINGER
+        );
+    }
+
+    public static GeneralSwipeAction swipeDown(final int swipeX,
+            final int swipeStartY, final int swipeAmountY) {
+        return new GeneralSwipeAction(
+                Swipe.SLOW,
+                new CoordinatesProvider() {
+                    @Override
+                    public float[] calculateCoordinates(View view) {
+                        return new float[] { swipeX, swipeStartY };
+                    }
+                },
+                new CoordinatesProvider() {
+                    @Override
+                    public float[] calculateCoordinates(View view) {
+                        return new float[] { swipeX, swipeStartY + swipeAmountY };
+                    }
+                },
+                Press.FINGER
+        );
+    }
+
+
+}
diff --git a/design/tests/src/android/support/design/testutils/TestUtils.java b/design/tests/src/android/support/design/testutils/TestUtils.java
index b9aae7a..2f9187d 100644
--- a/design/tests/src/android/support/design/testutils/TestUtils.java
+++ b/design/tests/src/android/support/design/testutils/TestUtils.java
@@ -16,7 +16,10 @@
 
 package android.support.design.testutils;
 
+import android.app.Activity;
 import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -124,4 +127,20 @@
             }
         }
     }
+
+    /**
+     * Rotates the given Activity to either portrait or landscape, depending on the current
+     * orientation.
+     */
+    public static void rotateOrientation(@NonNull Activity activity) {
+        switch (activity.getResources().getConfiguration().orientation) {
+            case Configuration.ORIENTATION_PORTRAIT:
+                activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+                break;
+            case Configuration.ORIENTATION_LANDSCAPE:
+            default:
+                activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+                break;
+        }
+    }
 }
\ No newline at end of file
diff --git a/design/tests/src/android/support/design/testutils/TestUtilsMatchers.java b/design/tests/src/android/support/design/testutils/TestUtilsMatchers.java
index 2f1f30d..9d13ce7 100644
--- a/design/tests/src/android/support/design/testutils/TestUtilsMatchers.java
+++ b/design/tests/src/android/support/design/testutils/TestUtilsMatchers.java
@@ -22,6 +22,7 @@
 import android.graphics.Rect;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.support.annotation.ColorInt;
 import android.support.annotation.NonNull;
 import android.support.design.widget.FloatingActionButton;
@@ -461,6 +462,24 @@
     }
 
     /**
+     * Returns a matcher that matches views which have a z-value greater than 0. Also matches if
+     * the platform we're running on does not support z-values.
+     */
+    public static Matcher<View> hasZ() {
+        return new TypeSafeMatcher<View>() {
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("has a z value greater than 0");
+            }
+
+            @Override
+            public boolean matchesSafely(View view) {
+                return Build.VERSION.SDK_INT < 21 || ViewCompat.getZ(view) > 0f;
+            }
+        };
+    }
+
+    /**
      * Returns a matcher that matches TextViews with the specified typeface.
      */
     public static Matcher withTypeface(@NonNull final Typeface typeface) {
diff --git a/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java b/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java
index 39486e5..6fd4470 100644
--- a/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java
+++ b/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java
@@ -16,31 +16,37 @@
 
 package android.support.design.widget;
 
+import static android.support.design.testutils.CollapsingToolbarLayoutActions.setContentScrimColor;
+import static android.support.design.testutils.SwipeUtils.swipeDown;
+import static android.support.design.testutils.SwipeUtils.swipeUp;
 import static android.support.design.testutils.TestUtilsActions.setText;
 import static android.support.design.testutils.TestUtilsActions.setTitle;
 import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 
 import static org.junit.Assert.assertEquals;
 
+import android.graphics.Color;
 import android.os.Build;
+import android.os.SystemClock;
 import android.support.annotation.CallSuper;
 import android.support.annotation.IdRes;
+import android.support.annotation.IntRange;
 import android.support.annotation.LayoutRes;
 import android.support.annotation.StringRes;
 import android.support.design.test.R;
 import android.support.design.testutils.Shakespeare;
-import android.support.test.espresso.action.CoordinatesProvider;
-import android.support.test.espresso.action.GeneralSwipeAction;
-import android.support.test.espresso.action.Press;
-import android.support.test.espresso.action.Swipe;
 import android.support.v4.view.ViewCompat;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.widget.Toolbar;
 import android.text.TextUtils;
-import android.view.View;
 import android.widget.TextView;
 
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
 public abstract class AppBarLayoutBaseTest extends BaseDynamicCoordinatorLayoutTest {
 
     protected AppBarLayout mAppBar;
@@ -55,38 +61,12 @@
 
     protected static void performVerticalSwipeUpGesture(@IdRes int containerId, final int swipeX,
             final int swipeStartY, final int swipeAmountY) {
-        onView(withId(containerId)).perform(new GeneralSwipeAction(
-                Swipe.SLOW,
-                new CoordinatesProvider() {
-                    @Override
-                    public float[] calculateCoordinates(View view) {
-                        return new float[] { swipeX, swipeStartY };
-                    }
-                },
-                new CoordinatesProvider() {
-                    @Override
-                    public float[] calculateCoordinates(View view) {
-                        return new float[] { swipeX, swipeStartY - swipeAmountY };
-                    }
-                }, Press.FINGER));
+        onView(withId(containerId)).perform(swipeUp(swipeX, swipeStartY, swipeAmountY));
     }
 
     protected static void performVerticalSwipeDownGesture(@IdRes int containerId, final int swipeX,
             final int swipeStartY, final int swipeAmountY) {
-        onView(withId(containerId)).perform(new GeneralSwipeAction(
-                Swipe.SLOW,
-                new CoordinatesProvider() {
-                    @Override
-                    public float[] calculateCoordinates(View view) {
-                        return new float[] { swipeX, swipeStartY };
-                    }
-                },
-                new CoordinatesProvider() {
-                    @Override
-                    public float[] calculateCoordinates(View view) {
-                        return new float[] { swipeX, swipeStartY + swipeAmountY };
-                    }
-                }, Press.FINGER));
+        onView(withId(containerId)).perform(swipeDown(swipeX, swipeStartY, swipeAmountY));
     }
 
     @CallSuper
@@ -110,13 +90,15 @@
         final CharSequence activityTitle = activity.getString(titleResId);
         activity.setTitle(activityTitle);
         if (mCollapsingToolbar != null) {
-            onView(withId(R.id.collapsing_app_bar)).perform(setTitle(activityTitle));
+            onView(withId(R.id.collapsing_app_bar))
+                    .perform(setTitle(activityTitle))
+                    .perform(setContentScrimColor(Color.MAGENTA));
         }
 
         TextView dialog = (TextView) mCoordinatorLayout.findViewById(R.id.textview_dialogue);
         if (dialog != null) {
-            onView(withId(R.id.textview_dialogue)).perform(
-                    setText(TextUtils.concat(Shakespeare.DIALOGUE)));
+            onView(withId(R.id.textview_dialogue))
+                    .perform(setText(TextUtils.concat(Shakespeare.DIALOGUE)));
         }
 
         mDefaultElevationValue = mAppBar.getResources()
@@ -128,4 +110,25 @@
             assertEquals(expectedValue, ViewCompat.getElevation(mAppBar), 0.05f);
         }
     }
+
+    protected void assertScrimAlpha(@IntRange(from = 0, to = 255) int alpha) {
+        SystemClock.sleep(300);
+        onView(withId(R.id.collapsing_app_bar))
+                .check(matches(withScrimAlpha(alpha)));
+    }
+
+    static Matcher withScrimAlpha(final int alpha) {
+        return new TypeSafeMatcher<CollapsingToolbarLayout>() {
+            @Override
+            public void describeTo(Description description) {
+                description.appendText(
+                        "CollapsingToolbarLayout has content scrim with alpha: " + alpha);
+            }
+
+            @Override
+            protected boolean matchesSafely(CollapsingToolbarLayout view) {
+                return alpha == view.getScrimAlpha();
+            }
+        };
+    }
 }
diff --git a/design/tests/src/android/support/design/widget/AppBarLayoutCollapsePinTestActivity.java b/design/tests/src/android/support/design/widget/AppBarLayoutCollapsePinTestActivity.java
new file mode 100644
index 0000000..38ea4fc
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/AppBarLayoutCollapsePinTestActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.support.design.test.R;
+import android.support.v7.widget.Toolbar;
+
+public class AppBarLayoutCollapsePinTestActivity extends BaseTestActivity {
+
+    @Override
+    protected int getContentViewLayoutResId() {
+        return R.layout.design_appbar_toolbar_collapse_pin_restore_test;
+    }
+
+    @Override
+    protected void onContentViewSet() {
+        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+    }
+}
diff --git a/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarStateRestoreTest.java b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarStateRestoreTest.java
new file mode 100644
index 0000000..52f4ab2
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarStateRestoreTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import static android.support.design.testutils.AppBarLayoutMatchers.isCollapsed;
+import static android.support.design.testutils.SwipeUtils.swipeUp;
+import static android.support.design.testutils.TestUtils.rotateOrientation;
+import static android.support.design.testutils.TestUtilsMatchers.hasZ;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+import android.app.Activity;
+import android.support.design.test.R;
+
+import org.junit.Test;
+
+public class AppBarWithCollapsingToolbarStateRestoreTest
+        extends BaseInstrumentationTestCase<AppBarLayoutCollapsePinTestActivity> {
+
+    public AppBarWithCollapsingToolbarStateRestoreTest() {
+        super(AppBarLayoutCollapsePinTestActivity.class);
+    }
+
+    @Test
+    public void testRotateAndRestore() {
+        Activity activity = mActivityTestRule.getActivity();
+        final AppBarLayout appBar = (AppBarLayout) activity.findViewById(R.id.app_bar);
+
+        // Swipe up and collapse the AppBarLayout
+        onView(withId(R.id.coordinator_layout))
+                .perform(swipeUp(
+                        appBar.getLeft() + (appBar.getWidth() / 2),
+                        appBar.getBottom() + 20,
+                        appBar.getHeight()));
+        onView(withId(R.id.app_bar))
+                .check(matches(hasZ()))
+                .check(matches(isCollapsed()));
+
+        // Now rotate the Activity
+        rotateOrientation(activity);
+
+        // And check that the app bar still is restored correctly
+        onView(withId(R.id.app_bar))
+                .check(matches(hasZ()))
+                .check(matches(isCollapsed()));
+    }
+
+}
diff --git a/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
index 84e1abe..adcfee8 100644
--- a/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
+++ b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
@@ -55,6 +55,7 @@
         final int shortSwipeAmount = toolbarHeight;
 
         assertAppBarElevation(0f);
+        assertScrimAlpha(0);
 
         // Perform a swipe-up gesture across the horizontal center of the screen.
         performVerticalSwipeUpGesture(
@@ -82,6 +83,7 @@
         assertEquals(originalAppbarTop + toolbarHeight + mAppBar.getTopInset(),
                 appbarOnScreenXY[1] + appbarHeight, 1);
         assertAppBarElevation(mDefaultElevationValue);
+        assertScrimAlpha(255);
 
         // Perform a short swipe-down gesture across the horizontal center of the screen.
         // Note that the swipe down is a bit longer than the swipe up to check that the app bar
@@ -99,6 +101,7 @@
         assertEquals(originalAppbarTop + toolbarHeight + mAppBar.getTopInset(),
                 appbarOnScreenXY[1] + appbarHeight, 1);
         assertAppBarElevation(mDefaultElevationValue);
+        assertScrimAlpha(255);
 
         // Perform another swipe-down gesture across the horizontal center of the screen.
         performVerticalSwipeDownGesture(
@@ -113,6 +116,7 @@
         assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
         assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
         assertAppBarElevation(0f);
+        assertScrimAlpha(0);
 
         // Perform yet another swipe-down gesture across the horizontal center of the screen.
         performVerticalSwipeDownGesture(
@@ -127,6 +131,7 @@
         assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
         assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
         assertAppBarElevation(0f);
+        assertScrimAlpha(0);
     }
 
     @Test
@@ -157,6 +162,7 @@
         final int shortSwipeAmount = toolbarHeight;
 
         assertAppBarElevation(0f);
+        assertScrimAlpha(0);
 
         // Perform a swipe-up gesture across the horizontal center of the screen, starting from
         // just below the AppBarLayout
@@ -173,6 +179,7 @@
         // Allow for off-by-a-pixel margin of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight - topInset, 1);
         assertAppBarElevation(0f);
+        assertScrimAlpha(255);
 
         // Perform another swipe-up gesture
         performVerticalSwipeUpGesture(
@@ -186,6 +193,7 @@
         // margin of error.
         assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight - topInset, 1);
         assertAppBarElevation(0f);
+        assertScrimAlpha(255);
 
         // Perform a short swipe-down gesture across the horizontal center of the screen.
         // Note that the swipe down is a bit longer than the swipe up to fully bring down
@@ -203,6 +211,7 @@
         assertEquals(originalAppbarTop + toolbarHeight + topInset,
                 appbarOnScreenXY[1] + appbarHeight, 1);
         assertAppBarElevation(mDefaultElevationValue);
+        assertScrimAlpha(255);
 
         // Perform another swipe-down gesture across the horizontal center of the screen.
         performVerticalSwipeDownGesture(
@@ -217,6 +226,7 @@
         assertEquals(originalAppbarTop, appbarOnScreenXY[1]);
         assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight);
         assertAppBarElevation(0f);
+        assertScrimAlpha(0);
 
         // Perform yet another swipe-down gesture across the horizontal center of the screen.
         performVerticalSwipeDownGesture(
@@ -231,6 +241,7 @@
         assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
         assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
         assertAppBarElevation(0f);
+        assertScrimAlpha(0);
     }
 
     @Test
diff --git a/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java b/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java
index 37a58a6..f06a85a 100644
--- a/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java
+++ b/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java
@@ -223,6 +223,16 @@
         checkAndVerifyExclusiveItem(menu, R.id.destination_people);
     }
 
+    @UiThreadTest
+    @Test
+    @SmallTest
+    public void testClearingMenu() throws Throwable {
+        mBottomNavigation.getMenu().clear();
+        assertEquals(0, mBottomNavigation.getMenu().size());
+        mBottomNavigation.inflateMenu(R.menu.bottom_navigation_view_content);
+        assertEquals(3, mBottomNavigation.getMenu().size());
+    }
+
     private void checkAndVerifyExclusiveItem(final Menu menu, final int id) throws Throwable {
         menu.findItem(id).setChecked(true);
         for (int i = 0; i < menu.size(); i++) {
diff --git a/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java b/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java
index 1d4f0a7..f8e4f99 100644
--- a/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java
+++ b/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java
@@ -16,6 +16,7 @@
 
 package android.support.design.widget;
 
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.action.ViewActions.swipeUp;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
@@ -40,8 +41,8 @@
 import android.graphics.Rect;
 import android.support.design.test.R;
 import android.support.design.testutils.CoordinatorLayoutUtils;
+import android.support.design.testutils.CoordinatorLayoutUtils.DependentBehavior;
 import android.support.design.widget.CoordinatorLayout.Behavior;
-import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 import android.support.test.filters.SdkSuppress;
 import android.support.v4.view.ViewCompat;
@@ -69,13 +70,13 @@
 
     @Before
     public void setup() {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mInstrumentation = getInstrumentation();
     }
 
     @Test
     @SdkSuppress(minSdkVersion = 21)
     public void testSetFitSystemWindows() throws Throwable {
-        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final Instrumentation instrumentation = getInstrumentation();
         final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
         final View view = new View(col.getContext());
 
@@ -183,7 +184,7 @@
 
     @Test
     public void testInsetEdge() throws Throwable {
-        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final Instrumentation instrumentation = getInstrumentation();
         final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
 
         final View insetView = new View(col.getContext());
@@ -236,7 +237,7 @@
 
     @Test
     public void testDependentViewChanged() throws Throwable {
-        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final Instrumentation instrumentation = getInstrumentation();
         final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
 
         // Add two views, A & B, where B depends on A
@@ -250,7 +251,7 @@
         lpB.width = 100;
         lpB.height = 100;
         final CoordinatorLayout.Behavior behavior =
-                spy(new CoordinatorLayoutUtils.DependentBehavior(viewA));
+                spy(new DependentBehavior(viewA));
         lpB.setBehavior(behavior);
 
         mActivityTestRule.runOnUiThread(new Runnable() {
@@ -282,7 +283,7 @@
 
     @Test
     public void testDependentViewRemoved() throws Throwable {
-        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final Instrumentation instrumentation = getInstrumentation();
         final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
 
         // Add two views, A & B, where B depends on A
@@ -290,7 +291,7 @@
         final View viewB = new View(col.getContext());
         final CoordinatorLayout.LayoutParams lpB = col.generateDefaultLayoutParams();
         final CoordinatorLayout.Behavior behavior =
-                spy(new CoordinatorLayoutUtils.DependentBehavior(viewA));
+                spy(new DependentBehavior(viewA));
         lpB.setBehavior(behavior);
 
         mActivityTestRule.runOnUiThread(new Runnable() {
@@ -316,7 +317,7 @@
 
     @Test
     public void testGetDependenciesAfterDependentViewRemoved() throws Throwable {
-        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final Instrumentation instrumentation = getInstrumentation();
         final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
 
         // Add two views, A & B, where B depends on A
@@ -572,6 +573,55 @@
     }
 
     @Test
+    public void testNestedScrollingTriggeringDependentViewChanged() throws Throwable {
+        final CoordinatorLayoutActivity activity = mActivityTestRule.getActivity();
+        final CoordinatorLayout col = activity.mCoordinatorLayout;
+
+        // First a NestedScrollView to trigger nested scrolling
+        final View scrollView = LayoutInflater.from(activity).inflate(
+                R.layout.include_nestedscrollview, col, false);
+
+        // Now create a View and Behavior which depend on the scrollview
+        final ImageView dependentView = new ImageView(activity);
+        final CoordinatorLayout.Behavior dependentBehavior = spy(new DependentBehavior(scrollView));
+
+        // Finally a view which accepts nested scrolling in the CoordinatorLayout
+        final ImageView nestedScrollAwareView = new ImageView(activity);
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                // First add the ScrollView
+                col.addView(scrollView);
+
+                // Now add the view which depends on the scrollview
+                CoordinatorLayout.LayoutParams clp = new CoordinatorLayout.LayoutParams(200, 200);
+                clp.setBehavior(dependentBehavior);
+                col.addView(dependentView, clp);
+
+                // Now add the nested scrolling aware view
+                clp = new CoordinatorLayout.LayoutParams(200, 200);
+                clp.setBehavior(new NestedScrollingBehavior());
+                col.addView(nestedScrollAwareView, clp);
+            }
+        });
+
+        // Wait for any layouts, and reset the Behavior so that the call counts are 0
+        getInstrumentation().waitForIdleSync();
+        reset(dependentBehavior);
+
+        // Now vertically swipe up on the NSV, causing nested scrolling to occur
+        onView(withId(R.id.nested_scrollview)).perform(swipeUp());
+
+        // Verify that the Behavior's onDependentViewChanged is not called due to the
+        // nested scroll
+        verify(dependentBehavior, never()).onDependentViewChanged(
+                eq(col), // parent
+                eq(dependentView), // child
+                eq(scrollView)); // axes
+    }
+
+    @Test
     public void testDodgeInsetViewWithEmptyBounds() throws Throwable {
         final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
 
@@ -614,9 +664,9 @@
                 .getInsetDodgeRect(same(col), same(view), any(Rect.class));
     }
 
-    public static class NestedScrollingBehavior extends CoordinatorLayout.Behavior<ImageView> {
+    public static class NestedScrollingBehavior extends CoordinatorLayout.Behavior<View> {
         @Override
-        public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, ImageView child,
+        public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child,
                 View directTargetChild, View target, int nestedScrollAxes) {
             // Return true so that we always accept nested scroll events
             return true;
diff --git a/design/tests/src/android/support/design/widget/FloatingActionButtonTest.java b/design/tests/src/android/support/design/widget/FloatingActionButtonTest.java
index 069055b..e8cc701 100644
--- a/design/tests/src/android/support/design/widget/FloatingActionButtonTest.java
+++ b/design/tests/src/android/support/design/widget/FloatingActionButtonTest.java
@@ -33,11 +33,15 @@
 import static android.support.design.testutils.TestUtilsMatchers.withFabContentHeight;
 import static android.support.design.widget.DesignViewActions.setVisibility;
 import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
 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 static org.hamcrest.Matchers.not;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.app.Activity;
 import android.content.res.ColorStateList;
@@ -229,6 +233,19 @@
     }
 
     @Test
+    public void testOnClickListener() {
+        final View.OnClickListener listener = mock(View.OnClickListener.class);
+        final View view = mActivityTestRule.getActivity().findViewById(R.id.fab_standard);
+        view.setOnClickListener(listener);
+
+        // Click on the fab
+        onView(withId(R.id.fab_standard)).perform(click());
+
+        // And verify that the listener was invoked once
+        verify(listener, times(1)).onClick(view);
+    }
+
+    @Test
     public void testSetCompatElevation() {
         onView(withId(R.id.fab_standard))
                 .perform(setEnabled(false))
diff --git a/design/tests/src/android/support/design/widget/NavigationTestView.java b/design/tests/src/android/support/design/widget/NavigationTestView.java
new file mode 100644
index 0000000..af3d69e
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/NavigationTestView.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.design.widget;
+
+import android.content.Context;
+import android.support.v4.view.WindowInsetsCompat;
+import android.util.AttributeSet;
+
+/**
+ * Expose hasSystemWindowInsets() for testing.
+ */
+public class NavigationTestView extends NavigationView {
+
+    boolean mHashSystemWindowInsets;
+
+    public NavigationTestView(Context context) {
+        this(context, null);
+    }
+
+    public NavigationTestView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public NavigationTestView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void onInsetsChanged(WindowInsetsCompat insets) {
+        super.onInsetsChanged(insets);
+        mHashSystemWindowInsets = insets.hasSystemWindowInsets();
+    }
+
+    public boolean hasSystemWindowInsets() {
+        return mHashSystemWindowInsets;
+    }
+}
diff --git a/design/tests/src/android/support/design/widget/NavigationViewTest.java b/design/tests/src/android/support/design/widget/NavigationViewTest.java
index 7031281..f0c064e 100755
--- a/design/tests/src/android/support/design/widget/NavigationViewTest.java
+++ b/design/tests/src/android/support/design/widget/NavigationViewTest.java
@@ -94,7 +94,7 @@
 
     private DrawerLayout mDrawerLayout;
 
-    private NavigationView mNavigationView;
+    private NavigationTestView mNavigationView;
 
     public NavigationViewTest() {
         super(NavigationViewActivity.class);
@@ -104,7 +104,7 @@
     public void setUp() throws Exception {
         final NavigationViewActivity activity = mActivityTestRule.getActivity();
         mDrawerLayout = (DrawerLayout) activity.findViewById(R.id.drawer_layout);
-        mNavigationView = (NavigationView) mDrawerLayout.findViewById(R.id.start_drawer);
+        mNavigationView = (NavigationTestView) mDrawerLayout.findViewById(R.id.start_drawer);
 
         // Close the drawer to reset the state for the next test
         onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
@@ -146,7 +146,11 @@
         onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
 
         if (Build.VERSION.SDK_INT >= 21) {
-            assertFalse(mNavigationView.willNotDraw());
+            if (mNavigationView.hasSystemWindowInsets()) {
+                assertFalse(mNavigationView.willNotDraw());
+            } else {
+                assertTrue(mNavigationView.willNotDraw());
+            }
         } else {
             assertTrue(mNavigationView.willNotDraw());
         }
diff --git a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
index 30c15b5..2ba1e6e 100755
--- a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
+++ b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
@@ -31,6 +31,7 @@
 import static android.support.design.testutils.TextInputLayoutActions.setTypeface;
 import static android.support.design.testutils.TextInputLayoutMatchers
         .hasPasswordToggleContentDescription;
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
 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.typeText;
@@ -38,6 +39,7 @@
 import static android.support.test.espresso.assertion.ViewAssertions.matches;
 import static android.support.test.espresso.contrib.AccessibilityChecks.accessibilityAssertion;
 import static android.support.test.espresso.matcher.ViewMatchers.hasContentDescription;
+import static android.support.test.espresso.matcher.ViewMatchers.hasFocus;
 import static android.support.test.espresso.matcher.ViewMatchers.isChecked;
 import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static android.support.test.espresso.matcher.ViewMatchers.isEnabled;
@@ -66,6 +68,7 @@
 import android.support.v4.widget.TextViewCompat;
 import android.util.AttributeSet;
 import android.util.SparseArray;
+import android.view.KeyEvent;
 import android.view.View;
 import android.view.inputmethod.EditorInfo;
 import android.widget.EditText;
@@ -456,6 +459,27 @@
                 .check(matches(withTextColor(textColor)));
     }
 
+    @Test
+    public void testTextSetViaAttributeCollapsedHint() {
+        onView(withId(R.id.textinput_with_text))
+                .check(isHintExpanded(false));
+    }
+
+    @Test
+    public void testFocusMovesToEditTextWithPasswordEnabled() {
+        // Focus the preceding EditText
+        onView(withId(R.id.textinput_edittext))
+                .perform(click())
+                .check(matches(hasFocus()));
+
+        // Then send a TAB to focus the next view
+        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
+
+        // And check that the EditText is focused
+        onView(withId(R.id.textinput_edittext_pwd))
+                .check(matches(hasFocus()));
+    }
+
     static ViewAssertion isHintExpanded(final boolean expanded) {
         return new ViewAssertion() {
             @Override
diff --git a/documents-archive/AndroidManifest.xml b/documents-archive/AndroidManifest.xml
index 2cd0f7a..fd64ed9 100644
--- a/documents-archive/AndroidManifest.xml
+++ b/documents-archive/AndroidManifest.xml
@@ -16,5 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.provider">
     <uses-sdk android:minSdkVersion="19"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application />
 </manifest>
diff --git a/exifinterface/AndroidManifest.xml b/exifinterface/AndroidManifest.xml
index 4812a71..bacbdbe 100644
--- a/exifinterface/AndroidManifest.xml
+++ b/exifinterface/AndroidManifest.xml
@@ -16,5 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.exifinterface">
     <uses-sdk android:minSdkVersion="9"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application />
 </manifest>
diff --git a/exifinterface/build.gradle b/exifinterface/build.gradle
index 42f50c1..f6fd633 100644
--- a/exifinterface/build.gradle
+++ b/exifinterface/build.gradle
@@ -1,5 +1,4 @@
 apply plugin: 'com.android.library'
-
 archivesBaseName = 'exifinterface'
 
 dependencies {
@@ -33,28 +32,11 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/fragment/AndroidManifest.xml b/fragment/AndroidManifest.xml
index 54e61d3..9b34e14 100644
--- a/fragment/AndroidManifest.xml
+++ b/fragment/AndroidManifest.xml
@@ -17,5 +17,6 @@
           xmlns:tools="http://schemas.android.com/tools"
           package="android.support.fragment">
     <uses-sdk android:minSdkVersion="9" tools:overrideLibrary="android.support.fragment"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application />
 </manifest>
diff --git a/fragment/api21/android/support/v4/app/FragmentTransitionCompat21.java b/fragment/api21/android/support/v4/app/FragmentTransitionCompat21.java
index a8323dc..879be4a 100644
--- a/fragment/api21/android/support/v4/app/FragmentTransitionCompat21.java
+++ b/fragment/api21/android/support/v4/app/FragmentTransitionCompat21.java
@@ -24,7 +24,6 @@
 import android.transition.TransitionSet;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -343,18 +342,15 @@
             }
         }
 
-        sceneRoot.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
-                        for (int i = 0; i < numSharedElements; i++) {
-                            sharedElementsIn.get(i).setTransitionName(inNames.get(i));
-                            sharedElementsOut.get(i).setTransitionName(outNames.get(i));
-                        }
-                        return true;
-                    }
-                });
+        OneShotPreDrawListener.add(sceneRoot, new Runnable() {
+            @Override
+            public void run() {
+                for (int i = 0; i < numSharedElements; i++) {
+                    sharedElementsIn.get(i).setTransitionName(inNames.get(i));
+                    sharedElementsOut.get(i).setTransitionName(outNames.get(i));
+                }
+            }
+        });
     }
 
     /**
@@ -406,23 +402,20 @@
 
     public static void setNameOverridesUnoptimized(final View sceneRoot,
             final ArrayList<View> sharedElementsIn, final Map<String, String> nameOverrides) {
-        sceneRoot.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
-                        final int numSharedElements = sharedElementsIn.size();
-                        for (int i = 0; i < numSharedElements; i++) {
-                            View view = sharedElementsIn.get(i);
-                            String name = view.getTransitionName();
-                            if (name != null) {
-                                String inName = findKeyForValue(nameOverrides, name);
-                                view.setTransitionName(inName);
-                            }
-                        }
-                        return true;
+        OneShotPreDrawListener.add(sceneRoot, new Runnable() {
+            @Override
+            public void run() {
+                final int numSharedElements = sharedElementsIn.size();
+                for (int i = 0; i < numSharedElements; i++) {
+                    View view = sharedElementsIn.get(i);
+                    String name = view.getTransitionName();
+                    if (name != null) {
+                        String inName = findKeyForValue(nameOverrides, name);
+                        view.setTransitionName(inName);
                     }
-                });
+                }
+            }
+        });
     }
 
     /**
@@ -566,20 +559,17 @@
 
     public static void scheduleNameReset(final ViewGroup sceneRoot,
             final ArrayList<View> sharedElementsIn, final Map<String, String> nameOverrides) {
-        sceneRoot.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
-                        final int numSharedElements = sharedElementsIn.size();
-                        for (int i = 0; i < numSharedElements; i++) {
-                            final View view = sharedElementsIn.get(i);
-                            final String name = view.getTransitionName();
-                            final String inName = nameOverrides.get(name);
-                            view.setTransitionName(inName);
-                        }
-                        return true;
-                    }
-                });
+        OneShotPreDrawListener.add(sceneRoot, new Runnable() {
+            @Override
+            public void run() {
+                final int numSharedElements = sharedElementsIn.size();
+                for (int i = 0; i < numSharedElements; i++) {
+                    final View view = sharedElementsIn.get(i);
+                    final String name = view.getTransitionName();
+                    final String inName = nameOverrides.get(name);
+                    view.setTransitionName(inName);
+                }
+            }
+        });
     }
 }
diff --git a/fragment/api21/android/support/v4/app/OneShotPreDrawListener.java b/fragment/api21/android/support/v4/app/OneShotPreDrawListener.java
new file mode 100644
index 0000000..502af1f
--- /dev/null
+++ b/fragment/api21/android/support/v4/app/OneShotPreDrawListener.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.app;
+
+
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+/**
+ * An OnPreDrawListener that will remove itself after one OnPreDraw call. Typical
+ * usage is:
+ * <pre><code>
+ *     OneShotPreDrawListener.add(view, () -> { view.doSomething(); })
+ * </code></pre>
+ * <p>
+ * The onPreDraw always returns true.
+ * <p>
+ * The listener will also remove itself from the ViewTreeObserver when the view
+ * is detached from the view hierarchy. In that case, the Runnable will never be
+ * executed.
+ */
+class OneShotPreDrawListener implements ViewTreeObserver.OnPreDrawListener,
+        View.OnAttachStateChangeListener {
+    private final View mView;
+    private ViewTreeObserver mViewTreeObserver;
+    private final Runnable mRunnable;
+
+    private OneShotPreDrawListener(View view, Runnable runnable) {
+        mView = view;
+        mViewTreeObserver = view.getViewTreeObserver();
+        mRunnable = runnable;
+    }
+
+    /**
+     * Creates a OneShotPreDrawListener and adds it to view's ViewTreeObserver.
+     * @param view The view whose ViewTreeObserver the OnPreDrawListener should listen.
+     * @param runnable The Runnable to execute in the OnPreDraw (once)
+     * @return The added OneShotPreDrawListener. It can be removed prior to
+     * the onPreDraw by calling {@link #removeListener()}.
+     */
+    public static OneShotPreDrawListener add(View view, Runnable runnable) {
+        OneShotPreDrawListener listener = new OneShotPreDrawListener(view, runnable);
+        view.getViewTreeObserver().addOnPreDrawListener(listener);
+        view.addOnAttachStateChangeListener(listener);
+        return listener;
+    }
+
+    @Override
+    public boolean onPreDraw() {
+        removeListener();
+        mRunnable.run();
+        return true;
+    }
+
+    /**
+     * Removes the listener from the ViewTreeObserver. This is useful to call if the
+     * callback should be removed prior to {@link #onPreDraw()}.
+     */
+    public void removeListener() {
+        if (mViewTreeObserver.isAlive()) {
+            mViewTreeObserver.removeOnPreDrawListener(this);
+        } else {
+            mView.getViewTreeObserver().removeOnPreDrawListener(this);
+        }
+        mView.removeOnAttachStateChangeListener(this);
+    }
+
+    @Override
+    public void onViewAttachedToWindow(View v) {
+        mViewTreeObserver = v.getViewTreeObserver();
+    }
+
+    @Override
+    public void onViewDetachedFromWindow(View v) {
+        removeListener();
+    }
+}
diff --git a/fragment/build.gradle b/fragment/build.gradle
index 26e9f1c..7c9098a 100644
--- a/fragment/build.gradle
+++ b/fragment/build.gradle
@@ -18,9 +18,6 @@
     testCompile 'junit:junit:4.12'
 }
 
-sourceCompatibility = JavaVersion.VERSION_1_7
-targetCompatibility = JavaVersion.VERSION_1_7
-
 android {
     compileSdkVersion project.ext.currentSdk
 
@@ -50,11 +47,6 @@
         sourceCompatibility JavaVersion.VERSION_1_7
         targetCompatibility JavaVersion.VERSION_1_7
     }
-
-    testOptions {
-        unitTests.returnDefaultValues = true
-        compileSdkVersion project.ext.currentSdk
-    }
 }
 
 android.libraryVariants.all { variant ->
@@ -65,22 +57,6 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
@@ -88,7 +64,6 @@
         exclude('android/service/media/**')
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/fragment/java/android/support/v4/app/BackStackRecord.java b/fragment/java/android/support/v4/app/BackStackRecord.java
index 85ded71..3b41d60 100644
--- a/fragment/java/android/support/v4/app/BackStackRecord.java
+++ b/fragment/java/android/support/v4/app/BackStackRecord.java
@@ -216,7 +216,7 @@
 
     ArrayList<String> mSharedElementSourceNames;
     ArrayList<String> mSharedElementTargetNames;
-    boolean mAllowOptimization = true;
+    boolean mAllowOptimization = false;
 
     @Override
     public String toString() {
@@ -513,6 +513,12 @@
             if (mSharedElementSourceNames == null) {
                 mSharedElementSourceNames = new ArrayList<String>();
                 mSharedElementTargetNames = new ArrayList<String>();
+            } else if (mSharedElementTargetNames.contains(name)) {
+                throw new IllegalArgumentException("A shared element with the target name '"
+                        + name + "' has already been added to the transaction.");
+            } else if (mSharedElementSourceNames.contains(transitionName)) {
+                throw new IllegalArgumentException("A shared element with the source name '"
+                        + transitionName + " has already been added to the transaction.");
             }
 
             mSharedElementSourceNames.add(transitionName);
@@ -789,7 +795,7 @@
                 default:
                     throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
             }
-            if (!mAllowOptimization && op.cmd != OP_ADD) {
+            if (!mAllowOptimization && op.cmd != OP_REMOVE) {
                 mManager.moveFragmentToExpectedState(f);
             }
         }
@@ -855,6 +861,29 @@
         }
     }
 
+    /**
+     * Removes fragments that are added or removed during a pop operation.
+     *
+     * @param added Initialized to the fragments that are in the mManager.mAdded, this
+     *              will be modified to contain the fragments that will be in mAdded
+     *              after the execution ({@link #executeOps()}.
+     */
+    void trackAddedFragmentsInPop(ArrayList<Fragment> added) {
+        for (int opNum = 0; opNum < mOps.size(); opNum++) {
+            final Op op = mOps.get(opNum);
+            switch (op.cmd) {
+                case OP_ADD:
+                case OP_ATTACH:
+                    added.remove(op.fragment);
+                    break;
+                case OP_REMOVE:
+                case OP_DETACH:
+                    added.add(op.fragment);
+                    break;
+            }
+        }
+    }
+
     boolean isPostponed() {
         for (int opNum = 0; opNum < mOps.size(); opNum++) {
             final Op op = mOps.get(opNum);
diff --git a/fragment/java/android/support/v4/app/Fragment.java b/fragment/java/android/support/v4/app/Fragment.java
index 0c7300e..881c2b4 100644
--- a/fragment/java/android/support/v4/app/Fragment.java
+++ b/fragment/java/android/support/v4/app/Fragment.java
@@ -321,6 +321,11 @@
     // True if mHidden has been changed and the animation should be scheduled.
     boolean mHiddenChanged;
 
+    // The alpha of the view when the view was added and then postponed. If the value is less
+    // than zero, this means that the view's add was canceled and should not participate in
+    // removal animations.
+    float mPostponedAlpha;
+
     /**
      * State information that has been retrieved from a fragment instance
      * through {@link FragmentManager#saveFragmentInstanceState(Fragment)
diff --git a/fragment/java/android/support/v4/app/FragmentManager.java b/fragment/java/android/support/v4/app/FragmentManager.java
index 829e974..bf1c371 100644
--- a/fragment/java/android/support/v4/app/FragmentManager.java
+++ b/fragment/java/android/support/v4/app/FragmentManager.java
@@ -32,6 +32,7 @@
 import android.support.annotation.RestrictTo;
 import android.support.annotation.StringRes;
 import android.support.v4.os.BuildCompat;
+import android.support.v4.util.ArraySet;
 import android.support.v4.util.DebugUtils;
 import android.support.v4.util.LogWriter;
 import android.support.v4.util.Pair;
@@ -1306,15 +1307,17 @@
                                 }
                                 if (container != null) {
                                     container.addView(f.mView);
-                                    f.mIsNewlyAdded = true;
                                 }
                                 if (f.mHidden) {
                                     f.mView.setVisibility(View.GONE);
-                                    f.mIsNewlyAdded = false; // No animation
                                 }
                                 f.onViewCreated(f.mView, f.mSavedFragmentState);
                                 dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState,
                                         false);
+                                // Only animate the view if it is visible. This is done after
+                                // dispatchOnFragmentViewCreated in case visibility is changed
+                                f.mIsNewlyAdded = (f.mView.getVisibility() == View.VISIBLE)
+                                        && f.mContainer != null;
                             } else {
                                 f.mInnerView = null;
                             }
@@ -1380,10 +1383,12 @@
                         if (f.mView != null && f.mContainer != null) {
                             Animation anim = null;
                             if (mCurState > Fragment.INITIALIZING && !mDestroyed
-                                    && f.mView.getVisibility() == View.VISIBLE) {
+                                    && f.mView.getVisibility() == View.VISIBLE
+                                    && f.mPostponedAlpha >= 0) {
                                 anim = loadAnimation(f, transit, false,
                                         transitionStyle);
                             }
+                            f.mPostponedAlpha = 0;
                             if (anim != null) {
                                 final Fragment fragment = f;
                                 f.setAnimatingAway(f.mView);
@@ -1538,7 +1543,12 @@
             }
             if (f.mIsNewlyAdded && f.mContainer != null) {
                 // Make it visible and run the animations
-                f.mView.setVisibility(View.VISIBLE);
+                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+                    f.mView.setVisibility(View.VISIBLE);
+                } else if (f.mPostponedAlpha > 0f) {
+                    f.mView.setAlpha(f.mPostponedAlpha);
+                }
+                f.mPostponedAlpha = 0f;
                 f.mIsNewlyAdded = false;
                 // run animations:
                 Animation anim = loadAnimation(f, f.getNextTransition(), true,
@@ -2118,6 +2128,8 @@
             final boolean isPop = isRecordPop.get(recordNum);
             if (!isPop) {
                 record.expandReplaceOps(mTmpAddedFragments);
+            } else {
+                record.trackAddedFragmentsInPop(mTmpAddedFragments);
             }
             final int bumpAmount = isPop ? -1 : 1;
             record.bumpBackStackNesting(bumpAmount);
@@ -2133,9 +2145,11 @@
 
         int postponeIndex = endIndex;
         if (allowOptimization) {
-            moveFragmentsToInvisible();
+            ArraySet<Fragment> addedFragments = new ArraySet<>();
+            addAddedFragments(addedFragments);
             postponeIndex = postponePostponableTransactions(records, isRecordPop,
-                    startIndex, endIndex);
+                    startIndex, endIndex, addedFragments);
+            makeRemovedFragmentsInvisible(addedFragments);
         }
 
         if (postponeIndex != startIndex && allowOptimization) {
@@ -2159,6 +2173,30 @@
     }
 
     /**
+     * Any fragments that were removed because they have been postponed should have their views
+     * made invisible by setting their alpha to 0 on API >= 11 or setting visibility to INVISIBLE
+     * on API < 11.
+     *
+     * @param fragments The fragments that were added during operation execution. Only the ones
+     *                  that are no longer added will have their alpha changed.
+     */
+    private void makeRemovedFragmentsInvisible(ArraySet<Fragment> fragments) {
+        final int numAdded = fragments.size();
+        for (int i = 0; i < numAdded; i++) {
+            final Fragment fragment = fragments.valueAt(i);
+            if (!fragment.mAdded) {
+                final View view = fragment.getView();
+                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+                    fragment.getView().setVisibility(View.INVISIBLE);
+                } else {
+                    fragment.mPostponedAlpha = view.getAlpha();
+                    view.setAlpha(0f);
+                }
+            }
+        }
+    }
+
+    /**
      * Examine all transactions and determine which ones are marked as postponed. Those will
      * have their operations rolled back and moved to the end of the record list (up to endIndex).
      * It will also add the postponed transaction to the queue.
@@ -2171,7 +2209,8 @@
      * postponed.
      */
     private int postponePostponableTransactions(ArrayList<BackStackRecord> records,
-            ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
+            ArrayList<Boolean> isRecordPop, int startIndex, int endIndex,
+            ArraySet<Fragment> added) {
         int postponeIndex = endIndex;
         for (int i = endIndex - 1; i >= startIndex; i--) {
             final BackStackRecord record = records.get(i);
@@ -2202,7 +2241,7 @@
                 }
 
                 // different views may be visible now
-                moveFragmentsToInvisible();
+                addAddedFragments(added);
             }
         }
         return postponeIndex;
@@ -2235,15 +2274,26 @@
         }
         if (moveToState) {
             moveToState(mCurState, true);
-        } else if (mActive != null) {
+        }
+
+        if (mActive != null) {
             final int numActive = mActive.size();
             for (int i = 0; i < numActive; i++) {
                 // Allow added fragments to be removed during the pop since we aren't going
                 // to move them to the final state with moveToState(mCurState).
                 Fragment fragment = mActive.get(i);
-                if (fragment.mView != null && fragment.mIsNewlyAdded
+                if (fragment != null && fragment.mView != null && fragment.mIsNewlyAdded
                         && record.interactsWith(fragment.mContainerId)) {
-                    fragment.mIsNewlyAdded = false;
+                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB
+                            && fragment.mPostponedAlpha > 0) {
+                        fragment.mView.setAlpha(fragment.mPostponedAlpha);
+                    }
+                    if (moveToState) {
+                        fragment.mPostponedAlpha = 0;
+                    } else {
+                        fragment.mPostponedAlpha = -1;
+                        fragment.mIsNewlyAdded = false;
+                    }
                 }
             }
         }
@@ -2307,10 +2357,11 @@
 
     /**
      * Ensure that fragments that are added are moved to at least the CREATED state.
-     * Any newly-added Views are made INVISIBLE so that the Transaction can be postponed
-     * with {@link Fragment#postponeEnterTransition()}.
+     * Any newly-added Views are inserted into {@code added} so that the Transaction can be
+     * postponed with {@link Fragment#postponeEnterTransition()}. They will later be made
+     * invisible (by setting their alpha to 0) if they have been removed when postponed.
      */
-    private void moveFragmentsToInvisible() {
+    private void addAddedFragments(ArraySet<Fragment> added) {
         if (mCurState < Fragment.CREATED) {
             return;
         }
@@ -2323,7 +2374,7 @@
                 moveToState(fragment, state, fragment.getNextAnim(), fragment.getNextTransition(),
                         false);
                 if (fragment.mView != null && !fragment.mHidden && fragment.mIsNewlyAdded) {
-                    fragment.mView.setVisibility(View.INVISIBLE);
+                    added.add(fragment);
                 }
             }
         }
@@ -2353,7 +2404,10 @@
                 final int stateAfterAnimating = fragment.getStateAfterAnimating();
                 final View animatingAway = fragment.getAnimatingAway();
                 fragment.setAnimatingAway(null);
-                animatingAway.clearAnimation();
+                Animation animation = animatingAway.getAnimation();
+                if (animation != null) {
+                    animation.cancel();
+                }
                 moveToState(fragment, stateAfterAnimating, 0, 0, false);
             }
         }
diff --git a/fragment/java/android/support/v4/app/FragmentTransaction.java b/fragment/java/android/support/v4/app/FragmentTransaction.java
index b03f002..0171681 100644
--- a/fragment/java/android/support/v4/app/FragmentTransaction.java
+++ b/fragment/java/android/support/v4/app/FragmentTransaction.java
@@ -296,7 +296,7 @@
      * With optimization, fragment B cannot expect fragment A to exist when
      * it has been created because fragment A's add/remove will be optimized out.
      * <p>
-     * The default is {@code true}.
+     * The default is {@code false}.
      *
      * @param allowOptimization {@code true} to enable optimizing operations
      *                          or {@code false} to disable optimizing
diff --git a/fragment/java/android/support/v4/app/FragmentTransition.java b/fragment/java/android/support/v4/app/FragmentTransition.java
index 9efd1bc..71ac889 100644
--- a/fragment/java/android/support/v4/app/FragmentTransition.java
+++ b/fragment/java/android/support/v4/app/FragmentTransition.java
@@ -22,7 +22,6 @@
 import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -250,15 +249,12 @@
             FragmentTransitionCompat21.scheduleHideFragmentView(exitTransition,
                     exitingFragment.getView(), exitingViews);
             final ViewGroup container = exitingFragment.mContainer;
-            container.getViewTreeObserver().addOnPreDrawListener(
-                    new ViewTreeObserver.OnPreDrawListener() {
-                        @Override
-                        public boolean onPreDraw() {
-                            container.getViewTreeObserver().removeOnPreDrawListener(this);
-                            setViewVisibility(exitingViews, View.INVISIBLE);
-                            return true;
-                        }
-                    });
+            OneShotPreDrawListener.add(container, new Runnable() {
+                @Override
+                public void run() {
+                    setViewVisibility(exitingViews, View.INVISIBLE);
+                }
+            });
         }
     }
 
@@ -356,33 +352,29 @@
             final ArrayList<View> sharedElementsIn,
             final Object enterTransition, final ArrayList<View> enteringViews,
             final Object exitTransition, final ArrayList<View> exitingViews) {
+        OneShotPreDrawListener.add(sceneRoot, new Runnable() {
+            @Override
+            public void run() {
+                if (enterTransition != null) {
+                    FragmentTransitionCompat21.removeTarget(enterTransition,
+                            nonExistentView);
+                    ArrayList<View> views = configureEnteringExitingViews(
+                            enterTransition, inFragment, sharedElementsIn, nonExistentView);
+                    enteringViews.addAll(views);
+                }
 
-        sceneRoot.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
-
-                        if (enterTransition != null) {
-                            FragmentTransitionCompat21.removeTarget(enterTransition,
-                                    nonExistentView);
-                            ArrayList<View> views = configureEnteringExitingViews(
-                                    enterTransition, inFragment, sharedElementsIn, nonExistentView);
-                            enteringViews.addAll(views);
-                        }
-
-                        if (exitingViews != null) {
-                            ArrayList<View> tempExiting = new ArrayList<>();
-                            tempExiting.add(nonExistentView);
-                            FragmentTransitionCompat21.replaceTargets(exitTransition, exitingViews,
-                                    tempExiting);
-                            exitingViews.clear();
-                            exitingViews.add(nonExistentView);
-                        }
-
-                        return true;
+                if (exitingViews != null) {
+                    if (exitTransition != null) {
+                        ArrayList<View> tempExiting = new ArrayList<>();
+                        tempExiting.add(nonExistentView);
+                        FragmentTransitionCompat21.replaceTargets(exitTransition, exitingViews,
+                                tempExiting);
                     }
-                });
+                    exitingViews.clear();
+                    exitingViews.add(nonExistentView);
+                }
+            }
+        });
     }
 
     /**
@@ -486,9 +478,17 @@
 
         if (nameOverrides.isEmpty()) {
             sharedElementTransition = null;
+            if (outSharedElements != null) {
+                outSharedElements.clear();
+            }
+            if (inSharedElements != null) {
+                inSharedElements.clear();
+            }
         } else {
-            sharedElementsOut.addAll(outSharedElements.values());
-            sharedElementsIn.addAll(inSharedElements.values());
+            addSharedElementsWithMatchingNames(sharedElementsOut, outSharedElements,
+                    nameOverrides.keySet());
+            addSharedElementsWithMatchingNames(sharedElementsIn, inSharedElements,
+                    nameOverrides.values());
         }
 
         if (enterTransition == null && exitTransition == null && sharedElementTransition == null) {
@@ -519,23 +519,39 @@
             epicenterView = null;
         }
 
-        sceneRoot.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
-                        callSharedElementStartEnd(inFragment, outFragment, inIsPop,
-                                inSharedElements, false);
-                        if (epicenterView != null) {
-                            FragmentTransitionCompat21.getBoundsOnScreen(epicenterView, epicenter);
-                        }
-                        return true;
-                    }
-                });
+        OneShotPreDrawListener.add(sceneRoot, new Runnable() {
+            @Override
+            public void run() {
+                callSharedElementStartEnd(inFragment, outFragment, inIsPop,
+                        inSharedElements, false);
+                if (epicenterView != null) {
+                    FragmentTransitionCompat21.getBoundsOnScreen(epicenterView, epicenter);
+                }
+            }
+        });
         return sharedElementTransition;
     }
 
     /**
+     * Add Views from sharedElements into views that have the transitionName in the
+     * nameOverridesSet.
+     *
+     * @param views               Views list to add shared elements to
+     * @param sharedElements      List of shared elements
+     * @param nameOverridesSet    The transition names for all views to be copied from
+     *                            sharedElements to views.
+     */
+    private static void addSharedElementsWithMatchingNames(ArrayList<View> views,
+            ArrayMap<String, View> sharedElements, Collection<String> nameOverridesSet) {
+        for (int i = sharedElements.size() - 1; i >= 0; i--) {
+            View view = sharedElements.valueAt(i);
+            if (nameOverridesSet.contains(ViewCompat.getTransitionName(view))) {
+                views.add(view);
+            }
+        }
+    }
+
+    /**
      * Configures the shared elements of an unoptimized fragment transaction's transition.
      * This retrieves the shared elements of the incoming fragments, and schedules capturing
      * the incoming fragment's shared elements. It also maps the views, and sets up the epicenter
@@ -612,38 +628,36 @@
             inEpicenter = null;
         }
 
+
         final Object finalSharedElementTransition = sharedElementTransition;
+        OneShotPreDrawListener.add(sceneRoot, new Runnable() {
+            @Override
+            public void run() {
+                ArrayMap<String, View> inSharedElements = captureInSharedElements(
+                        nameOverrides, finalSharedElementTransition, fragments);
 
-        sceneRoot.getViewTreeObserver().addOnPreDrawListener(
-                new ViewTreeObserver.OnPreDrawListener() {
-                    @Override
-                    public boolean onPreDraw() {
-                        sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
-                        ArrayMap<String, View> inSharedElements = captureInSharedElements(
-                                nameOverrides, finalSharedElementTransition, fragments);
+                if (inSharedElements != null) {
+                    sharedElementsIn.addAll(inSharedElements.values());
+                    sharedElementsIn.add(nonExistentView);
+                }
 
-                        if (inSharedElements != null) {
-                            sharedElementsIn.addAll(inSharedElements.values());
-                            sharedElementsIn.add(nonExistentView);
-                        }
+                callSharedElementStartEnd(inFragment, outFragment, inIsPop,
+                        inSharedElements, false);
+                if (finalSharedElementTransition != null) {
+                    FragmentTransitionCompat21.swapSharedElementTargets(
+                            finalSharedElementTransition, sharedElementsOut,
+                            sharedElementsIn);
 
-                        callSharedElementStartEnd(inFragment, outFragment, inIsPop,
-                                inSharedElements, false);
-                        if (finalSharedElementTransition != null) {
-                            FragmentTransitionCompat21.swapSharedElementTargets(
-                                    finalSharedElementTransition, sharedElementsOut,
-                                    sharedElementsIn);
-
-                            final View inEpicenterView = getInEpicenterView(inSharedElements,
-                                    fragments, enterTransition, inIsPop);
-                            if (inEpicenterView != null) {
-                                FragmentTransitionCompat21.getBoundsOnScreen(inEpicenterView,
-                                        inEpicenter);
-                            }
-                        }
-                        return true;
+                    final View inEpicenterView = getInEpicenterView(inSharedElements,
+                            fragments, enterTransition, inIsPop);
+                    if (inEpicenterView != null) {
+                        FragmentTransitionCompat21.getBoundsOnScreen(inEpicenterView,
+                                inEpicenter);
                     }
-                });
+                }
+            }
+        });
+
         return sharedElementTransition;
     }
 
@@ -1028,7 +1042,8 @@
             case BackStackRecord.OP_DETACH:
                 if (isOptimizedTransaction) {
                     setFirstOut = !fragment.mAdded && fragment.mView != null
-                            && fragment.mView.getVisibility() == View.VISIBLE;
+                            && fragment.mView.getVisibility() == View.VISIBLE
+                            && fragment.mPostponedAlpha >= 0;
                 } else {
                     setFirstOut = fragment.mAdded && !fragment.mHidden;
                 }
diff --git a/fragment/tests/java/android/support/v4/app/FragmentAnimationTest.java b/fragment/tests/java/android/support/v4/app/FragmentAnimationTest.java
index 9f6b263..33e20d0 100644
--- a/fragment/tests/java/android/support/v4/app/FragmentAnimationTest.java
+++ b/fragment/tests/java/android/support/v4/app/FragmentAnimationTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.app.Instrumentation;
+import android.os.Build;
 import android.os.Parcelable;
 import android.support.annotation.AnimRes;
 import android.support.fragment.test.R;
@@ -234,6 +235,7 @@
                 .setCustomAnimations(ENTER, EXIT, POP_ENTER, POP_EXIT)
                 .add(R.id.fragmentContainer, fragment)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.waitForExecution(mActivityRule);
 
@@ -258,6 +260,7 @@
                 .setCustomAnimations(ENTER, EXIT, POP_ENTER, POP_EXIT)
                 .remove(fragment)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.waitForExecution(mActivityRule);
 
@@ -274,6 +277,7 @@
         fm.beginTransaction()
                 .add(R.id.fragmentContainer, fragment1)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.waitForExecution(mActivityRule);
 
@@ -284,6 +288,7 @@
                 .setCustomAnimations(ENTER, EXIT, POP_ENTER, POP_EXIT)
                 .replace(R.id.fragmentContainer, fragment2)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
 
         FragmentTestUtil.waitForExecution(mActivityRule);
@@ -291,6 +296,9 @@
         assertPostponed(fragment2, 0);
         assertNotNull(fragment1.getView());
         assertEquals(View.VISIBLE, fragment1.getView().getVisibility());
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+            assertEquals(1f, fragment1.getView().getAlpha(), 0f);
+        }
         assertTrue(ViewCompat.isAttachedToWindow(fragment1.getView()));
 
         fragment2.startPostponedEnterTransition();
@@ -307,6 +315,7 @@
         final AnimatorFragment fragment1 = new AnimatorFragment();
         fm.beginTransaction()
                 .add(R.id.fragmentContainer, fragment1)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.waitForExecution(mActivityRule);
         assertEquals(0, fragment1.numAnimators);
@@ -318,6 +327,7 @@
                 .setCustomAnimations(ENTER, EXIT, POP_ENTER, POP_EXIT)
                 .replace(R.id.fragmentContainer, fragment2)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
 
         FragmentTestUtil.waitForExecution(mActivityRule);
@@ -329,6 +339,9 @@
 
         assertNotNull(fragment1.getView());
         assertEquals(View.VISIBLE, fragment1.getView().getVisibility());
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+            assertEquals(1f, fragment1.getView().getAlpha(), 0f);
+        }
         assertTrue(ViewCompat.isAttachedToWindow(fragment1.getView()));
         assertTrue(fragment1.isAdded());
 
@@ -450,7 +463,12 @@
     private void assertPostponed(AnimatorFragment fragment, int expectedAnimators)
             throws InterruptedException {
         assertTrue(fragment.mOnCreateViewCalled);
-        assertEquals(View.INVISIBLE, fragment.getView().getVisibility());
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+            assertEquals(View.INVISIBLE, fragment.getView().getVisibility());
+        } else {
+            assertEquals(View.VISIBLE, fragment.getView().getVisibility());
+            assertEquals(0f, fragment.getView().getAlpha(), 0f);
+        }
         assertEquals(expectedAnimators, fragment.numAnimators);
     }
 
diff --git a/fragment/tests/java/android/support/v4/app/FragmentOptimizationTest.java b/fragment/tests/java/android/support/v4/app/FragmentOptimizationTest.java
index 39dbf11..06abd69 100644
--- a/fragment/tests/java/android/support/v4/app/FragmentOptimizationTest.java
+++ b/fragment/tests/java/android/support/v4/app/FragmentOptimizationTest.java
@@ -24,7 +24,9 @@
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v4.app.test.FragmentTestActivity;
+import android.view.View;
 import android.view.ViewGroup;
+import android.widget.EditText;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -62,10 +64,12 @@
                 mFM.beginTransaction()
                         .add(R.id.fragmentContainer, fragment1)
                         .addToBackStack(null)
+                        .setAllowOptimization(true)
                         .commit();
                 mFM.beginTransaction()
                         .replace(R.id.fragmentContainer, fragment2)
                         .addToBackStack(null)
+                        .setAllowOptimization(true)
                         .commit();
                 mFM.executePendingTransactions();
             }
@@ -90,7 +94,11 @@
     public void startWithPop() throws Throwable {
         // Start with a single fragment on the back stack
         final CountCallsFragment fragment1 = new CountCallsFragment();
-        mFM.beginTransaction().add(R.id.fragmentContainer, fragment1).addToBackStack(null).commit();
+        mFM.beginTransaction()
+                .add(R.id.fragmentContainer, fragment1)
+                .addToBackStack(null)
+                .setAllowOptimization(true)
+                .commit();
 
         FragmentTestUtil.executePendingTransactions(mActivityRule);
         FragmentTestUtil.assertChildren(mContainer, fragment1);
@@ -103,6 +111,7 @@
                 mFM.beginTransaction()
                         .add(R.id.fragmentContainer, fragment1)
                         .addToBackStack(null)
+                        .setAllowOptimization(true)
                         .commit();
                 mFM.executePendingTransactions();
             }
@@ -126,11 +135,13 @@
                 mFM.beginTransaction()
                         .add(R.id.fragmentContainer, fragment1)
                         .addToBackStack(null)
+                        .setAllowOptimization(true)
                         .commit();
                 mFM.popBackStack();
                 mFM.beginTransaction()
                         .add(R.id.fragmentContainer, fragment2)
                         .addToBackStack(null)
+                        .setAllowOptimization(true)
                         .commit();
                 mFM.executePendingTransactions();
             }
@@ -156,9 +167,18 @@
                 id[0] = mFM.beginTransaction()
                         .add(R.id.fragmentContainer, fragment1)
                         .addToBackStack(null)
+                        .setAllowOptimization(true)
                         .commit();
-                mFM.beginTransaction().hide(fragment1).addToBackStack(null).commit();
-                mFM.beginTransaction().remove(fragment1).addToBackStack(null).commit();
+                mFM.beginTransaction()
+                        .hide(fragment1)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
+                mFM.beginTransaction()
+                        .remove(fragment1)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
                 mFM.executePendingTransactions();
             }
         });
@@ -186,6 +206,7 @@
         int id = mFM.beginTransaction()
                 .add(R.id.fragmentContainer, fragment1)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.executePendingTransactions(mActivityRule);
         assertEquals(1, fragment1.onCreateViewCount);
@@ -196,10 +217,12 @@
                 mFM.beginTransaction()
                         .remove(fragment1)
                         .addToBackStack(null)
+                        .setAllowOptimization(true)
                         .commit();
                 mFM.beginTransaction()
                         .add(R.id.fragmentContainer, fragment1)
                         .addToBackStack(null)
+                        .setAllowOptimization(true)
                         .commit();
                 mFM.executePendingTransactions();
             }
@@ -223,6 +246,7 @@
         int id = mFM.beginTransaction()
                 .add(R.id.fragmentContainer, fragment1)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.executePendingTransactions(mActivityRule);
         assertEquals(1, fragment1.onAttachCount);
@@ -231,9 +255,21 @@
         mInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                mFM.beginTransaction().detach(fragment1).addToBackStack(null).commit();
-                mFM.beginTransaction().hide(fragment1).addToBackStack(null).commit();
-                mFM.beginTransaction().attach(fragment1).addToBackStack(null).commit();
+                mFM.beginTransaction()
+                        .detach(fragment1)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
+                mFM.beginTransaction()
+                        .hide(fragment1)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
+                mFM.beginTransaction()
+                        .attach(fragment1)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
                 mFM.executePendingTransactions();
             }
         });
@@ -268,6 +304,7 @@
                 .add(R.id.fragmentContainer, fragment1)
                 .detach(fragment1)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.executePendingTransactions(mActivityRule);
 
@@ -281,9 +318,21 @@
         mInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                mFM.beginTransaction().attach(fragment1).addToBackStack(null).commit();
-                mFM.beginTransaction().hide(fragment1).addToBackStack(null).commit();
-                mFM.beginTransaction().detach(fragment1).addToBackStack(null).commit();
+                mFM.beginTransaction()
+                        .attach(fragment1)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
+                mFM.beginTransaction()
+                        .hide(fragment1)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
+                mFM.beginTransaction()
+                        .detach(fragment1)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
                 mFM.executePendingTransactions();
             }
         });
@@ -316,6 +365,7 @@
                 .add(R.id.fragmentContainer, fragment1)
                 .hide(fragment1)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.executePendingTransactions(mActivityRule);
         assertEquals(0, fragment1.onShowCount);
@@ -328,18 +378,22 @@
                 mFM.beginTransaction()
                         .show(fragment1)
                         .addToBackStack(null)
+                        .setAllowOptimization(true)
                         .commit();
                 mFM.beginTransaction()
                         .remove(fragment1)
                         .addToBackStack(null)
+                        .setAllowOptimization(true)
                         .commit();
                 mFM.beginTransaction()
                         .add(R.id.fragmentContainer, fragment1)
                         .addToBackStack(null)
+                        .setAllowOptimization(true)
                         .commit();
                 mFM.beginTransaction()
                         .hide(fragment1)
                         .addToBackStack(null)
+                        .setAllowOptimization(true)
                         .commit();
                 mFM.executePendingTransactions();
             }
@@ -360,8 +414,16 @@
         mInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                mFM.beginTransaction().show(fragment1).addToBackStack(null).commit();
-                mFM.beginTransaction().hide(fragment1).addToBackStack(null).commit();
+                mFM.beginTransaction()
+                        .show(fragment1)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
+                mFM.beginTransaction()
+                        .hide(fragment1)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
                 mFM.executePendingTransactions();
             }
         });
@@ -380,10 +442,26 @@
         mInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                mFM.beginTransaction().show(fragment1).addToBackStack(null).commit();
-                mFM.beginTransaction().detach(fragment1).addToBackStack(null).commit();
-                mFM.beginTransaction().attach(fragment1).addToBackStack(null).commit();
-                mFM.beginTransaction().hide(fragment1).addToBackStack(null).commit();
+                mFM.beginTransaction()
+                        .show(fragment1)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
+                mFM.beginTransaction()
+                        .detach(fragment1)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
+                mFM.beginTransaction()
+                        .attach(fragment1)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
+                mFM.beginTransaction()
+                        .hide(fragment1)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
                 mFM.executePendingTransactions();
             }
         });
@@ -406,6 +484,7 @@
         int id = mFM.beginTransaction()
                 .add(R.id.fragmentContainer, fragment1)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.executePendingTransactions(mActivityRule);
         assertEquals(0, fragment1.onShowCount);
@@ -415,10 +494,26 @@
         mInstrumentation.runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                mFM.beginTransaction().hide(fragment1).addToBackStack(null).commit();
-                mFM.beginTransaction().detach(fragment1).addToBackStack(null).commit();
-                mFM.beginTransaction().attach(fragment1).addToBackStack(null).commit();
-                mFM.beginTransaction().show(fragment1).addToBackStack(null).commit();
+                mFM.beginTransaction()
+                        .hide(fragment1)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
+                mFM.beginTransaction()
+                        .detach(fragment1)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
+                mFM.beginTransaction()
+                        .attach(fragment1)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
+                mFM.beginTransaction()
+                        .show(fragment1)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
                 mFM.executePendingTransactions();
             }
         });
@@ -443,6 +538,7 @@
         int id = mFM.beginTransaction()
                 .add(R.id.fragmentContainer, fragment1)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.executePendingTransactions(mActivityRule);
         FragmentTestUtil.assertChildren(mContainer, fragment1);
@@ -455,10 +551,12 @@
                 mFM.beginTransaction()
                         .replace(R.id.fragmentContainer, fragment2)
                         .addToBackStack(null)
+                        .setAllowOptimization(true)
                         .commit();
                 mFM.beginTransaction()
                         .add(R.id.fragmentContainer, fragment1)
                         .addToBackStack(null)
+                        .setAllowOptimization(true)
                         .commit();
 
                 mFM.executePendingTransactions();
@@ -480,6 +578,7 @@
                 mFM.beginTransaction()
                         .add(R.id.fragmentContainer, fragment1)
                         .addToBackStack(null)
+                        .setAllowOptimization(true)
                         .commit();
                 mFM.popBackStack();
                 mFM.executePendingTransactions();
@@ -503,9 +602,11 @@
                 mFM.beginTransaction()
                         .add(R.id.fragmentContainer, fragment1)
                         .addToBackStack(null)
+                        .setAllowOptimization(true)
                         .commit();
                 mFM.beginTransaction()
                         .replace(R.id.fragmentContainer, fragment2)
+                        .setAllowOptimization(true)
                         .commit();
                 mFM.popBackStack();
                 mFM.executePendingTransactions();
@@ -529,6 +630,7 @@
                 mFM.beginTransaction()
                         .add(R.id.fragmentContainer, fragment1)
                         .addToBackStack(null)
+                        .setAllowOptimization(true)
                         .commit();
                 mFM.beginTransaction()
                         .replace(R.id.fragmentContainer, fragment2)
@@ -544,4 +646,42 @@
         assertEquals(1, fragment1.onCreateViewCount);
     }
 
+    // Test that a fragment view that is created with focus has focus after the transaction
+    // completes.
+    @Test
+    public void focusedView() throws Throwable {
+        FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
+        mContainer = (ViewGroup) mActivityRule.getActivity().findViewById(R.id.fragmentContainer1);
+        final EditText firstEditText = new EditText(mContainer.getContext());
+        mInstrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mContainer.addView(firstEditText);
+                firstEditText.requestFocus();
+            }
+        });
+        assertTrue(firstEditText.isFocused());
+        final CountCallsFragment fragment1 = new CountCallsFragment();
+        final CountCallsFragment fragment2 = new CountCallsFragment();
+        fragment2.setLayoutId(R.layout.with_edit_text);
+        mInstrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mFM.beginTransaction()
+                        .add(R.id.fragmentContainer2, fragment1)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
+                mFM.beginTransaction()
+                        .replace(R.id.fragmentContainer2, fragment2)
+                        .addToBackStack(null)
+                        .setAllowOptimization(true)
+                        .commit();
+                mFM.executePendingTransactions();
+            }
+        });
+        final View editText = fragment2.getView().findViewById(R.id.editText);
+        assertTrue(editText.isFocused());
+        assertFalse(firstEditText.isFocused());
+    }
 }
diff --git a/fragment/tests/java/android/support/v4/app/FragmentTransitionTest.java b/fragment/tests/java/android/support/v4/app/FragmentTransitionTest.java
index bc8e9e1..ab319b7 100644
--- a/fragment/tests/java/android/support/v4/app/FragmentTransitionTest.java
+++ b/fragment/tests/java/android/support/v4/app/FragmentTransitionTest.java
@@ -19,6 +19,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
@@ -26,6 +27,7 @@
 import android.app.Instrumentation;
 import android.graphics.Rect;
 import android.os.Build;
+import android.os.Bundle;
 import android.support.fragment.test.R;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
@@ -440,6 +442,7 @@
                 .addSharedElement(startGreen, "greenSquare")
                 .replace(R.id.fragmentContainer, fragment2)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.waitForExecution(mActivityRule);
 
@@ -592,6 +595,109 @@
         verifyNoOtherTransitions(fragment2);
     }
 
+    // Ensure that shared element without matching transition name doesn't error out
+    @Test
+    public void sharedElementMismatch() throws Throwable {
+        final TransitionFragment fragment1 = setupInitialFragment();
+
+        // Now do a transition to scene2
+        TransitionFragment fragment2 = new TransitionFragment();
+        fragment2.setLayoutId(R.layout.scene2);
+
+        final View startBlue = findBlue();
+        final View startGreen = findGreen();
+        final Rect startBlueBounds = getBoundsOnScreen(startBlue);
+
+        mFragmentManager.beginTransaction()
+                .addSharedElement(startBlue, "fooSquare")
+                .replace(R.id.fragmentContainer, fragment2)
+                .setAllowOptimization(mOptimize)
+                .addToBackStack(null)
+                .commit();
+        FragmentTestUtil.waitForExecution(mActivityRule);
+
+        fragment1.waitForTransition();
+        fragment2.waitForTransition();
+
+        final View endBlue = findBlue();
+        final View endGreen = findGreen();
+
+        if (mOptimize) {
+            verifyAndClearTransition(fragment1.exitTransition, null, startGreen, startBlue);
+        } else {
+            verifyAndClearTransition(fragment1.exitTransition, startBlueBounds, startGreen);
+            verifyAndClearTransition(fragment2.sharedElementEnter, startBlueBounds, startBlue);
+        }
+        verifyNoOtherTransitions(fragment1);
+
+        verifyAndClearTransition(fragment2.enterTransition, null, endGreen, endBlue);
+        verifyNoOtherTransitions(fragment2);
+    }
+
+    // Ensure that using the same source or target shared element results in an exception.
+    @Test
+    public void sharedDuplicateTargetNames() throws Throwable {
+        setupInitialFragment();
+
+        final View startBlue = findBlue();
+        final View startGreen = findGreen();
+
+        FragmentTransaction ft = mFragmentManager.beginTransaction();
+        ft.addSharedElement(startBlue, "blueSquare");
+        try {
+            ft.addSharedElement(startGreen, "blueSquare");
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try {
+            ft.addSharedElement(startBlue, "greenSquare");
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    // Test that invisible fragment views don't participate in transitions
+    @Test
+    public void invisibleNoTransitions() throws Throwable {
+        if (!mOptimize) {
+            return; // only optimized transitions can avoid interaction
+        }
+        // enter transition
+        TransitionFragment fragment = new InvisibleFragment();
+        fragment.setLayoutId(R.layout.scene1);
+        mFragmentManager.beginTransaction()
+                .setAllowOptimization(mOptimize)
+                .add(R.id.fragmentContainer, fragment)
+                .addToBackStack(null)
+                .commit();
+        FragmentTestUtil.waitForExecution(mActivityRule);
+        fragment.waitForNoTransition();
+        verifyNoOtherTransitions(fragment);
+
+        // exit transition
+        mFragmentManager.beginTransaction()
+                .setAllowOptimization(mOptimize)
+                .remove(fragment)
+                .addToBackStack(null)
+                .commit();
+
+        fragment.waitForNoTransition();
+        verifyNoOtherTransitions(fragment);
+
+        // reenter transition
+        FragmentTestUtil.popBackStackImmediate(mActivityRule);
+        fragment.waitForNoTransition();
+        verifyNoOtherTransitions(fragment);
+
+        // return transition
+        FragmentTestUtil.popBackStackImmediate(mActivityRule);
+        fragment.waitForNoTransition();
+        verifyNoOtherTransitions(fragment);
+    }
+
     private TransitionFragment setupInitialFragment() throws Throwable {
         TransitionFragment fragment1 = new TransitionFragment();
         fragment1.setLayoutId(R.layout.scene1);
@@ -888,6 +994,13 @@
             setSharedElementEnterTransition(sharedElementEnterTransition);
             setSharedElementReturnTransition(sharedElementReturnTransition);
         }
+    }
 
+    public static class InvisibleFragment extends TransitionFragment {
+        @Override
+        public void onViewCreated(View view, Bundle savedInstanceState) {
+            view.setVisibility(View.INVISIBLE);
+            super.onViewCreated(view, savedInstanceState);
+        }
     }
 }
diff --git a/fragment/tests/java/android/support/v4/app/FragmentViewTests.java b/fragment/tests/java/android/support/v4/app/FragmentViewTests.java
index 22a52ed..98c9d40 100644
--- a/fragment/tests/java/android/support/v4/app/FragmentViewTests.java
+++ b/fragment/tests/java/android/support/v4/app/FragmentViewTests.java
@@ -25,6 +25,7 @@
 import static org.junit.Assert.fail;
 
 import android.app.Instrumentation;
+import android.os.Bundle;
 import android.support.fragment.test.R;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
@@ -873,6 +874,71 @@
         assertNotNull(findViewById(R.id.textC));
     }
 
+    // Test that adding a fragment with invisible or gone views does not end up with the view
+    // being visible
+    @Test
+    public void addInvisibleAndGoneFragments() throws Throwable {
+        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
+        ViewGroup container = (ViewGroup)
+                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
+        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
+
+        final StrictViewFragment fragment1 = new InvisibleFragment();
+        fm.beginTransaction().add(R.id.fragmentContainer, fragment1).addToBackStack(null).commit();
+        FragmentTestUtil.executePendingTransactions(mActivityRule);
+        FragmentTestUtil.assertChildren(container, fragment1);
+
+        assertEquals(View.INVISIBLE, fragment1.getView().getVisibility());
+
+        final InvisibleFragment fragment2 = new InvisibleFragment();
+        fragment2.visibility = View.GONE;
+        fm.beginTransaction()
+                .replace(R.id.fragmentContainer, fragment2)
+                .addToBackStack(null)
+                .commit();
+        FragmentTestUtil.executePendingTransactions(mActivityRule);
+        FragmentTestUtil.assertChildren(container, fragment2);
+
+        assertEquals(View.GONE, fragment2.getView().getVisibility());
+    }
+
+    // Test to ensure that popping and adding a fragment properly track the fragments added
+    // and removed.
+    @Test
+    public void popAdd() throws Throwable {
+        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
+        ViewGroup container = (ViewGroup)
+                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
+        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
+
+        // One fragment with a view
+        final StrictViewFragment fragment1 = new StrictViewFragment();
+        fm.beginTransaction().add(R.id.fragmentContainer, fragment1).addToBackStack(null).commit();
+        FragmentTestUtil.executePendingTransactions(mActivityRule);
+        FragmentTestUtil.assertChildren(container, fragment1);
+
+        final StrictViewFragment fragment2 = new StrictViewFragment();
+        final StrictViewFragment fragment3 = new StrictViewFragment();
+        mInstrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fm.popBackStack();
+                fm.beginTransaction()
+                        .replace(R.id.fragmentContainer, fragment2)
+                        .addToBackStack(null)
+                        .commit();
+                fm.executePendingTransactions();
+                fm.popBackStack();
+                fm.beginTransaction()
+                        .replace(R.id.fragmentContainer, fragment3)
+                        .addToBackStack(null)
+                        .commit();
+                fm.executePendingTransactions();
+            }
+        });
+        FragmentTestUtil.assertChildren(container, fragment3);
+    }
+
     private View findViewById(int viewId) {
         return mActivityRule.getActivity().findViewById(viewId);
     }
@@ -886,4 +952,14 @@
                     fragments[i].getView());
         }
     }
+
+    public static class InvisibleFragment extends StrictViewFragment {
+        public int visibility = View.INVISIBLE;
+
+        @Override
+        public void onViewCreated(View view, Bundle savedInstanceState) {
+            view.setVisibility(visibility);
+            super.onViewCreated(view, savedInstanceState);
+        }
+    }
 }
diff --git a/fragment/tests/java/android/support/v4/app/PostponedTransitionTest.java b/fragment/tests/java/android/support/v4/app/PostponedTransitionTest.java
index b20298d..0f07b89 100644
--- a/fragment/tests/java/android/support/v4/app/PostponedTransitionTest.java
+++ b/fragment/tests/java/android/support/v4/app/PostponedTransitionTest.java
@@ -61,6 +61,7 @@
         mBeginningFragment = new PostponedFragment1();
         fm.beginTransaction()
                 .add(R.id.fragmentContainer, mBeginningFragment)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.waitForExecution(mActivityRule);
 
@@ -81,6 +82,7 @@
                 .addSharedElement(startBlue, "blueSquare")
                 .replace(R.id.fragmentContainer, fragment)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
 
         FragmentTestUtil.waitForExecution(mActivityRule);
@@ -126,12 +128,14 @@
                         .addSharedElement(startBlue, "blueSquare")
                         .replace(R.id.fragmentContainer, fragment2)
                         .addToBackStack(null)
+                        .setAllowOptimization(true)
                         .commit();
 
                 fm.beginTransaction()
                         .addSharedElement(startBlue, "blueSquare")
                         .replace(R.id.fragmentContainer, fragment3)
                         .addToBackStack(null)
+                        .setAllowOptimization(true)
                         .commit();
             }
         });
@@ -176,6 +180,7 @@
                 .addSharedElement(startBlue, "blueSquare")
                 .replace(R.id.fragmentContainer, fragment2)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
 
         FragmentTestUtil.waitForExecution(mActivityRule);
@@ -187,6 +192,7 @@
                 .addSharedElement(startBlue, "blueSquare")
                 .replace(R.id.fragmentContainer, fragment3)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
 
         // This should cancel the mBeginningFragment -> fragment2 transition
@@ -238,6 +244,7 @@
                 .attach(fragment2)
                 .show(fragment2)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
 
         FragmentTestUtil.waitForExecution(mActivityRule);
@@ -266,7 +273,10 @@
     @Test
     public void differentContainers() throws Throwable {
         final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        fm.beginTransaction().remove(mBeginningFragment).commit();
+        fm.beginTransaction()
+                .remove(mBeginningFragment)
+                .setAllowOptimization(true)
+                .commit();
         FragmentTestUtil.waitForExecution(mActivityRule);
         FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
 
@@ -276,6 +286,7 @@
         fm.beginTransaction()
                 .add(R.id.fragmentContainer1, fragment1)
                 .add(R.id.fragmentContainer2, fragment2)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.waitForExecution(mActivityRule);
         fragment1.startPostponedEnterTransition();
@@ -294,6 +305,7 @@
                 .addSharedElement(startBlue1, "blueSquare")
                 .replace(R.id.fragmentContainer1, fragment3)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
 
         FragmentTestUtil.waitForExecution(mActivityRule);
@@ -306,6 +318,7 @@
                 .addSharedElement(startBlue2, "blueSquare")
                 .replace(R.id.fragmentContainer2, fragment4)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
 
         FragmentTestUtil.waitForExecution(mActivityRule);
@@ -358,7 +371,10 @@
     @Test
     public void outOfOrderContainers() throws Throwable {
         final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        fm.beginTransaction().remove(mBeginningFragment).commit();
+        fm.beginTransaction()
+                .remove(mBeginningFragment)
+                .setAllowOptimization(true)
+                .commit();
         FragmentTestUtil.waitForExecution(mActivityRule);
         FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
 
@@ -368,6 +384,7 @@
         fm.beginTransaction()
                 .add(R.id.fragmentContainer1, fragment1)
                 .add(R.id.fragmentContainer2, fragment2)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.waitForExecution(mActivityRule);
         fragment1.startPostponedEnterTransition();
@@ -386,6 +403,7 @@
                 .addSharedElement(startBlue1, "blueSquare")
                 .replace(R.id.fragmentContainer1, fragment3)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
 
         FragmentTestUtil.waitForExecution(mActivityRule);
@@ -398,6 +416,7 @@
                 .addSharedElement(startBlue2, "blueSquare")
                 .replace(R.id.fragmentContainer2, fragment4)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
 
         FragmentTestUtil.waitForExecution(mActivityRule);
@@ -450,7 +469,10 @@
     @Test
     public void commitNowNoEffect() throws Throwable {
         final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        fm.beginTransaction().remove(mBeginningFragment).commit();
+        fm.beginTransaction()
+                .remove(mBeginningFragment)
+                .setAllowOptimization(true)
+                .commit();
         FragmentTestUtil.waitForExecution(mActivityRule);
         FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
 
@@ -460,6 +482,7 @@
         fm.beginTransaction()
                 .add(R.id.fragmentContainer1, fragment1)
                 .add(R.id.fragmentContainer2, fragment2)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.waitForExecution(mActivityRule);
         fragment1.startPostponedEnterTransition();
@@ -480,6 +503,7 @@
                 .replace(R.id.fragmentContainer1, fragment3)
                 .add(strictFragment1, "1")
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
 
         FragmentTestUtil.waitForExecution(mActivityRule);
@@ -497,6 +521,7 @@
                         .replace(R.id.fragmentContainer2, fragment4)
                         .remove(strictFragment1)
                         .add(strictFragment2, "2")
+                        .setAllowOptimization(true)
                         .commitNow();
             }
         });
@@ -534,6 +559,7 @@
                 .addSharedElement(startBlue1, "blueSquare")
                 .replace(R.id.fragmentContainer, fragment2)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.waitForExecution(mActivityRule);
 
@@ -545,6 +571,7 @@
                 fm.beginTransaction()
                         .addSharedElement(startBlue2, "blueSquare")
                         .replace(R.id.fragmentContainer, fragment1)
+                        .setAllowOptimization(true)
                         .commitNow();
             }
         });
@@ -562,7 +589,10 @@
     @Test
     public void noAccidentalRemoval() throws Throwable {
         final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
-        fm.beginTransaction().remove(mBeginningFragment).commit();
+        fm.beginTransaction()
+                .remove(mBeginningFragment)
+                .setAllowOptimization(true)
+                .commit();
         FragmentTestUtil.waitForExecution(mActivityRule);
         FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
 
@@ -570,6 +600,7 @@
 
         fm.beginTransaction()
                 .add(R.id.fragmentContainer1, fragment1)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.waitForExecution(mActivityRule);
         fragment1.startPostponedEnterTransition();
@@ -580,6 +611,7 @@
         // Create a postponed transaction that removes a view
         fm.beginTransaction()
                 .replace(R.id.fragmentContainer1, fragment2)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.waitForExecution(mActivityRule);
         assertPostponedTransition(fragment1, fragment2, null);
@@ -588,6 +620,7 @@
         // Create a transaction that doesn't interfere with the previously postponed one
         fm.beginTransaction()
                 .replace(R.id.fragmentContainer2, fragment3)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.waitForExecution(mActivityRule);
 
@@ -613,6 +646,7 @@
                 .addSharedElement(startBlue, "blueSquare")
                 .replace(R.id.fragmentContainer, fragment)
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.waitForExecution(mActivityRule);
 
@@ -630,6 +664,9 @@
         assertNull(fragment.getView());
         assertNotNull(mBeginningFragment.getView());
         assertEquals(View.VISIBLE, mBeginningFragment.getView().getVisibility());
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+            assertEquals(1f, mBeginningFragment.getView().getAlpha(), 0f);
+        }
         assertTrue(mBeginningFragment.getView().isAttachedToWindow());
     }
 
@@ -646,6 +683,7 @@
         fm1.beginTransaction()
                 .add(R.id.fragmentContainer, fragment1, "1")
                 .addToBackStack(null)
+                .setAllowOptimization(true)
                 .commit();
         FragmentTestUtil.waitForExecution(mActivityRule);
 
@@ -660,6 +698,9 @@
         assertNotNull(fragment2);
         assertNotNull(fragment2.getView());
         assertEquals(View.VISIBLE, fragment2.getView().getVisibility());
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+            assertEquals(1f, fragment2.getView().getAlpha(), 0f);
+        }
         assertTrue(fragment2.isResumed());
         assertTrue(fragment2.isAdded());
         assertTrue(fragment2.getView().isAttachedToWindow());
@@ -691,7 +732,12 @@
         assertTrue(fromFragment.getView().isAttachedToWindow());
         assertTrue(toFragment.getView().isAttachedToWindow());
         assertEquals(View.VISIBLE, fromFragment.getView().getVisibility());
-        assertEquals(View.INVISIBLE, toFragment.getView().getVisibility());
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+            assertEquals(View.VISIBLE, toFragment.getView().getVisibility());
+            assertEquals(0f, toFragment.getView().getAlpha(), 0f);
+        } else {
+            assertEquals(View.INVISIBLE, toFragment.getView().getVisibility());
+        }
         assureNoTransition(fromFragment);
         assureNoTransition(toFragment);
         assertTrue(fromFragment.isResumed());
diff --git a/fragment/tests/java/android/support/v4/app/TransitionFragment.java b/fragment/tests/java/android/support/v4/app/TransitionFragment.java
index fda2784..e6493c2 100644
--- a/fragment/tests/java/android/support/v4/app/TransitionFragment.java
+++ b/fragment/tests/java/android/support/v4/app/TransitionFragment.java
@@ -51,9 +51,11 @@
         setSharedElementEnterTransition(sharedElementEnter);
         setSharedElementReturnTransition(sharedElementReturn);
         enterTransition.addListener(mListener);
+        sharedElementEnter.addListener(mListener);
         reenterTransition.addListener(mListener);
         exitTransition.addListener(mListener);
         returnTransition.addListener(mListener);
+        sharedElementReturn.addListener(mListener);
     }
 
     @Override
diff --git a/fragment/tests/res/layout/with_edit_text.xml b/fragment/tests/res/layout/with_edit_text.xml
new file mode 100644
index 0000000..7fd21a0
--- /dev/null
+++ b/fragment/tests/res/layout/with_edit_text.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical">
+    <EditText android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:id="@+id/editText"
+              android:text="@string/hello">
+        <requestFocus/>
+    </EditText>
+</LinearLayout>
diff --git a/graphics/drawable/animated/AndroidManifest.xml b/graphics/drawable/animated/AndroidManifest.xml
index 98f9e17..58017dc 100644
--- a/graphics/drawable/animated/AndroidManifest.xml
+++ b/graphics/drawable/animated/AndroidManifest.xml
@@ -16,5 +16,6 @@
   -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.graphics.drawable.animated">
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application/>
 </manifest>
diff --git a/graphics/drawable/animated/build.gradle b/graphics/drawable/animated/build.gradle
index bb110a1..10d112a 100644
--- a/graphics/drawable/animated/build.gradle
+++ b/graphics/drawable/animated/build.gradle
@@ -1,5 +1,4 @@
 apply plugin: 'com.android.library'
-
 archivesBaseName = 'animated-vector-drawable'
 
 dependencies {
@@ -41,14 +40,6 @@
         additionalParameters "--no-version-vectors"
     }
 
-    packagingOptions {
-        exclude 'LICENSE.txt'
-    }
-
-    testOptions {
-        unitTests.returnDefaultValues = true
-    }
-
     buildTypes.all {
         consumerProguardFiles 'proguard-rules.pro'
     }
@@ -62,28 +53,11 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar) {
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/graphics/drawable/static/AndroidManifest.xml b/graphics/drawable/static/AndroidManifest.xml
index e91290d..0383e4c 100644
--- a/graphics/drawable/static/AndroidManifest.xml
+++ b/graphics/drawable/static/AndroidManifest.xml
@@ -14,6 +14,8 @@
    See the License for the specific language governing permissions and
    limitations under the License.
   -->
-<manifest package="android.support.graphics.drawable">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.support.graphics.drawable">
     <application/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
 </manifest>
diff --git a/graphics/drawable/static/build.gradle b/graphics/drawable/static/build.gradle
index 2c72f9f..fdb306c 100644
--- a/graphics/drawable/static/build.gradle
+++ b/graphics/drawable/static/build.gradle
@@ -42,14 +42,6 @@
     aaptOptions {
         additionalParameters "--no-version-vectors"
     }
-
-    packagingOptions {
-        exclude 'LICENSE.txt'
-    }
-
-    testOptions {
-        unitTests.returnDefaultValues = true
-    }
 }
 
 android.libraryVariants.all { variant ->
@@ -60,28 +52,11 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar) {
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/media-compat/AndroidManifest.xml b/media-compat/AndroidManifest.xml
index c971549..5f7b051 100644
--- a/media-compat/AndroidManifest.xml
+++ b/media-compat/AndroidManifest.xml
@@ -17,5 +17,6 @@
           xmlns:tools="http://schemas.android.com/tools"
           package="android.support.mediacompat">
     <uses-sdk android:minSdkVersion="9" tools:overrideLibrary="android.support.mediacompat"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application />
 </manifest>
diff --git a/media-compat/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java b/media-compat/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
index affb130..4035e63 100644
--- a/media-compat/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
+++ b/media-compat/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
@@ -71,6 +71,7 @@
                 mResultObj.sendResult(parcelListToItemList((List<Parcel>)result));
             } else if (result instanceof Parcel) {
                 Parcel parcel = (Parcel) result;
+                parcel.setDataPosition(0);
                 mResultObj.sendResult(MediaBrowser.MediaItem.CREATOR.createFromParcel(parcel));
                 parcel.recycle();
             } else {
diff --git a/media-compat/build.gradle b/media-compat/build.gradle
index e26a1bc..8dd44bf 100644
--- a/media-compat/build.gradle
+++ b/media-compat/build.gradle
@@ -15,14 +15,13 @@
     androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
 }
 
-sourceCompatibility = JavaVersion.VERSION_1_7
-targetCompatibility = JavaVersion.VERSION_1_7
-
 android {
     compileSdkVersion project.ext.currentSdk
 
     defaultConfig {
         minSdkVersion 9
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
     }
 
     sourceSets {
@@ -38,6 +37,11 @@
                 'java'
         ]
         main.aidl.srcDirs = ['java']
+
+        androidTest.setRoot('tests')
+        androidTest.java.srcDir 'tests/src'
+        androidTest.res.srcDir 'tests/res'
+        androidTest.manifest.srcFile 'tests/AndroidManifest.xml'
     }
 
     compileOptions {
@@ -54,22 +58,6 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
@@ -77,7 +65,6 @@
         exclude('android/service/media/**')
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
@@ -113,4 +100,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java b/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java
index a9fc7e8..de5047b 100644
--- a/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -403,6 +403,11 @@
 
         @Override
         public Bundle getBrowserRootHints() {
+            // If EXTRA_MESSENGER_BINDER is used, mCurConnection is not null.
+            if (mCurConnection != null) {
+                return mCurConnection.rootHints == null ? null
+                        : new Bundle(mCurConnection.rootHints);
+            }
             return MediaBrowserServiceCompatApi24.getBrowserRootHints(mServiceObj);
         }
     }
diff --git a/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java b/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
index 78b0c50..d835985 100644
--- a/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -2302,7 +2302,9 @@
         public void setCallback(Callback callback, Handler handler) {
             MediaSessionCompatApi21.setCallback(mSessionObj,
                     callback == null ? null : callback.mCallbackObj, handler);
-            callback.mSessionImpl = new WeakReference<MediaSessionImpl>(this);
+            if (callback != null) {
+                callback.mSessionImpl = new WeakReference<MediaSessionImpl>(this);
+            }
         }
 
         @Override
diff --git a/media-compat/tests/AndroidManifest.xml b/media-compat/tests/AndroidManifest.xml
new file mode 100644
index 0000000..1216194
--- /dev/null
+++ b/media-compat/tests/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   Copyright (C) 2016 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="android.support.mediacompat.test">
+
+    <uses-sdk
+            android:minSdkVersion="9"
+            android:targetSdkVersion="23"
+            tools:overrideLibrary="android.support.test, android.app, android.support.test.rule,
+                      android.support.test.espresso, android.support.test.espresso.idling"/>
+
+    <application android:supportsRtl="true">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.support.v4.media.session.TestActivity" />
+        <receiver android:name="android.support.v4.media.session.MediaButtonReceiver" >
+            <intent-filter>
+                <action android:name="android.intent.action.MEDIA_BUTTON" />
+            </intent-filter>
+        </receiver>
+    </application>
+
+    <instrumentation
+            android:name="android.test.InstrumentationTestRunner"
+            android:targetPackage="android.support.mediacompat.test"/>
+
+</manifest>
diff --git a/media-compat/tests/NO_DOCS b/media-compat/tests/NO_DOCS
new file mode 100644
index 0000000..092a39c
--- /dev/null
+++ b/media-compat/tests/NO_DOCS
@@ -0,0 +1,17 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+Having this file, named NO_DOCS, in a directory will prevent
+Android javadocs from being generated for java files under
+the directory. This is especially useful for test projects.
diff --git a/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java b/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java
new file mode 100644
index 0000000..455a706
--- /dev/null
+++ b/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media.session;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import static junit.framework.Assert.fail;
+
+public class MediaSessionCompatTest {
+    @Rule
+    public ActivityTestRule<TestActivity> mActivityRule =
+            new ActivityTestRule<>(TestActivity.class);
+    Context mContext;
+    Map<String, LockedObject> results = new HashMap<>();
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getContext();
+    }
+
+    @Test
+    public void testSetNullCallback() throws Throwable {
+        initWait("testSetNullCallback");
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    MediaSessionCompat session = new MediaSessionCompat(mContext, "TEST");
+                    session.setCallback(null);
+                } catch (Exception e) {
+                    fail("Fail with an exception: " + e);
+                } finally {
+                    setResultData("testSetNullCallback", true);
+                }
+            }
+        });
+        waitFor("testSetNullCallback");
+    }
+
+    private void initWait(String key) throws InterruptedException {
+        results.put(key, new LockedObject());
+    }
+
+    private Object[] waitFor(String key) throws InterruptedException {
+        return results.get(key).waitFor();
+    }
+
+    private void setResultData(String key, Object... args) {
+        if (results.containsKey(key)) {
+            results.get(key).set(args);
+        }
+    }
+
+    private class LockedObject {
+        private Semaphore mLock = new Semaphore(1);
+        private volatile Object[] mArgs;
+
+        public LockedObject() {
+            mLock.drainPermits();
+        }
+
+        public void set(Object... args) {
+            mArgs = args;
+            mLock.release(1);
+        }
+
+        public Object[] waitFor() throws InterruptedException {
+            mLock.tryAcquire(1, 2, TimeUnit.SECONDS);
+            return mArgs;
+        }
+    }
+}
diff --git a/media-compat/tests/src/android/support/v4/media/session/TestActivity.java b/media-compat/tests/src/android/support/v4/media/session/TestActivity.java
new file mode 100644
index 0000000..dd56467
--- /dev/null
+++ b/media-compat/tests/src/android/support/v4/media/session/TestActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media.session;
+
+import android.app.Activity;
+
+public class TestActivity extends Activity {
+}
diff --git a/percent/AndroidManifest.xml b/percent/AndroidManifest.xml
index e979013..0d55165 100644
--- a/percent/AndroidManifest.xml
+++ b/percent/AndroidManifest.xml
@@ -16,5 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.percent">
     <uses-sdk android:minSdkVersion="9"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application />
 </manifest>
diff --git a/percent/build.gradle b/percent/build.gradle
index c3f386e..b120075 100644
--- a/percent/build.gradle
+++ b/percent/build.gradle
@@ -1,5 +1,4 @@
 apply plugin: 'com.android.library'
-
 archivesBaseName = 'percent'
 
 dependencies {
@@ -53,28 +52,11 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/recommendation/AndroidManifest.xml b/recommendation/AndroidManifest.xml
index ef1223e..e36c822 100644
--- a/recommendation/AndroidManifest.xml
+++ b/recommendation/AndroidManifest.xml
@@ -16,5 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.recommendation">
     <uses-sdk android:minSdkVersion="21"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application />
 </manifest>
diff --git a/recommendation/build.gradle b/recommendation/build.gradle
index 75d68d1..dadad58 100644
--- a/recommendation/build.gradle
+++ b/recommendation/build.gradle
@@ -1,5 +1,4 @@
 apply plugin: 'com.android.library'
-
 archivesBaseName = 'recommendation'
 
 dependencies {
@@ -41,27 +40,10 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
diff --git a/samples/SupportDesignDemos/src/com/example/android/support/design/widget/AppBarLayoutUsageBase.java b/samples/SupportDesignDemos/src/com/example/android/support/design/widget/AppBarLayoutUsageBase.java
index 76d6208..edb7493 100644
--- a/samples/SupportDesignDemos/src/com/example/android/support/design/widget/AppBarLayoutUsageBase.java
+++ b/samples/SupportDesignDemos/src/com/example/android/support/design/widget/AppBarLayoutUsageBase.java
@@ -50,8 +50,11 @@
 
         CollapsingToolbarLayout collapsingToolbarLayout = (CollapsingToolbarLayout)
                 findViewById(R.id.collapsing_app_bar);
-        if (collapsingToolbarLayout != null && displayTitle()) {
-            collapsingToolbarLayout.setTitle(getTitle());
+        if (collapsingToolbarLayout != null) {
+            if (displayTitle()) {
+                collapsingToolbarLayout.setTitle(getTitle());
+            }
+            collapsingToolbarLayout.setContentScrimColor(0xFFFF00FF);
         }
 
         TextView dialog = (TextView) findViewById(R.id.textview_dialogue);
diff --git a/samples/SupportLeanbackDemos/AndroidManifest.xml b/samples/SupportLeanbackDemos/AndroidManifest.xml
index e1acf45..ea3c1ee 100644
--- a/samples/SupportLeanbackDemos/AndroidManifest.xml
+++ b/samples/SupportLeanbackDemos/AndroidManifest.xml
@@ -43,6 +43,22 @@
             android:theme="@style/Theme.Example.Leanback.Details"
             android:exported="true" />
 
+        <activity android:name="DetailsVideoActivity"
+            android:theme="@style/Theme.Example.Leanback.Details"
+            android:exported="true" />
+
+        <activity android:name="DetailsVideoSupportActivity"
+            android:theme="@style/Theme.Example.Leanback.Details"
+            android:exported="true" />
+
+        <activity android:name="DetailsCustomTitleActivity"
+            android:theme="@style/Theme.Example.Leanback.Details.CustomTitle"
+            android:exported="true" />
+
+        <activity android:name="DetailsCustomTitleSupportActivity"
+            android:theme="@style/Theme.Example.Leanback.Details.CustomTitle"
+            android:exported="true" />
+
         <activity android:name="SearchDetailsActivity"
             android:theme="@style/Theme.Example.Leanback.SearchDetails"
             android:exported="true" />
@@ -59,6 +75,22 @@
             android:theme="@style/Theme.Example.Leanback.Rows"
             android:exported="true" />
 
+        <activity android:name="PlaybackActivity"
+            android:configChanges=
+                "screenSize|smallestScreenSize|screenLayout|orientation"
+            android:resizeableActivity="true"
+            android:supportsPictureInPicture="true"
+            android:launchMode="singleTask"
+            android:exported="true" />
+
+        <activity android:name="PlaybackSupportActivity"
+            android:configChanges=
+                "screenSize|smallestScreenSize|screenLayout|orientation"
+            android:resizeableActivity="true"
+            android:supportsPictureInPicture="true"
+            android:launchMode="singleTask"
+            android:exported="true" />
+
         <activity android:name="PlaybackOverlayActivity"
             android:configChanges=
                 "screenSize|smallestScreenSize|screenLayout|orientation"
diff --git a/samples/SupportLeanbackDemos/generatev4.py b/samples/SupportLeanbackDemos/generatev4.py
index efaf0b4..c79f1b1 100755
--- a/samples/SupportLeanbackDemos/generatev4.py
+++ b/samples/SupportLeanbackDemos/generatev4.py
@@ -134,8 +134,6 @@
     line = line.replace('DetailsActivity', 'DetailsSupportActivity')
     line = line.replace('android.app.Activity', 'android.support.v4.app.FragmentActivity')
     line = line.replace('extends Activity', 'extends FragmentActivity')
-    line = line.replace('R.layout.details', 'R.layout.details_support')
-    line = line.replace('R.layout.legacy_details', 'R.layout.legacy_details_support')
     line = line.replace('getFragmentManager()', 'getSupportFragmentManager()')
     line = line.replace('DetailsFragment', 'DetailsSupportFragment')
     line = line.replace('NewDetailsFragment', 'NewDetailsSupportFragment')
@@ -143,26 +141,6 @@
 file.close()
 outfile.close()
 
-file = open('res/layout/details.xml', 'r')
-outfile = open('res/layout/details_support.xml', 'w')
-for line in file:
-    line = replace_xml_head(line, "details")
-    line = line.replace('com.example.android.leanback.NewDetailsFragment', 'com.example.android.leanback.NewDetailsSupportFragment')
-    outfile.write(line)
-file.close()
-outfile.close()
-
-
-file = open('res/layout/legacy_details.xml', 'r')
-outfile = open('res/layout/legacy_details_support.xml', 'w')
-for line in file:
-    line = replace_xml_head(line, "legacy_details")
-    line = line.replace('com.example.android.leanback.DetailsFragment', 'com.example.android.leanback.DetailsSupportFragment')
-    outfile.write(line)
-file.close()
-outfile.close()
-
-
 file = open('src/com/example/android/leanback/SearchDetailsActivity.java', 'r')
 outfile = open('src/com/example/android/leanback/SearchDetailsSupportActivity.java', 'w')
 write_java_head(outfile, "SearchDetailsActivity")
@@ -300,16 +278,58 @@
 file.close()
 outfile.close()
 
+file = open('src/com/example/android/leanback/PlaybackFragment.java', 'r')
+outfile = open('src/com/example/android/leanback/PlaybackSupportFragment.java', 'w')
+write_java_head(outfile, "PlaybackFragment")
+for line in file:
+    line = line.replace('PlaybackFragment', 'PlaybackSupportFragment')
+    line = line.replace('PlaybackActivity', 'PlaybackSupportActivity')
+    outfile.write(line)
+file.close()
+outfile.close()
+
+file = open('src/com/example/android/leanback/PlaybackActivity.java', 'r')
+outfile = open('src/com/example/android/leanback/PlaybackSupportActivity.java', 'w')
+write_java_head(outfile, "PlaybackActivity")
+for line in file:
+    line = line.replace('PlaybackActivity', 'PlaybackSupportActivity')
+    line = line.replace('extends Activity', 'extends FragmentActivity')
+    line = line.replace('R.layout.playback_activity', 'R.layout.playback_activity_support')
+    line = line.replace('android.app.Activity', 'android.support.v4.app.FragmentActivity')
+    outfile.write(line)
+file.close()
+outfile.close()
+
+file = open('res/layout/playback_activity.xml', 'r')
+outfile = open('res/layout/playback_activity_support.xml', 'w')
+for line in file:
+    line = replace_xml_head(line, "playback_controls")
+    line = line.replace('com.example.android.leanback.PlaybackFragment', 'com.example.android.leanback.PlaybackSupportFragment')
+    outfile.write(line)
+file.close()
+outfile.close()
+
+
 
 file = open('src/com/example/android/leanback/PlaybackOverlayFragment.java', 'r')
 outfile = open('src/com/example/android/leanback/PlaybackOverlaySupportFragment.java', 'w')
 write_java_head(outfile, "PlaybackOverlayFragment")
 for line in file:
     line = line.replace('PlaybackOverlayFragment', 'PlaybackOverlaySupportFragment')
+    line = line.replace('PlaybackControlHelper', 'PlaybackControlSupportHelper')
     line = line.replace('PlaybackOverlayActivity', 'PlaybackOverlaySupportActivity')
-    line = line.replace('PlaybackFragmentGlueHost', 'PlaybackSupportFragmentGlueHost')
-    line = line.replace('VideoFragment', 'VideoSupportFragment')
-    line = line.replace('PlaybackFragment', 'PlaybackSupportFragment')
+    outfile.write(line)
+file.close()
+outfile.close()
+
+
+file = open('src/com/example/android/leanback/PlaybackControlHelper.java', 'r')
+outfile = open('src/com/example/android/leanback/PlaybackControlSupportHelper.java', 'w')
+write_java_head(outfile, "PlaybackControlHelper")
+for line in file:
+    line = line.replace('PlaybackControlHelper', 'PlaybackControlSupportHelper')
+    line = line.replace('PlaybackControlGlue', 'PlaybackControlSupportGlue')
+    line = line.replace('PlaybackOverlayFragment', 'PlaybackOverlaySupportFragment')
     outfile.write(line)
 file.close()
 outfile.close()
diff --git a/samples/SupportLeanbackDemos/res/layout/custom_title.xml b/samples/SupportLeanbackDemos/res/layout/custom_title.xml
new file mode 100644
index 0000000..4a88021
--- /dev/null
+++ b/samples/SupportLeanbackDemos/res/layout/custom_title.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.example.android.leanback.CustomTitleView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/browse_title_group"
+    android:orientation="horizontal" android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+    <TextView
+        android:text="@string/custom_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+</com.example.android.leanback.CustomTitleView>
\ No newline at end of file
diff --git a/samples/SupportLeanbackDemos/res/layout/details.xml b/samples/SupportLeanbackDemos/res/layout/details.xml
deleted file mode 100644
index 3159f54..0000000
--- a/samples/SupportLeanbackDemos/res/layout/details.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2014 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.
--->
-
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
-    android:name="com.example.android.leanback.NewDetailsFragment"
-    android:id="@+id/details_fragment"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-/>
diff --git a/samples/SupportLeanbackDemos/res/layout/legacy_details.xml b/samples/SupportLeanbackDemos/res/layout/details_activity.xml
similarity index 85%
rename from samples/SupportLeanbackDemos/res/layout/legacy_details.xml
rename to samples/SupportLeanbackDemos/res/layout/details_activity.xml
index 4af4e6a..7fc1400 100644
--- a/samples/SupportLeanbackDemos/res/layout/legacy_details.xml
+++ b/samples/SupportLeanbackDemos/res/layout/details_activity.xml
@@ -15,8 +15,7 @@
      limitations under the License.
 -->
 
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
-    android:name="com.example.android.leanback.DetailsFragment"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/details_fragment"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
diff --git a/samples/SupportLeanbackDemos/res/layout/legacy_details_support.xml b/samples/SupportLeanbackDemos/res/layout/legacy_details_support.xml
deleted file mode 100644
index 8146f9b..0000000
--- a/samples/SupportLeanbackDemos/res/layout/legacy_details_support.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This file is auto-generated from legacy_details.xml.  DO NOT MODIFY. -->
-
-<!--
-     Copyright (C) 2014 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.
--->
-
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
-    android:name="com.example.android.leanback.DetailsSupportFragment"
-    android:id="@+id/details_fragment"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-/>
diff --git a/samples/SupportLeanbackDemos/res/layout/details_support.xml b/samples/SupportLeanbackDemos/res/layout/playback_activity.xml
similarity index 66%
rename from samples/SupportLeanbackDemos/res/layout/details_support.xml
rename to samples/SupportLeanbackDemos/res/layout/playback_activity.xml
index 103dc25..91754ff 100644
--- a/samples/SupportLeanbackDemos/res/layout/details_support.xml
+++ b/samples/SupportLeanbackDemos/res/layout/playback_activity.xml
@@ -1,6 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- This file is auto-generated from details.xml.  DO NOT MODIFY. -->
-
 <!--
      Copyright (C) 2014 The Android Open Source Project
 
@@ -16,10 +14,14 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
-    android:name="com.example.android.leanback.NewDetailsSupportFragment"
-    android:id="@+id/details_fragment"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-/>
+    android:layout_height="match_parent">
+
+    <fragment
+        android:id="@+id/playback_controls_fragment"
+        android:name="com.example.android.leanback.PlaybackFragment"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackDemos/res/layout/details_support.xml b/samples/SupportLeanbackDemos/res/layout/playback_activity_support.xml
similarity index 61%
copy from samples/SupportLeanbackDemos/res/layout/details_support.xml
copy to samples/SupportLeanbackDemos/res/layout/playback_activity_support.xml
index 103dc25..0f2cbc1 100644
--- a/samples/SupportLeanbackDemos/res/layout/details_support.xml
+++ b/samples/SupportLeanbackDemos/res/layout/playback_activity_support.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- This file is auto-generated from details.xml.  DO NOT MODIFY. -->
+<!-- This file is auto-generated from playback_controls.xml.  DO NOT MODIFY. -->
 
 <!--
      Copyright (C) 2014 The Android Open Source Project
@@ -16,10 +16,14 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
-    android:name="com.example.android.leanback.NewDetailsSupportFragment"
-    android:id="@+id/details_fragment"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-/>
+    android:layout_height="match_parent">
+
+    <fragment
+        android:id="@+id/playback_controls_fragment"
+        android:name="com.example.android.leanback.PlaybackSupportFragment"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackDemos/res/values/strings.xml b/samples/SupportLeanbackDemos/res/values/strings.xml
index 6a9b4a3..6de7d7b 100644
--- a/samples/SupportLeanbackDemos/res/values/strings.xml
+++ b/samples/SupportLeanbackDemos/res/values/strings.xml
@@ -29,18 +29,31 @@
     <string name="search_support_description">SearchSupportFragment test</string>
     <string name="details">Details</string>
     <string name="details_description">DetailsFragment test</string>
+    <string name="details_video">Details with Video</string>
+    <string name="details_video_description">DetailsFragment with Video test</string>
+    <string name="details_custom_title">Details with custom title</string>
+    <string name="details_custom_title_description">DetailsFragment with custom title test</string>
     <string name="details_support">Details(support version)</string>
     <string name="details_support_description">DetailsSupportFragment test</string>
+    <string name="details_video_support">Details with Video(support version)</string>
+    <string name="details_video_support_description">DetailsFragment with Video test(support version)</string>
+    <string name="details_custom_title_support">Details with custom title(support version)</string>
+    <string name="details_custom_title_support_description">DetailsSupportFragment with custom title test</string>
+    <string name="custom_title">Custom Title</string>
     <string name="search_details">Search Details</string>
     <string name="search_details_description">Search style DetailsFragment test</string>
     <string name="search_details_support">Search Details(support version)</string>
     <string name="search_details_support_description">Search style DetailsSupportFragment test</string>
-    <string name="playback">Playback</string>
     <string name="video_playback">Video Playback</string>
     <string name="video_playback_support">Video Playback(support version)</string>
-    <string name="playback_description">PlaybackOverlayFragment test</string>
+    <string name="playback">Playback</string>
+    <string name="playback_description">PlaybackFragment test</string>
     <string name="playback_support">Playback(support version)</string>
-    <string name="playback_support_description">PlaybackOverlaySupportFragment test</string>
+    <string name="playback_support_description">PlaybackSupportFragment test</string>
+    <string name="playbackoverlay">PlaybackOverlay</string>
+    <string name="playbackoverlay_description">PlaybackOverlayFragment test</string>
+    <string name="playbackoverlay_support">PlaybackOverlay(support version)</string>
+    <string name="playbackoverlay_support_description">PlaybackOverlaySupportFragment test</string>
     <string name="hgrid">Horizontal Grid</string>
     <string name="hgrid_description">HorizontalGridView test</string>
     <string name="vgrid">Vertical Grid</string>
diff --git a/samples/SupportLeanbackDemos/res/values/themes.xml b/samples/SupportLeanbackDemos/res/values/themes.xml
index 458c5c0..58ae373 100644
--- a/samples/SupportLeanbackDemos/res/values/themes.xml
+++ b/samples/SupportLeanbackDemos/res/values/themes.xml
@@ -23,6 +23,9 @@
     </style>
     <style name="Theme.Example.Leanback.Details" parent="Theme.Leanback.Details">
     </style>
+    <style name="Theme.Example.Leanback.Details.CustomTitle">
+        <item name="browseTitleViewLayout">@layout/custom_title</item>.
+    </style>
     <style name="Theme.Example.Leanback.VerticalGrid" parent="Theme.Leanback.VerticalGrid">
     </style>
     <style name="Theme.Example.Leanback.Rows" parent="Theme.Leanback">
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseSupportFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseSupportFragment.java
index 1a8ab1f..fa1425b 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseSupportFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/BrowseSupportFragment.java
@@ -17,6 +17,7 @@
 package com.example.android.leanback;
 
 import android.support.v4.app.Fragment;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.Handler;
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/CustomTitleView.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/CustomTitleView.java
new file mode 100644
index 0000000..3ceb17d
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/CustomTitleView.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.example.android.leanback;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.v17.leanback.widget.SearchOrbView;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.support.v17.leanback.widget.TitleViewAdapter;
+
+public class CustomTitleView extends LinearLayout implements TitleViewAdapter.Provider {
+
+    private final TitleViewAdapter mTitleViewAdapter = new TitleViewAdapter() {
+        @Override
+        public View getSearchAffordanceView() {
+            return null;
+        }
+
+        @Override
+        public void setOnSearchClickedListener(View.OnClickListener listener) {
+        }
+
+        @Override
+        public void setAnimationEnabled(boolean enable) {
+        }
+
+        @Override
+        public Drawable getBadgeDrawable() {
+            return null;
+        }
+
+        @Override
+        public SearchOrbView.Colors getSearchAffordanceColors() {
+            return null;
+        }
+
+        @Override
+        public CharSequence getTitle() {
+            return null;
+        }
+
+        @Override
+        public void setBadgeDrawable(Drawable drawable) {
+        }
+
+        @Override
+        public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
+        }
+
+        @Override
+        public void setTitle(CharSequence titleText) {
+        }
+
+        @Override
+        public void updateComponentsVisibility(int flags) {
+        }
+    };
+
+    public CustomTitleView(Context context) {
+        this(context, null);
+    }
+
+    public CustomTitleView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CustomTitleView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    public TitleViewAdapter getTitleViewAdapter() {
+        return mTitleViewAdapter;
+    }
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsActivity.java
index 43c6910..6e2cbb4 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsActivity.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsActivity.java
@@ -26,22 +26,41 @@
                 && !(this instanceof SearchDetailsActivity));
     }
 
+    protected boolean hasBackgroundVideo() {
+        return false;
+    }
+
     /** Called when the activity is first created. */
     @Override
     public void onCreate(Bundle savedInstanceState)
     {
         super.onCreate(savedInstanceState);
         getFragmentManager().enableDebugLogging(true);
-        setContentView(useLegacyFragment() ? R.layout.legacy_details : R.layout.details);
+        setContentView(R.layout.details_activity);
         if (savedInstanceState == null) {
-            // Only pass object to fragment when activity is first time created,
-            // later object is modified and persisted with fragment state.
             if (useLegacyFragment()) {
-                ((DetailsFragment)getFragmentManager().findFragmentById(R.id.details_fragment))
-                    .setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+                DetailsFragment fragment = new DetailsFragment();
+                fragment.setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+                getFragmentManager().beginTransaction()
+                        .replace(R.id.details_fragment, fragment)
+                        .commit();
             } else {
-                ((NewDetailsFragment)getFragmentManager().findFragmentById(R.id.details_fragment))
-                    .setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+                NewDetailsFragment fragment = new NewDetailsFragment();
+                fragment.setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+                fragment.setBackgroundVideo(hasBackgroundVideo());
+                getFragmentManager().beginTransaction()
+                        .replace(R.id.details_fragment, fragment)
+                        .commit();
+            }
+        } else {
+            if (useLegacyFragment()) {
+                DetailsFragment fragment = (DetailsFragment) getFragmentManager()
+                        .findFragmentById(R.id.details_fragment);
+                fragment.setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+            } else {
+                NewDetailsFragment fragment = (NewDetailsFragment) getFragmentManager()
+                        .findFragmentById(R.id.details_fragment);
+                fragment.setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
             }
         }
     }
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsCustomTitleActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsCustomTitleActivity.java
new file mode 100644
index 0000000..0796fb7
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsCustomTitleActivity.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.example.android.leanback;
+
+/**
+ * Same function as DetailsActivity, using different theme in AndroidManifest.
+ */
+public class DetailsCustomTitleActivity extends DetailsActivity {
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsCustomTitleSupportActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsCustomTitleSupportActivity.java
new file mode 100644
index 0000000..4ad2f7a
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsCustomTitleSupportActivity.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.example.android.leanback;
+
+/**
+ * Same function as DetailsSupportActivity, using different theme in AndroidManifest.
+ */
+public class DetailsCustomTitleSupportActivity extends DetailsSupportActivity {
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsFragment.java
index 09d9526..56acc05 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsFragment.java
@@ -126,12 +126,7 @@
         ps.addClassPresenter(ListRow.class, new ListRowPresenter());
 
         mRowsAdapter = new ArrayObjectAdapter(ps);
-
-        PhotoItem item = (PhotoItem) (savedInstanceState != null ?
-                savedInstanceState.getParcelable(ITEM) : null);
-        if (item != null) {
-            setItem(item);
-        }
+        updateAdapter();
 
         setOnItemViewClickedListener(new OnItemViewClickedListener() {
             @Override
@@ -179,7 +174,13 @@
 
     public void setItem(PhotoItem photoItem) {
         mPhotoItem = photoItem;
+        updateAdapter();
+    }
 
+    void updateAdapter() {
+        if (mRowsAdapter == null) {
+            return;
+        }
         mRowsAdapter.clear();
         new Handler().postDelayed(new Runnable() {
             public void run() {
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportActivity.java
index 3ebf101..48d15e5 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportActivity.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportActivity.java
@@ -29,22 +29,41 @@
                 && !(this instanceof SearchDetailsSupportActivity));
     }
 
+    protected boolean hasBackgroundVideo() {
+        return false;
+    }
+
     /** Called when the activity is first created. */
     @Override
     public void onCreate(Bundle savedInstanceState)
     {
         super.onCreate(savedInstanceState);
         getSupportFragmentManager().enableDebugLogging(true);
-        setContentView(useLegacyFragment() ? R.layout.legacy_details_support : R.layout.details_support);
+        setContentView(R.layout.details_activity);
         if (savedInstanceState == null) {
-            // Only pass object to fragment when activity is first time created,
-            // later object is modified and persisted with fragment state.
             if (useLegacyFragment()) {
-                ((DetailsSupportFragment)getSupportFragmentManager().findFragmentById(R.id.details_fragment))
-                    .setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+                DetailsSupportFragment fragment = new DetailsSupportFragment();
+                fragment.setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+                getSupportFragmentManager().beginTransaction()
+                        .replace(R.id.details_fragment, fragment)
+                        .commit();
             } else {
-                ((NewDetailsSupportFragment)getSupportFragmentManager().findFragmentById(R.id.details_fragment))
-                    .setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+                NewDetailsSupportFragment fragment = new NewDetailsSupportFragment();
+                fragment.setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+                fragment.setBackgroundVideo(hasBackgroundVideo());
+                getSupportFragmentManager().beginTransaction()
+                        .replace(R.id.details_fragment, fragment)
+                        .commit();
+            }
+        } else {
+            if (useLegacyFragment()) {
+                DetailsSupportFragment fragment = (DetailsSupportFragment) getSupportFragmentManager()
+                        .findFragmentById(R.id.details_fragment);
+                fragment.setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
+            } else {
+                NewDetailsSupportFragment fragment = (NewDetailsSupportFragment) getSupportFragmentManager()
+                        .findFragmentById(R.id.details_fragment);
+                fragment.setItem((PhotoItem) getIntent().getParcelableExtra(EXTRA_ITEM));
             }
         }
     }
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportFragment.java
index 28a61d9..e58e2e7 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsSupportFragment.java
@@ -129,12 +129,7 @@
         ps.addClassPresenter(ListRow.class, new ListRowPresenter());
 
         mRowsAdapter = new ArrayObjectAdapter(ps);
-
-        PhotoItem item = (PhotoItem) (savedInstanceState != null ?
-                savedInstanceState.getParcelable(ITEM) : null);
-        if (item != null) {
-            setItem(item);
-        }
+        updateAdapter();
 
         setOnItemViewClickedListener(new OnItemViewClickedListener() {
             @Override
@@ -182,7 +177,13 @@
 
     public void setItem(PhotoItem photoItem) {
         mPhotoItem = photoItem;
+        updateAdapter();
+    }
 
+    void updateAdapter() {
+        if (mRowsAdapter == null) {
+            return;
+        }
         mRowsAdapter.clear();
         new Handler().postDelayed(new Runnable() {
             public void run() {
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsVideoActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsVideoActivity.java
new file mode 100644
index 0000000..06b65b7
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsVideoActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.example.android.leanback;
+
+public class DetailsVideoActivity extends DetailsActivity {
+
+    @Override
+    protected boolean hasBackgroundVideo() {
+        return true;
+    }
+
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsVideoSupportActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsVideoSupportActivity.java
new file mode 100644
index 0000000..0c31d36
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/DetailsVideoSupportActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.example.android.leanback;
+
+public class DetailsVideoSupportActivity extends DetailsSupportActivity {
+
+    @Override
+    protected boolean hasBackgroundVideo() {
+        return true;
+    }
+
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/MainActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/MainActivity.java
index eab5bbe..fcd8e65 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/MainActivity.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/MainActivity.java
@@ -26,6 +26,7 @@
 import android.support.v17.leanback.widget.GuidedAction;
 import android.support.v4.app.ActivityOptionsCompat;
 import android.support.v4.content.res.ResourcesCompat;
+
 import java.util.List;
 
 /**
@@ -60,12 +61,35 @@
                     R.string.browse_support_description);
             addAction(actions, SearchActivity.class, R.string.search, R.string.search_description);
             addAction(actions, SearchSupportActivity.class, R.string.search_support, R.string.search_support_description);
-            addAction(actions, DetailsActivity.class, R.string.details, R.string.details_description);
+
+            addAction(actions, DetailsActivity.class, R.string.details,
+                    R.string.details_description);
             actions.get(actions.size()-1).getIntent().putExtra(DetailsActivity.EXTRA_ITEM,
                     new PhotoItem("Hello world", R.drawable.gallery_photo_1));
-            addAction(actions, DetailsSupportActivity.class, R.string.details_support, R.string.details_support_description);
+            addAction(actions, DetailsSupportActivity.class, R.string.details_support,
+                    R.string.details_support_description);
             actions.get(actions.size()-1).getIntent().putExtra(DetailsSupportActivity.EXTRA_ITEM,
                     new PhotoItem("Hello world", R.drawable.gallery_photo_1));
+
+            addAction(actions, DetailsVideoActivity.class, R.string.details_video,
+                    R.string.details_video_description);
+            actions.get(actions.size()-1).getIntent().putExtra(DetailsActivity.EXTRA_ITEM,
+                    new PhotoItem("Hello world", R.drawable.gallery_photo_1));
+            addAction(actions, DetailsVideoSupportActivity.class, R.string.details_video_support,
+                    R.string.details_video_support_description);
+            actions.get(actions.size()-1).getIntent().putExtra(DetailsSupportActivity.EXTRA_ITEM,
+                    new PhotoItem("Hello world", R.drawable.gallery_photo_1));
+
+            addAction(actions, DetailsCustomTitleActivity.class, R.string.details_custom_title,
+                    R.string.details_custom_title_description);
+            actions.get(actions.size()-1).getIntent().putExtra(DetailsActivity.EXTRA_ITEM,
+                    new PhotoItem("Hello world", R.drawable.gallery_photo_1));
+            addAction(actions, DetailsCustomTitleSupportActivity.class,
+                    R.string.details_custom_title_support,
+                    R.string.details_custom_title_support_description);
+            actions.get(actions.size()-1).getIntent().putExtra(DetailsSupportActivity.EXTRA_ITEM,
+                    new PhotoItem("Hello world", R.drawable.gallery_photo_1));
+
             addAction(actions, SearchDetailsActivity.class, R.string.search_details,
                     R.string.search_details_description);
             actions.get(actions.size()-1).getIntent().putExtra(SearchDetailsActivity.EXTRA_ITEM,
@@ -91,10 +115,14 @@
                     R.string.browseerror_description);
             addAction(actions, BrowseErrorSupportActivity.class, R.string.browseerror_support,
                     R.string.browseerror_support_description);
-            addAction(actions, PlaybackOverlayActivity.class, R.string.playback,
+            addAction(actions, PlaybackActivity.class, R.string.playback,
                     R.string.playback_description);
-            addAction(actions, PlaybackOverlaySupportActivity.class, R.string.playback_support,
+            addAction(actions, PlaybackSupportActivity.class, R.string.playback_support,
                     R.string.playback_support_description);
+            addAction(actions, PlaybackOverlayActivity.class, R.string.playbackoverlay,
+                    R.string.playbackoverlay_description);
+            addAction(actions, PlaybackOverlaySupportActivity.class,
+                    R.string.playbackoverlay_support, R.string.playbackoverlay_support_description);
             addAction(actions, VideoActivity.class, R.string.video_playback,
                     R.string.playback_description);
             addAction(actions, VideoSupportActivity.class, R.string.video_playback_support,
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsFragment.java
index 7803c29..395d557 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsFragment.java
@@ -20,8 +20,7 @@
 import android.os.Handler;
 import android.support.v17.leanback.app.DetailsBackgroundParallaxHelper;
 import android.support.v17.leanback.app.DetailsFragmentVideoHelper;
-import android.support.v17.leanback.app.MediaPlayerGlue;
-import android.support.v17.leanback.app.VideoFragment;
+import android.support.v17.leanback.media.MediaPlayerGlue;
 import android.support.v17.leanback.widget.Action;
 import android.support.v17.leanback.widget.ArrayObjectAdapter;
 import android.support.v17.leanback.widget.ClassPresenterSelector;
@@ -63,6 +62,7 @@
     private boolean TEST_OVERVIEW_ROW_ON_SECOND;
     private boolean TEST_SHARED_ELEMENT_TRANSITION;
     private boolean TEST_ENTRANCE_TRANSITION;
+    private boolean TEST_BACKGROUND_PLAYER;
 
     private static final long TIME_TO_LOAD_OVERVIEW_ROW_MS = 1000;
     private static final long TIME_TO_LOAD_RELATED_ROWS_MS = 2000;
@@ -77,7 +77,6 @@
     private BackgroundHelper mBackgroundHelper;
     private int mBitmapMinVerticalOffset = -100;
     private MediaPlayerGlue mMediaPlayerGlue;
-    private VideoFragment mVideoFragment;
 
     private void initializeTest() {
         TEST_SHARED_ELEMENT_TRANSITION = null != getActivity().getWindow()
@@ -97,27 +96,32 @@
                 getActivity(), getParallaxManager())
                 .setCoverImageMinVerticalOffset(mBitmapMinVerticalOffset)
                 .build();
-        mMediaPlayerGlue = new MediaPlayerGlue(getActivity(), null);
-        mMediaPlayerGlue.setHost(createPlaybackGlueHost());
-        mVideoHelper = new DetailsFragmentVideoHelper(mMediaPlayerGlue, getParallaxManager());
-        mVideoHelper.setBackgroundDrawable(mParallaxHelper.getCoverImageDrawable());
+        if (TEST_BACKGROUND_PLAYER) {
+            mMediaPlayerGlue = new MediaPlayerGlue(getActivity());
+            mMediaPlayerGlue.setHost(createPlaybackGlueHost());
+            mVideoHelper = new DetailsFragmentVideoHelper(mMediaPlayerGlue, getParallaxManager());
+            mVideoHelper.setBackgroundDrawable(mParallaxHelper.getCoverImageDrawable());
 
-        mMediaPlayerGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
-        mMediaPlayerGlue.setArtist("A Googleer");
-        mMediaPlayerGlue.setTitle("Diving with Sharks");
-        mMediaPlayerGlue.setVideoUrl("http://techslides.com/demos/sample-videos/small.mp4");
+            mMediaPlayerGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
+            mMediaPlayerGlue.setArtist("A Googleer");
+            mMediaPlayerGlue.setTitle("Diving with Sharks");
+            mMediaPlayerGlue.setVideoUrl("http://techslides.com/demos/sample-videos/small.mp4");
+
+        }
 
         final Context context = getActivity();
         setBadgeDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_title,
                 context.getTheme()));
         setTitle("Leanback Sample App");
-        setOnSearchClickedListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                Intent intent = new Intent(getActivity(), SearchActivity.class);
-                startActivity(intent);
-            }
-        });
+        if (!TEST_BACKGROUND_PLAYER) {
+            setOnSearchClickedListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    Intent intent = new Intent(getActivity(), SearchActivity.class);
+                    startActivity(intent);
+                }
+            });
+        }
 
         mActionPlay = new Action(ACTION_PLAY, "Play");
         mActionRent = new Action(ACTION_RENT, "Rent", "$3.99", ResourcesCompat.getDrawable(
@@ -165,12 +169,7 @@
         ps.addClassPresenter(ListRow.class, new ListRowPresenter());
 
         mRowsAdapter = new ArrayObjectAdapter(ps);
-
-        PhotoItem item = (PhotoItem) (savedInstanceState != null ?
-                savedInstanceState.getParcelable(ITEM) : null);
-        if (item != null) {
-            setItem(item);
-        }
+        updateAdapter();
 
         setOnItemViewClickedListener(new OnItemViewClickedListener() {
             @Override
@@ -223,9 +222,19 @@
         return view;
     }
 
+    public void setBackgroundVideo(boolean backgroundVideo) {
+        TEST_BACKGROUND_PLAYER = backgroundVideo;
+    }
+
     public void setItem(PhotoItem photoItem) {
         mPhotoItem = photoItem;
+        updateAdapter();
+    }
 
+    void updateAdapter() {
+        if (mRowsAdapter == null) {
+            return;
+        }
         mRowsAdapter.clear();
         new Handler().postDelayed(new Runnable() {
             public void run() {
@@ -295,6 +304,8 @@
     @Override
     public void onStop() {
         super.onStop();
-        mMediaPlayerGlue.pause();
+        if (TEST_BACKGROUND_PLAYER) {
+            mMediaPlayerGlue.pause();
+        }
     }
 }
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsSupportFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsSupportFragment.java
index 8f0172f..ba482b1 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsSupportFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/NewDetailsSupportFragment.java
@@ -23,8 +23,7 @@
 import android.os.Handler;
 import android.support.v17.leanback.app.DetailsBackgroundParallaxHelper;
 import android.support.v17.leanback.app.DetailsFragmentVideoHelper;
-import android.support.v17.leanback.app.MediaPlayerGlue;
-import android.support.v17.leanback.app.VideoSupportFragment;
+import android.support.v17.leanback.media.MediaPlayerGlue;
 import android.support.v17.leanback.widget.Action;
 import android.support.v17.leanback.widget.ArrayObjectAdapter;
 import android.support.v17.leanback.widget.ClassPresenterSelector;
@@ -66,6 +65,7 @@
     private boolean TEST_OVERVIEW_ROW_ON_SECOND;
     private boolean TEST_SHARED_ELEMENT_TRANSITION;
     private boolean TEST_ENTRANCE_TRANSITION;
+    private boolean TEST_BACKGROUND_PLAYER;
 
     private static final long TIME_TO_LOAD_OVERVIEW_ROW_MS = 1000;
     private static final long TIME_TO_LOAD_RELATED_ROWS_MS = 2000;
@@ -80,7 +80,6 @@
     private BackgroundHelper mBackgroundHelper;
     private int mBitmapMinVerticalOffset = -100;
     private MediaPlayerGlue mMediaPlayerGlue;
-    private VideoSupportFragment mVideoSupportFragment;
 
     private void initializeTest() {
         TEST_SHARED_ELEMENT_TRANSITION = null != getActivity().getWindow()
@@ -100,27 +99,32 @@
                 getActivity(), getParallaxManager())
                 .setCoverImageMinVerticalOffset(mBitmapMinVerticalOffset)
                 .build();
-        mMediaPlayerGlue = new MediaPlayerGlue(getActivity(), null);
-        mMediaPlayerGlue.setHost(createPlaybackGlueHost());
-        mVideoHelper = new DetailsFragmentVideoHelper(mMediaPlayerGlue, getParallaxManager());
-        mVideoHelper.setBackgroundDrawable(mParallaxHelper.getCoverImageDrawable());
+        if (TEST_BACKGROUND_PLAYER) {
+            mMediaPlayerGlue = new MediaPlayerGlue(getActivity());
+            mMediaPlayerGlue.setHost(createPlaybackGlueHost());
+            mVideoHelper = new DetailsFragmentVideoHelper(mMediaPlayerGlue, getParallaxManager());
+            mVideoHelper.setBackgroundDrawable(mParallaxHelper.getCoverImageDrawable());
 
-        mMediaPlayerGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
-        mMediaPlayerGlue.setArtist("A Googleer");
-        mMediaPlayerGlue.setTitle("Diving with Sharks");
-        mMediaPlayerGlue.setVideoUrl("http://techslides.com/demos/sample-videos/small.mp4");
+            mMediaPlayerGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
+            mMediaPlayerGlue.setArtist("A Googleer");
+            mMediaPlayerGlue.setTitle("Diving with Sharks");
+            mMediaPlayerGlue.setVideoUrl("http://techslides.com/demos/sample-videos/small.mp4");
+
+        }
 
         final Context context = getActivity();
         setBadgeDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_title,
                 context.getTheme()));
         setTitle("Leanback Sample App");
-        setOnSearchClickedListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                Intent intent = new Intent(getActivity(), SearchSupportActivity.class);
-                startActivity(intent);
-            }
-        });
+        if (!TEST_BACKGROUND_PLAYER) {
+            setOnSearchClickedListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    Intent intent = new Intent(getActivity(), SearchSupportActivity.class);
+                    startActivity(intent);
+                }
+            });
+        }
 
         mActionPlay = new Action(ACTION_PLAY, "Play");
         mActionRent = new Action(ACTION_RENT, "Rent", "$3.99", ResourcesCompat.getDrawable(
@@ -168,12 +172,7 @@
         ps.addClassPresenter(ListRow.class, new ListRowPresenter());
 
         mRowsAdapter = new ArrayObjectAdapter(ps);
-
-        PhotoItem item = (PhotoItem) (savedInstanceState != null ?
-                savedInstanceState.getParcelable(ITEM) : null);
-        if (item != null) {
-            setItem(item);
-        }
+        updateAdapter();
 
         setOnItemViewClickedListener(new OnItemViewClickedListener() {
             @Override
@@ -226,9 +225,19 @@
         return view;
     }
 
+    public void setBackgroundVideo(boolean backgroundVideo) {
+        TEST_BACKGROUND_PLAYER = backgroundVideo;
+    }
+
     public void setItem(PhotoItem photoItem) {
         mPhotoItem = photoItem;
+        updateAdapter();
+    }
 
+    void updateAdapter() {
+        if (mRowsAdapter == null) {
+            return;
+        }
         mRowsAdapter.clear();
         new Handler().postDelayed(new Runnable() {
             public void run() {
@@ -298,6 +307,8 @@
     @Override
     public void onStop() {
         super.onStop();
-        mMediaPlayerGlue.pause();
+        if (TEST_BACKGROUND_PLAYER) {
+            mMediaPlayerGlue.pause();
+        }
     }
 }
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackActivity.java
new file mode 100644
index 0000000..14a13cd
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackActivity.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.leanback;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Host PlaybackFragment and provide PIP events.
+ */
+public class PlaybackActivity extends Activity {
+    private List<PictureInPictureListener> mListeners = new ArrayList<>();
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.playback_activity);
+    }
+
+    @Override
+    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+        for (PictureInPictureListener listener : mListeners) {
+            listener.onPictureInPictureModeChanged(isInPictureInPictureMode);
+        }
+    }
+
+    /**
+     * Register a PIP listener.
+     */
+    public void registerPictureInPictureListener(PictureInPictureListener listener) {
+        mListeners.add(listener);
+    }
+
+    /**
+     * Unregister a PIP listener.
+     */
+    public void unregisterPictureInPictureListener(PictureInPictureListener listener) {
+        mListeners.remove(listener);
+    }
+
+    /**
+     * Interface of PIP event on Activity.
+     */
+    public interface PictureInPictureListener {
+        /**
+         * Called when Activity's PIP mode is changed.
+         */
+        void onPictureInPictureModeChanged(boolean isInPictureInPictureMode);
+    }
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlGlue.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlGlue.java
new file mode 100644
index 0000000..e04fa46
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlGlue.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.leanback;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.Toast;
+
+abstract class PlaybackControlGlue extends android.support.v17.leanback.media.PlaybackControlGlue {
+    /**
+     * Change the location of the thumbs up/down controls
+     */
+    private static final boolean THUMBS_PRIMARY = true;
+
+    private static final String FAUX_TITLE = "A short song of silence";
+    private static final String FAUX_SUBTITLE = "2014";
+    private static final int FAUX_DURATION = 33 * 1000;
+
+    // These should match the playback service FF behavior
+    private static int[] sFastForwardSpeeds = { 2, 3, 4, 5 };
+
+    private boolean mIsPlaying;
+    private int mSpeed = PlaybackControlGlue.PLAYBACK_SPEED_PAUSED;
+    private long mStartTime;
+    private long mStartPosition = 0;
+
+    private PlaybackControlsRow.RepeatAction mRepeatAction;
+    private PlaybackControlsRow.ThumbsUpAction mThumbsUpAction;
+    private PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
+    private PlaybackControlsRow.PictureInPictureAction mPipAction;
+
+    private Handler mHandler = new Handler();
+    private final Runnable mUpdateProgressRunnable = new Runnable() {
+        @Override
+        public void run() {
+            updateProgress();
+            mHandler.postDelayed(this, getUpdatePeriod());
+        }
+    };
+
+    PlaybackControlGlue(Context context) {
+        super(context, sFastForwardSpeeds);
+        mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context);
+        mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsUpAction.OUTLINE);
+        mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context);
+        mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsDownAction.OUTLINE);
+        mRepeatAction = new PlaybackControlsRow.RepeatAction(context);
+        mPipAction = new PlaybackControlsRow.PictureInPictureAction(context);
+    }
+
+    @Override
+    protected void onCreateSecondaryActions(ArrayObjectAdapter adapter) {
+        if (!THUMBS_PRIMARY) {
+            adapter.add(mThumbsDownAction);
+        }
+        if (android.os.Build.VERSION.SDK_INT > 23) {
+            adapter.add(mPipAction);
+        }
+        adapter.add(mRepeatAction);
+        if (!THUMBS_PRIMARY) {
+            adapter.add(mThumbsUpAction);
+        }
+    }
+
+    @Override
+    protected void onCreatePrimaryActions(SparseArrayObjectAdapter adapter) {
+        if (THUMBS_PRIMARY) {
+            adapter.set(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction);
+            adapter.set(PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction);
+        }
+    }
+
+    @Override
+    public void onActionClicked(Action action) {
+        if (shouldDispatchAction(action)) {
+            dispatchAction(action);
+            return;
+        }
+        super.onActionClicked(action);
+    }
+
+    @Override
+    public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
+        if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+            Action action = getControlsRow().getActionForKeyCode(keyEvent.getKeyCode());
+            if (shouldDispatchAction(action)) {
+                dispatchAction(action);
+                return true;
+            }
+        }
+        return super.onKey(view, keyCode, keyEvent);
+    }
+
+    private boolean shouldDispatchAction(Action action) {
+        return action == mRepeatAction || action == mThumbsUpAction || action == mThumbsDownAction;
+    }
+
+    private void dispatchAction(Action action) {
+        Toast.makeText(getContext(), action.toString(), Toast.LENGTH_SHORT).show();
+        PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action;
+        multiAction.nextIndex();
+        notifyActionChanged(multiAction);
+    }
+
+    private void notifyActionChanged(PlaybackControlsRow.MultiAction action) {
+        int index;
+        index = getPrimaryActionsAdapter().indexOf(action);
+        if (index >= 0) {
+            getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+        } else {
+            index = getSecondaryActionsAdapter().indexOf(action);
+            if (index >= 0) {
+                getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+            }
+        }
+    }
+
+    private SparseArrayObjectAdapter getPrimaryActionsAdapter() {
+        return (SparseArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter();
+    }
+
+    private ArrayObjectAdapter getSecondaryActionsAdapter() {
+        return (ArrayObjectAdapter) getControlsRow().getSecondaryActionsAdapter();
+    }
+
+    @Override
+    public boolean hasValidMedia() {
+        return true;
+    }
+
+    @Override
+    public boolean isMediaPlaying() {
+        return mIsPlaying;
+    }
+
+    @Override
+    public CharSequence getMediaTitle() {
+        return FAUX_TITLE;
+    }
+
+    @Override
+    public CharSequence getMediaSubtitle() {
+        return FAUX_SUBTITLE;
+    }
+
+    @Override
+    public int getMediaDuration() {
+        return FAUX_DURATION;
+    }
+
+    @Override
+    public Drawable getMediaArt() {
+        return null;
+    }
+
+    @Override
+    public long getSupportedActions() {
+        return PlaybackControlGlue.ACTION_PLAY_PAUSE
+                | PlaybackControlGlue.ACTION_FAST_FORWARD
+                | PlaybackControlGlue.ACTION_REWIND;
+    }
+
+    @Override
+    public int getCurrentSpeedId() {
+        return mSpeed;
+    }
+
+    @Override
+    public int getCurrentPosition() {
+        int speed;
+        if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
+            speed = 0;
+        } else if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_NORMAL) {
+            speed = 1;
+        } else if (mSpeed >= PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
+            int index = mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
+            speed = getFastForwardSpeeds()[index];
+        } else if (mSpeed <= -PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
+            int index = -mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
+            speed = -getRewindSpeeds()[index];
+        } else {
+            return -1;
+        }
+        long position = mStartPosition + (System.currentTimeMillis() - mStartTime) * speed;
+        if (position > getMediaDuration()) {
+            position = getMediaDuration();
+            onPlaybackComplete(true);
+        } else if (position < 0) {
+            position = 0;
+            onPlaybackComplete(false);
+        }
+        return (int) position;
+    }
+
+    void onPlaybackComplete(final boolean ended) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.NONE) {
+                    pause();
+                } else {
+                    play(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL);
+                }
+                mStartPosition = 0;
+                onStateChanged();
+            }
+        });
+    }
+
+    @Override
+    public void play(int speed) {
+        if (speed == mSpeed) {
+            return;
+        }
+        mStartPosition = getCurrentPosition();
+        mSpeed = speed;
+        mIsPlaying = true;
+        mStartTime = System.currentTimeMillis();
+    }
+
+    @Override
+    public void pause() {
+        if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
+            return;
+        }
+        mStartPosition = getCurrentPosition();
+        mSpeed = PlaybackControlGlue.PLAYBACK_SPEED_PAUSED;
+        mIsPlaying = false;
+    }
+
+    @Override
+    public void next() {
+        // Not supported
+    }
+
+    @Override
+    public void previous() {
+        // Not supported
+    }
+
+    @Override
+    public void enableProgressUpdating(boolean enable) {
+        mHandler.removeCallbacks(mUpdateProgressRunnable);
+        if (enable) {
+            mUpdateProgressRunnable.run();
+        }
+    }
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlHelper.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlHelper.java
index 308c7f4..a6752eb 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlHelper.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlHelper.java
@@ -19,9 +19,7 @@
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
-import android.support.v17.leanback.app.MediaPlayerGlue;
 import android.support.v17.leanback.app.PlaybackControlGlue;
-import android.support.v17.leanback.app.PlaybackGlue;
 import android.support.v17.leanback.widget.Action;
 import android.support.v17.leanback.widget.ArrayObjectAdapter;
 import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
@@ -33,7 +31,7 @@
 import android.view.View;
 import android.widget.Toast;
 
-abstract class PlaybackControlHelper extends MediaPlayerGlue {
+abstract class PlaybackControlHelper extends PlaybackControlGlue {
     /**
      * Change the location of the thumbs up/down controls
      */
@@ -56,17 +54,20 @@
     private PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
     private PlaybackControlsRow.PictureInPictureAction mPipAction;
 
-    private static Handler sHandler = new Handler();
+    private Handler mHandler = new Handler();
+    // simulating whether the media is yet prepared and ready to play
+    private boolean mInitialized = true;
+
     private final Runnable mUpdateProgressRunnable = new Runnable() {
         @Override
         public void run() {
             updateProgress();
-            sHandler.postDelayed(this, getUpdatePeriod());
+            mHandler.postDelayed(this, getUpdatePeriod());
         }
     };
 
-    public PlaybackControlHelper(Context context, PlaybackGlue.PlaybackGlueHost host) {
-        super(context, host, sFastForwardSpeeds, sFastForwardSpeeds);
+    PlaybackControlHelper(Context context, PlaybackOverlayFragment fragment) {
+        super(context, fragment, sFastForwardSpeeds);
         mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context);
         mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsUpAction.OUTLINE);
         mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context);
@@ -161,7 +162,7 @@
 
     @Override
     public boolean hasValidMedia() {
-        return true;
+        return mInitialized;
     }
 
     @Override
@@ -181,7 +182,7 @@
 
     @Override
     public int getMediaDuration() {
-        return FAUX_DURATION;
+        return mInitialized ? FAUX_DURATION : 0;
     }
 
     @Override
@@ -230,7 +231,7 @@
     }
 
     void onPlaybackComplete(final boolean ended) {
-        sHandler.post(new Runnable() {
+        mHandler.post(new Runnable() {
             @Override
             public void run() {
                 if (mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.NONE) {
@@ -277,9 +278,21 @@
 
     @Override
     public void enableProgressUpdating(boolean enable) {
-        sHandler.removeCallbacks(mUpdateProgressRunnable);
+        mHandler.removeCallbacks(mUpdateProgressRunnable);
         if (enable) {
             mUpdateProgressRunnable.run();
         }
     }
-}
+
+    public boolean isInitialized() {
+        return mInitialized;
+    }
+
+    public void setInitialized(boolean initialized) {
+        if (mInitialized != initialized) {
+            mInitialized = initialized;
+            onMetadataChanged();
+            onStateChanged();
+        }
+    }
+};
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlSupportHelper.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlSupportHelper.java
new file mode 100644
index 0000000..0425e60
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackControlSupportHelper.java
@@ -0,0 +1,301 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from PlaybackControlHelper.java.  DO NOT MODIFY. */
+
+/*
+ * 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.example.android.leanback;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.support.v17.leanback.app.PlaybackControlSupportGlue;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.Toast;
+
+abstract class PlaybackControlSupportHelper extends PlaybackControlSupportGlue {
+    /**
+     * Change the location of the thumbs up/down controls
+     */
+    private static final boolean THUMBS_PRIMARY = true;
+
+    private static final String FAUX_TITLE = "A short song of silence";
+    private static final String FAUX_SUBTITLE = "2014";
+    private static final int FAUX_DURATION = 33 * 1000;
+
+    // These should match the playback service FF behavior
+    private static int[] sFastForwardSpeeds = { 2, 3, 4, 5 };
+
+    private boolean mIsPlaying;
+    private int mSpeed = PlaybackControlSupportGlue.PLAYBACK_SPEED_PAUSED;
+    private long mStartTime;
+    private long mStartPosition = 0;
+
+    private PlaybackControlsRow.RepeatAction mRepeatAction;
+    private PlaybackControlsRow.ThumbsUpAction mThumbsUpAction;
+    private PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
+    private PlaybackControlsRow.PictureInPictureAction mPipAction;
+
+    private Handler mHandler = new Handler();
+    // simulating whether the media is yet prepared and ready to play
+    private boolean mInitialized = true;
+
+    private final Runnable mUpdateProgressRunnable = new Runnable() {
+        @Override
+        public void run() {
+            updateProgress();
+            mHandler.postDelayed(this, getUpdatePeriod());
+        }
+    };
+
+    PlaybackControlSupportHelper(Context context, PlaybackOverlaySupportFragment fragment) {
+        super(context, fragment, sFastForwardSpeeds);
+        mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context);
+        mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsUpAction.OUTLINE);
+        mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context);
+        mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsDownAction.OUTLINE);
+        mRepeatAction = new PlaybackControlsRow.RepeatAction(context);
+        mPipAction = new PlaybackControlsRow.PictureInPictureAction(context);
+    }
+
+    @Override
+    public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
+        PlaybackControlsRowPresenter presenter = super.createControlsRowAndPresenter();
+
+        ArrayObjectAdapter adapter = new ArrayObjectAdapter(new ControlButtonPresenterSelector());
+        getControlsRow().setSecondaryActionsAdapter(adapter);
+        if (!THUMBS_PRIMARY) {
+            adapter.add(mThumbsDownAction);
+        }
+        if (android.os.Build.VERSION.SDK_INT > 23) {
+            adapter.add(mPipAction);
+        }
+        adapter.add(mRepeatAction);
+        if (!THUMBS_PRIMARY) {
+            adapter.add(mThumbsUpAction);
+        }
+
+        return presenter;
+    }
+
+    @Override
+    protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
+            PresenterSelector presenterSelector) {
+        SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
+        if (THUMBS_PRIMARY) {
+            adapter.set(PlaybackControlSupportGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction);
+            adapter.set(PlaybackControlSupportGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction);
+        }
+        return adapter;
+    }
+
+    @Override
+    public void onActionClicked(Action action) {
+        if (shouldDispatchAction(action)) {
+            dispatchAction(action);
+            return;
+        }
+        super.onActionClicked(action);
+    }
+
+    @Override
+    public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
+        if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+            Action action = getControlsRow().getActionForKeyCode(keyEvent.getKeyCode());
+            if (shouldDispatchAction(action)) {
+                dispatchAction(action);
+                return true;
+            }
+        }
+        return super.onKey(view, keyCode, keyEvent);
+    }
+
+    private boolean shouldDispatchAction(Action action) {
+        return action == mRepeatAction || action == mThumbsUpAction || action == mThumbsDownAction;
+    }
+
+    private void dispatchAction(Action action) {
+        Toast.makeText(getContext(), action.toString(), Toast.LENGTH_SHORT).show();
+        PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action;
+        multiAction.nextIndex();
+        notifyActionChanged(multiAction);
+    }
+
+    private void notifyActionChanged(PlaybackControlsRow.MultiAction action) {
+        int index;
+        index = getPrimaryActionsAdapter().indexOf(action);
+        if (index >= 0) {
+            getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+        } else {
+            index = getSecondaryActionsAdapter().indexOf(action);
+            if (index >= 0) {
+                getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+            }
+        }
+    }
+
+    private SparseArrayObjectAdapter getPrimaryActionsAdapter() {
+        return (SparseArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter();
+    }
+
+    private ArrayObjectAdapter getSecondaryActionsAdapter() {
+        return (ArrayObjectAdapter) getControlsRow().getSecondaryActionsAdapter();
+    }
+
+    @Override
+    public boolean hasValidMedia() {
+        return mInitialized;
+    }
+
+    @Override
+    public boolean isMediaPlaying() {
+        return mIsPlaying;
+    }
+
+    @Override
+    public CharSequence getMediaTitle() {
+        return FAUX_TITLE;
+    }
+
+    @Override
+    public CharSequence getMediaSubtitle() {
+        return FAUX_SUBTITLE;
+    }
+
+    @Override
+    public int getMediaDuration() {
+        return mInitialized ? FAUX_DURATION : 0;
+    }
+
+    @Override
+    public Drawable getMediaArt() {
+        return null;
+    }
+
+    @Override
+    public long getSupportedActions() {
+        return PlaybackControlSupportGlue.ACTION_PLAY_PAUSE |
+                PlaybackControlSupportGlue.ACTION_FAST_FORWARD |
+                PlaybackControlSupportGlue.ACTION_REWIND;
+    }
+
+    @Override
+    public int getCurrentSpeedId() {
+        return mSpeed;
+    }
+
+    @Override
+    public int getCurrentPosition() {
+        int speed;
+        if (mSpeed == PlaybackControlSupportGlue.PLAYBACK_SPEED_PAUSED) {
+            speed = 0;
+        } else if (mSpeed == PlaybackControlSupportGlue.PLAYBACK_SPEED_NORMAL) {
+            speed = 1;
+        } else if (mSpeed >= PlaybackControlSupportGlue.PLAYBACK_SPEED_FAST_L0) {
+            int index = mSpeed - PlaybackControlSupportGlue.PLAYBACK_SPEED_FAST_L0;
+            speed = getFastForwardSpeeds()[index];
+        } else if (mSpeed <= -PlaybackControlSupportGlue.PLAYBACK_SPEED_FAST_L0) {
+            int index = -mSpeed - PlaybackControlSupportGlue.PLAYBACK_SPEED_FAST_L0;
+            speed = -getRewindSpeeds()[index];
+        } else {
+            return -1;
+        }
+        long position = mStartPosition +
+                (System.currentTimeMillis() - mStartTime) * speed;
+        if (position > getMediaDuration()) {
+            position = getMediaDuration();
+            onPlaybackComplete(true);
+        } else if (position < 0) {
+            position = 0;
+            onPlaybackComplete(false);
+        }
+        return (int) position;
+    }
+
+    void onPlaybackComplete(final boolean ended) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.NONE) {
+                    pausePlayback();
+                } else {
+                    startPlayback(PlaybackControlSupportGlue.PLAYBACK_SPEED_NORMAL);
+                }
+                mStartPosition = 0;
+                onStateChanged();
+            }
+        });
+    }
+
+    @Override
+    protected void startPlayback(int speed) {
+        if (speed == mSpeed) {
+            return;
+        }
+        mStartPosition = getCurrentPosition();
+        mSpeed = speed;
+        mIsPlaying = true;
+        mStartTime = System.currentTimeMillis();
+    }
+
+    @Override
+    protected void pausePlayback() {
+        if (mSpeed == PlaybackControlSupportGlue.PLAYBACK_SPEED_PAUSED) {
+            return;
+        }
+        mStartPosition = getCurrentPosition();
+        mSpeed = PlaybackControlSupportGlue.PLAYBACK_SPEED_PAUSED;
+        mIsPlaying = false;
+    }
+
+    @Override
+    protected void skipToNext() {
+        // Not supported
+    }
+
+    @Override
+    protected void skipToPrevious() {
+        // Not supported
+    }
+
+    @Override
+    public void enableProgressUpdating(boolean enable) {
+        mHandler.removeCallbacks(mUpdateProgressRunnable);
+        if (enable) {
+            mUpdateProgressRunnable.run();
+        }
+    }
+
+    public boolean isInitialized() {
+        return mInitialized;
+    }
+
+    public void setInitialized(boolean initialized) {
+        if (mInitialized != initialized) {
+            mInitialized = initialized;
+            onMetadataChanged();
+            onStateChanged();
+        }
+    }
+};
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackFragment.java
new file mode 100644
index 0000000..a29a995
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackFragment.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.leanback;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v17.leanback.app.PlaybackFragmentGlueHost;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.util.Log;
+
+/**
+ * Example of PlaybackFragment working with a PlaybackControlGlue.
+ */
+public class PlaybackFragment
+        extends android.support.v17.leanback.app.PlaybackFragment
+        implements PlaybackActivity.PictureInPictureListener {
+    private static final String TAG = "leanback.PlaybackControlsFragment";
+
+    /**
+     * Change this to choose a different overlay background.
+     */
+    private static final int BACKGROUND_TYPE = PlaybackFragment.BG_LIGHT;
+
+    /**
+     * Change the number of related content rows.
+     */
+    private static final int RELATED_CONTENT_ROWS = 3;
+
+    /**
+     * Change this to select hidden
+     */
+    private static final boolean SECONDARY_HIDDEN = false;
+
+    private static final int ROW_CONTROLS = 0;
+
+    private PlaybackControlGlue mGlue;
+    private ListRowPresenter mListRowPresenter;
+
+    public SparseArrayObjectAdapter getAdapter() {
+        return (SparseArrayObjectAdapter) super.getAdapter();
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate");
+        super.onCreate(savedInstanceState);
+
+        setBackgroundType(BACKGROUND_TYPE);
+
+        createComponents(getActivity());
+    }
+
+    private void createComponents(Context context) {
+        mGlue = new PlaybackControlGlue(context) {
+            @Override
+            public int getUpdatePeriod() {
+                int totalTime = getControlsRow().getTotalTime();
+                if (getView() == null || getView().getWidth() == 0 || totalTime <= 0) {
+                    return 1000;
+                }
+                return Math.max(16, totalTime / getView().getWidth());
+            }
+
+            @Override
+            public void onActionClicked(Action action) {
+                if (action.getId() == R.id.lb_control_picture_in_picture) {
+                    getActivity().enterPictureInPictureMode();
+                    return;
+                }
+                super.onActionClicked(action);
+            }
+
+            @Override
+            protected void onCreateControlsRowAndPresenter() {
+                super.onCreateControlsRowAndPresenter();
+                getControlsRowPresenter().setSecondaryActionsHidden(SECONDARY_HIDDEN);
+            }
+        };
+
+        mGlue.setHost(new PlaybackFragmentGlueHost(this));
+        mListRowPresenter = new ListRowPresenter();
+
+        setAdapter(new SparseArrayObjectAdapter(new PresenterSelector() {
+            @Override
+            public Presenter getPresenter(Object object) {
+                if (object instanceof PlaybackControlsRow) {
+                    return mGlue.getControlsRowPresenter();
+                } else if (object instanceof ListRow) {
+                    return mListRowPresenter;
+                }
+                throw new IllegalArgumentException("Unhandled object: " + object);
+            }
+        }));
+
+        // Add the controls row
+        getAdapter().set(ROW_CONTROLS, mGlue.getControlsRow());
+
+        // Add related content rows
+        for (int i = 0; i < RELATED_CONTENT_ROWS; ++i) {
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new StringPresenter());
+            listRowAdapter.add("Some related content");
+            listRowAdapter.add("Other related content");
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            getAdapter().set(ROW_CONTROLS + 1 + i, new ListRow(header, listRowAdapter));
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        ((PlaybackActivity) getActivity()).registerPictureInPictureListener(this);
+    }
+
+    @Override
+    public void onStop() {
+        ((PlaybackActivity) getActivity()).unregisterPictureInPictureListener(this);
+        super.onStop();
+    }
+
+    @Override
+    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+        if (isInPictureInPictureMode) {
+            // Hide the controls in picture-in-picture mode.
+            setFadingEnabled(true);
+            fadeOut();
+        } else {
+            setFadingEnabled(mGlue.isMediaPlaying());
+        }
+    }
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlayFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlayFragment.java
index 06ec502..bca057f 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlayFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlayFragment.java
@@ -15,30 +15,32 @@
 
 import android.content.Context;
 import android.os.Bundle;
-import android.support.v17.leanback.app.BackgroundManager;
-import android.support.v17.leanback.app.PlaybackFragment;
-import android.support.v17.leanback.app.PlaybackFragmentGlueHost;
+import android.os.Handler;
 import android.support.v17.leanback.widget.Action;
 import android.support.v17.leanback.widget.ArrayObjectAdapter;
 import android.support.v17.leanback.widget.HeaderItem;
 import android.support.v17.leanback.widget.ListRow;
 import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
 import android.support.v17.leanback.widget.PlaybackControlsRow;
 import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
 import android.support.v17.leanback.widget.Presenter;
 import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
 import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
 import android.util.Log;
 
 public class PlaybackOverlayFragment
-        extends android.support.v17.leanback.app.PlaybackFragment
+        extends android.support.v17.leanback.app.PlaybackOverlayFragment
         implements PlaybackOverlayActivity.PictureInPictureListener {
     private static final String TAG = "leanback.PlaybackControlsFragment";
 
     /**
      * Change this to choose a different overlay background.
      */
-    private static final int BACKGROUND_TYPE = PlaybackFragment.BG_LIGHT;
+    private static final int BACKGROUND_TYPE = PlaybackOverlayFragment.BG_LIGHT;
 
     /**
      * Change the number of related content rows.
@@ -55,6 +57,31 @@
     private PlaybackControlHelper mGlue;
     private PlaybackControlsRowPresenter mPlaybackControlsRowPresenter;
     private ListRowPresenter mListRowPresenter;
+    final Handler mHandler = new Handler();
+
+    // Artificial delay to simulate a media being prepared. The onRowChanged callback should be
+    // called and the playback row UI should be updated after this delay.
+    private static final int MEDIA_PREPARATION_DELAY = 500;
+
+    private OnItemViewClickedListener mOnItemViewClickedListener = new OnItemViewClickedListener() {
+        @Override
+        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                                  RowPresenter.ViewHolder rowViewHolder, Row row) {
+            Log.i(TAG, "onItemClicked: " + item + " row " + row);
+            if (item instanceof Action) {
+                mGlue.onActionClicked((Action) item);
+            }
+        }
+    };
+
+    private OnItemViewSelectedListener mOnItemViewSelectedListener =
+            new OnItemViewSelectedListener() {
+                @Override
+                public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                                           RowPresenter.ViewHolder rowViewHolder, Row row) {
+                    Log.i(TAG, "onItemSelected: " + item + " row " + row);
+                }
+    };
 
     public SparseArrayObjectAdapter getAdapter() {
         return (SparseArrayObjectAdapter) super.getAdapter();
@@ -66,12 +93,13 @@
         super.onCreate(savedInstanceState);
 
         setBackgroundType(BACKGROUND_TYPE);
+        setOnItemViewSelectedListener(mOnItemViewSelectedListener);
 
         createComponents(getActivity());
     }
 
     private void createComponents(Context context) {
-        mGlue = new PlaybackControlHelper(context, new PlaybackFragmentGlueHost(this)) {
+        mGlue = new PlaybackControlHelper(context, this) {
             @Override
             public int getUpdatePeriod() {
                 int totalTime = getControlsRow().getTotalTime();
@@ -102,6 +130,15 @@
             }
         };
 
+        mGlue.setInitialized(false);
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                mGlue.setInitialized(true);
+            }
+        }, MEDIA_PREPARATION_DELAY);
+        mGlue.setOnItemViewClickedListener(mOnItemViewClickedListener);
+
         mPlaybackControlsRowPresenter = mGlue.createControlsRowAndPresenter();
         mPlaybackControlsRowPresenter.setSecondaryActionsHidden(SECONDARY_HIDDEN);
         mListRowPresenter = new ListRowPresenter();
@@ -129,13 +166,14 @@
             HeaderItem header = new HeaderItem(i, "Row " + i);
             getAdapter().set(ROW_CONTROLS + 1 + i, new ListRow(header, listRowAdapter));
         }
+
     }
 
     @Override
     public void onStart() {
         super.onStart();
         mGlue.setFadingEnabled(true);
-        mGlue.enableProgressUpdating(mGlue.hasValidMedia() && mGlue.isMediaPlaying());
+        mGlue.enableProgressUpdating(true);
         ((PlaybackOverlayActivity) getActivity()).registerPictureInPictureListener(this);
     }
 
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlaySupportFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlaySupportFragment.java
index c7dc7b7..b806a9a 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlaySupportFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackOverlaySupportFragment.java
@@ -18,30 +18,32 @@
 
 import android.content.Context;
 import android.os.Bundle;
-import android.support.v17.leanback.app.BackgroundManager;
-import android.support.v17.leanback.app.PlaybackSupportFragment;
-import android.support.v17.leanback.app.PlaybackSupportFragmentGlueHost;
+import android.os.Handler;
 import android.support.v17.leanback.widget.Action;
 import android.support.v17.leanback.widget.ArrayObjectAdapter;
 import android.support.v17.leanback.widget.HeaderItem;
 import android.support.v17.leanback.widget.ListRow;
 import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
 import android.support.v17.leanback.widget.PlaybackControlsRow;
 import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
 import android.support.v17.leanback.widget.Presenter;
 import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
 import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
 import android.util.Log;
 
 public class PlaybackOverlaySupportFragment
-        extends android.support.v17.leanback.app.PlaybackSupportFragment
+        extends android.support.v17.leanback.app.PlaybackOverlaySupportFragment
         implements PlaybackOverlaySupportActivity.PictureInPictureListener {
     private static final String TAG = "leanback.PlaybackControlsFragment";
 
     /**
      * Change this to choose a different overlay background.
      */
-    private static final int BACKGROUND_TYPE = PlaybackSupportFragment.BG_LIGHT;
+    private static final int BACKGROUND_TYPE = PlaybackOverlaySupportFragment.BG_LIGHT;
 
     /**
      * Change the number of related content rows.
@@ -55,9 +57,34 @@
 
     private static final int ROW_CONTROLS = 0;
 
-    private PlaybackControlHelper mGlue;
+    private PlaybackControlSupportHelper mGlue;
     private PlaybackControlsRowPresenter mPlaybackControlsRowPresenter;
     private ListRowPresenter mListRowPresenter;
+    final Handler mHandler = new Handler();
+
+    // Artificial delay to simulate a media being prepared. The onRowChanged callback should be
+    // called and the playback row UI should be updated after this delay.
+    private static final int MEDIA_PREPARATION_DELAY = 500;
+
+    private OnItemViewClickedListener mOnItemViewClickedListener = new OnItemViewClickedListener() {
+        @Override
+        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                                  RowPresenter.ViewHolder rowViewHolder, Row row) {
+            Log.i(TAG, "onItemClicked: " + item + " row " + row);
+            if (item instanceof Action) {
+                mGlue.onActionClicked((Action) item);
+            }
+        }
+    };
+
+    private OnItemViewSelectedListener mOnItemViewSelectedListener =
+            new OnItemViewSelectedListener() {
+                @Override
+                public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                                           RowPresenter.ViewHolder rowViewHolder, Row row) {
+                    Log.i(TAG, "onItemSelected: " + item + " row " + row);
+                }
+    };
 
     public SparseArrayObjectAdapter getAdapter() {
         return (SparseArrayObjectAdapter) super.getAdapter();
@@ -69,12 +96,13 @@
         super.onCreate(savedInstanceState);
 
         setBackgroundType(BACKGROUND_TYPE);
+        setOnItemViewSelectedListener(mOnItemViewSelectedListener);
 
         createComponents(getActivity());
     }
 
     private void createComponents(Context context) {
-        mGlue = new PlaybackControlHelper(context, new PlaybackSupportFragmentGlueHost(this)) {
+        mGlue = new PlaybackControlSupportHelper(context, this) {
             @Override
             public int getUpdatePeriod() {
                 int totalTime = getControlsRow().getTotalTime();
@@ -105,6 +133,15 @@
             }
         };
 
+        mGlue.setInitialized(false);
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                mGlue.setInitialized(true);
+            }
+        }, MEDIA_PREPARATION_DELAY);
+        mGlue.setOnItemViewClickedListener(mOnItemViewClickedListener);
+
         mPlaybackControlsRowPresenter = mGlue.createControlsRowAndPresenter();
         mPlaybackControlsRowPresenter.setSecondaryActionsHidden(SECONDARY_HIDDEN);
         mListRowPresenter = new ListRowPresenter();
@@ -132,13 +169,14 @@
             HeaderItem header = new HeaderItem(i, "Row " + i);
             getAdapter().set(ROW_CONTROLS + 1 + i, new ListRow(header, listRowAdapter));
         }
+
     }
 
     @Override
     public void onStart() {
         super.onStart();
         mGlue.setFadingEnabled(true);
-        mGlue.enableProgressUpdating(mGlue.hasValidMedia() && mGlue.isMediaPlaying());
+        mGlue.enableProgressUpdating(true);
         ((PlaybackOverlaySupportActivity) getActivity()).registerPictureInPictureListener(this);
     }
 
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackSupportActivity.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackSupportActivity.java
new file mode 100644
index 0000000..069b37c
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackSupportActivity.java
@@ -0,0 +1,71 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from PlaybackActivity.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.leanback;
+
+import android.support.v4.app.FragmentActivity;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Host PlaybackFragment and provide PIP events.
+ */
+public class PlaybackSupportActivity extends FragmentActivity {
+    private List<PictureInPictureListener> mListeners = new ArrayList<>();
+
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.playback_activity_support);
+    }
+
+    @Override
+    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+        for (PictureInPictureListener listener : mListeners) {
+            listener.onPictureInPictureModeChanged(isInPictureInPictureMode);
+        }
+    }
+
+    /**
+     * Register a PIP listener.
+     */
+    public void registerPictureInPictureListener(PictureInPictureListener listener) {
+        mListeners.add(listener);
+    }
+
+    /**
+     * Unregister a PIP listener.
+     */
+    public void unregisterPictureInPictureListener(PictureInPictureListener listener) {
+        mListeners.remove(listener);
+    }
+
+    /**
+     * Interface of PIP event on Activity.
+     */
+    public interface PictureInPictureListener {
+        /**
+         * Called when Activity's PIP mode is changed.
+         */
+        void onPictureInPictureModeChanged(boolean isInPictureInPictureMode);
+    }
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackSupportFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackSupportFragment.java
new file mode 100644
index 0000000..6d2fa51
--- /dev/null
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/PlaybackSupportFragment.java
@@ -0,0 +1,155 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from PlaybackFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.leanback;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v17.leanback.app.PlaybackSupportFragmentGlueHost;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.util.Log;
+
+/**
+ * Example of PlaybackSupportFragment working with a PlaybackControlGlue.
+ */
+public class PlaybackSupportFragment
+        extends android.support.v17.leanback.app.PlaybackSupportFragment
+        implements PlaybackSupportActivity.PictureInPictureListener {
+    private static final String TAG = "leanback.PlaybackControlsFragment";
+
+    /**
+     * Change this to choose a different overlay background.
+     */
+    private static final int BACKGROUND_TYPE = PlaybackSupportFragment.BG_LIGHT;
+
+    /**
+     * Change the number of related content rows.
+     */
+    private static final int RELATED_CONTENT_ROWS = 3;
+
+    /**
+     * Change this to select hidden
+     */
+    private static final boolean SECONDARY_HIDDEN = false;
+
+    private static final int ROW_CONTROLS = 0;
+
+    private PlaybackControlGlue mGlue;
+    private ListRowPresenter mListRowPresenter;
+
+    public SparseArrayObjectAdapter getAdapter() {
+        return (SparseArrayObjectAdapter) super.getAdapter();
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate");
+        super.onCreate(savedInstanceState);
+
+        setBackgroundType(BACKGROUND_TYPE);
+
+        createComponents(getActivity());
+    }
+
+    private void createComponents(Context context) {
+        mGlue = new PlaybackControlGlue(context) {
+            @Override
+            public int getUpdatePeriod() {
+                int totalTime = getControlsRow().getTotalTime();
+                if (getView() == null || getView().getWidth() == 0 || totalTime <= 0) {
+                    return 1000;
+                }
+                return Math.max(16, totalTime / getView().getWidth());
+            }
+
+            @Override
+            public void onActionClicked(Action action) {
+                if (action.getId() == R.id.lb_control_picture_in_picture) {
+                    getActivity().enterPictureInPictureMode();
+                    return;
+                }
+                super.onActionClicked(action);
+            }
+
+            @Override
+            protected void onCreateControlsRowAndPresenter() {
+                super.onCreateControlsRowAndPresenter();
+                getControlsRowPresenter().setSecondaryActionsHidden(SECONDARY_HIDDEN);
+            }
+        };
+
+        mGlue.setHost(new PlaybackSupportFragmentGlueHost(this));
+        mListRowPresenter = new ListRowPresenter();
+
+        setAdapter(new SparseArrayObjectAdapter(new PresenterSelector() {
+            @Override
+            public Presenter getPresenter(Object object) {
+                if (object instanceof PlaybackControlsRow) {
+                    return mGlue.getControlsRowPresenter();
+                } else if (object instanceof ListRow) {
+                    return mListRowPresenter;
+                }
+                throw new IllegalArgumentException("Unhandled object: " + object);
+            }
+        }));
+
+        // Add the controls row
+        getAdapter().set(ROW_CONTROLS, mGlue.getControlsRow());
+
+        // Add related content rows
+        for (int i = 0; i < RELATED_CONTENT_ROWS; ++i) {
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new StringPresenter());
+            listRowAdapter.add("Some related content");
+            listRowAdapter.add("Other related content");
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            getAdapter().set(ROW_CONTROLS + 1 + i, new ListRow(header, listRowAdapter));
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        ((PlaybackSupportActivity) getActivity()).registerPictureInPictureListener(this);
+    }
+
+    @Override
+    public void onStop() {
+        ((PlaybackSupportActivity) getActivity()).unregisterPictureInPictureListener(this);
+        super.onStop();
+    }
+
+    @Override
+    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+        if (isInPictureInPictureMode) {
+            // Hide the controls in picture-in-picture mode.
+            setFadingEnabled(true);
+            fadeOut();
+        } else {
+            setFadingEnabled(mGlue.isMediaPlaying());
+        }
+    }
+}
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleSupportVideoFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleSupportVideoFragment.java
index af95920..f9dd055 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleSupportVideoFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleSupportVideoFragment.java
@@ -16,9 +16,9 @@
 package com.example.android.leanback;
 
 import android.os.Bundle;
-import android.support.v17.leanback.app.MediaPlayerGlue;
-import android.support.v17.leanback.app.PlaybackGlue;
 import android.support.v17.leanback.app.VideoFragmentGlueHost;
+import android.support.v17.leanback.media.MediaPlayerGlue;
+import android.support.v17.leanback.media.PlaybackGlue;
 
 /**
  * Fragment demonstrating the use of {@link android.support.v17.leanback.app.VideoFragment} to
@@ -30,7 +30,7 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        mMediaPlayerGlue = new MediaPlayerGlue(getActivity(), null);
+        mMediaPlayerGlue = new MediaPlayerGlue(getActivity());
         mMediaPlayerGlue.setHost(new VideoFragmentGlueHost(this));
         mMediaPlayerGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
     }
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoFragment.java
index 3ffe161..0d55c89 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoFragment.java
@@ -15,9 +15,9 @@
 
 import android.net.Uri;
 import android.os.Bundle;
-import android.support.v17.leanback.app.MediaPlayerGlue;
-import android.support.v17.leanback.app.PlaybackGlue;
 import android.support.v17.leanback.app.VideoFragmentGlueHost;
+import android.support.v17.leanback.media.MediaPlayerGlue;
+import android.support.v17.leanback.media.PlaybackGlue;
 
 /**
  * Fragment demonstrating the use of {@link android.support.v17.leanback.app.VideoFragment} to
@@ -44,7 +44,7 @@
         getView().postDelayed(new Runnable() {
             @Override
             public void run() {
-                mMediaPlayerGlue = new MediaPlayerGlue(getActivity(), null);
+                mMediaPlayerGlue = new MediaPlayerGlue(getActivity());
                 mMediaPlayerGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
                 mMediaPlayerGlue.setPlayerCallback(new PlaybackGlue.PlayerCallback() {
                     @Override
@@ -64,7 +64,7 @@
         getView().postDelayed(new Runnable() {
             @Override
             public void run() {
-                mMediaPlayerGlue = new MediaPlayerGlue(getActivity(), null);
+                mMediaPlayerGlue = new MediaPlayerGlue(getActivity());
                 mMediaPlayerGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
                 mMediaPlayerGlue.setPlayerCallback(new PlaybackGlue.PlayerCallback() {
                     @Override
diff --git a/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoSupportFragment.java b/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoSupportFragment.java
index dde7bd2..3c2c1ac 100644
--- a/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoSupportFragment.java
+++ b/samples/SupportLeanbackDemos/src/com/example/android/leanback/SampleVideoSupportFragment.java
@@ -18,9 +18,9 @@
 
 import android.net.Uri;
 import android.os.Bundle;
-import android.support.v17.leanback.app.MediaPlayerGlue;
-import android.support.v17.leanback.app.PlaybackGlue;
 import android.support.v17.leanback.app.VideoSupportFragmentGlueHost;
+import android.support.v17.leanback.media.MediaPlayerGlue;
+import android.support.v17.leanback.media.PlaybackGlue;
 
 /**
  * Fragment demonstrating the use of {@link android.support.v17.leanback.app.VideoSupportFragment} to
@@ -47,7 +47,7 @@
         getView().postDelayed(new Runnable() {
             @Override
             public void run() {
-                mMediaPlayerGlue = new MediaPlayerGlue(getActivity(), null);
+                mMediaPlayerGlue = new MediaPlayerGlue(getActivity());
                 mMediaPlayerGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
                 mMediaPlayerGlue.setPlayerCallback(new PlaybackGlue.PlayerCallback() {
                     @Override
@@ -67,7 +67,7 @@
         getView().postDelayed(new Runnable() {
             @Override
             public void run() {
-                mMediaPlayerGlue = new MediaPlayerGlue(getActivity(), null);
+                mMediaPlayerGlue = new MediaPlayerGlue(getActivity());
                 mMediaPlayerGlue.setMode(MediaPlayerGlue.REPEAT_ALL);
                 mMediaPlayerGlue.setPlayerCallback(new PlaybackGlue.PlayerCallback() {
                     @Override
diff --git a/transition/AndroidManifest.xml b/transition/AndroidManifest.xml
index 672e1b1..1059f63 100644
--- a/transition/AndroidManifest.xml
+++ b/transition/AndroidManifest.xml
@@ -16,5 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.transition">
     <uses-sdk android:minSdkVersion="14"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application />
 </manifest>
diff --git a/transition/build.gradle b/transition/build.gradle
index 05675f9..2f47f83 100644
--- a/transition/build.gradle
+++ b/transition/build.gradle
@@ -61,28 +61,11 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/v13/AndroidManifest.xml b/v13/AndroidManifest.xml
index ea25a74..7449688 100644
--- a/v13/AndroidManifest.xml
+++ b/v13/AndroidManifest.xml
@@ -17,5 +17,6 @@
           xmlns:tools="http://schemas.android.com/tools"
           package="android.support.v13">
     <uses-sdk android:minSdkVersion="13" tools:overrideLibrary="android.support.v13"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}"/>
     <application />
 </manifest>
diff --git a/v13/build.gradle b/v13/build.gradle
index 85aa8a8..fb1c25d 100644
--- a/v13/build.gradle
+++ b/v13/build.gradle
@@ -55,28 +55,11 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/v14/preference/AndroidManifest.xml b/v14/preference/AndroidManifest.xml
index 8b502c9..74cff2e 100644
--- a/v14/preference/AndroidManifest.xml
+++ b/v14/preference/AndroidManifest.xml
@@ -1,8 +1,21 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.support.v14.preference"
-    android:versionCode="1"
-    android:versionName="1.0">
+    package="android.support.v14.preference">
     <uses-sdk android:minSdkVersion="14" />
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application />
 </manifest>
diff --git a/v14/preference/build.gradle b/v14/preference/build.gradle
index a7e63d5..5583e93 100644
--- a/v14/preference/build.gradle
+++ b/v14/preference/build.gradle
@@ -14,10 +14,7 @@
  * limitations under the License
  */
 
-
-
 apply plugin: 'com.android.library'
-
 archivesBaseName = 'preference-v14'
 
 dependencies {
@@ -62,28 +59,11 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/v17/leanback/AndroidManifest.xml b/v17/leanback/AndroidManifest.xml
index 20ef094..ded4ce8 100644
--- a/v17/leanback/AndroidManifest.xml
+++ b/v17/leanback/AndroidManifest.xml
@@ -16,5 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.v17.leanback">
     <uses-sdk android:minSdkVersion="17"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application />
 </manifest>
diff --git a/v17/leanback/build.gradle b/v17/leanback/build.gradle
index f5befa7..9ed65a8 100644
--- a/v17/leanback/build.gradle
+++ b/v17/leanback/build.gradle
@@ -64,28 +64,11 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
index d4d8390..fcb3fa9 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
@@ -18,6 +18,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.support.v17.leanback.R;
+import android.support.v17.leanback.media.PlaybackGlueHost;
 import android.support.v17.leanback.transition.TransitionHelper;
 import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
 import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
@@ -190,6 +191,7 @@
             getChildFragmentManager().beginTransaction()
                     .replace(R.id.details_rows_dock, mRowsFragment).commit();
         }
+        installTitleView(inflater, mRootView, savedInstanceState);
         mRowsFragment.setAdapter(mAdapter);
         mRowsFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
         mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
@@ -328,7 +330,7 @@
      * Creates an instance of {@link VideoFragment}. Subclasses can override this method
      * and provide their own instance of a {@link Fragment}. When you provide your own instance of
      * video fragment, you MUST also provide a custom
-     * {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost}.
+     * {@link android.support.v17.leanback.media.PlaybackGlueHost}.
      * @hide
      */
     public Fragment onCreateVideoFragment() {
@@ -337,11 +339,11 @@
 
     /**
      * Creates an instance of
-     * {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost}. The implementation
+     * {@link android.support.v17.leanback.media.PlaybackGlueHost}. The implementation
      * of this host depends on the instance of video fragment {@link #onCreateVideoFragment()}.
      * @hide
      */
-    public PlaybackGlue.PlaybackGlueHost onCreateVideoFragmentHost(Fragment fragment) {
+    public PlaybackGlueHost onCreateVideoFragmentHost(Fragment fragment) {
         return new VideoFragmentGlueHost((VideoFragment) fragment);
     }
 
@@ -366,19 +368,22 @@
 
     /**
      * This method initializes a video fragment, create an instance of
-     * {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost} using that fragment
+     * {@link android.support.v17.leanback.media.PlaybackGlueHost} using that fragment
      * and return it.
      * @hide
      */
-    public final PlaybackGlue.PlaybackGlueHost createPlaybackGlueHost() {
+    public final PlaybackGlueHost createPlaybackGlueHost() {
         Fragment fragment = findOrCreateVideoFragment();
         return onCreateVideoFragmentHost(fragment);
     }
 
     void onRowSelected(int selectedPosition, int selectedSubPosition) {
         ObjectAdapter adapter = getAdapter();
-        if (adapter == null || adapter.size() == 0
-                || (selectedPosition == 0 && selectedSubPosition == 0)) {
+        if (( mRowsFragment != null && mRowsFragment.getView() != null
+                && mRowsFragment.getView().hasFocus())
+                && (adapter == null || adapter.size() == 0
+                || (getVerticalGridView().getSelectedPosition() == 0
+                && getVerticalGridView().getSelectedSubPosition() == 0))) {
             showTitle(true);
         } else {
             showTitle(false);
@@ -547,20 +552,31 @@
         mRootView.setOnFocusSearchListener(new BrowseFrameLayout.OnFocusSearchListener() {
             @Override
             public View onFocusSearch(View focused, int direction) {
-                if (mVideoFragment == null) {
-                    return null;
-                }
                 if (mRowsFragment.getVerticalGridView() != null
                         && mRowsFragment.getVerticalGridView().hasFocus()) {
                     if (direction == View.FOCUS_UP) {
-                        slideOutGridView();
-                        return mVideoFragment.getView();
+                        if (mVideoFragment != null && mVideoFragment.getView() != null) {
+                            slideOutGridView();
+                            showTitle(false);
+                            return mVideoFragment.getView();
+                        } else if (getTitleView() != null) {
+                            return getTitleView();
+                        }
                     }
-                } else if (mVideoFragment.getView() != null
+                } else if (mVideoFragment != null && mVideoFragment.getView() != null
                         && mVideoFragment.getView().hasFocus()) {
                     if (direction == View.FOCUS_DOWN) {
-                        slideInGridView();
-                        return mRowsFragment.getVerticalGridView();
+                        if (mRowsFragment.getVerticalGridView() != null) {
+                            showTitle(true);
+                            slideInGridView();
+                            return mRowsFragment.getVerticalGridView();
+                        }
+                    }
+                } else if (getTitleView() != null && getTitleView().hasFocus()) {
+                    if (direction == View.FOCUS_DOWN) {
+                        if (mRowsFragment.getVerticalGridView() != null) {
+                            return mRowsFragment.getVerticalGridView();
+                        }
                     }
                 }
                 return focused;
@@ -578,6 +594,7 @@
                 if (mVideoFragment != null && mVideoFragment.getView() != null
                         && mVideoFragment.getView().hasFocus()) {
                     if (keyCode == KeyEvent.KEYCODE_BACK) {
+                        showTitle(true);
                         slideInGridView();
                         getVerticalGridView().requestFocus();
                         return true;
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragmentVideoHelper.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragmentVideoHelper.java
index 7d58b13..415b850 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragmentVideoHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragmentVideoHelper.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.ValueAnimator;
 import android.graphics.drawable.Drawable;
+import android.support.v17.leanback.media.PlaybackGlue;
 import android.support.v17.leanback.widget.Parallax;
 import android.support.v17.leanback.widget.ParallaxRecyclerViewSource;
 import android.support.v17.leanback.widget.ParallaxTarget;
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
index cbf6324..a9bbf28 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
@@ -21,6 +21,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.support.v17.leanback.R;
+import android.support.v17.leanback.media.PlaybackGlueHost;
 import android.support.v17.leanback.transition.TransitionHelper;
 import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
 import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
@@ -193,6 +194,7 @@
             getChildFragmentManager().beginTransaction()
                     .replace(R.id.details_rows_dock, mRowsSupportFragment).commit();
         }
+        installTitleView(inflater, mRootView, savedInstanceState);
         mRowsSupportFragment.setAdapter(mAdapter);
         mRowsSupportFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
         mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
@@ -331,7 +333,7 @@
      * Creates an instance of {@link VideoSupportFragment}. Subclasses can override this method
      * and provide their own instance of a {@link Fragment}. When you provide your own instance of
      * video fragment, you MUST also provide a custom
-     * {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost}.
+     * {@link android.support.v17.leanback.media.PlaybackGlueHost}.
      * @hide
      */
     public Fragment onCreateVideoSupportFragment() {
@@ -340,11 +342,11 @@
 
     /**
      * Creates an instance of
-     * {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost}. The implementation
+     * {@link android.support.v17.leanback.media.PlaybackGlueHost}. The implementation
      * of this host depends on the instance of video fragment {@link #onCreateVideoSupportFragment()}.
      * @hide
      */
-    public PlaybackGlue.PlaybackGlueHost onCreateVideoSupportFragmentHost(Fragment fragment) {
+    public PlaybackGlueHost onCreateVideoSupportFragmentHost(Fragment fragment) {
         return new VideoSupportFragmentGlueHost((VideoSupportFragment) fragment);
     }
 
@@ -369,19 +371,22 @@
 
     /**
      * This method initializes a video fragment, create an instance of
-     * {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost} using that fragment
+     * {@link android.support.v17.leanback.media.PlaybackGlueHost} using that fragment
      * and return it.
      * @hide
      */
-    public final PlaybackGlue.PlaybackGlueHost createPlaybackGlueHost() {
+    public final PlaybackGlueHost createPlaybackGlueHost() {
         Fragment fragment = findOrCreateVideoSupportFragment();
         return onCreateVideoSupportFragmentHost(fragment);
     }
 
     void onRowSelected(int selectedPosition, int selectedSubPosition) {
         ObjectAdapter adapter = getAdapter();
-        if (adapter == null || adapter.size() == 0
-                || (selectedPosition == 0 && selectedSubPosition == 0)) {
+        if (( mRowsSupportFragment != null && mRowsSupportFragment.getView() != null
+                && mRowsSupportFragment.getView().hasFocus())
+                && (adapter == null || adapter.size() == 0
+                || (getVerticalGridView().getSelectedPosition() == 0
+                && getVerticalGridView().getSelectedSubPosition() == 0))) {
             showTitle(true);
         } else {
             showTitle(false);
@@ -550,20 +555,31 @@
         mRootView.setOnFocusSearchListener(new BrowseFrameLayout.OnFocusSearchListener() {
             @Override
             public View onFocusSearch(View focused, int direction) {
-                if (mVideoSupportFragment == null) {
-                    return null;
-                }
                 if (mRowsSupportFragment.getVerticalGridView() != null
                         && mRowsSupportFragment.getVerticalGridView().hasFocus()) {
                     if (direction == View.FOCUS_UP) {
-                        slideOutGridView();
-                        return mVideoSupportFragment.getView();
+                        if (mVideoSupportFragment != null && mVideoSupportFragment.getView() != null) {
+                            slideOutGridView();
+                            showTitle(false);
+                            return mVideoSupportFragment.getView();
+                        } else if (getTitleView() != null) {
+                            return getTitleView();
+                        }
                     }
-                } else if (mVideoSupportFragment.getView() != null
+                } else if (mVideoSupportFragment != null && mVideoSupportFragment.getView() != null
                         && mVideoSupportFragment.getView().hasFocus()) {
                     if (direction == View.FOCUS_DOWN) {
-                        slideInGridView();
-                        return mRowsSupportFragment.getVerticalGridView();
+                        if (mRowsSupportFragment.getVerticalGridView() != null) {
+                            showTitle(true);
+                            slideInGridView();
+                            return mRowsSupportFragment.getVerticalGridView();
+                        }
+                    }
+                } else if (getTitleView() != null && getTitleView().hasFocus()) {
+                    if (direction == View.FOCUS_DOWN) {
+                        if (mRowsSupportFragment.getVerticalGridView() != null) {
+                            return mRowsSupportFragment.getVerticalGridView();
+                        }
                     }
                 }
                 return focused;
@@ -581,6 +597,7 @@
                 if (mVideoSupportFragment != null && mVideoSupportFragment.getView() != null
                         && mVideoSupportFragment.getView().hasFocus()) {
                     if (keyCode == KeyEvent.KEYCODE_BACK) {
+                        showTitle(true);
                         slideInGridView();
                         getVerticalGridView().requestFocus();
                         return true;
diff --git a/v17/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java b/v17/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java
index 1f2788f..f9af12f 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java
@@ -41,6 +41,7 @@
     }
 
     void initialize() {
+        mLastVisibleRowIndex = -1;
         int i = mAdapter.size() - 1;
         while (i >= 0) {
             Row item = (Row) mAdapter.get(i);
@@ -123,7 +124,7 @@
             int totalItems = lastVisibleRowIndex - mLastVisibleRowIndex;
             if (totalItems > 0) {
                 onEventFired(ON_ITEM_RANGE_REMOVED,
-                        Math.min(lastVisibleRowIndex + 1, positionStart),
+                        Math.min(mLastVisibleRowIndex + 1, positionStart),
                         totalItems);
             }
         }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/MediaControllerGlue.java b/v17/leanback/src/android/support/v17/leanback/app/MediaControllerGlue.java
index 5c75506..7949bfb 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/MediaControllerGlue.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/MediaControllerGlue.java
@@ -14,7 +14,9 @@
  * A helper class for implementing a glue layer between a
  * {@link PlaybackOverlayFragment} and a
  * {@link android.support.v4.media.session.MediaControllerCompat}.
+ * @deprecated Use {@link android.support.v17.leanback.media.MediaControllerGlue}.
  */
+@Deprecated
 public abstract class MediaControllerGlue extends PlaybackControlGlue {
     static final String TAG = "MediaControllerGlue";
     static final boolean DEBUG = false;
@@ -55,10 +57,7 @@
      * @param context
      * @param fragment
      * @param seekSpeeds Array of seek speeds for fast forward and rewind.
-     * @deprecated Use
-     * {@link #MediaControllerGlue(Context, PlaybackGlue.PlaybackGlueHost, int[], int[])}.
      */
-    @Deprecated
     public MediaControllerGlue(Context context,
                                PlaybackOverlayFragment fragment,
                                int[] seekSpeeds) {
@@ -78,10 +77,7 @@
      * @param fragment
      * @param fastForwardSpeeds Array of seek speeds for fast forward.
      * @param rewindSpeeds Array of seek speeds for rewind.
-     * @deprecated Use
-     * {@link #MediaControllerGlue(Context, PlaybackGlue.PlaybackGlueHost, int[], int[])}.
      */
-    @Deprecated
     public MediaControllerGlue(Context context,
                                PlaybackOverlayFragment fragment,
                                int[] fastForwardSpeeds,
@@ -90,21 +86,6 @@
     }
 
     /**
-     * Constructor for the glue.
-     *
-     * @param context
-     * @param host Optional; if using a {@link PlaybackGlue.PlaybackGlueHost}, pass it in.
-     * @param fastForwardSpeeds Array of seek speeds for fast forward.
-     * @param rewindSpeeds Array of seek speeds for rewind.
-     */
-    public MediaControllerGlue(Context context,
-            PlaybackGlueHost host,
-            int[] fastForwardSpeeds,
-            int[] rewindSpeeds) {
-        super(context, host, fastForwardSpeeds, rewindSpeeds);
-    }
-
-    /**
      * Attaches to the given media controller.
      */
     public void attachToMediaController(MediaControllerCompat mediaController) {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
index 4892b69..190ce22 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
@@ -14,31 +14,25 @@
 package android.support.v17.leanback.app;
 
 import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Message;
-import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
+import android.support.v17.leanback.media.PlaybackGlueHost;
 import android.support.v17.leanback.widget.Action;
-import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
 import android.support.v17.leanback.widget.OnActionClickedListener;
 import android.support.v17.leanback.widget.OnItemViewClickedListener;
 import android.support.v17.leanback.widget.PlaybackControlsRow;
 import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
+import android.support.v17.leanback.widget.PlaybackRowPresenter;
 import android.support.v17.leanback.widget.Presenter;
 import android.support.v17.leanback.widget.PresenterSelector;
 import android.support.v17.leanback.widget.Row;
 import android.support.v17.leanback.widget.RowPresenter;
 import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
-import android.util.Log;
 import android.view.InputEvent;
 import android.view.KeyEvent;
 import android.view.View;
 
-import java.lang.ref.WeakReference;
-
 /**
  * A helper class for managing a {@link android.support.v17.leanback.widget.PlaybackControlsRow}
- * and {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost} that implements a
+ * and {@link PlaybackGlueHost} that implements a
  * recommended approach to handling standard playback control actions such as play/pause,
  * fast forward/rewind at progressive speed levels, and skip to next/previous. This helper class
  * is a glue layer in that manages the configuration of and interaction between the
@@ -51,8 +45,7 @@
  * </p>
  *
  * <p>To use an instance of the glue layer, first construct an instance.  Constructor parameters
- * inform the glue what speed levels are supported for fast forward/rewind. Providing a
- * {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost} is optional.
+ * inform the glue what speed levels are supported for fast forward/rewind.
  * </p>
  *
  * <p>If you have your own controls row you must pass it to {@link #setControlsRow}.
@@ -73,7 +66,7 @@
  * </p>
  *
  * <p>This helper implements a key event handler. If you pass a
- * {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost}, it will configure it's
+ * {@link PlaybackOverlayFragment}, it will configure it's
  * fragment to intercept all key events.  Otherwise, you should set the glue object as key event
  * handler to the ViewHolder when bound by your row presenter; see
  * {@link RowPresenter.ViewHolder#setOnKeyListener(android.view.View.OnKeyListener)}.
@@ -83,144 +76,13 @@
  * to manage the lifecycle of a periodic callback to {@link #updateProgress()}.
  * {@link #getUpdatePeriod()} provides a recommended update period.
  * </p>
- *
+ * @deprecated Use {@link android.support.v17.leanback.media.PlaybackControlGlue}
  */
-public abstract class PlaybackControlGlue extends PlaybackGlue
-        implements OnActionClickedListener, View.OnKeyListener {
-    /**
-     * The adapter key for the first custom control on the left side
-     * of the predefined primary controls.
-     */
-    public static final int ACTION_CUSTOM_LEFT_FIRST = 0x1;
+@Deprecated
+public abstract class PlaybackControlGlue extends
+        android.support.v17.leanback.media.PlaybackControlGlue {
 
-    /**
-     * The adapter key for the skip to previous control.
-     */
-    public static final int ACTION_SKIP_TO_PREVIOUS = 0x10;
-
-    /**
-     * The adapter key for the rewind control.
-     */
-    public static final int ACTION_REWIND = 0x20;
-
-    /**
-     * The adapter key for the play/pause control.
-     */
-    public static final int ACTION_PLAY_PAUSE = 0x40;
-
-    /**
-     * The adapter key for the fast forward control.
-     */
-    public static final int ACTION_FAST_FORWARD = 0x80;
-
-    /**
-     * The adapter key for the skip to next control.
-     */
-    public static final int ACTION_SKIP_TO_NEXT = 0x100;
-
-    /**
-     * The adapter key for the first custom control on the right side
-     * of the predefined primary controls.
-     */
-    public static final int ACTION_CUSTOM_RIGHT_FIRST = 0x1000;
-
-    /**
-     * Invalid playback speed.
-     */
-    public static final int PLAYBACK_SPEED_INVALID = -1;
-
-    /**
-     * Speed representing playback state that is paused.
-     */
-    public static final int PLAYBACK_SPEED_PAUSED = 0;
-
-    /**
-     * Speed representing playback state that is playing normally.
-     */
-    public static final int PLAYBACK_SPEED_NORMAL = 1;
-
-    /**
-     * The initial (level 0) fast forward playback speed.
-     * The negative of this value is for rewind at the same speed.
-     */
-    public static final int PLAYBACK_SPEED_FAST_L0 = 10;
-
-    /**
-     * The level 1 fast forward playback speed.
-     * The negative of this value is for rewind at the same speed.
-     */
-    public static final int PLAYBACK_SPEED_FAST_L1 = 11;
-
-    /**
-     * The level 2 fast forward playback speed.
-     * The negative of this value is for rewind at the same speed.
-     */
-    public static final int PLAYBACK_SPEED_FAST_L2 = 12;
-
-    /**
-     * The level 3 fast forward playback speed.
-     * The negative of this value is for rewind at the same speed.
-     */
-    public static final int PLAYBACK_SPEED_FAST_L3 = 13;
-
-    /**
-     * The level 4 fast forward playback speed.
-     * The negative of this value is for rewind at the same speed.
-     */
-    public static final int PLAYBACK_SPEED_FAST_L4 = 14;
-
-    static final String TAG = "PlaybackControlGlue";
-    static final boolean DEBUG = false;
-
-    static final int MSG_UPDATE_PLAYBACK_STATE = 100;
-    private static final int UPDATE_PLAYBACK_STATE_DELAY_MS = 2000;
-    private static final int NUMBER_OF_SEEK_SPEEDS = PLAYBACK_SPEED_FAST_L4
-            - PLAYBACK_SPEED_FAST_L0 + 1;
-
-    private final int[] mFastForwardSpeeds;
-    private final int[] mRewindSpeeds;
-    private PlaybackControlsRow mControlsRow;
-    private SparseArrayObjectAdapter mPrimaryActionsAdapter;
-    private PlaybackControlsRow.PlayPauseAction mPlayPauseAction;
-    private PlaybackControlsRow.SkipNextAction mSkipNextAction;
-    private PlaybackControlsRow.SkipPreviousAction mSkipPreviousAction;
-    private PlaybackControlsRow.FastForwardAction mFastForwardAction;
-    private PlaybackControlsRow.RewindAction mRewindAction;
     OnItemViewClickedListener mExternalOnItemViewClickedListener;
-    private int mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
-    private boolean mFadeWhenPlaying = true;
-
-    static class UpdatePlaybackStateHandler extends Handler {
-        @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == MSG_UPDATE_PLAYBACK_STATE) {
-                PlaybackControlGlue glue = ((WeakReference<PlaybackControlGlue>) msg.obj).get();
-                if (glue != null) {
-                    glue.updatePlaybackState();
-                }
-            }
-        }
-    }
-
-    static final Handler sHandler = new UpdatePlaybackStateHandler();
-
-    final WeakReference<PlaybackControlGlue> mGlueWeakReference =  new WeakReference(this);
-
-    /**
-     * Interface allowing the application to handle input events.
-     * @deprecated Use
-     * {@link PlaybackGlue.PlaybackGlueHost#setOnKeyInterceptListener(View.OnKeyListener)}.
-     */
-    @Deprecated
-    public interface InputEventHandler {
-        /**
-         * Called when an {@link InputEvent} is received.
-         *
-         * @return If the event should be consumed, return true. To allow the event to
-         * continue on to the next handler, return false.
-         */
-        boolean handleInputEvent(InputEvent event);
-    }
 
     /**
      * Constructor for the glue.
@@ -229,7 +91,7 @@
      * @param seekSpeeds Array of seek speeds for fast forward and rewind.
      */
     public PlaybackControlGlue(Context context, int[] seekSpeeds) {
-        this(context, (PlaybackGlueHost) null, seekSpeeds, seekSpeeds);
+        super(context, seekSpeeds, seekSpeeds);
     }
 
     /**
@@ -242,7 +104,7 @@
     public PlaybackControlGlue(Context context,
                                int[] fastForwardSpeeds,
                                int[] rewindSpeeds) {
-        this(context, (PlaybackGlueHost) null, fastForwardSpeeds, rewindSpeeds);
+        super(context, fastForwardSpeeds, rewindSpeeds);
     }
 
     /**
@@ -251,10 +113,7 @@
      * @param context
      * @param fragment Optional; if using a {@link PlaybackOverlayFragment}, pass it in.
      * @param seekSpeeds Array of seek speeds for fast forward and rewind.
-     * @deprecated Use
-     * {@link #PlaybackControlGlue(Context, PlaybackGlue.PlaybackGlueHost, int[], int[])}.
      */
-    @Deprecated
     public PlaybackControlGlue(Context context,
                                PlaybackOverlayFragment fragment,
                                int[] seekSpeeds) {
@@ -268,110 +127,26 @@
      * @param fragment Optional; if using a {@link PlaybackOverlayFragment}, pass it in.
      * @param fastForwardSpeeds Array of seek speeds for fast forward.
      * @param rewindSpeeds Array of seek speeds for rewind.
-     * @deprecated Use
-     * {@link #PlaybackControlGlue(Context, PlaybackGlue.PlaybackGlueHost, int[], int[])}.
      */
-    @Deprecated
     public PlaybackControlGlue(Context context,
                                PlaybackOverlayFragment fragment,
                                int[] fastForwardSpeeds,
                                int[] rewindSpeeds) {
-        this(context, fragment == null ? (PlaybackGlueHost) null:
-                new PlaybackGlueHostOld(fragment), fastForwardSpeeds, rewindSpeeds);
-    }
-
-    /**
-     * Constructor for the glue.
-     *
-     * @param context
-     * @param host Optional; if using a {@link PlaybackGlue.PlaybackGlueHost}, pass it in.
-     * @param fastForwardSpeeds Array of seek speeds for fast forward.
-     * @param rewindSpeeds Array of seek speeds for rewind.
-     */
-    public PlaybackControlGlue(Context context,
-                               PlaybackGlueHost host,
-                               int[] fastForwardSpeeds,
-                               int[] rewindSpeeds) {
-        super(context);
-        if (fastForwardSpeeds.length == 0 || fastForwardSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
-            throw new IllegalStateException("invalid fastForwardSpeeds array size");
-        }
-        mFastForwardSpeeds = fastForwardSpeeds;
-        if (rewindSpeeds.length == 0 || rewindSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
-            throw new IllegalStateException("invalid rewindSpeeds array size");
-        }
-        mRewindSpeeds = rewindSpeeds;
-        setHost(host);
+        super(context, fastForwardSpeeds, rewindSpeeds);
+        setHost(fragment == null ? (PlaybackGlueHost) null : new PlaybackGlueHostOld(fragment));
     }
 
     @Override
-    public void setHost(PlaybackGlueHost host) {
-        super.setHost(host);
-        if (getHost() != null) {
-            if (getHost() instanceof PlaybackGlueHostOld) {
-                ((PlaybackGlueHostOld) getHost()).mGlue = this;
-            }
-            getHost().setOnKeyInterceptListener(this);
-            getHost().setOnActionClickedListener(this);
-            getHost().setPlaybackRowPresenter(createControlsRowAndPresenter());
-            getHost().setPlaybackRow(getControlsRow());
-            getHost().setHostLifeCycleCallback(new HostLifecycleCallback() {
-                @Override
-                public void onHostStart() {
-                    enableProgressUpdating(true);
-                }
-
-                @Override
-                public void onHostStop() {
-                    enableProgressUpdating(false);
-                }
-            });
+    protected void onAttachedToHost(PlaybackGlueHost host) {
+        super.onAttachedToHost(host);
+        if (host instanceof PlaybackGlueHostOld) {
+            ((PlaybackGlueHostOld) host).mGlue = this;
         }
     }
 
     /**
-     * Helper method for instantiating a {@link PlaybackControlsRow} and corresponding
-     * {@link PlaybackControlsRowPresenter}.
-     */
-    public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
-        PlaybackControlsRow controlsRow = new PlaybackControlsRow(this);
-        setControlsRow(controlsRow);
-
-        final AbstractDetailsDescriptionPresenter detailsPresenter =
-                new AbstractDetailsDescriptionPresenter() {
-            @Override
-            protected void onBindDescription(AbstractDetailsDescriptionPresenter.ViewHolder
-                                                     viewHolder, Object object) {
-                PlaybackControlGlue glue = (PlaybackControlGlue) object;
-                if (glue.hasValidMedia()) {
-                    viewHolder.getTitle().setText(glue.getMediaTitle());
-                    viewHolder.getSubtitle().setText(glue.getMediaSubtitle());
-                } else {
-                    viewHolder.getTitle().setText("");
-                    viewHolder.getSubtitle().setText("");
-                }
-            }
-        };
-
-        return new PlaybackControlsRowPresenter(detailsPresenter) {
-            @Override
-            protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
-                super.onBindRowViewHolder(vh, item);
-                vh.setOnKeyListener(PlaybackControlGlue.this);
-            }
-            @Override
-            protected void onUnbindRowViewHolder(RowPresenter.ViewHolder vh) {
-                super.onUnbindRowViewHolder(vh);
-                vh.setOnKeyListener(null);
-            }
-        };
-    }
-
-    /**
      * Returns the fragment.
-     * @deprecated The glue is no longer associated with a fragment, use {@link #getHost()}.
      */
-    @Deprecated
     public PlaybackOverlayFragment getFragment() {
         if (getHost() instanceof PlaybackGlueHostOld) {
             return ((PlaybackGlueHostOld)getHost()).mFragment;
@@ -380,456 +155,6 @@
     }
 
     /**
-     * Returns the fast forward speeds.
-     */
-    public int[] getFastForwardSpeeds() {
-        return mFastForwardSpeeds;
-    }
-
-    /**
-     * Returns the rewind speeds.
-     */
-    public int[] getRewindSpeeds() {
-        return mRewindSpeeds;
-    }
-
-    /**
-     * Sets the controls to fade after a timeout when media is playing.
-     */
-    public void setFadingEnabled(boolean enable) {
-        mFadeWhenPlaying = enable;
-        if (!mFadeWhenPlaying && getHost() != null) {
-            getHost().setFadingEnabled(false);
-        }
-    }
-
-    /**
-     * Returns true if controls are set to fade when media is playing.
-     */
-    public boolean isFadingEnabled() {
-        return mFadeWhenPlaying;
-    }
-
-    /**
-     * Set the {@link OnItemViewClickedListener} to be called if the click event
-     * is not handled internally.
-     * @param listener
-     * @deprecated Don't call this. Instead use the listener on the fragment yourself.
-     */
-    @Deprecated
-    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
-        mExternalOnItemViewClickedListener = listener;
-    }
-
-    /**
-     * Returns the {@link OnItemViewClickedListener}.
-     * @deprecated Don't call this. Instead use the listener on the fragment yourself.
-     */
-    @Deprecated
-    public OnItemViewClickedListener getOnItemViewClickedListener() {
-        return mExternalOnItemViewClickedListener;
-    }
-
-    /**
-     * Sets the controls row to be managed by the glue layer.
-     * The primary actions and playback state related aspects of the row
-     * are updated by the glue.
-     */
-    public void setControlsRow(PlaybackControlsRow controlsRow) {
-        mControlsRow = controlsRow;
-        mPrimaryActionsAdapter = createPrimaryActionsAdapter(
-                new ControlButtonPresenterSelector());
-        mControlsRow.setPrimaryActionsAdapter(mPrimaryActionsAdapter);
-        updateControlsRow();
-    }
-
-    /**
-     * Returns the playback controls row managed by the glue layer.
-     */
-    public PlaybackControlsRow getControlsRow() {
-        return mControlsRow;
-    }
-
-    /**
-     * Override this to start/stop a runnable to call {@link #updateProgress} at
-     * an interval such as {@link #getUpdatePeriod}.
-     */
-    public void enableProgressUpdating(boolean enable) {
-    }
-
-    /**
-     * Returns the time period in milliseconds that should be used
-     * to update the progress.  See {@link #updateProgress()}.
-     */
-    public int getUpdatePeriod() {
-        // TODO: calculate a better update period based on total duration and screen size
-        return 500;
-    }
-
-    /**
-     * Updates the progress bar based on the current media playback position.
-     */
-    public void updateProgress() {
-        int position = getCurrentPosition();
-        if (DEBUG) Log.v(TAG, "updateProgress " + position);
-        mControlsRow.setCurrentTime(position);
-    }
-
-    /**
-     * Handles action clicks.  A subclass may override this add support for additional actions.
-     */
-    @Override
-    public void onActionClicked(Action action) {
-        dispatchAction(action, null);
-    }
-
-    /**
-     * Handles key events and returns true if handled.  A subclass may override this to provide
-     * additional support.
-     */
-    @Override
-    public boolean onKey(View v, int keyCode, KeyEvent event) {
-        switch (keyCode) {
-            case KeyEvent.KEYCODE_DPAD_UP:
-            case KeyEvent.KEYCODE_DPAD_DOWN:
-            case KeyEvent.KEYCODE_DPAD_RIGHT:
-            case KeyEvent.KEYCODE_DPAD_LEFT:
-            case KeyEvent.KEYCODE_BACK:
-            case KeyEvent.KEYCODE_ESCAPE:
-                boolean abortSeek = mPlaybackSpeed >= PLAYBACK_SPEED_FAST_L0
-                        || mPlaybackSpeed <= -PLAYBACK_SPEED_FAST_L0;
-                if (abortSeek) {
-                    mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
-                    startPlayback(mPlaybackSpeed);
-                    updatePlaybackStatusAfterUserAction();
-                    return keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE;
-                }
-                return false;
-        }
-        Action action = mControlsRow.getActionForKeyCode(mPrimaryActionsAdapter, keyCode);
-
-        if (action != null) {
-            if (action == mPrimaryActionsAdapter.lookup(ACTION_PLAY_PAUSE)
-                    || action == mPrimaryActionsAdapter.lookup(ACTION_REWIND)
-                    || action == mPrimaryActionsAdapter.lookup(ACTION_FAST_FORWARD)
-                    || action == mPrimaryActionsAdapter.lookup(ACTION_SKIP_TO_PREVIOUS)
-                    || action == mPrimaryActionsAdapter.lookup(ACTION_SKIP_TO_NEXT)) {
-                if (event.getAction() == KeyEvent.ACTION_DOWN) {
-                    dispatchAction(action, event);
-                }
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Called when the given action is invoked, either by click or keyevent.
-     */
-    boolean dispatchAction(Action action, KeyEvent keyEvent) {
-        boolean handled = false;
-        if (action == mPlayPauseAction) {
-            boolean canPlay = keyEvent == null
-                    || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
-                    || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY;
-            boolean canPause = keyEvent == null
-                    || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
-                    || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PAUSE;
-            //            PLAY_PAUSE    PLAY      PAUSE
-            // playing    paused                  paused
-            // paused     playing       playing
-            // ff/rw      playing       playing   paused
-            if (canPause &&
-                (canPlay ? mPlaybackSpeed == PLAYBACK_SPEED_NORMAL :
-                        mPlaybackSpeed != PLAYBACK_SPEED_PAUSED)) {
-                mPlaybackSpeed = PLAYBACK_SPEED_PAUSED;
-                pausePlayback();
-            } else if (canPlay && mPlaybackSpeed != PLAYBACK_SPEED_NORMAL) {
-                mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
-                startPlayback(mPlaybackSpeed);
-            }
-            updatePlaybackStatusAfterUserAction();
-            handled = true;
-        } else if (action == mSkipNextAction) {
-            skipToNext();
-            handled = true;
-        } else if (action == mSkipPreviousAction) {
-            skipToPrevious();
-            handled = true;
-        } else if (action == mFastForwardAction) {
-            if (mPlaybackSpeed < getMaxForwardSpeedId()) {
-                switch (mPlaybackSpeed) {
-                    case PLAYBACK_SPEED_FAST_L0:
-                    case PLAYBACK_SPEED_FAST_L1:
-                    case PLAYBACK_SPEED_FAST_L2:
-                    case PLAYBACK_SPEED_FAST_L3:
-                        mPlaybackSpeed++;
-                        break;
-                    default:
-                        mPlaybackSpeed = PLAYBACK_SPEED_FAST_L0;
-                        break;
-                }
-                startPlayback(mPlaybackSpeed);
-                updatePlaybackStatusAfterUserAction();
-            }
-            handled = true;
-        } else if (action == mRewindAction) {
-            if (mPlaybackSpeed > -getMaxRewindSpeedId()) {
-                switch (mPlaybackSpeed) {
-                    case -PLAYBACK_SPEED_FAST_L0:
-                    case -PLAYBACK_SPEED_FAST_L1:
-                    case -PLAYBACK_SPEED_FAST_L2:
-                    case -PLAYBACK_SPEED_FAST_L3:
-                        mPlaybackSpeed--;
-                        break;
-                    default:
-                        mPlaybackSpeed = -PLAYBACK_SPEED_FAST_L0;
-                        break;
-                }
-                startPlayback(mPlaybackSpeed);
-                updatePlaybackStatusAfterUserAction();
-            }
-            handled = true;
-        }
-        return handled;
-    }
-
-    private int getMaxForwardSpeedId() {
-        return PLAYBACK_SPEED_FAST_L0 + (mFastForwardSpeeds.length - 1);
-    }
-
-    private int getMaxRewindSpeedId() {
-        return PLAYBACK_SPEED_FAST_L0 + (mRewindSpeeds.length - 1);
-    }
-
-    private void updateControlsRow() {
-        updateRowMetadata();
-        sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
-        updatePlaybackState();
-    }
-
-    private void updatePlaybackStatusAfterUserAction() {
-        updatePlaybackState(mPlaybackSpeed);
-        // Sync playback state after a delay
-        sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
-        sHandler.sendMessageDelayed(sHandler.obtainMessage(MSG_UPDATE_PLAYBACK_STATE,
-                mGlueWeakReference), UPDATE_PLAYBACK_STATE_DELAY_MS);
-    }
-
-    private void updateRowMetadata() {
-        if (mControlsRow == null) {
-            return;
-        }
-
-        if (DEBUG) Log.v(TAG, "updateRowMetadata");
-
-        if (!hasValidMedia()) {
-            mControlsRow.setImageDrawable(null);
-            mControlsRow.setTotalTime(0);
-            mControlsRow.setCurrentTime(0);
-        } else {
-            mControlsRow.setImageDrawable(getMediaArt());
-            mControlsRow.setTotalTime(getMediaDuration());
-            mControlsRow.setCurrentTime(getCurrentPosition());
-        }
-
-        if (getHost() != null) {
-            getHost().notifyPlaybackRowChanged();
-        }
-    }
-
-    void updatePlaybackState() {
-        if (hasValidMedia()) {
-            mPlaybackSpeed = getCurrentSpeedId();
-            updatePlaybackState(mPlaybackSpeed);
-        }
-    }
-
-    private void updatePlaybackState(int playbackSpeed) {
-        if (mControlsRow == null) {
-            return;
-        }
-
-        final long actions = getSupportedActions();
-        if ((actions & ACTION_SKIP_TO_PREVIOUS) != 0) {
-            if (mSkipPreviousAction == null) {
-                mSkipPreviousAction = new PlaybackControlsRow.SkipPreviousAction(getContext());
-            }
-            mPrimaryActionsAdapter.set(ACTION_SKIP_TO_PREVIOUS, mSkipPreviousAction);
-        } else {
-            mPrimaryActionsAdapter.clear(ACTION_SKIP_TO_PREVIOUS);
-            mSkipPreviousAction = null;
-        }
-        if ((actions & ACTION_REWIND) != 0) {
-            if (mRewindAction == null) {
-                mRewindAction = new PlaybackControlsRow.RewindAction(
-                        getContext(),
-                        mRewindSpeeds.length);
-            }
-            mPrimaryActionsAdapter.set(ACTION_REWIND, mRewindAction);
-        } else {
-            mPrimaryActionsAdapter.clear(ACTION_REWIND);
-            mRewindAction = null;
-        }
-        if ((actions & ACTION_PLAY_PAUSE) != 0) {
-            if (mPlayPauseAction == null) {
-                mPlayPauseAction = new PlaybackControlsRow.PlayPauseAction(getContext());
-            }
-            mPrimaryActionsAdapter.set(ACTION_PLAY_PAUSE, mPlayPauseAction);
-        } else {
-            mPrimaryActionsAdapter.clear(ACTION_PLAY_PAUSE);
-            mPlayPauseAction = null;
-        }
-        if ((actions & ACTION_FAST_FORWARD) != 0) {
-            if (mFastForwardAction == null) {
-                mFastForwardAction = new PlaybackControlsRow.FastForwardAction(
-                        getContext(),
-                        mFastForwardSpeeds.length);
-            }
-            mPrimaryActionsAdapter.set(ACTION_FAST_FORWARD, mFastForwardAction);
-        } else {
-            mPrimaryActionsAdapter.clear(ACTION_FAST_FORWARD);
-            mFastForwardAction = null;
-        }
-        if ((actions & ACTION_SKIP_TO_NEXT) != 0) {
-            if (mSkipNextAction == null) {
-                mSkipNextAction = new PlaybackControlsRow.SkipNextAction(getContext());
-            }
-            mPrimaryActionsAdapter.set(ACTION_SKIP_TO_NEXT, mSkipNextAction);
-        } else {
-            mPrimaryActionsAdapter.clear(ACTION_SKIP_TO_NEXT);
-            mSkipNextAction = null;
-        }
-
-        if (mFastForwardAction != null) {
-            int index = 0;
-            if (playbackSpeed >= PLAYBACK_SPEED_FAST_L0) {
-                index = playbackSpeed - PLAYBACK_SPEED_FAST_L0 + 1;
-            }
-            if (mFastForwardAction.getIndex() != index) {
-                mFastForwardAction.setIndex(index);
-                notifyItemChanged(mPrimaryActionsAdapter, mFastForwardAction);
-            }
-        }
-        if (mRewindAction != null) {
-            int index = 0;
-            if (playbackSpeed <= -PLAYBACK_SPEED_FAST_L0) {
-                index = -playbackSpeed - PLAYBACK_SPEED_FAST_L0 + 1;
-            }
-            if (mRewindAction.getIndex() != index) {
-                mRewindAction.setIndex(index);
-                notifyItemChanged(mPrimaryActionsAdapter, mRewindAction);
-            }
-        }
-
-        if (playbackSpeed == PLAYBACK_SPEED_PAUSED) {
-            updateProgress();
-            enableProgressUpdating(false);
-        } else {
-            enableProgressUpdating(true);
-        }
-
-        if (mFadeWhenPlaying && getHost() != null) {
-            getHost().setFadingEnabled(playbackSpeed == PLAYBACK_SPEED_NORMAL);
-        }
-
-        if (mPlayPauseAction != null) {
-            int index = playbackSpeed == PLAYBACK_SPEED_PAUSED
-                    ? PlaybackControlsRow.PlayPauseAction.PLAY
-                    : PlaybackControlsRow.PlayPauseAction.PAUSE;
-            if (mPlayPauseAction.getIndex() != index) {
-                mPlayPauseAction.setIndex(index);
-                notifyItemChanged(mPrimaryActionsAdapter, mPlayPauseAction);
-            }
-        }
-    }
-
-    private static void notifyItemChanged(SparseArrayObjectAdapter adapter, Object object) {
-        int index = adapter.indexOf(object);
-        if (index >= 0) {
-            adapter.notifyArrayItemRangeChanged(index, 1);
-        }
-    }
-
-    private static String getSpeedString(int speed) {
-        switch (speed) {
-            case PLAYBACK_SPEED_INVALID:
-                return "PLAYBACK_SPEED_INVALID";
-            case PLAYBACK_SPEED_PAUSED:
-                return "PLAYBACK_SPEED_PAUSED";
-            case PLAYBACK_SPEED_NORMAL:
-                return "PLAYBACK_SPEED_NORMAL";
-            case PLAYBACK_SPEED_FAST_L0:
-                return "PLAYBACK_SPEED_FAST_L0";
-            case PLAYBACK_SPEED_FAST_L1:
-                return "PLAYBACK_SPEED_FAST_L1";
-            case PLAYBACK_SPEED_FAST_L2:
-                return "PLAYBACK_SPEED_FAST_L2";
-            case PLAYBACK_SPEED_FAST_L3:
-                return "PLAYBACK_SPEED_FAST_L3";
-            case PLAYBACK_SPEED_FAST_L4:
-                return "PLAYBACK_SPEED_FAST_L4";
-            case -PLAYBACK_SPEED_FAST_L0:
-                return "-PLAYBACK_SPEED_FAST_L0";
-            case -PLAYBACK_SPEED_FAST_L1:
-                return "-PLAYBACK_SPEED_FAST_L1";
-            case -PLAYBACK_SPEED_FAST_L2:
-                return "-PLAYBACK_SPEED_FAST_L2";
-            case -PLAYBACK_SPEED_FAST_L3:
-                return "-PLAYBACK_SPEED_FAST_L3";
-            case -PLAYBACK_SPEED_FAST_L4:
-                return "-PLAYBACK_SPEED_FAST_L4";
-        }
-        return null;
-    }
-
-    /**
-     * Returns true if there is a valid media item.
-     */
-    public abstract boolean hasValidMedia();
-
-    /**
-     * Returns true if media is currently playing.
-     */
-    public abstract boolean isMediaPlaying();
-
-    /**
-     * Returns the title of the media item.
-     */
-    public abstract CharSequence getMediaTitle();
-
-    /**
-     * Returns the subtitle of the media item.
-     */
-    public abstract CharSequence getMediaSubtitle();
-
-    /**
-     * Returns the duration of the media item in milliseconds.
-     */
-    public abstract int getMediaDuration();
-
-    /**
-     * Returns a bitmap of the art for the media item.
-     */
-    public abstract Drawable getMediaArt();
-
-    /**
-     * Returns a bitmask of actions supported by the media player.
-     */
-    public abstract long getSupportedActions();
-
-    /**
-     * Returns the current playback speed.  When playing normally,
-     * {@link #PLAYBACK_SPEED_NORMAL} should be returned.
-     */
-    public abstract int getCurrentSpeedId();
-
-    /**
-     * Returns the current position of the media item in milliseconds.
-     */
-    public abstract int getCurrentPosition();
-
-    /**
      * Start playback at the given speed.
      * @deprecated use {@link #play()} instead.
      *
@@ -861,68 +186,112 @@
     @Deprecated
     protected void skipToPrevious() {}
 
+    @Override
+    public final void next() {
+        skipToNext();
+    }
+
+    @Override
+    public final void previous() {
+        skipToPrevious();
+    }
+
+    @Override
+    public final void play(int speed) {
+        startPlayback(speed);
+    }
+
+    @Override
+    public final void pause() {
+        pausePlayback();
+    }
+
     /**
      * This method invoked when the playback controls row has changed. The adapter
-     * containing this row should be notified. This method would be delegated to
-     * {@see android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost#notifyPlaybackRowChanged}.
-     * @deprecated see {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost}.
+     * containing this row should be notified.
+     */
+    protected void onRowChanged(PlaybackControlsRow row) {
+    }
+
+    /**
+     * Set the {@link OnItemViewClickedListener} to be called if the click event
+     * is not handled internally.
+     * @param listener
+     * @deprecated Don't call this. Instead use the listener on the fragment yourself.
      */
     @Deprecated
-    protected void onRowChanged(PlaybackControlsRow row) {
-        if (getHost() != null) {
-            getHost().notifyPlaybackRowChanged();
-        }
+    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        mExternalOnItemViewClickedListener = listener;
     }
 
     /**
-     * Creates the primary action adapter.  May be overridden to add additional primary
-     * actions to the adapter.
+     * Returns the {@link OnItemViewClickedListener}.
+     * @deprecated Don't call this. Instead use the listener on the fragment yourself.
      */
+    @Deprecated
+    public OnItemViewClickedListener getOnItemViewClickedListener() {
+        return mExternalOnItemViewClickedListener;
+    }
+
+    @Override
+    protected void onCreateControlsRowAndPresenter() {
+        // backward compatible, we dont create row / presenter by default.
+        // User is expected to call createControlsRowAndPresenter() or setControlsRow()
+        // explicitly.
+    }
+
+    /**
+     * Helper method for instantiating a
+     * {@link android.support.v17.leanback.widget.PlaybackControlsRow} and corresponding
+     * {@link android.support.v17.leanback.widget.PlaybackControlsRowPresenter}.
+     */
+    public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
+        super.onCreateControlsRowAndPresenter();
+        return getControlsRowPresenter();
+    }
+
     protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
             PresenterSelector presenterSelector) {
-        return new SparseArrayObjectAdapter(presenterSelector);
+        return super.createPrimaryActionsAdapter(presenterSelector);
     }
 
     /**
-     * Must be called appropriately by a subclass when the playback state has changed.
-     * It updates the playback state displayed on the media player.
+     * Interface allowing the application to handle input events.
+     * @deprecated Use
+     * {@link PlaybackGlueHost#setOnKeyInterceptListener(View.OnKeyListener)}.
      */
-    protected void onStateChanged() {
-        if (DEBUG) Log.v(TAG, "onStateChanged");
-        // If a pending control button update is present, delay
-        // the update until the state settles.
-        if (!hasValidMedia()) {
-            return;
-        }
-        if (sHandler.hasMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference)) {
-            sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
-            if (getCurrentSpeedId() != mPlaybackSpeed) {
-                if (DEBUG) Log.v(TAG, "Status expectation mismatch, delaying update");
-                sHandler.sendMessageDelayed(sHandler.obtainMessage(MSG_UPDATE_PLAYBACK_STATE,
-                        mGlueWeakReference), UPDATE_PLAYBACK_STATE_DELAY_MS);
-            } else {
-                if (DEBUG) Log.v(TAG, "Update state matches expectation");
-                updatePlaybackState();
-            }
-        } else {
-            updatePlaybackState();
-        }
-    }
-
-    /**
-     * Must be called appropriately by a subclass when the metadata state has changed.
-     */
-    protected void onMetadataChanged() {
-        if (DEBUG) Log.v(TAG, "onMetadataChanged");
-        updateRowMetadata();
+    @Deprecated
+    public interface InputEventHandler {
+        /**
+         * Called when an {@link InputEvent} is received.
+         *
+         * @return If the event should be consumed, return true. To allow the event to
+         * continue on to the next handler, return false.
+         */
+        boolean handleInputEvent(InputEvent event);
     }
 
     static final class PlaybackGlueHostOld extends PlaybackGlueHost {
         final PlaybackOverlayFragment mFragment;
         PlaybackControlGlue mGlue;
+        OnActionClickedListener mActionClickedListener;
 
         public PlaybackGlueHostOld(PlaybackOverlayFragment fragment) {
             mFragment = fragment;
+            mFragment.setOnItemViewClickedListener(new OnItemViewClickedListener() {
+                @Override
+                public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                                          RowPresenter.ViewHolder rowViewHolder, Row row) {
+                    if (item instanceof Action
+                            && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder
+                            && mActionClickedListener != null) {
+                        mActionClickedListener.onActionClicked((Action) item);
+                    } else if (mGlue != null && mGlue.getOnItemViewClickedListener() != null) {
+                        mGlue.getOnItemViewClickedListener().onItemClicked(itemViewHolder,
+                                item, rowViewHolder, row);
+                    }
+                }
+            });
         }
 
         @Override
@@ -931,13 +300,13 @@
         }
 
         @Override
-        public void setOnKeyInterceptListener(final View.OnKeyListener onKeyListenerr) {
+        public void setOnKeyInterceptListener(final View.OnKeyListener onKeyListener) {
             mFragment.setEventHandler( new InputEventHandler() {
                 @Override
                 public boolean handleInputEvent(InputEvent event) {
                     if (event instanceof KeyEvent) {
                         KeyEvent keyEvent = (KeyEvent) event;
-                        return onKeyListenerr.onKey(null, keyEvent.getKeyCode(), keyEvent);
+                        return onKeyListener.onKey(null, keyEvent.getKeyCode(), keyEvent);
                     }
                     return false;
                 }
@@ -946,19 +315,22 @@
 
         @Override
         public void setOnActionClickedListener(final OnActionClickedListener listener) {
-            mFragment.setOnItemViewClickedListener(new OnItemViewClickedListener() {
-                @Override
-                public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-                                          RowPresenter.ViewHolder rowViewHolder, Row row) {
-                    if (item instanceof Action) {
-                        listener.onActionClicked((Action)item);
-                        if (mGlue.mExternalOnItemViewClickedListener != null) {
-                            mGlue.mExternalOnItemViewClickedListener.onItemClicked(itemViewHolder,
-                                    item, rowViewHolder, row);
-                        }
-                    }
-                }
-            });
+            mActionClickedListener = listener;
+        }
+
+        @Override
+        public void setHostCallback(HostCallback callback) {
+            mFragment.setHostCallback(callback);
+        }
+
+        @Override
+        public void fadeOut() {
+            mFragment.fadeOut();
+        }
+
+        @Override
+        public void notifyPlaybackRowChanged() {
+            mGlue.onRowChanged(mGlue.getControlsRow());
         }
     }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java
index 5beb87f..b3d19ae 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java
@@ -3,9 +3,11 @@
 package android.support.v17.leanback.app;
 
 import android.content.Context;
+import android.support.v17.leanback.media.PlaybackGlueHost;
 import android.support.v17.leanback.widget.Action;
 import android.support.v17.leanback.widget.OnActionClickedListener;
 import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.PlaybackRowPresenter;
 import android.support.v17.leanback.widget.Presenter;
 import android.support.v17.leanback.widget.Row;
 import android.support.v17.leanback.widget.RowPresenter;
@@ -14,8 +16,8 @@
 import android.view.View;
 
 /**
- * @deprecated Use {@link PlaybackControlGlue} and {@link PlaybackSupportFragmentGlueHost} for
- * {@link PlaybackSupportFragment}.
+ * @deprecated Use {@link android.support.v17.leanback.media.PlaybackControlGlue} and
+ * {@link PlaybackSupportFragmentGlueHost} for {@link PlaybackSupportFragment}.
  */
 @Deprecated
 public abstract class PlaybackControlSupportGlue extends PlaybackControlGlue {
@@ -103,22 +105,19 @@
     public static final int PLAYBACK_SPEED_FAST_L4 = PlaybackControlGlue.PLAYBACK_SPEED_FAST_L4;
 
     public PlaybackControlSupportGlue(Context context, int[] seekSpeeds) {
-        super(context, seekSpeeds);
+        this(context, null, seekSpeeds, seekSpeeds);
     }
 
     public PlaybackControlSupportGlue(
             Context context, int[] fastForwardSpeeds, int[] rewindSpeeds) {
-        super(context, fastForwardSpeeds, rewindSpeeds);
+        this(context, null, fastForwardSpeeds, rewindSpeeds);
     }
 
     public PlaybackControlSupportGlue(
             Context context,
             PlaybackOverlaySupportFragment fragment,
             int[] seekSpeeds) {
-        super(context,
-                fragment == null ? null: new PlaybackSupportGlueHostOld(fragment),
-                seekSpeeds,
-                seekSpeeds);
+        this(context, fragment, seekSpeeds, seekSpeeds);
     }
 
     public PlaybackControlSupportGlue(
@@ -126,15 +125,13 @@
             PlaybackOverlaySupportFragment fragment,
             int[] fastForwardSpeeds,
             int[] rewindSpeeds) {
-        super(context,
-                fragment == null ? null: new PlaybackSupportGlueHostOld(fragment),
-                fastForwardSpeeds,
-                rewindSpeeds);
+        super(context, fastForwardSpeeds, rewindSpeeds);
+        setHost(fragment == null ? null : new PlaybackSupportGlueHostOld(fragment));
     }
 
     @Override
-    public void setHost(PlaybackGlueHost host) {
-        super.setHost(host);
+    protected void onAttachedToHost(PlaybackGlueHost host) {
+        super.onAttachedToHost(host);
         if (host instanceof PlaybackSupportGlueHostOld) {
             ((PlaybackSupportGlueHostOld) host).mGlue = this;
         }
@@ -142,10 +139,25 @@
 
     static final class PlaybackSupportGlueHostOld extends PlaybackGlueHost {
         final PlaybackOverlaySupportFragment mFragment;
-        PlaybackControlGlue mGlue;
+        PlaybackControlSupportGlue mGlue;
+        OnActionClickedListener mActionClickedListener;
 
         public PlaybackSupportGlueHostOld(PlaybackOverlaySupportFragment fragment) {
             mFragment = fragment;
+            mFragment.setOnItemViewClickedListener(new OnItemViewClickedListener() {
+                @Override
+                public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                                          RowPresenter.ViewHolder rowViewHolder, Row row) {
+                    if (item instanceof Action
+                            && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder
+                            && mActionClickedListener != null) {
+                        mActionClickedListener.onActionClicked((Action) item);
+                    } else if (mGlue != null && mGlue.getOnItemViewClickedListener() != null) {
+                        mGlue.getOnItemViewClickedListener().onItemClicked(itemViewHolder,
+                                item, rowViewHolder, row);
+                    }
+                }
+            });
         }
 
         @Override
@@ -169,19 +181,22 @@
 
         @Override
         public void setOnActionClickedListener(final OnActionClickedListener listener) {
-            mFragment.setOnItemViewClickedListener(new OnItemViewClickedListener() {
-                @Override
-                public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-                                          RowPresenter.ViewHolder rowViewHolder, Row row) {
-                    if (item instanceof Action) {
-                        listener.onActionClicked((Action)item);
-                        if (mGlue.mExternalOnItemViewClickedListener != null) {
-                            mGlue.mExternalOnItemViewClickedListener.onItemClicked(itemViewHolder,
-                                    item, rowViewHolder, row);
-                        }
-                    }
-                }
-            });
+            mActionClickedListener = listener;
+        }
+
+        @Override
+        public void setHostCallback(HostCallback callback) {
+            mFragment.setHostCallback(callback);
+        }
+
+        @Override
+        public void fadeOut() {
+            mFragment.fadeOut();
+        }
+
+        @Override
+        public void notifyPlaybackRowChanged() {
+            mGlue.onRowChanged(mGlue.getControlsRow());
         }
     }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragment.java
index b97930a..02a0257 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragment.java
@@ -28,8 +28,10 @@
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.animation.LogAccelerateInterpolator;
 import android.support.v17.leanback.animation.LogDecelerateInterpolator;
+import android.support.v17.leanback.media.PlaybackGlueHost;
 import android.support.v17.leanback.widget.ArrayObjectAdapter;
 import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
+import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
 import android.support.v17.leanback.widget.ClassPresenterSelector;
 import android.support.v17.leanback.widget.ItemBridgeAdapter;
 import android.support.v17.leanback.widget.ObjectAdapter;
@@ -74,10 +76,11 @@
      * A dark translucent background.
      */
     public static final int BG_DARK = 1;
-    private PlaybackGlue.HostLifecycleCallback mHostLifecycleCallback;
+    private PlaybackGlueHost.HostCallback mHostCallback;
 
     /**
      * Resets the focus on the button in the middle of control row.
+     * @hide
      */
     public void resetFocus() {
         ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView()
@@ -109,25 +112,43 @@
     private ObjectAdapter mAdapter;
     private PlaybackRowPresenter mPresenter;
     private Row mRow;
+    private BaseOnItemViewSelectedListener mExternalItemSelectedListener;
     private BaseOnItemViewClickedListener mExternalItemClickedListener;
     private BaseOnItemViewClickedListener mPlaybackItemClickedListener;
-    private BaseOnItemViewClickedListener mOnItemViewClickedListener = new BaseOnItemViewClickedListener() {
-        @Override
-        public void onItemClicked(Presenter.ViewHolder itemViewHolder,
-                                  Object item,
-                                  RowPresenter.ViewHolder rowViewHolder,
-                                  Object row) {
-            if (mPlaybackItemClickedListener != null
-                    && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder) {
-                mPlaybackItemClickedListener.onItemClicked(
-                        itemViewHolder, item, rowViewHolder, row);
-            }
-            if (mExternalItemClickedListener != null) {
-                mExternalItemClickedListener.onItemClicked(
-                        itemViewHolder, item, rowViewHolder, row);
-            }
-        }
-    };
+
+    private final BaseOnItemViewClickedListener mOnItemViewClickedListener =
+            new BaseOnItemViewClickedListener() {
+                @Override
+                public void onItemClicked(Presenter.ViewHolder itemViewHolder,
+                                          Object item,
+                                          RowPresenter.ViewHolder rowViewHolder,
+                                          Object row) {
+                    if (mPlaybackItemClickedListener != null
+                            && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder) {
+                        mPlaybackItemClickedListener.onItemClicked(
+                                itemViewHolder, item, rowViewHolder, row);
+                    }
+                    if (mExternalItemClickedListener != null) {
+                        mExternalItemClickedListener.onItemClicked(
+                                itemViewHolder, item, rowViewHolder, row);
+                    }
+                }
+            };
+
+    private final BaseOnItemViewSelectedListener mOnItemViewSelectedListener =
+            new BaseOnItemViewSelectedListener() {
+                @Override
+                public void onItemSelected(Presenter.ViewHolder itemViewHolder,
+                                           Object item,
+                                           RowPresenter.ViewHolder rowViewHolder,
+                                           Object row) {
+                    if (mExternalItemSelectedListener != null) {
+                        mExternalItemSelectedListener.onItemSelected(
+                                itemViewHolder, item, rowViewHolder, row);
+                    }
+                }
+            };
+
     private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
 
     public ObjectAdapter getAdapter() {
@@ -137,6 +158,7 @@
     /**
      * Listener allowing the application to receive notification of fade in and/or fade out
      * completion events.
+     * @hide
      */
     public static class OnFadeCompleteListener {
         public void onFadeInComplete() {
@@ -295,6 +317,7 @@
 
     /**
      * Sets the listener to be called when fade in or out has completed.
+     * @hide
      */
     public void setFadeCompleteListener(OnFadeCompleteListener listener) {
         mFadeCompleteListener = listener;
@@ -302,6 +325,7 @@
 
     /**
      * Returns the listener to be called when fade in or out has completed.
+     * @hide
      */
     public OnFadeCompleteListener getFadeCompleteListener() {
         return mFadeCompleteListener;
@@ -315,13 +339,6 @@
     }
 
     /**
-     * Returns the input event handler.
-     */
-    public final View.OnKeyListener getEventHandler() {
-        return mInputEventHandler;
-    }
-
-    /**
      * Tickles the playback controls.  Fades in the view if it was faded out,
      * otherwise resets the fade out timer.  Tickling on input events is handled
      * by the fragment.
@@ -409,6 +426,9 @@
         }
         getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
         getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
+        if (mHostCallback != null) {
+            mHostCallback.onHostResume();
+        }
     }
 
     private void startFadeTimer() {
@@ -742,6 +762,7 @@
         } else {
             mRowsFragment.setAdapter(mAdapter);
         }
+        mRowsFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
         mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
 
         mBgAlpha = 255;
@@ -751,11 +772,11 @@
     }
 
     /**
-     * Sets the {@link PlaybackGlue.HostLifecycleCallback}. Implementor of this interface will
+     * Sets the {@link PlaybackGlueHost.HostCallback}. Implementor of this interface will
      * take appropriate actions to take action when the hosting fragment starts/stops processing.
      */
-    public void setHostLifecycleCallback(PlaybackGlue.HostLifecycleCallback hostLifecycleCallback) {
-        this.mHostLifecycleCallback = hostLifecycleCallback;
+    public void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {
+        this.mHostCallback = hostCallback;
     }
 
     @Override
@@ -763,17 +784,34 @@
         super.onStart();
         setupChildFragmentLayout();
         mRowsFragment.setAdapter(mAdapter);
-        if (mHostLifecycleCallback != null) {
-            mHostLifecycleCallback.onHostStart();
+        if (mHostCallback != null) {
+            mHostCallback.onHostStart();
         }
     }
 
     @Override
     public void onStop() {
-        super.onStop();
-        if (mHostLifecycleCallback != null) {
-            mHostLifecycleCallback.onHostStop();
+        if (mHostCallback != null) {
+            mHostCallback.onHostStop();
         }
+        super.onStop();
+    }
+
+    @Override
+    public void onPause() {
+        if (mHostCallback != null) {
+            mHostCallback.onHostPause();
+        }
+        super.onPause();
+    }
+
+    /**
+     * This listener is called every time there is a selection in {@link RowsFragment}. This can
+     * be used by users to take additional actions such as animations.
+     * @hide
+     */
+    public void setOnItemViewSelectedListener(final BaseOnItemViewSelectedListener listener) {
+        mExternalItemSelectedListener = listener;
     }
 
     /**
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
index fc384a5..fdaa6ef 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackFragmentGlueHost.java
@@ -13,6 +13,7 @@
  */
 package android.support.v17.leanback.app;
 
+import android.support.v17.leanback.media.PlaybackGlueHost;
 import android.support.v17.leanback.widget.Action;
 import android.support.v17.leanback.widget.OnActionClickedListener;
 import android.support.v17.leanback.widget.OnItemViewClickedListener;
@@ -23,10 +24,10 @@
 import android.view.View;
 
 /**
- * {@link PlaybackGlue.PlaybackGlueHost} implementation
+ * {@link PlaybackGlueHost} implementation
  * the interaction between this class and {@link PlaybackFragment}.
  */
-public class PlaybackFragmentGlueHost extends PlaybackGlue.PlaybackGlueHost {
+public class PlaybackFragmentGlueHost extends PlaybackGlueHost {
     private final PlaybackFragment mFragment;
 
     public PlaybackFragmentGlueHost(PlaybackFragment fragment) {
@@ -45,20 +46,24 @@
 
     @Override
     public void setOnActionClickedListener(final OnActionClickedListener listener) {
-        mFragment.setOnPlaybackItemViewClickedListener(new OnItemViewClickedListener() {
-            @Override
-            public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-                                      RowPresenter.ViewHolder rowViewHolder, Row row) {
-                if (item instanceof Action) {
-                    listener.onActionClicked((Action)item);
+        if (listener == null) {
+            mFragment.setOnPlaybackItemViewClickedListener(null);
+        } else {
+            mFragment.setOnPlaybackItemViewClickedListener(new OnItemViewClickedListener() {
+                @Override
+                public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                                          RowPresenter.ViewHolder rowViewHolder, Row row) {
+                    if (item instanceof Action) {
+                        listener.onActionClicked((Action) item);
+                    }
                 }
-            }
-        });
+            });
+        }
     }
 
     @Override
-    public void setHostLifeCycleCallback(PlaybackGlue.HostLifecycleCallback callback) {
-        mFragment.setHostLifecycleCallback(callback);
+    public void setHostCallback(HostCallback callback) {
+        mFragment.setHostCallback(callback);
     }
 
     @Override
@@ -75,4 +80,9 @@
     public void setPlaybackRow(Row row) {
         mFragment.setPlaybackRow(row);
     }
+
+    @Override
+    public void fadeOut() {
+        mFragment.fadeOut();
+    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackGlue.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackGlue.java
deleted file mode 100644
index 2243186..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackGlue.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package android.support.v17.leanback.app;
-
-import android.content.Context;
-import android.support.v17.leanback.widget.OnActionClickedListener;
-import android.support.v17.leanback.widget.PlaybackRowPresenter;
-import android.support.v17.leanback.widget.Row;
-import android.view.View;
-
-/**
- * Base class for {@link PlaybackControlGlue}.
- */
-public abstract class PlaybackGlue {
-    private final Context mContext;
-    private PlaybackGlueHost mPlaybackGlueHost;
-
-    /**
-     * Returns true when the media player is ready to start media playback. Subclasses must
-     * implement this method correctly.
-     */
-    public boolean isReadyForPlayback() { return true; }
-
-    /**
-     * Interface to allow clients to take action once the video is ready to play.
-     */
-    public static abstract class PlayerCallback {
-        /**
-         * This method is fired when the video is ready for playback.
-         */
-        public abstract void onReadyForPlayback();
-    }
-
-    /**
-     * Lifecycle callbacks triggered by the host(fragment e.g.) hosting the video controls/surface.
-     */
-    public static abstract class HostLifecycleCallback {
-        /**
-         * Callback triggered once the host(fragment) has started.
-         */
-        public abstract void onHostStart();
-
-        /**
-         * Callback triggered once the host(fragment) has finished.
-         */
-        public abstract void onHostStop();
-    }
-
-    /**
-     * Sets the {@link PlayerCallback} callback.
-     */
-    public void setPlayerCallback(MediaPlayerGlue.PlayerCallback mPlayerCallback) {}
-
-    /**
-     * Starts the media player.
-     */
-    public void play() {}
-
-    /**
-     * Pauses the media player.
-     */
-    public void pause() {}
-
-    /**
-     * Goes to the next media item.
-     */
-    public void next() {}
-
-    /**
-     * Goes to the previous media item.
-     */
-    public void previous() {}
-
-    /**
-     * This class represents the UI {@link PlaybackFragment} hosting playback controls and
-     * defines the interaction between {@link PlaybackGlue} and the host.
-     */
-    public static class PlaybackGlueHost {
-        PlaybackGlue mGlue;
-
-        /**
-         * Enables or disables view fading.  If enabled, the view will be faded in when the
-         * fragment starts and will fade out after a time period.
-         */
-        public void setFadingEnabled(boolean enable) {
-        }
-
-        /**
-         * Sets the {@link android.view.View.OnKeyListener} on the host. This would trigger
-         * the listener when a {@link android.view.KeyEvent} is unhandled by the host.
-         */
-        public void setOnKeyInterceptListener(View.OnKeyListener onKeyListener) {
-        }
-
-        /**
-         * Sets the {@link android.view.View.OnClickListener} on this fragment.
-         */
-        public void setOnActionClickedListener(OnActionClickedListener listener) {}
-
-        /**
-         * Sets the host {@link HostLifecycleCallback} callback on the host.
-         */
-        public void setHostLifeCycleCallback(HostLifecycleCallback callback) {
-        }
-
-        /**
-         * Notifies host about a change so it can update the view.
-         */
-        public void notifyPlaybackRowChanged() {}
-
-        /**
-         * Sets {@link PlaybackRowPresenter} for rendering the playback controls.
-         */
-        public void setPlaybackRowPresenter(PlaybackRowPresenter presenter) {}
-
-        /**
-         * Sets the {@link Row} that represents the information on control items that needs
-         * to be rendered.
-         */
-        public void setPlaybackRow(Row row) {}
-
-        final void attachToGlue(PlaybackGlue glue) {
-            if (mGlue != null) {
-                mGlue.onDetachedFromHost();
-            }
-            mGlue = glue;
-        }
-    }
-
-    /**
-     * Constructor.
-     */
-    public PlaybackGlue(Context context) {
-        this.mContext = context;
-    }
-
-    /**
-     * Returns the context.
-     */
-    public Context getContext() {
-        return mContext;
-    }
-
-    /**
-     * This method is used to configure the {@link PlaybackGlueHost} with required listeners
-     * and presenters.
-     */
-    public void setHost(PlaybackGlueHost host) {
-        mPlaybackGlueHost = host;
-        if (host != null) {
-            host.attachToGlue(this);
-        }
-    }
-
-    /**
-     * This method is called when we try to attach a new host by calling
-     * {@link #setHost(PlaybackGlueHost)}.
-     */
-    public void onDetachedFromHost() {
-        mPlaybackGlueHost = null;
-    }
-
-    /**
-     * @return Associated {@link PlaybackGlueHost} or null if not attached to host.
-     */
-    public PlaybackGlueHost getHost() {
-        return mPlaybackGlueHost;
-    }
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
index 32b9c92..33e35eb 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
@@ -27,6 +27,7 @@
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.animation.LogAccelerateInterpolator;
 import android.support.v17.leanback.animation.LogDecelerateInterpolator;
+import android.support.v17.leanback.media.PlaybackGlueHost;
 import android.support.v17.leanback.widget.ItemBridgeAdapter;
 import android.support.v17.leanback.widget.ObjectAdapter;
 import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
@@ -123,6 +124,7 @@
     private ValueAnimator mDescriptionFadeInAnimator, mDescriptionFadeOutAnimator;
     private ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator;
     boolean mResetControlsToPrimaryActionsPending;
+    PlaybackGlueHost.HostCallback mHostCallback;
 
     private final Animator.AnimatorListener mFadeListener =
             new Animator.AnimatorListener() {
@@ -323,6 +325,30 @@
         fade(false);
     }
 
+    /**
+     * Sets the {@link PlaybackGlueHost.HostCallback}. Implementor of this interface will
+     * take appropriate actions to take action when the hosting fragment starts/stops processing.
+     */
+    void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {
+        this.mHostCallback = hostCallback;
+    }
+
+    @Override
+    public void onStop() {
+        if (mHostCallback != null) {
+            mHostCallback.onHostStop();
+        }
+        super.onStop();
+    }
+
+    @Override
+    public void onPause() {
+        if (mHostCallback != null) {
+            mHostCallback.onHostPause();
+        }
+        super.onPause();
+    }
+
     private boolean areControlsHidden() {
         return mFadingStatus == IDLE && mBgAlpha == 0;
     }
@@ -382,6 +408,9 @@
         }
         getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
         getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
+        if (mHostCallback != null) {
+            mHostCallback.onHostResume();
+        }
     }
 
     void startFadeTimer() {
@@ -765,6 +794,9 @@
         super.onStart();
         // Workaround problem VideoView forcing itself to focused, let controls take focus.
         getRowsFragment().getView().requestFocus();
+        if (mHostCallback != null) {
+            mHostCallback.onHostStart();
+        }
     }
 
     private final DataObserver mObserver = new DataObserver() {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
index c1d1452..d41d65f 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
@@ -30,6 +30,7 @@
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.animation.LogAccelerateInterpolator;
 import android.support.v17.leanback.animation.LogDecelerateInterpolator;
+import android.support.v17.leanback.media.PlaybackGlueHost;
 import android.support.v17.leanback.widget.ItemBridgeAdapter;
 import android.support.v17.leanback.widget.ObjectAdapter;
 import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
@@ -126,6 +127,7 @@
     private ValueAnimator mDescriptionFadeInAnimator, mDescriptionFadeOutAnimator;
     private ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator;
     boolean mResetControlsToPrimaryActionsPending;
+    PlaybackGlueHost.HostCallback mHostCallback;
 
     private final Animator.AnimatorListener mFadeListener =
             new Animator.AnimatorListener() {
@@ -326,6 +328,30 @@
         fade(false);
     }
 
+    /**
+     * Sets the {@link PlaybackGlueHost.HostCallback}. Implementor of this interface will
+     * take appropriate actions to take action when the hosting fragment starts/stops processing.
+     */
+    void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {
+        this.mHostCallback = hostCallback;
+    }
+
+    @Override
+    public void onStop() {
+        if (mHostCallback != null) {
+            mHostCallback.onHostStop();
+        }
+        super.onStop();
+    }
+
+    @Override
+    public void onPause() {
+        if (mHostCallback != null) {
+            mHostCallback.onHostPause();
+        }
+        super.onPause();
+    }
+
     private boolean areControlsHidden() {
         return mFadingStatus == IDLE && mBgAlpha == 0;
     }
@@ -385,6 +411,9 @@
         }
         getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
         getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
+        if (mHostCallback != null) {
+            mHostCallback.onHostResume();
+        }
     }
 
     void startFadeTimer() {
@@ -768,6 +797,9 @@
         super.onStart();
         // Workaround problem VideoView forcing itself to focused, let controls take focus.
         getRowsSupportFragment().getView().requestFocus();
+        if (mHostCallback != null) {
+            mHostCallback.onHostStart();
+        }
     }
 
     private final DataObserver mObserver = new DataObserver() {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragment.java
index f01313d..07701f9 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragment.java
@@ -31,8 +31,10 @@
 import android.support.v17.leanback.R;
 import android.support.v17.leanback.animation.LogAccelerateInterpolator;
 import android.support.v17.leanback.animation.LogDecelerateInterpolator;
+import android.support.v17.leanback.media.PlaybackGlueHost;
 import android.support.v17.leanback.widget.ArrayObjectAdapter;
 import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
+import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
 import android.support.v17.leanback.widget.ClassPresenterSelector;
 import android.support.v17.leanback.widget.ItemBridgeAdapter;
 import android.support.v17.leanback.widget.ObjectAdapter;
@@ -77,10 +79,11 @@
      * A dark translucent background.
      */
     public static final int BG_DARK = 1;
-    private PlaybackGlue.HostLifecycleCallback mHostLifecycleCallback;
+    private PlaybackGlueHost.HostCallback mHostCallback;
 
     /**
      * Resets the focus on the button in the middle of control row.
+     * @hide
      */
     public void resetFocus() {
         ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView()
@@ -112,25 +115,43 @@
     private ObjectAdapter mAdapter;
     private PlaybackRowPresenter mPresenter;
     private Row mRow;
+    private BaseOnItemViewSelectedListener mExternalItemSelectedListener;
     private BaseOnItemViewClickedListener mExternalItemClickedListener;
     private BaseOnItemViewClickedListener mPlaybackItemClickedListener;
-    private BaseOnItemViewClickedListener mOnItemViewClickedListener = new BaseOnItemViewClickedListener() {
-        @Override
-        public void onItemClicked(Presenter.ViewHolder itemViewHolder,
-                                  Object item,
-                                  RowPresenter.ViewHolder rowViewHolder,
-                                  Object row) {
-            if (mPlaybackItemClickedListener != null
-                    && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder) {
-                mPlaybackItemClickedListener.onItemClicked(
-                        itemViewHolder, item, rowViewHolder, row);
-            }
-            if (mExternalItemClickedListener != null) {
-                mExternalItemClickedListener.onItemClicked(
-                        itemViewHolder, item, rowViewHolder, row);
-            }
-        }
-    };
+
+    private final BaseOnItemViewClickedListener mOnItemViewClickedListener =
+            new BaseOnItemViewClickedListener() {
+                @Override
+                public void onItemClicked(Presenter.ViewHolder itemViewHolder,
+                                          Object item,
+                                          RowPresenter.ViewHolder rowViewHolder,
+                                          Object row) {
+                    if (mPlaybackItemClickedListener != null
+                            && rowViewHolder instanceof PlaybackRowPresenter.ViewHolder) {
+                        mPlaybackItemClickedListener.onItemClicked(
+                                itemViewHolder, item, rowViewHolder, row);
+                    }
+                    if (mExternalItemClickedListener != null) {
+                        mExternalItemClickedListener.onItemClicked(
+                                itemViewHolder, item, rowViewHolder, row);
+                    }
+                }
+            };
+
+    private final BaseOnItemViewSelectedListener mOnItemViewSelectedListener =
+            new BaseOnItemViewSelectedListener() {
+                @Override
+                public void onItemSelected(Presenter.ViewHolder itemViewHolder,
+                                           Object item,
+                                           RowPresenter.ViewHolder rowViewHolder,
+                                           Object row) {
+                    if (mExternalItemSelectedListener != null) {
+                        mExternalItemSelectedListener.onItemSelected(
+                                itemViewHolder, item, rowViewHolder, row);
+                    }
+                }
+            };
+
     private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
 
     public ObjectAdapter getAdapter() {
@@ -140,6 +161,7 @@
     /**
      * Listener allowing the application to receive notification of fade in and/or fade out
      * completion events.
+     * @hide
      */
     public static class OnFadeCompleteListener {
         public void onFadeInComplete() {
@@ -298,6 +320,7 @@
 
     /**
      * Sets the listener to be called when fade in or out has completed.
+     * @hide
      */
     public void setFadeCompleteListener(OnFadeCompleteListener listener) {
         mFadeCompleteListener = listener;
@@ -305,6 +328,7 @@
 
     /**
      * Returns the listener to be called when fade in or out has completed.
+     * @hide
      */
     public OnFadeCompleteListener getFadeCompleteListener() {
         return mFadeCompleteListener;
@@ -318,13 +342,6 @@
     }
 
     /**
-     * Returns the input event handler.
-     */
-    public final View.OnKeyListener getEventHandler() {
-        return mInputEventHandler;
-    }
-
-    /**
      * Tickles the playback controls.  Fades in the view if it was faded out,
      * otherwise resets the fade out timer.  Tickling on input events is handled
      * by the fragment.
@@ -412,6 +429,9 @@
         }
         getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
         getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
+        if (mHostCallback != null) {
+            mHostCallback.onHostResume();
+        }
     }
 
     private void startFadeTimer() {
@@ -745,6 +765,7 @@
         } else {
             mRowsSupportFragment.setAdapter(mAdapter);
         }
+        mRowsSupportFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
         mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
 
         mBgAlpha = 255;
@@ -754,11 +775,11 @@
     }
 
     /**
-     * Sets the {@link PlaybackGlue.HostLifecycleCallback}. Implementor of this interface will
+     * Sets the {@link PlaybackGlueHost.HostCallback}. Implementor of this interface will
      * take appropriate actions to take action when the hosting fragment starts/stops processing.
      */
-    public void setHostLifecycleCallback(PlaybackGlue.HostLifecycleCallback hostLifecycleCallback) {
-        this.mHostLifecycleCallback = hostLifecycleCallback;
+    public void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {
+        this.mHostCallback = hostCallback;
     }
 
     @Override
@@ -766,17 +787,34 @@
         super.onStart();
         setupChildFragmentLayout();
         mRowsSupportFragment.setAdapter(mAdapter);
-        if (mHostLifecycleCallback != null) {
-            mHostLifecycleCallback.onHostStart();
+        if (mHostCallback != null) {
+            mHostCallback.onHostStart();
         }
     }
 
     @Override
     public void onStop() {
-        super.onStop();
-        if (mHostLifecycleCallback != null) {
-            mHostLifecycleCallback.onHostStop();
+        if (mHostCallback != null) {
+            mHostCallback.onHostStop();
         }
+        super.onStop();
+    }
+
+    @Override
+    public void onPause() {
+        if (mHostCallback != null) {
+            mHostCallback.onHostPause();
+        }
+        super.onPause();
+    }
+
+    /**
+     * This listener is called every time there is a selection in {@link RowsSupportFragment}. This can
+     * be used by users to take additional actions such as animations.
+     * @hide
+     */
+    public void setOnItemViewSelectedListener(final BaseOnItemViewSelectedListener listener) {
+        mExternalItemSelectedListener = listener;
     }
 
     /**
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java
index 3227747..da644ae 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackSupportFragmentGlueHost.java
@@ -16,6 +16,7 @@
  */
 package android.support.v17.leanback.app;
 
+import android.support.v17.leanback.media.PlaybackGlueHost;
 import android.support.v17.leanback.widget.Action;
 import android.support.v17.leanback.widget.OnActionClickedListener;
 import android.support.v17.leanback.widget.OnItemViewClickedListener;
@@ -26,10 +27,10 @@
 import android.view.View;
 
 /**
- * {@link PlaybackGlue.PlaybackGlueHost} implementation
+ * {@link PlaybackGlueHost} implementation
  * the interaction between this class and {@link PlaybackSupportFragment}.
  */
-public class PlaybackSupportFragmentGlueHost extends PlaybackGlue.PlaybackGlueHost {
+public class PlaybackSupportFragmentGlueHost extends PlaybackGlueHost {
     private final PlaybackSupportFragment mFragment;
 
     public PlaybackSupportFragmentGlueHost(PlaybackSupportFragment fragment) {
@@ -48,20 +49,24 @@
 
     @Override
     public void setOnActionClickedListener(final OnActionClickedListener listener) {
-        mFragment.setOnPlaybackItemViewClickedListener(new OnItemViewClickedListener() {
-            @Override
-            public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
-                                      RowPresenter.ViewHolder rowViewHolder, Row row) {
-                if (item instanceof Action) {
-                    listener.onActionClicked((Action)item);
+        if (listener == null) {
+            mFragment.setOnPlaybackItemViewClickedListener(null);
+        } else {
+            mFragment.setOnPlaybackItemViewClickedListener(new OnItemViewClickedListener() {
+                @Override
+                public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                                          RowPresenter.ViewHolder rowViewHolder, Row row) {
+                    if (item instanceof Action) {
+                        listener.onActionClicked((Action) item);
+                    }
                 }
-            }
-        });
+            });
+        }
     }
 
     @Override
-    public void setHostLifeCycleCallback(PlaybackGlue.HostLifecycleCallback callback) {
-        mFragment.setHostLifecycleCallback(callback);
+    public void setHostCallback(HostCallback callback) {
+        mFragment.setHostCallback(callback);
     }
 
     @Override
@@ -78,4 +83,9 @@
     public void setPlaybackRow(Row row) {
         mFragment.setPlaybackRow(row);
     }
+
+    @Override
+    public void fadeOut() {
+        mFragment.fadeOut();
+    }
 }
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
index ca486ed..d3a45a0 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
@@ -133,12 +133,13 @@
 
     static final String TAG = "RowsFragment";
     static final boolean DEBUG = false;
+    static final int ALIGN_TOP_NOT_SET = Integer.MIN_VALUE;
 
     ItemBridgeAdapter.ViewHolder mSelectedViewHolder;
     private int mSubPosition;
     boolean mExpand = true;
     boolean mViewsCreated;
-    private int mAlignedTop;
+    private int mAlignedTop = ALIGN_TOP_NOT_SET;
     boolean mAfterEntranceTransition = true;
 
     BaseOnItemViewSelectedListener mOnItemViewSelectedListener;
@@ -540,6 +541,9 @@
 
     @Override
     public void setAlignment(int windowAlignOffsetFromTop) {
+        if (windowAlignOffsetFromTop == ALIGN_TOP_NOT_SET) {
+            return;
+        }
         mAlignedTop = windowAlignOffsetFromTop;
         final VerticalGridView gridView = getVerticalGridView();
 
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
index d6c8d3f..5582644 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
@@ -136,12 +136,13 @@
 
     static final String TAG = "RowsSupportFragment";
     static final boolean DEBUG = false;
+    static final int ALIGN_TOP_NOT_SET = Integer.MIN_VALUE;
 
     ItemBridgeAdapter.ViewHolder mSelectedViewHolder;
     private int mSubPosition;
     boolean mExpand = true;
     boolean mViewsCreated;
-    private int mAlignedTop;
+    private int mAlignedTop = ALIGN_TOP_NOT_SET;
     boolean mAfterEntranceTransition = true;
 
     BaseOnItemViewSelectedListener mOnItemViewSelectedListener;
@@ -543,6 +544,9 @@
 
     @Override
     public void setAlignment(int windowAlignOffsetFromTop) {
+        if (windowAlignOffsetFromTop == ALIGN_TOP_NOT_SET) {
+            return;
+        }
         mAlignedTop = windowAlignOffsetFromTop;
         final VerticalGridView gridView = getVerticalGridView();
 
diff --git a/v17/leanback/src/android/support/v17/leanback/app/SurfaceHolderGlueHost.java b/v17/leanback/src/android/support/v17/leanback/app/SurfaceHolderGlueHost.java
deleted file mode 100644
index 64498f6..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/SurfaceHolderGlueHost.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.support.v17.leanback.app;
-
-import android.view.SurfaceHolder;
-
-/**
- * Interface to be implemented by any host that contains a {@link android.view.SurfaceView}. This
- * will allow MediaPlayerGlue to setup the surface holder callback on the host.
- */
-public interface SurfaceHolderGlueHost {
-    /**
-     * Sets the {@link SurfaceHolder.Callback} on the the host.
-     */
-    void setSurfaceHolderCallback(SurfaceHolder.Callback callback);
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VideoFragmentGlueHost.java b/v17/leanback/src/android/support/v17/leanback/app/VideoFragmentGlueHost.java
index 0db2a38..0b4e34b 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/VideoFragmentGlueHost.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/VideoFragmentGlueHost.java
@@ -13,10 +13,12 @@
  */
 package android.support.v17.leanback.app;
 
+import android.support.v17.leanback.media.PlaybackGlueHost;
+import android.support.v17.leanback.media.SurfaceHolderGlueHost;
 import android.view.SurfaceHolder;
 
 /**
- * {@link PlaybackGlue.PlaybackGlueHost} implementation
+ * {@link PlaybackGlueHost} implementation
  * the interaction between this class and {@link VideoFragment}.
  * @hide
  */
@@ -31,7 +33,7 @@
 
     /**
      * Sets the {@link android.view.SurfaceHolder.Callback} on the host.
-     * {@link PlaybackGlue.PlaybackGlueHost} is assumed to either host the {@link SurfaceHolder} or
+     * {@link PlaybackGlueHost} is assumed to either host the {@link SurfaceHolder} or
      * have a reference to the component hosting it for rendering the video.
      */
     @Override
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java b/v17/leanback/src/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java
index eb9d9d4..83769ab 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/VideoSupportFragmentGlueHost.java
@@ -16,10 +16,12 @@
  */
 package android.support.v17.leanback.app;
 
+import android.support.v17.leanback.media.PlaybackGlueHost;
+import android.support.v17.leanback.media.SurfaceHolderGlueHost;
 import android.view.SurfaceHolder;
 
 /**
- * {@link PlaybackGlue.PlaybackGlueHost} implementation
+ * {@link PlaybackGlueHost} implementation
  * the interaction between this class and {@link VideoSupportFragment}.
  * @hide
  */
@@ -34,7 +36,7 @@
 
     /**
      * Sets the {@link android.view.SurfaceHolder.Callback} on the host.
-     * {@link PlaybackGlue.PlaybackGlueHost} is assumed to either host the {@link SurfaceHolder} or
+     * {@link PlaybackGlueHost} is assumed to either host the {@link SurfaceHolder} or
      * have a reference to the component hosting it for rendering the video.
      */
     @Override
diff --git a/v17/leanback/src/android/support/v17/leanback/media/MediaControllerGlue.java b/v17/leanback/src/android/support/v17/leanback/media/MediaControllerGlue.java
new file mode 100644
index 0000000..730bf3a
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/media/MediaControllerGlue.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v17.leanback.media;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.util.Log;
+
+/**
+ * A helper class for implementing a glue layer for {@link MediaControllerCompat}.
+ */
+public abstract class MediaControllerGlue extends PlaybackControlGlue {
+    static final String TAG = "MediaControllerGlue";
+    static final boolean DEBUG = false;
+
+    MediaControllerCompat mMediaController;
+
+    private final MediaControllerCompat.Callback mCallback = new MediaControllerCompat.Callback() {
+        @Override
+        public void onMetadataChanged(MediaMetadataCompat metadata) {
+            if (DEBUG) Log.v(TAG, "onMetadataChanged");
+            MediaControllerGlue.this.onMetadataChanged();
+        }
+        @Override
+        public void onPlaybackStateChanged(PlaybackStateCompat state) {
+            if (DEBUG) Log.v(TAG, "onPlaybackStateChanged");
+            onStateChanged();
+        }
+        @Override
+        public void onSessionDestroyed() {
+            if (DEBUG) Log.v(TAG, "onSessionDestroyed");
+            mMediaController = null;
+        }
+        @Override
+        public void onSessionEvent(String event, Bundle extras) {
+            if (DEBUG) Log.v(TAG, "onSessionEvent");
+        }
+    };
+
+    /**
+     * Constructor for the glue.
+     *
+     * @param context
+     * @param fastForwardSpeeds Array of seek speeds for fast forward.
+     * @param rewindSpeeds Array of seek speeds for rewind.
+     */
+    public MediaControllerGlue(Context context,
+                               int[] fastForwardSpeeds,
+                               int[] rewindSpeeds) {
+        super(context, fastForwardSpeeds, rewindSpeeds);
+    }
+
+    /**
+     * Attaches to the given media controller.
+     */
+    public void attachToMediaController(MediaControllerCompat mediaController) {
+        if (mediaController != mMediaController) {
+            if (DEBUG) Log.v(TAG, "New media controller " + mediaController);
+            detach();
+            mMediaController = mediaController;
+            if (mMediaController != null) {
+                mMediaController.registerCallback(mCallback);
+            }
+            onMetadataChanged();
+            onStateChanged();
+        }
+    }
+
+    /**
+     * Detaches from the media controller.  Must be called when the object is no longer
+     * needed.
+     */
+    public void detach() {
+        if (mMediaController != null) {
+            mMediaController.unregisterCallback(mCallback);
+        }
+        mMediaController = null;
+    }
+
+    /**
+     * Returns the media controller currently attached.
+     */
+    public final MediaControllerCompat getMediaController() {
+        return mMediaController;
+    }
+
+    @Override
+    public boolean hasValidMedia() {
+        return mMediaController != null && mMediaController.getMetadata() != null;
+    }
+
+    @Override
+    public boolean isMediaPlaying() {
+        return mMediaController.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING;
+    }
+
+    @Override
+    public int getCurrentSpeedId() {
+        int speed = (int) mMediaController.getPlaybackState().getPlaybackSpeed();
+        if (speed == 0) {
+            return PLAYBACK_SPEED_PAUSED;
+        } else if (speed == 1) {
+            return PLAYBACK_SPEED_NORMAL;
+        } else if (speed > 0) {
+            int[] seekSpeeds = getFastForwardSpeeds();
+            for (int index = 0; index < seekSpeeds.length; index++) {
+                if (speed == seekSpeeds[index]) {
+                    return PLAYBACK_SPEED_FAST_L0 + index;
+                }
+            }
+        } else {
+            int[] seekSpeeds = getRewindSpeeds();
+            for (int index = 0; index < seekSpeeds.length; index++) {
+                if (-speed == seekSpeeds[index]) {
+                    return -PLAYBACK_SPEED_FAST_L0 - index;
+                }
+            }
+        }
+        Log.w(TAG, "Couldn't find index for speed " + speed);
+        return PLAYBACK_SPEED_INVALID;
+    }
+
+    @Override
+    public CharSequence getMediaTitle() {
+        return mMediaController.getMetadata().getDescription().getTitle();
+    }
+
+    @Override
+    public CharSequence getMediaSubtitle() {
+        return mMediaController.getMetadata().getDescription().getSubtitle();
+    }
+
+    @Override
+    public int getMediaDuration() {
+        return (int) mMediaController.getMetadata().getLong(
+                MediaMetadataCompat.METADATA_KEY_DURATION);
+    }
+
+    @Override
+    public int getCurrentPosition() {
+        return (int) mMediaController.getPlaybackState().getPosition();
+    }
+
+    @Override
+    public Drawable getMediaArt() {
+        Bitmap bitmap = mMediaController.getMetadata().getDescription().getIconBitmap();
+        return bitmap == null ? null : new BitmapDrawable(getContext().getResources(), bitmap);
+    }
+
+    @Override
+    public long getSupportedActions() {
+        long result = 0;
+        long actions = mMediaController.getPlaybackState().getActions();
+        if ((actions & PlaybackStateCompat.ACTION_PLAY_PAUSE) != 0) {
+            result |= ACTION_PLAY_PAUSE;
+        }
+        if ((actions & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
+            result |= ACTION_SKIP_TO_NEXT;
+        }
+        if ((actions & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
+            result |= ACTION_SKIP_TO_PREVIOUS;
+        }
+        if ((actions & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) {
+            result |= ACTION_FAST_FORWARD;
+        }
+        if ((actions & PlaybackStateCompat.ACTION_REWIND) != 0) {
+            result |= ACTION_REWIND;
+        }
+        return result;
+    }
+
+    @Override
+    public void play(int speed) {
+        if (DEBUG) Log.v(TAG, "startPlayback speed " + speed);
+        if (speed == PLAYBACK_SPEED_NORMAL) {
+            mMediaController.getTransportControls().play();
+        } else if (speed > 0) {
+            mMediaController.getTransportControls().fastForward();
+        } else {
+            mMediaController.getTransportControls().rewind();
+        }
+    }
+
+    @Override
+    public void pause() {
+        if (DEBUG) Log.v(TAG, "pausePlayback");
+        mMediaController.getTransportControls().pause();
+    }
+
+    @Override
+    public void next() {
+        if (DEBUG) Log.v(TAG, "skipToNext");
+        mMediaController.getTransportControls().skipToNext();
+    }
+
+    @Override
+    public void previous() {
+        if (DEBUG) Log.v(TAG, "skipToPrevious");
+        mMediaController.getTransportControls().skipToPrevious();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/MediaPlayerGlue.java b/v17/leanback/src/android/support/v17/leanback/media/MediaPlayerGlue.java
similarity index 80%
rename from v17/leanback/src/android/support/v17/leanback/app/MediaPlayerGlue.java
rename to v17/leanback/src/android/support/v17/leanback/media/MediaPlayerGlue.java
index e06316c..abee2dc 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/MediaPlayerGlue.java
+++ b/v17/leanback/src/android/support/v17/leanback/media/MediaPlayerGlue.java
@@ -1,19 +1,20 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
+ * 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
+ *      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.
- *
+ * 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.support.v17.leanback.app;
+package android.support.v17.leanback.media;
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
@@ -23,10 +24,8 @@
 import android.os.Handler;
 import android.support.v17.leanback.widget.Action;
 import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
 import android.support.v17.leanback.widget.OnItemViewSelectedListener;
 import android.support.v17.leanback.widget.PlaybackControlsRow;
-import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
 import android.support.v17.leanback.widget.Presenter;
 import android.support.v17.leanback.widget.Row;
 import android.support.v17.leanback.widget.RowPresenter;
@@ -38,8 +37,8 @@
 import java.io.IOException;
 
 /**
- * This glue extends the {@link PlaybackControlGlue} with a {@link MediaPlayer} synchronization. It
- * supports 7 actions:
+ * This glue extends the {@link android.support.v17.leanback.media.PlaybackControlGlue} with a
+ * {@link MediaPlayer} synchronization. It supports 7 actions:
  *
  * <ul>
  * <li>{@link android.support.v17.leanback.widget.PlaybackControlsRow.FastForwardAction}</li>
@@ -64,10 +63,8 @@
     private static final String TAG = "MediaPlayerGlue";
     protected final PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
     protected final PlaybackControlsRow.ThumbsUpAction mThumbsUpAction;
-    private final Context mContext;
     MediaPlayer mPlayer = new MediaPlayer();
     private final PlaybackControlsRow.RepeatAction mRepeatAction;
-    private PlaybackControlsRow mControlsRow;
     private Runnable mRunnable;
     private Handler mHandler = new Handler();
     private boolean mInitialized = false; // true when the MediaPlayer is prepared/initialized
@@ -113,32 +110,31 @@
     /**
      * Constructor.
      */
-    public MediaPlayerGlue(Context context, PlaybackGlueHost host) {
-        this(context, host, new int[]{1}, new int[]{1});
+    public MediaPlayerGlue(Context context) {
+        this(context, new int[]{1}, new int[]{1});
     }
 
     /**
      * Constructor.
      */
     public MediaPlayerGlue(
-            Context context, PlaybackGlueHost host, int[] fastForwardSpeeds, int[] rewindSpeeds) {
-        super(context, host, fastForwardSpeeds, rewindSpeeds);
-        mContext = context;
+            Context context, int[] fastForwardSpeeds, int[] rewindSpeeds) {
+        super(context, fastForwardSpeeds, rewindSpeeds);
 
         // Instantiate secondary actions
-        mRepeatAction = new PlaybackControlsRow.RepeatAction(mContext);
-        mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(mContext);
-        mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(mContext);
+        mRepeatAction = new PlaybackControlsRow.RepeatAction(getContext());
+        mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(getContext());
+        mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(getContext());
         mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsAction.OUTLINE);
         mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsAction.OUTLINE);
     }
 
     @Override
-    public void setHost(PlaybackGlueHost host) {
-        super.setHost(host);
-        if (getHost() instanceof SurfaceHolderGlueHost) {
-            ((SurfaceHolderGlueHost) getHost()).setSurfaceHolderCallback(
-                    new VideoFragmentSurfaceHolderCallback());
+    protected void onAttachedToHost(PlaybackGlueHost host) {
+        super.onAttachedToHost(host);
+        if (host instanceof SurfaceHolderGlueHost) {
+            ((SurfaceHolderGlueHost) host).setSurfaceHolderCallback(
+                    new VideoPlayerSurfaceHolderCallback());
         }
     }
 
@@ -159,23 +155,22 @@
         mPlayer.reset();
     }
 
+    /**
+     * Release internal MediaPlayer. Should not use the object after call release().
+     */
     public void release() {
         mPlayer.release();
     }
 
     @Override
-    public void onDetachedFromHost() {
+    protected void onDetachedFromHost() {
         super.onDetachedFromHost();
         reset();
         release();
     }
 
-    /**
-     * Override this method in case you need to add different secondary actions.
-     *
-     * @param secondaryActionsAdapter The adapter you need to add the {@link Action}s to.
-     */
-    protected void addSecondaryActions(ArrayObjectAdapter secondaryActionsAdapter) {
+    @Override
+    protected void onCreateSecondaryActions(ArrayObjectAdapter secondaryActionsAdapter) {
         secondaryActionsAdapter.add(mRepeatAction);
         secondaryActionsAdapter.add(mThumbsDownAction);
         secondaryActionsAdapter.add(mThumbsUpAction);
@@ -188,22 +183,6 @@
         mPlayer.setDisplay(surfaceHolder);
     }
 
-    /**
-     * Creates a presenter ({@link PlaybackControlsRowPresenter}) for rendering playback controls.
-     * @return
-     */
-    public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
-        PlaybackControlsRowPresenter presenter = super.createControlsRowAndPresenter();
-        mControlsRow = getControlsRow();
-
-        // Add secondary actions and change the control row color.
-        ArrayObjectAdapter secondaryActions = new ArrayObjectAdapter(
-                new ControlButtonPresenterSelector());
-        mControlsRow.setSecondaryActionsAdapter(secondaryActions);
-        addSecondaryActions(secondaryActions);
-        return presenter;
-    }
-
     @Override
     public void enableProgressUpdating(final boolean enabled) {
         if (!enabled) {
@@ -325,27 +304,7 @@
     }
 
     @Override
-    protected void startPlayback(int speed) throws IllegalStateException {
-        play();
-    }
-
-    @Override
-    protected void pausePlayback() {
-        pause();
-    }
-
-    @Override
-    protected void skipToNext() {
-        // Not supported.
-    }
-
-    @Override
-    protected void skipToPrevious() {
-        // Not supported.
-    }
-
-    @Override
-    public void play() {
+    public void play(int speed) {
         mPlayer.start();
         onMetadataChanged();
         onStateChanged();
@@ -477,7 +436,7 @@
         mPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
             @Override
             public void onBufferingUpdate(MediaPlayer mp, int percent) {
-                mControlsRow.setBufferedProgress((int) (mp.getDuration() * (percent / 100f)));
+                getControlsRow().setBufferedProgress((int) (mp.getDuration() * (percent / 100f)));
             }
         });
         mPlayer.prepareAsync();
@@ -485,23 +444,12 @@
     }
 
     /**
-     * Call to <code>startPlayback(1)</code>.
-     *
-     * @throws IllegalStateException See {@link MediaPlayer} for further information about it's
-     *                               different states when setting a data source and preparing it
-     *                               to be played.
-     */
-    public void startPlayback() throws IllegalStateException {
-        startPlayback(1);
-    }
-
-    /**
-     * This is a listener implementation for the {@link OnItemViewSelectedListener} of the {@link
-     * PlaybackFragment}. This implementation is required in order to detect KEY_DOWN events
+     * This is a listener implementation for the {@link OnItemViewSelectedListener}.
+     * This implementation is required in order to detect KEY_DOWN events
      * on the {@link android.support.v17.leanback.widget.PlaybackControlsRow.FastForwardAction} and
      * {@link android.support.v17.leanback.widget.PlaybackControlsRow.RewindAction}. Thus you
      * should <u>NOT</u> set another {@link OnItemViewSelectedListener} on your
-     * {@link PlaybackFragment}. Instead, override this method and call its super (this)
+     * Fragment. Instead, override this method and call its super (this)
      * implementation.
      *
      * @see OnItemViewSelectedListener#onItemSelected(
@@ -523,10 +471,10 @@
     }
 
     /**
-     * Implements {@link SurfaceHolder.Callback} that can then be set on the \
-     * {@link android.support.v17.leanback.app.PlaybackGlue.PlaybackGlueHost}
+     * Implements {@link SurfaceHolder.Callback} that can then be set on the
+     * {@link PlaybackGlueHost}.
      */
-    private class VideoFragmentSurfaceHolderCallback implements SurfaceHolder.Callback {
+    class VideoPlayerSurfaceHolderCallback implements SurfaceHolder.Callback {
         private boolean mMediaPlayerReset = true;
 
         @Override
diff --git a/v17/leanback/src/android/support/v17/leanback/media/PlaybackControlGlue.java b/v17/leanback/src/android/support/v17/leanback/media/PlaybackControlGlue.java
new file mode 100644
index 0000000..bdd67fb
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/media/PlaybackControlGlue.java
@@ -0,0 +1,821 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v17.leanback.media;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
+import android.support.v17.leanback.widget.OnActionClickedListener;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * A helper class for managing a {@link PlaybackControlsRow}
+ * and {@link PlaybackGlueHost} that implements a
+ * recommended approach to handling standard playback control actions such as play/pause,
+ * fast forward/rewind at progressive speed levels, and skip to next/previous. This helper class
+ * is a glue layer in that manages the configuration of and interaction between the
+ * leanback UI components by defining a functional interface to the media player.
+ *
+ * <p>You can instantiate a concrete subclass such as MediaPlayerGlue or you must
+ * subclass this abstract helper.  To create a subclass you must implement all of the
+ * abstract methods and the subclass must invoke {@link #onMetadataChanged()} and
+ * {@link #onStateChanged()} appropriately.
+ * </p>
+ *
+ * <p>To use an instance of the glue layer, first construct an instance.  Constructor parameters
+ * inform the glue what speed levels are supported for fast forward/rewind.
+ * </p>
+ *
+ * <p>You may override {@link #onCreateControlsRowAndPresenter()} which will set a controls
+ * row and return a row presenter you can use to present the row.
+ * </p>
+ *
+ * <p>The helper sets a {@link SparseArrayObjectAdapter}
+ * on the controls row as the primary actions adapter, and adds actions to it. You can provide
+ * additional actions by overriding {@link #onCreatePrimaryActions}. This helper does not
+ * deal in secondary actions so those you may add separately.
+ * </p>
+ *
+ * <p>Provide a click listener on your fragment and if an action is clicked, call
+ * {@link #onActionClicked}.
+ * </p>
+ *
+ * <p>This helper implements a key event handler. If you pass a
+ * {@link PlaybackGlueHost}, it will configure it's
+ * fragment to intercept all key events.  Otherwise, you should set the glue object as key event
+ * handler to the ViewHolder when bound by your row presenter; see
+ * {@link RowPresenter.ViewHolder#setOnKeyListener(android.view.View.OnKeyListener)}.
+ * </p>
+ *
+ * <p>To update the controls row progress during playback, override {@link #enableProgressUpdating}
+ * to manage the lifecycle of a periodic callback to {@link #updateProgress()}.
+ * {@link #getUpdatePeriod()} provides a recommended update period.
+ * </p>
+ *
+ */
+public abstract class PlaybackControlGlue extends PlaybackGlue
+        implements OnActionClickedListener, View.OnKeyListener {
+    /**
+     * The adapter key for the first custom control on the left side
+     * of the predefined primary controls.
+     */
+    public static final int ACTION_CUSTOM_LEFT_FIRST = 0x1;
+
+    /**
+     * The adapter key for the skip to previous control.
+     */
+    public static final int ACTION_SKIP_TO_PREVIOUS = 0x10;
+
+    /**
+     * The adapter key for the rewind control.
+     */
+    public static final int ACTION_REWIND = 0x20;
+
+    /**
+     * The adapter key for the play/pause control.
+     */
+    public static final int ACTION_PLAY_PAUSE = 0x40;
+
+    /**
+     * The adapter key for the fast forward control.
+     */
+    public static final int ACTION_FAST_FORWARD = 0x80;
+
+    /**
+     * The adapter key for the skip to next control.
+     */
+    public static final int ACTION_SKIP_TO_NEXT = 0x100;
+
+    /**
+     * The adapter key for the first custom control on the right side
+     * of the predefined primary controls.
+     */
+    public static final int ACTION_CUSTOM_RIGHT_FIRST = 0x1000;
+
+    /**
+     * Invalid playback speed.
+     */
+    public static final int PLAYBACK_SPEED_INVALID = -1;
+
+    /**
+     * Speed representing playback state that is paused.
+     */
+    public static final int PLAYBACK_SPEED_PAUSED = 0;
+
+    /**
+     * Speed representing playback state that is playing normally.
+     */
+    public static final int PLAYBACK_SPEED_NORMAL = 1;
+
+    /**
+     * The initial (level 0) fast forward playback speed.
+     * The negative of this value is for rewind at the same speed.
+     */
+    public static final int PLAYBACK_SPEED_FAST_L0 = 10;
+
+    /**
+     * The level 1 fast forward playback speed.
+     * The negative of this value is for rewind at the same speed.
+     */
+    public static final int PLAYBACK_SPEED_FAST_L1 = 11;
+
+    /**
+     * The level 2 fast forward playback speed.
+     * The negative of this value is for rewind at the same speed.
+     */
+    public static final int PLAYBACK_SPEED_FAST_L2 = 12;
+
+    /**
+     * The level 3 fast forward playback speed.
+     * The negative of this value is for rewind at the same speed.
+     */
+    public static final int PLAYBACK_SPEED_FAST_L3 = 13;
+
+    /**
+     * The level 4 fast forward playback speed.
+     * The negative of this value is for rewind at the same speed.
+     */
+    public static final int PLAYBACK_SPEED_FAST_L4 = 14;
+
+    static final String TAG = "PlaybackControlGlue";
+    static final boolean DEBUG = false;
+
+    static final int MSG_UPDATE_PLAYBACK_STATE = 100;
+    private static final int UPDATE_PLAYBACK_STATE_DELAY_MS = 2000;
+    private static final int NUMBER_OF_SEEK_SPEEDS = PLAYBACK_SPEED_FAST_L4
+            - PLAYBACK_SPEED_FAST_L0 + 1;
+
+    private final int[] mFastForwardSpeeds;
+    private final int[] mRewindSpeeds;
+    private PlaybackControlsRow mControlsRow;
+    private PlaybackControlsRowPresenter mControlsRowPresenter;
+    private PlaybackControlsRow.PlayPauseAction mPlayPauseAction;
+    private PlaybackControlsRow.SkipNextAction mSkipNextAction;
+    private PlaybackControlsRow.SkipPreviousAction mSkipPreviousAction;
+    private PlaybackControlsRow.FastForwardAction mFastForwardAction;
+    private PlaybackControlsRow.RewindAction mRewindAction;
+    private int mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
+    private boolean mFadeWhenPlaying = true;
+
+    static class UpdatePlaybackStateHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_UPDATE_PLAYBACK_STATE) {
+                PlaybackControlGlue glue = ((WeakReference<PlaybackControlGlue>) msg.obj).get();
+                if (glue != null) {
+                    glue.updatePlaybackState();
+                }
+            }
+        }
+    }
+
+    static final Handler sHandler = new UpdatePlaybackStateHandler();
+
+    final WeakReference<PlaybackControlGlue> mGlueWeakReference =  new WeakReference(this);
+
+    /**
+     * Constructor for the glue.
+     *
+     * @param context
+     * @param seekSpeeds Array of seek speeds for fast forward and rewind.
+     */
+    public PlaybackControlGlue(Context context, int[] seekSpeeds) {
+        this(context, seekSpeeds, seekSpeeds);
+    }
+
+    /**
+     * Constructor for the glue.
+     *
+     * @param context
+     * @param fastForwardSpeeds Array of seek speeds for fast forward.
+     * @param rewindSpeeds Array of seek speeds for rewind.
+     */
+    public PlaybackControlGlue(Context context,
+                               int[] fastForwardSpeeds,
+                               int[] rewindSpeeds) {
+        super(context);
+        if (fastForwardSpeeds.length == 0 || fastForwardSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
+            throw new IllegalStateException("invalid fastForwardSpeeds array size");
+        }
+        mFastForwardSpeeds = fastForwardSpeeds;
+        if (rewindSpeeds.length == 0 || rewindSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
+            throw new IllegalStateException("invalid rewindSpeeds array size");
+        }
+        mRewindSpeeds = rewindSpeeds;
+    }
+
+    @Override
+    protected void onAttachedToHost(PlaybackGlueHost host) {
+        super.onAttachedToHost(host);
+        host.setOnKeyInterceptListener(this);
+        host.setOnActionClickedListener(this);
+        if (getControlsRow() == null || getControlsRowPresenter() == null) {
+            onCreateControlsRowAndPresenter();
+        }
+        host.setPlaybackRowPresenter(getControlsRowPresenter());
+        host.setPlaybackRow(getControlsRow());
+    }
+
+    @Override
+    protected void onHostStart() {
+        enableProgressUpdating(true);
+    }
+
+    @Override
+    protected void onHostStop() {
+        enableProgressUpdating(false);
+    }
+
+    /**
+     * Instantiating a {@link PlaybackControlsRow} and corresponding
+     * {@link PlaybackControlsRowPresenter}. Subclass may override.
+     */
+    protected void onCreateControlsRowAndPresenter() {
+        PlaybackControlsRow controlsRow = new PlaybackControlsRow(this);
+        setControlsRow(controlsRow);
+
+        final AbstractDetailsDescriptionPresenter detailsPresenter =
+                new AbstractDetailsDescriptionPresenter() {
+            @Override
+            protected void onBindDescription(ViewHolder
+                                                     viewHolder, Object object) {
+                PlaybackControlGlue glue = (PlaybackControlGlue) object;
+                if (glue.hasValidMedia()) {
+                    viewHolder.getTitle().setText(glue.getMediaTitle());
+                    viewHolder.getSubtitle().setText(glue.getMediaSubtitle());
+                } else {
+                    viewHolder.getTitle().setText("");
+                    viewHolder.getSubtitle().setText("");
+                }
+            }
+        };
+
+        setControlsRowPresenter(new PlaybackControlsRowPresenter(detailsPresenter) {
+            @Override
+            protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
+                super.onBindRowViewHolder(vh, item);
+                vh.setOnKeyListener(PlaybackControlGlue.this);
+            }
+            @Override
+            protected void onUnbindRowViewHolder(RowPresenter.ViewHolder vh) {
+                super.onUnbindRowViewHolder(vh);
+                vh.setOnKeyListener(null);
+            }
+        });
+    }
+
+    /**
+     * Returns the fast forward speeds.
+     */
+    public int[] getFastForwardSpeeds() {
+        return mFastForwardSpeeds;
+    }
+
+    /**
+     * Returns the rewind speeds.
+     */
+    public int[] getRewindSpeeds() {
+        return mRewindSpeeds;
+    }
+
+    /**
+     * Sets the controls to fade after a timeout when media is playing.
+     */
+    public void setFadingEnabled(boolean enable) {
+        mFadeWhenPlaying = enable;
+        if (!mFadeWhenPlaying && getHost() != null) {
+            getHost().setFadingEnabled(false);
+        }
+    }
+
+    /**
+     * Returns true if controls are set to fade when media is playing.
+     */
+    public boolean isFadingEnabled() {
+        return mFadeWhenPlaying;
+    }
+
+    /**
+     * Sets the controls row to be managed by the glue layer.
+     * The primary actions and playback state related aspects of the row
+     * are updated by the glue.
+     */
+    public void setControlsRow(PlaybackControlsRow controlsRow) {
+        mControlsRow = controlsRow;
+        mControlsRow.setPrimaryActionsAdapter(
+                createPrimaryActionsAdapter(new ControlButtonPresenterSelector()));
+        // Add secondary actions
+        ArrayObjectAdapter secondaryActions = new ArrayObjectAdapter(
+                new ControlButtonPresenterSelector());
+        onCreateSecondaryActions(secondaryActions);
+        getControlsRow().setSecondaryActionsAdapter(secondaryActions);
+        updateControlsRow();
+    }
+
+    /**
+     * @hide
+     */
+    protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
+            PresenterSelector presenterSelector) {
+        SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
+        onCreatePrimaryActions(adapter);
+        return adapter;
+    }
+
+    /**
+     * Sets the controls row Presenter to be managed by the glue layer.
+     */
+    public void setControlsRowPresenter(PlaybackControlsRowPresenter presenter) {
+        mControlsRowPresenter = presenter;
+    }
+
+    /**
+     * Returns the playback controls row managed by the glue layer.
+     */
+    public PlaybackControlsRow getControlsRow() {
+        return mControlsRow;
+    }
+
+    /**
+     * Returns the playback controls row Presenter managed by the glue layer.
+     */
+    public PlaybackControlsRowPresenter getControlsRowPresenter() {
+        return mControlsRowPresenter;
+    }
+
+    /**
+     * Override this to start/stop a runnable to call {@link #updateProgress} at
+     * an interval such as {@link #getUpdatePeriod}.
+     */
+    public void enableProgressUpdating(boolean enable) {
+    }
+
+    /**
+     * Returns the time period in milliseconds that should be used
+     * to update the progress.  See {@link #updateProgress()}.
+     */
+    public int getUpdatePeriod() {
+        // TODO: calculate a better update period based on total duration and screen size
+        return 500;
+    }
+
+    /**
+     * Updates the progress bar based on the current media playback position.
+     */
+    public void updateProgress() {
+        int position = getCurrentPosition();
+        if (DEBUG) Log.v(TAG, "updateProgress " + position);
+        mControlsRow.setCurrentTime(position);
+    }
+
+    /**
+     * Handles action clicks.  A subclass may override this add support for additional actions.
+     */
+    @Override
+    public void onActionClicked(Action action) {
+        dispatchAction(action, null);
+    }
+
+    /**
+     * Handles key events and returns true if handled.  A subclass may override this to provide
+     * additional support.
+     */
+    @Override
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DPAD_UP:
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+            case KeyEvent.KEYCODE_BACK:
+            case KeyEvent.KEYCODE_ESCAPE:
+                boolean abortSeek = mPlaybackSpeed >= PLAYBACK_SPEED_FAST_L0
+                        || mPlaybackSpeed <= -PLAYBACK_SPEED_FAST_L0;
+                if (abortSeek) {
+                    mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
+                    play(mPlaybackSpeed);
+                    updatePlaybackStatusAfterUserAction();
+                    return keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE;
+                }
+                return false;
+        }
+        final SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
+                mControlsRow.getPrimaryActionsAdapter();
+        Action action = mControlsRow.getActionForKeyCode(primaryActionsAdapter, keyCode);
+
+        if (action != null) {
+            if (action == primaryActionsAdapter.lookup(ACTION_PLAY_PAUSE)
+                    || action == primaryActionsAdapter.lookup(ACTION_REWIND)
+                    || action == primaryActionsAdapter.lookup(ACTION_FAST_FORWARD)
+                    || action == primaryActionsAdapter.lookup(ACTION_SKIP_TO_PREVIOUS)
+                    || action == primaryActionsAdapter.lookup(ACTION_SKIP_TO_NEXT)) {
+                if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                    dispatchAction(action, event);
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Called when the given action is invoked, either by click or keyevent.
+     */
+    boolean dispatchAction(Action action, KeyEvent keyEvent) {
+        boolean handled = false;
+        if (action == mPlayPauseAction) {
+            boolean canPlay = keyEvent == null
+                    || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
+                    || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY;
+            boolean canPause = keyEvent == null
+                    || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
+                    || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PAUSE;
+            //            PLAY_PAUSE    PLAY      PAUSE
+            // playing    paused                  paused
+            // paused     playing       playing
+            // ff/rw      playing       playing   paused
+            if (canPause
+                    && (canPlay ? mPlaybackSpeed == PLAYBACK_SPEED_NORMAL :
+                        mPlaybackSpeed != PLAYBACK_SPEED_PAUSED)) {
+                mPlaybackSpeed = PLAYBACK_SPEED_PAUSED;
+                pause();
+            } else if (canPlay && mPlaybackSpeed != PLAYBACK_SPEED_NORMAL) {
+                mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
+                play(mPlaybackSpeed);
+            }
+            updatePlaybackStatusAfterUserAction();
+            handled = true;
+        } else if (action == mSkipNextAction) {
+            next();
+            handled = true;
+        } else if (action == mSkipPreviousAction) {
+            previous();
+            handled = true;
+        } else if (action == mFastForwardAction) {
+            if (mPlaybackSpeed < getMaxForwardSpeedId()) {
+                switch (mPlaybackSpeed) {
+                    case PLAYBACK_SPEED_FAST_L0:
+                    case PLAYBACK_SPEED_FAST_L1:
+                    case PLAYBACK_SPEED_FAST_L2:
+                    case PLAYBACK_SPEED_FAST_L3:
+                        mPlaybackSpeed++;
+                        break;
+                    default:
+                        mPlaybackSpeed = PLAYBACK_SPEED_FAST_L0;
+                        break;
+                }
+                play(mPlaybackSpeed);
+                updatePlaybackStatusAfterUserAction();
+            }
+            handled = true;
+        } else if (action == mRewindAction) {
+            if (mPlaybackSpeed > -getMaxRewindSpeedId()) {
+                switch (mPlaybackSpeed) {
+                    case -PLAYBACK_SPEED_FAST_L0:
+                    case -PLAYBACK_SPEED_FAST_L1:
+                    case -PLAYBACK_SPEED_FAST_L2:
+                    case -PLAYBACK_SPEED_FAST_L3:
+                        mPlaybackSpeed--;
+                        break;
+                    default:
+                        mPlaybackSpeed = -PLAYBACK_SPEED_FAST_L0;
+                        break;
+                }
+                play(mPlaybackSpeed);
+                updatePlaybackStatusAfterUserAction();
+            }
+            handled = true;
+        }
+        return handled;
+    }
+
+    private int getMaxForwardSpeedId() {
+        return PLAYBACK_SPEED_FAST_L0 + (mFastForwardSpeeds.length - 1);
+    }
+
+    private int getMaxRewindSpeedId() {
+        return PLAYBACK_SPEED_FAST_L0 + (mRewindSpeeds.length - 1);
+    }
+
+    private void updateControlsRow() {
+        updateRowMetadata();
+        sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
+        updatePlaybackState();
+    }
+
+    private void updatePlaybackStatusAfterUserAction() {
+        updatePlaybackState(mPlaybackSpeed);
+        // Sync playback state after a delay
+        sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
+        sHandler.sendMessageDelayed(sHandler.obtainMessage(MSG_UPDATE_PLAYBACK_STATE,
+                mGlueWeakReference), UPDATE_PLAYBACK_STATE_DELAY_MS);
+    }
+
+    /**
+     * Start playback at the given speed.
+     *
+     * @param speed The desired playback speed.  For normal playback this will be
+     *              {@link #PLAYBACK_SPEED_NORMAL}; higher positive values for fast forward,
+     *              and negative values for rewind.
+     */
+    public void play(int speed) {
+    }
+
+    @Override
+    public final void play() {
+        play(PLAYBACK_SPEED_NORMAL);
+    }
+
+    private void updateRowMetadata() {
+        if (mControlsRow == null) {
+            return;
+        }
+
+        if (DEBUG) Log.v(TAG, "updateRowMetadata");
+
+        if (!hasValidMedia()) {
+            mControlsRow.setImageDrawable(null);
+            mControlsRow.setTotalTime(0);
+            mControlsRow.setCurrentTime(0);
+        } else {
+            mControlsRow.setImageDrawable(getMediaArt());
+            mControlsRow.setTotalTime(getMediaDuration());
+            mControlsRow.setCurrentTime(getCurrentPosition());
+        }
+
+        if (getHost() != null) {
+            getHost().notifyPlaybackRowChanged();
+        }
+    }
+
+    void updatePlaybackState() {
+        if (hasValidMedia()) {
+            mPlaybackSpeed = getCurrentSpeedId();
+            updatePlaybackState(mPlaybackSpeed);
+        }
+    }
+
+    private void updatePlaybackState(int playbackSpeed) {
+        if (mControlsRow == null) {
+            return;
+        }
+
+        final SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
+                getControlsRow().getPrimaryActionsAdapter();
+        final long actions = getSupportedActions();
+        if ((actions & ACTION_SKIP_TO_PREVIOUS) != 0) {
+            if (mSkipPreviousAction == null) {
+                mSkipPreviousAction = new PlaybackControlsRow.SkipPreviousAction(getContext());
+            }
+            primaryActionsAdapter.set(ACTION_SKIP_TO_PREVIOUS, mSkipPreviousAction);
+        } else {
+            primaryActionsAdapter.clear(ACTION_SKIP_TO_PREVIOUS);
+            mSkipPreviousAction = null;
+        }
+        if ((actions & ACTION_REWIND) != 0) {
+            if (mRewindAction == null) {
+                mRewindAction = new PlaybackControlsRow.RewindAction(
+                        getContext(),
+                        mRewindSpeeds.length);
+            }
+            primaryActionsAdapter.set(ACTION_REWIND, mRewindAction);
+        } else {
+            primaryActionsAdapter.clear(ACTION_REWIND);
+            mRewindAction = null;
+        }
+        if ((actions & ACTION_PLAY_PAUSE) != 0) {
+            if (mPlayPauseAction == null) {
+                mPlayPauseAction = new PlaybackControlsRow.PlayPauseAction(getContext());
+            }
+            primaryActionsAdapter.set(ACTION_PLAY_PAUSE, mPlayPauseAction);
+        } else {
+            primaryActionsAdapter.clear(ACTION_PLAY_PAUSE);
+            mPlayPauseAction = null;
+        }
+        if ((actions & ACTION_FAST_FORWARD) != 0) {
+            if (mFastForwardAction == null) {
+                mFastForwardAction = new PlaybackControlsRow.FastForwardAction(
+                        getContext(),
+                        mFastForwardSpeeds.length);
+            }
+            primaryActionsAdapter.set(ACTION_FAST_FORWARD, mFastForwardAction);
+        } else {
+            primaryActionsAdapter.clear(ACTION_FAST_FORWARD);
+            mFastForwardAction = null;
+        }
+        if ((actions & ACTION_SKIP_TO_NEXT) != 0) {
+            if (mSkipNextAction == null) {
+                mSkipNextAction = new PlaybackControlsRow.SkipNextAction(getContext());
+            }
+            primaryActionsAdapter.set(ACTION_SKIP_TO_NEXT, mSkipNextAction);
+        } else {
+            primaryActionsAdapter.clear(ACTION_SKIP_TO_NEXT);
+            mSkipNextAction = null;
+        }
+
+        if (mFastForwardAction != null) {
+            int index = 0;
+            if (playbackSpeed >= PLAYBACK_SPEED_FAST_L0) {
+                index = playbackSpeed - PLAYBACK_SPEED_FAST_L0 + 1;
+            }
+            if (mFastForwardAction.getIndex() != index) {
+                mFastForwardAction.setIndex(index);
+                notifyItemChanged(primaryActionsAdapter, mFastForwardAction);
+            }
+        }
+        if (mRewindAction != null) {
+            int index = 0;
+            if (playbackSpeed <= -PLAYBACK_SPEED_FAST_L0) {
+                index = -playbackSpeed - PLAYBACK_SPEED_FAST_L0 + 1;
+            }
+            if (mRewindAction.getIndex() != index) {
+                mRewindAction.setIndex(index);
+                notifyItemChanged(primaryActionsAdapter, mRewindAction);
+            }
+        }
+
+        if (playbackSpeed == PLAYBACK_SPEED_PAUSED) {
+            updateProgress();
+            enableProgressUpdating(false);
+        } else {
+            enableProgressUpdating(true);
+        }
+
+        if (mFadeWhenPlaying && getHost() != null) {
+            getHost().setFadingEnabled(playbackSpeed == PLAYBACK_SPEED_NORMAL);
+        }
+
+        if (mPlayPauseAction != null) {
+            int index = playbackSpeed == PLAYBACK_SPEED_PAUSED
+                    ? PlaybackControlsRow.PlayPauseAction.PLAY
+                    : PlaybackControlsRow.PlayPauseAction.PAUSE;
+            if (mPlayPauseAction.getIndex() != index) {
+                mPlayPauseAction.setIndex(index);
+                notifyItemChanged(primaryActionsAdapter, mPlayPauseAction);
+            }
+        }
+    }
+
+    private static void notifyItemChanged(SparseArrayObjectAdapter adapter, Object object) {
+        int index = adapter.indexOf(object);
+        if (index >= 0) {
+            adapter.notifyArrayItemRangeChanged(index, 1);
+        }
+    }
+
+    private static String getSpeedString(int speed) {
+        switch (speed) {
+            case PLAYBACK_SPEED_INVALID:
+                return "PLAYBACK_SPEED_INVALID";
+            case PLAYBACK_SPEED_PAUSED:
+                return "PLAYBACK_SPEED_PAUSED";
+            case PLAYBACK_SPEED_NORMAL:
+                return "PLAYBACK_SPEED_NORMAL";
+            case PLAYBACK_SPEED_FAST_L0:
+                return "PLAYBACK_SPEED_FAST_L0";
+            case PLAYBACK_SPEED_FAST_L1:
+                return "PLAYBACK_SPEED_FAST_L1";
+            case PLAYBACK_SPEED_FAST_L2:
+                return "PLAYBACK_SPEED_FAST_L2";
+            case PLAYBACK_SPEED_FAST_L3:
+                return "PLAYBACK_SPEED_FAST_L3";
+            case PLAYBACK_SPEED_FAST_L4:
+                return "PLAYBACK_SPEED_FAST_L4";
+            case -PLAYBACK_SPEED_FAST_L0:
+                return "-PLAYBACK_SPEED_FAST_L0";
+            case -PLAYBACK_SPEED_FAST_L1:
+                return "-PLAYBACK_SPEED_FAST_L1";
+            case -PLAYBACK_SPEED_FAST_L2:
+                return "-PLAYBACK_SPEED_FAST_L2";
+            case -PLAYBACK_SPEED_FAST_L3:
+                return "-PLAYBACK_SPEED_FAST_L3";
+            case -PLAYBACK_SPEED_FAST_L4:
+                return "-PLAYBACK_SPEED_FAST_L4";
+        }
+        return null;
+    }
+
+    /**
+     * Returns true if there is a valid media item.
+     */
+    public abstract boolean hasValidMedia();
+
+    /**
+     * Returns true if media is currently playing.
+     */
+    public abstract boolean isMediaPlaying();
+
+    /**
+     * Returns the title of the media item.
+     */
+    public abstract CharSequence getMediaTitle();
+
+    /**
+     * Returns the subtitle of the media item.
+     */
+    public abstract CharSequence getMediaSubtitle();
+
+    /**
+     * Returns the duration of the media item in milliseconds.
+     */
+    public abstract int getMediaDuration();
+
+    /**
+     * Returns a bitmap of the art for the media item.
+     */
+    public abstract Drawable getMediaArt();
+
+    /**
+     * Returns a bitmask of actions supported by the media player.
+     */
+    public abstract long getSupportedActions();
+
+    /**
+     * Returns the current playback speed.  When playing normally,
+     * {@link #PLAYBACK_SPEED_NORMAL} should be returned.
+     */
+    public abstract int getCurrentSpeedId();
+
+    /**
+     * Returns the current position of the media item in milliseconds.
+     */
+    public abstract int getCurrentPosition();
+
+    /**
+     * May be overridden to add primary actions to the adapter.
+     *
+     * @param primaryActionsAdapter The adapter to add primary {@link Action}s.
+     */
+    protected void onCreatePrimaryActions(SparseArrayObjectAdapter primaryActionsAdapter) {
+    }
+
+    /**
+     * May be overridden to add secondary actions to the adapter.
+     *
+     * @param secondaryActionsAdapter The adapter you need to add the {@link Action}s to.
+     */
+    protected void onCreateSecondaryActions(ArrayObjectAdapter secondaryActionsAdapter) {
+    }
+
+    /**
+     * Must be called appropriately by a subclass when the playback state has changed.
+     * It updates the playback state displayed on the media player.
+     */
+    protected void onStateChanged() {
+        if (DEBUG) Log.v(TAG, "onStateChanged");
+        // If a pending control button update is present, delay
+        // the update until the state settles.
+        if (!hasValidMedia()) {
+            return;
+        }
+        if (sHandler.hasMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference)) {
+            sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
+            if (getCurrentSpeedId() != mPlaybackSpeed) {
+                if (DEBUG) Log.v(TAG, "Status expectation mismatch, delaying update");
+                sHandler.sendMessageDelayed(sHandler.obtainMessage(MSG_UPDATE_PLAYBACK_STATE,
+                        mGlueWeakReference), UPDATE_PLAYBACK_STATE_DELAY_MS);
+            } else {
+                if (DEBUG) Log.v(TAG, "Update state matches expectation");
+                updatePlaybackState();
+            }
+        } else {
+            updatePlaybackState();
+        }
+    }
+
+    /**
+     * Must be called appropriately by a subclass when the metadata state has changed.
+     */
+    protected void onMetadataChanged() {
+        if (DEBUG) Log.v(TAG, "onMetadataChanged");
+        updateRowMetadata();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/media/PlaybackGlue.java b/v17/leanback/src/android/support/v17/leanback/media/PlaybackGlue.java
new file mode 100644
index 0000000..8ed4b93
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/media/PlaybackGlue.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v17.leanback.media;
+
+import android.content.Context;
+import android.support.annotation.CallSuper;
+
+/**
+ * Base class for abstraction of media play/pause feature. A subclass of PlaybackGlue will contain
+ * implementation of Media Player. App initializes PlaybackGlue subclass, associated it with a
+ * {@link PlaybackGlueHost}. {@link PlaybackGlueHost} is typically implemented by a Fragment or
+ * an Activity, it provides the environment to render UI for PlaybackGlue object, it optionally
+ * provides SurfaceHolder via {@link SurfaceHolderGlueHost} to render video.
+ *
+ * @see PlaybackGlueHost
+ */
+public abstract class PlaybackGlue {
+    private final Context mContext;
+    private PlaybackGlueHost mPlaybackGlueHost;
+
+    /**
+     * Interface to allow clients to take action once the video is ready to play.
+     */
+    public abstract static class PlayerCallback {
+        /**
+         * This method is fired when the video is ready for playback.
+         */
+        public abstract void onReadyForPlayback();
+    }
+
+    /**
+     * Constructor.
+     */
+    public PlaybackGlue(Context context) {
+        this.mContext = context;
+    }
+
+    /**
+     * Returns the context.
+     */
+    public Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Returns true when the media player is ready to start media playback. Subclasses must
+     * implement this method correctly.
+     */
+    public boolean isReadyForPlayback() {
+        return true;
+    }
+
+    /**
+     * Sets the {@link PlayerCallback} callback.
+     */
+    public void setPlayerCallback(PlayerCallback playerCallback) {
+    }
+
+    /**
+     * Starts the media player.
+     */
+    public void play() {
+    }
+
+    /**
+     * Pauses the media player.
+     */
+    public void pause() {
+    }
+
+    /**
+     * Goes to the next media item. This method is optional.
+     */
+    public void next() {
+    }
+
+    /**
+     * Goes to the previous media item. This method is optional.
+     */
+    public void previous() {
+    }
+
+    /**
+     * This method is used to configure the {@link PlaybackGlueHost} with required listeners.
+     */
+    public final void setHost(PlaybackGlueHost host) {
+        if (mPlaybackGlueHost == host) {
+            return;
+        }
+        if (mPlaybackGlueHost != null) {
+            mPlaybackGlueHost.attachToGlue(null);
+        }
+        mPlaybackGlueHost = host;
+        if (mPlaybackGlueHost != null) {
+            mPlaybackGlueHost.attachToGlue(this);
+        }
+    }
+
+    /**
+     * This method is called when {@link PlaybackGlueHost is started. Subclass may override.
+     */
+    protected void onHostStart() {
+    }
+
+    /**
+     * This method is called when {@link PlaybackGlueHost is stopped. Subclass may override.
+     */
+    protected void onHostStop() {
+    }
+
+    /**
+     * This method is called when {@link PlaybackGlueHost is resumed. Subclass may override.
+     */
+    protected void onHostResume() {
+    }
+
+    /**
+     * This method is called when {@link PlaybackGlueHost is paused. Subclass may override.
+     */
+    protected void onHostPause() {
+    }
+
+    /**
+     * This method is called attached to associated {@link PlaybackGlueHost}. Subclass may override
+     * and call super.onAttachedToHost().
+     */
+    @CallSuper
+    protected void onAttachedToHost(PlaybackGlueHost host) {
+        mPlaybackGlueHost = host;
+        mPlaybackGlueHost.setHostCallback(new PlaybackGlueHost.HostCallback() {
+            @Override
+            public void onHostStart() {
+                PlaybackGlue.this.onHostStart();
+            }
+
+            @Override
+            public void onHostStop() {
+                PlaybackGlue.this.onHostStop();
+            }
+
+            @Override
+            public void onHostResume() {
+                PlaybackGlue.this.onHostResume();
+            }
+
+            @Override
+            public void onHostPause() {
+                PlaybackGlue.this.onHostPause();
+            }
+        });
+    }
+
+    /**
+     * This method is called when current associated {@link PlaybackGlueHost} is attached to a
+     * different {@link PlaybackGlue}. Subclass may override and call super.onDetachedFromHost()
+     * at last.
+     */
+    @CallSuper
+    protected void onDetachedFromHost() {
+        if (mPlaybackGlueHost != null) {
+            mPlaybackGlueHost.setHostCallback(null);
+            mPlaybackGlueHost = null;
+        }
+    }
+
+    /**
+     * @return Associated {@link PlaybackGlueHost} or null if not attached to host.
+     */
+    public PlaybackGlueHost getHost() {
+        return mPlaybackGlueHost;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/media/PlaybackGlueHost.java b/v17/leanback/src/android/support/v17/leanback/media/PlaybackGlueHost.java
new file mode 100644
index 0000000..3cf086b
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/media/PlaybackGlueHost.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v17.leanback.media;
+
+import android.support.v17.leanback.widget.OnActionClickedListener;
+import android.support.v17.leanback.widget.PlaybackRowPresenter;
+import android.support.v17.leanback.widget.Row;
+import android.view.View;
+
+/**
+ * This class represents the UI (e.g. Fragment/Activity) hosting playback controls and
+ * defines the interaction between {@link PlaybackGlue} and the host.
+ * PlaybackGlueHost provides the following functions:
+ * <li>Render UI of PlaybackGlue: {@link #setPlaybackRow(Row)},
+ * {@link #setPlaybackRowPresenter(PlaybackRowPresenter)}.
+ * </li>
+ * <li>Callback for fragment/activity onStart/onStop: {@link #setHostCallback(HostCallback)}.
+ * </li>
+ * <li>Auto fade out controls after a short period: {@link #setFadingEnabled(boolean)}.
+ * </li>
+ * <li>Key listener and ActionListener. {@link #setOnKeyInterceptListener(View.OnKeyListener)},
+ * {@link #setOnActionClickedListener(OnActionClickedListener)}.
+ * </li>
+ *
+ * Subclass of PlaybackGlueHost may implement optional interface e.g. {@link SurfaceHolderGlueHost}
+ * to provide SurfaceView. These optional interface should be used during
+ * {@link PlaybackGlue#setHost(PlaybackGlueHost)}.
+ */
+public abstract class PlaybackGlueHost {
+    PlaybackGlue mGlue;
+
+    /**
+     * Callbacks triggered by the host(e.g. fragment) hosting the video controls/surface.
+     *
+     * @see #setHostCallback(HostCallback)
+     */
+    public abstract static class HostCallback {
+        /**
+         * Callback triggered once the host(fragment) has started.
+         */
+        public void onHostStart() {
+        }
+
+        /**
+         * Callback triggered once the host(fragment) has stopped.
+         */
+        public void onHostStop() {
+        }
+
+        /**
+         * Callback triggered once the host(fragment) has paused.
+         */
+        public void onHostPause() {
+        }
+
+        /**
+         * Callback triggered once the host(fragment) has resumed.
+         */
+        public void onHostResume() {
+        }
+    }
+
+    /**
+     * Enables or disables view fading.  If enabled, the view will be faded in when the
+     * fragment starts and will fade out after a time period.
+     */
+    public void setFadingEnabled(boolean enable) {
+    }
+
+    /**
+     * Fade out views immediately.
+     */
+    public void fadeOut() {
+    }
+
+    /**
+     * Sets the {@link android.view.View.OnKeyListener} on the host. This would trigger
+     * the listener when a {@link android.view.KeyEvent} is unhandled by the host.
+     */
+    public void setOnKeyInterceptListener(View.OnKeyListener onKeyListener) {
+    }
+
+    /**
+     * Sets the {@link View.OnClickListener} on this fragment.
+     */
+    public void setOnActionClickedListener(OnActionClickedListener listener) {}
+
+    /**
+     * Sets the host {@link HostCallback} callback on the host. This method should only be called
+     * by {@link PlaybackGlue}. App should not directly call this method, app should override
+     * {@link PlaybackGlue#onHostStart()} etc.
+     */
+    public void setHostCallback(HostCallback callback) {
+    }
+
+    /**
+     * Notifies host about a change so it can update the view.
+     */
+    public void notifyPlaybackRowChanged() {}
+
+    /**
+     * Sets {@link PlaybackRowPresenter} for rendering the playback controls.
+     */
+    public void setPlaybackRowPresenter(PlaybackRowPresenter presenter) {}
+
+    /**
+     * Sets the {@link Row} that represents the information on control items that needs
+     * to be rendered.
+     */
+    public void setPlaybackRow(Row row) {}
+
+    final void attachToGlue(PlaybackGlue glue) {
+        if (mGlue != null) {
+            mGlue.onDetachedFromHost();
+        }
+        mGlue = glue;
+        if (mGlue != null) {
+            mGlue.onAttachedToHost(this);
+        }
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/media/SurfaceHolderGlueHost.java b/v17/leanback/src/android/support/v17/leanback/media/SurfaceHolderGlueHost.java
new file mode 100644
index 0000000..119626a
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/media/SurfaceHolderGlueHost.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v17.leanback.media;
+
+import android.view.SurfaceHolder;
+
+/**
+ * Optional interface to be implemented by any subclass of {@link PlaybackGlueHost} that contains
+ * a {@link android.view.SurfaceView}. This will allow subclass of {@link PlaybackGlue} to setup
+ * the surface holder callback during {@link PlaybackGlue#setHost(PlaybackGlueHost)}.
+ *
+ * @see PlaybackGlue#setHost(PlaybackGlueHost)
+ * @see MediaPlayerGlue
+ */
+public interface SurfaceHolderGlueHost {
+    /**
+     * Sets the {@link SurfaceHolder.Callback} on the the host.
+     */
+    void setSurfaceHolderCallback(SurfaceHolder.Callback callback);
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
index ca6ee77..7d2904a 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
@@ -151,6 +151,9 @@
      * @param actions The list of actions to be managed.
      */
     public void setActions(List<GuidedAction> actions) {
+        if (!mIsSubAdapter) {
+            mStylist.collapseAction(false);
+        }
         mActionOnFocusListener.unFocus();
         mActions.clear();
         mActions.addAll(actions);
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
index 66dc12d..0d2b45e 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
@@ -18,6 +18,7 @@
 import android.graphics.Paint;
 import android.support.annotation.RestrictTo;
 import android.support.v17.leanback.R;
+import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -125,6 +126,11 @@
         } else {
             vh.mTitleView.setText(headerItem.getName());
             if (vh.mDescriptionView != null) {
+                if (TextUtils.isEmpty(headerItem.getDescription())) {
+                    vh.mDescriptionView.setVisibility(View.GONE);
+                } else {
+                    vh.mDescriptionView.setVisibility(View.VISIBLE);
+                }
                 vh.mDescriptionView.setText(headerItem.getDescription());
             }
             viewHolder.view.setContentDescription(headerItem.getContentDescription());
diff --git a/v17/leanback/tests/AndroidManifest.xml b/v17/leanback/tests/AndroidManifest.xml
index 16b78cb..6e6b157 100644
--- a/v17/leanback/tests/AndroidManifest.xml
+++ b/v17/leanback/tests/AndroidManifest.xml
@@ -38,6 +38,10 @@
                 android:theme="@style/Theme.Leanback.GuidedStep"
                 android:exported="true" />
 
+        <activity android:name="android.support.v17.leanback.app.RowsFragmentTestActivity"
+            android:theme="@style/Theme.Leanback"
+            android:exported="true" />
+
         <activity android:name="android.support.v17.leanback.app.BrowseFragmentTestActivity"
                   android:theme="@style/Theme.Leanback.Browse"
                   android:exported="true" />
@@ -46,6 +50,10 @@
                   android:theme="@style/Theme.Leanback"
                   android:exported="true" />
 
+        <activity android:name="android.support.v17.leanback.app.RowsSupportFragmentTestActivity"
+            android:theme="@style/Theme.Leanback"
+            android:exported="true" />
+
         <activity android:name="android.support.v17.leanback.app.BrowseSupportFragmentTestActivity"
                   android:theme="@style/Theme.Leanback.Browse"
                   android:exported="true" />
@@ -58,6 +66,14 @@
                   android:theme="@style/Theme.Leanback.GuidedStep"
                   android:exported="true" />
 
+        <activity android:name="android.support.v17.leanback.app.PlaybackTestActivity"
+                  android:theme="@style/Theme.Leanback"
+                  android:exported="true" />
+
+        <activity android:name="android.support.v17.leanback.app.PlaybackSupportTestActivity"
+                  android:theme="@style/Theme.Leanback"
+                  android:exported="true" />
+
         <activity android:name="android.support.v17.leanback.app.PlaybackOverlayTestActivity"
                   android:theme="@style/Theme.Leanback"
                   android:exported="true" />
diff --git a/v17/leanback/tests/generatev4.py b/v17/leanback/tests/generatev4.py
index 3e25503..ecae656 100755
--- a/v17/leanback/tests/generatev4.py
+++ b/v17/leanback/tests/generatev4.py
@@ -19,11 +19,11 @@
 
 print "Generate v4 fragment related code for leanback"
 
-files = ['BrowseTest', 'GuidedStepTest']
+files = ['BrowseTest', 'GuidedStepTest', 'RowsTest']
 
 cls = ['BrowseTest', 'Background', 'Base', 'BaseRow', 'Browse', 'Details', 'Error', 'Headers',
       'PlaybackOverlay', 'Rows', 'Search', 'VerticalGrid', 'Branded',
-      'GuidedStepTest', 'GuidedStep']
+      'GuidedStepTest', 'GuidedStep', 'RowsTest']
 
 for w in files:
     print "copy {}Fragment to {}SupportFragment".format(w, w)
@@ -68,7 +68,7 @@
     file.close()
     outfile.close()
 
-testcls = ['Browse', 'GuidedStep', 'VerticalGrid']
+testcls = ['Browse', 'GuidedStep', 'VerticalGrid', 'Rows']
 
 for w in testcls:
     print "copy {}FrgamentTest to {}SupportFragmentTest".format(w, w)
@@ -95,7 +95,7 @@
     file.close()
     outfile.close()
 
-testcls = ['Browse', 'GuidedStep']
+testcls = ['Browse', 'GuidedStep', 'Rows']
 
 for w in testcls:
     print "copy {}FragmentTestActivity to {}SupportFragmentTestActivity".format(w, w)
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsFragmentTest.java
index c6662ab..c24a7e5 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsFragmentTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/DetailsFragmentTest.java
@@ -16,6 +16,7 @@
 package android.support.v17.leanback.app;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import android.content.Intent;
 import android.graphics.Rect;
@@ -87,6 +88,9 @@
                 bitmapDrawable.getBounds().height());
         assertEquals(0, bitmapDrawable.getVerticalOffset());
 
+        assertTrue("TitleView is visible", detailsFragment.getView()
+                .findViewById(R.id.browse_title_group).getVisibility() == View.VISIBLE);
+
         activityTestRule.runOnUiThread(new Runnable() {
             @Override
             public void run() {
@@ -97,7 +101,9 @@
         PollingCheck.waitFor(4000, new PollingCheck.PollingCheckCondition() {
             @Override
             public boolean canProceed() {
-                return bitmapDrawable.getVerticalOffset() == mDefaultVerticalOffset;
+                return bitmapDrawable.getVerticalOffset() == mDefaultVerticalOffset
+                        && detailsFragment.getView()
+                        .findViewById(R.id.browse_title_group).getVisibility() != View.VISIBLE;
             }
         });
 
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java
index 192b20e..50aaa62 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java
@@ -29,6 +29,7 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v17.leanback.testutils.PollingCheck;
 import android.support.v17.leanback.widget.GuidedAction;
+import android.support.v17.leanback.widget.GuidedActionsStylist;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -354,4 +355,58 @@
         PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
         verify(first, timeout(ON_DESTROY_TIMEOUT).times(1)).onDestroy();
     }
+
+    @Test
+    public void setActionsWhenSubActionsExpanded() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        GuidedStepTestFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                List<GuidedAction> subActions = new ArrayList<GuidedAction>();
+                subActions.add(new GuidedAction.Builder().id(2000).title("item1").build());
+                actions.add(new GuidedAction.Builder().id(1000).subActions(subActions)
+                        .title("list").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), any(Bundle.class));
+        doAnswer(new Answer<Boolean>() {
+            public Boolean answer(InvocationOnMock invocation) {
+                GuidedStepTestFragment.Provider obj = (GuidedStepTestFragment.Provider)
+                        invocation.getMock();
+                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
+                if (action.getId() == 2000) {
+                    List<GuidedAction> newActions = new ArrayList<GuidedAction>();
+                    newActions.add(new GuidedAction.Builder().id(1001).title("item2").build());
+                    obj.getFragment().setActions(newActions);
+                    return false;
+                }
+                return false;
+            }
+        }).when(first).onSubGuidedActionClicked(any(GuidedAction.class));
+
+        final GuidedStepFragmentTestActivity activity = launchTestActivity(firstFragmentName);
+
+        // after clicked, it sub actions list should expand
+        View firstView = first.getFragment().getActionItemView(0);
+        assertTrue(firstView.hasFocus());
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+        assertFalse(firstView.hasFocus());
+
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        ArgumentCaptor<GuidedAction> actionCapture = ArgumentCaptor.forClass(GuidedAction.class);
+        verify(first, times(1)).onSubGuidedActionClicked(actionCapture.capture());
+        // after clicked a sub action, whole action list is replaced.
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+        assertFalse(first.getFragment().isExpanded());
+        View newFirstView  = first.getFragment().getActionItemView(0);
+        assertTrue(newFirstView.hasFocus());
+        assertTrue(newFirstView.getVisibility() == View.VISIBLE);
+        GuidedActionsStylist.ViewHolder vh = (GuidedActionsStylist.ViewHolder) first.getFragment()
+                .getGuidedActionsStylist().getActionsGridView().getChildViewHolder(newFirstView);
+        assertEquals(1001, vh.getAction().getId());
+
+    }
+
 }
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java
index 879292f..7d6b54f 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java
@@ -32,6 +32,7 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v17.leanback.testutils.PollingCheck;
 import android.support.v17.leanback.widget.GuidedAction;
+import android.support.v17.leanback.widget.GuidedActionsStylist;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -357,4 +358,58 @@
         PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
         verify(first, timeout(ON_DESTROY_TIMEOUT).times(1)).onDestroy();
     }
+
+    @Test
+    public void setActionsWhenSubActionsExpanded() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        GuidedStepTestSupportFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                List<GuidedAction> subActions = new ArrayList<GuidedAction>();
+                subActions.add(new GuidedAction.Builder().id(2000).title("item1").build());
+                actions.add(new GuidedAction.Builder().id(1000).subActions(subActions)
+                        .title("list").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), any(Bundle.class));
+        doAnswer(new Answer<Boolean>() {
+            public Boolean answer(InvocationOnMock invocation) {
+                GuidedStepTestSupportFragment.Provider obj = (GuidedStepTestSupportFragment.Provider)
+                        invocation.getMock();
+                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
+                if (action.getId() == 2000) {
+                    List<GuidedAction> newActions = new ArrayList<GuidedAction>();
+                    newActions.add(new GuidedAction.Builder().id(1001).title("item2").build());
+                    obj.getFragment().setActions(newActions);
+                    return false;
+                }
+                return false;
+            }
+        }).when(first).onSubGuidedActionClicked(any(GuidedAction.class));
+
+        final GuidedStepSupportFragmentTestActivity activity = launchTestActivity(firstFragmentName);
+
+        // after clicked, it sub actions list should expand
+        View firstView = first.getFragment().getActionItemView(0);
+        assertTrue(firstView.hasFocus());
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+        assertFalse(firstView.hasFocus());
+
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        ArgumentCaptor<GuidedAction> actionCapture = ArgumentCaptor.forClass(GuidedAction.class);
+        verify(first, times(1)).onSubGuidedActionClicked(actionCapture.capture());
+        // after clicked a sub action, whole action list is replaced.
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+        assertFalse(first.getFragment().isExpanded());
+        View newFirstView  = first.getFragment().getActionItemView(0);
+        assertTrue(newFirstView.hasFocus());
+        assertTrue(newFirstView.getVisibility() == View.VISIBLE);
+        GuidedActionsStylist.ViewHolder vh = (GuidedActionsStylist.ViewHolder) first.getFragment()
+                .getGuidedActionsStylist().getActionsGridView().getChildViewHolder(newFirstView);
+        assertEquals(1001, vh.getAction().getId());
+
+    }
+
 }
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/ListRowDataAdapterTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/ListRowDataAdapterTest.java
index b7cb4e8..0b40920 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/ListRowDataAdapterTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/ListRowDataAdapterTest.java
@@ -197,6 +197,48 @@
     }
 
     @Test
+    public void adapterSize_rowsRemoveAll() {
+        ArrayObjectAdapter adapter = new ArrayObjectAdapter(presenterSelector);
+        adapter.add(new SectionRow("section 1"));
+        for (int i = 0; i < 4; i++) {
+            HeaderItem headerItem = new HeaderItem(i, "header "+i);
+            adapter.add(new ListRow(headerItem, createListRowAdapter()));
+        }
+
+        ListRowDataAdapter listRowDataAdapter = new ListRowDataAdapter(adapter);
+        assertEquals(5, listRowDataAdapter.size());
+
+        adapter.clear();
+        assertEquals(0, listRowDataAdapter.size());
+
+        HeaderItem headerItem = new HeaderItem(10, "header "+10);
+        adapter.add(new ListRow(headerItem, createListRowAdapter()));
+        assertEquals(1, listRowDataAdapter.size());
+    }
+
+    @Test
+    public void changeRemove_revealInvisibleItems() {
+        ArrayObjectAdapter adapter = new ArrayObjectAdapter(presenterSelector);
+        for (int i = 0; i < 4; i++) {
+            HeaderItem headerItem = new HeaderItem(i, "header "+i);
+            adapter.add(new ListRow(headerItem, createListRowAdapter()));
+        }
+        adapter.add(new SectionRow("section"));
+        for (int i = 4; i < 8; i++) {
+            HeaderItem headerItem = new HeaderItem(i, "header "+i);
+            adapter.add(new ListRow(headerItem, createListRowAdapter()));
+        }
+
+        ListRowDataAdapter listRowDataAdapter = new ListRowDataAdapter(adapter);
+        assertEquals(9, listRowDataAdapter.size());
+
+        listRowDataAdapter.registerObserver(dataObserver);
+        adapter.removeItems(5, 4);
+        verify(dataObserver, times(1)).onItemRangeRemoved(4, 5);
+        assertEquals(4, listRowDataAdapter.size());
+    }
+
+    @Test
     public void adapterSize_rowsRemoved() {
         int itemCount = 4;
         ArrayObjectAdapter adapter = new ArrayObjectAdapter(presenterSelector);
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlGlueTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlGlueTest.java
index 45b8802..326d2be 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlGlueTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlGlueTest.java
@@ -19,20 +19,29 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
 import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.PlaybackRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
 import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
 import android.view.KeyEvent;
+import android.view.View;
 
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
 @RunWith(AndroidJUnit4.class)
 @MediumTest
@@ -41,6 +50,8 @@
 
     static class PlayControlGlueImpl extends PlaybackControlGlue {
         int mSpeedId = PLAYBACK_SPEED_PAUSED;
+        // number of times onRowChanged callback is called
+        int mOnRowChangedCallCount = 0;
 
         PlayControlGlueImpl(Context context, int[] seekSpeeds) {
             super(context, seekSpeeds);
@@ -50,6 +61,11 @@
             super(context, ffSpeeds, rwSpeeds);
         }
 
+        PlayControlGlueImpl(Context context, PlaybackOverlayFragment fragment,
+                                   int[] seekSpeeds) {
+            super(context, fragment, seekSpeeds);
+        }
+
         @Override
         public boolean hasValidMedia() {
             return true;
@@ -115,6 +131,16 @@
 
         @Override
         protected void onRowChanged(PlaybackControlsRow row) {
+            mOnRowChangedCallCount++;
+        }
+
+        public void notifyMetaDataChanged() {
+            onMetadataChanged();
+            onStateChanged();
+        }
+
+        public int getOnRowChangedCallCount() {
+            return mOnRowChangedCallCount;
         }
     }
 
@@ -502,4 +528,78 @@
         assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
     }
 
+    @Test
+    public void testOnItemClickedListener() {
+        PlaybackControlsRow row = new PlaybackControlsRow();
+        final PlaybackOverlayFragment[] fragmentResult = new PlaybackOverlayFragment[1];
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fragmentResult[0] = new PlaybackOverlayFragment();
+            }
+        });
+        PlaybackOverlayFragment fragment = fragmentResult[0];
+        glue.setHost(new PlaybackControlGlue.PlaybackGlueHostOld(fragment));
+        glue.setControlsRow(row);
+        SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+                row.getPrimaryActionsAdapter();
+        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+                .lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+        OnItemViewClickedListener listener = Mockito.mock(OnItemViewClickedListener.class);
+        glue.setOnItemViewClickedListener(listener);
+
+        // create fake row ViewHolder and fade item ViewHolder
+        View rowView = new View(context);
+        View view = new View(context);
+        PlaybackRowPresenter.ViewHolder rowVh = new PlaybackRowPresenter.ViewHolder(rowView);
+        Presenter.ViewHolder vh = new Presenter.ViewHolder(view);
+
+        // Initially media is paused
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId());
+
+        // simulate a click inside PlaybackOverlayFragment's PlaybackRow.
+        fragment.getOnItemViewClickedListener().onItemClicked(vh, playPause, rowVh, row);
+        verify(listener, times(0)).onItemClicked(vh, playPause, rowVh, row);
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+
+        // simulate a click on object other than PlaybackRow.
+        Object regularItem = new Object();
+        Row regularRow = new Row();
+        RowPresenter.ViewHolder regularRowViewHolder = new RowPresenter.ViewHolder(rowView);
+        Presenter.ViewHolder regularViewHOlder = new Presenter.ViewHolder(view);
+        fragment.getOnItemViewClickedListener().onItemClicked(regularViewHOlder, regularItem,
+                regularRowViewHolder, regularRow);
+        verify(listener, times(1)).onItemClicked(regularViewHOlder, regularItem,
+                regularRowViewHolder, regularRow);
+        assertEquals(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+    }
+
+    @Test
+    public void testOnRowChangedCallback() throws Exception {
+        final PlaybackOverlayFragment[] fragmentResult = new
+                PlaybackOverlayFragment[1];
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fragmentResult[0] = new PlaybackOverlayFragment();
+            }
+        });
+        PlaybackOverlayFragment fragment = fragmentResult[0];
+        PlayControlGlueImpl playbackGlue = new PlayControlGlueImpl(context, fragment,
+                new int[]{
+                        PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0,
+                        PlaybackControlGlue.PLAYBACK_SPEED_FAST_L1,
+                        PlaybackControlGlue.PLAYBACK_SPEED_FAST_L2
+                });
+
+        // before any controls row is created the count is zero
+        assertEquals(playbackGlue.getOnRowChangedCallCount(), 0);
+        playbackGlue.createControlsRowAndPresenter();
+        // after a controls row is created, onRowChanged() call back is called once
+        assertEquals(playbackGlue.getOnRowChangedCallCount(), 1);
+        playbackGlue.notifyMetaDataChanged();
+        // onMetaDataChanged() calls updateRowMetadata which ends up calling
+        // notifyPlaybackRowChanged on the old host and finally onRowChanged on the glue.
+        assertEquals(playbackGlue.getOnRowChangedCallCount(), 2);
+    }
 }
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlSupportGlueTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlSupportGlueTest.java
index d3e3271..748a39f 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlSupportGlueTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlSupportGlueTest.java
@@ -22,28 +22,39 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
 import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.PlaybackRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
 import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
 import android.view.KeyEvent;
+import android.view.View;
 
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
 @RunWith(AndroidJUnit4.class)
 @MediumTest
 public class PlaybackControlSupportGlueTest {
 
 
-    static class PlayControlGlueImpl extends PlaybackControlSupportGlue {
+    public  static class PlayControlGlueImpl extends PlaybackControlSupportGlue {
         int mSpeedId = PLAYBACK_SPEED_PAUSED;
+        // number of times onRowChanged callback is called
+        int mOnRowChangedCallCount = 0;
 
         PlayControlGlueImpl(Context context, int[] seekSpeeds) {
             super(context, seekSpeeds);
@@ -53,6 +64,11 @@
             super(context, ffSpeeds, rwSpeeds);
         }
 
+        PlayControlGlueImpl(Context context, PlaybackOverlaySupportFragment fragment,
+                int[] seekSpeeds) {
+            super(context, fragment, seekSpeeds);
+        }
+
         @Override
         public boolean hasValidMedia() {
             return true;
@@ -118,6 +134,16 @@
 
         @Override
         protected void onRowChanged(PlaybackControlsRow row) {
+            mOnRowChangedCallCount++;
+        }
+
+        public void notifyMetaDataChanged() {
+            onMetadataChanged();
+            onStateChanged();
+        }
+
+        public int getOnRowChangedCallCount() {
+            return mOnRowChangedCallCount;
         }
     }
 
@@ -505,4 +531,79 @@
         assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
     }
 
+    @Test
+    public void testOnItemClickedListener() {
+        PlaybackControlsRow row = new PlaybackControlsRow();
+        final PlaybackOverlayFragment[] fragmentResult = new PlaybackOverlayFragment[1];
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fragmentResult[0] = new PlaybackOverlayFragment();
+            }
+        });
+        PlaybackOverlayFragment fragment = fragmentResult[0];
+        glue.setHost(new PlaybackControlSupportGlue.PlaybackGlueHostOld(fragment));
+        glue.setControlsRow(row);
+        SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter)
+                row.getPrimaryActionsAdapter();
+        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter
+                .lookup(PlaybackControlSupportGlue.ACTION_PLAY_PAUSE);
+        OnItemViewClickedListener listener = Mockito.mock(OnItemViewClickedListener.class);
+        glue.setOnItemViewClickedListener(listener);
+
+        // create fake row ViewHolder and fade item ViewHolder
+        View rowView = new View(context);
+        View view = new View(context);
+        PlaybackRowPresenter.ViewHolder rowVh = new PlaybackRowPresenter.ViewHolder(rowView);
+        Presenter.ViewHolder vh = new Presenter.ViewHolder(view);
+
+        // Initially media is paused
+        assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId());
+
+        // simulate a click inside PlaybackOverlayFragment's PlaybackRow.
+        fragment.getOnItemViewClickedListener().onItemClicked(vh, playPause, rowVh, row);
+        verify(listener, times(0)).onItemClicked(vh, playPause, rowVh, row);
+        assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+
+        // simulate a click on object other than PlaybackRow.
+        Object regularItem = new Object();
+        Row regularRow = new Row();
+        RowPresenter.ViewHolder regularRowViewHolder = new RowPresenter.ViewHolder(rowView);
+        Presenter.ViewHolder regularViewHOlder = new Presenter.ViewHolder(view);
+        fragment.getOnItemViewClickedListener().onItemClicked(regularViewHOlder, regularItem,
+                regularRowViewHolder, regularRow);
+        verify(listener, times(1)).onItemClicked(regularViewHOlder, regularItem,
+                regularRowViewHolder, regularRow);
+        assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId());
+    }
+
+    @Test
+    public void testOnRowChangedCallback() throws Exception {
+        final PlaybackOverlaySupportFragment[] fragmentResult = new
+                PlaybackOverlaySupportFragment[1];
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fragmentResult[0] = new PlaybackOverlaySupportFragment();
+            }
+        });
+        PlaybackOverlaySupportFragment fragment = fragmentResult[0];
+        PlayControlGlueImpl playbackGlue = new PlayControlGlueImpl(context, fragment,
+                new int[]{
+                        PlaybackControlSupportGlue.PLAYBACK_SPEED_FAST_L0,
+                        PlaybackControlSupportGlue.PLAYBACK_SPEED_FAST_L1,
+                        PlaybackControlSupportGlue.PLAYBACK_SPEED_FAST_L2
+                });
+
+        // before any controls row is created the count is zero
+        assertEquals(playbackGlue.getOnRowChangedCallCount(), 0);
+        playbackGlue.createControlsRowAndPresenter();
+        // after a controls row is created, onRowChanged() call back is called once
+        assertEquals(playbackGlue.getOnRowChangedCallCount(), 1);
+        playbackGlue.notifyMetaDataChanged();
+        // onMetaDataChanged() calls updateRowMetadata which ends up calling
+        // notifyPlaybackRowChanged on the old host and finally onRowChanged on the glue.
+        assertEquals(playbackGlue.getOnRowChangedCallCount(), 2);
+    }
+
 }
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackFragmentTest.java
new file mode 100644
index 0000000..bf7077b
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackFragmentTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.media.PlaybackControlGlue;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.view.KeyEvent;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class PlaybackFragmentTest {
+
+    private static final String TAG = "PlaybackFragmentTest";
+    private static final long TRANSITION_LENGTH = 1000;
+
+    @Rule
+    public ActivityTestRule<PlaybackTestActivity> activityTestRule =
+            new ActivityTestRule<>(PlaybackTestActivity.class, false, false);
+    private PlaybackTestActivity mActivity;
+
+    @Test
+    public void testSelectedListener() throws Throwable {
+        Intent intent = new Intent();
+        mActivity = activityTestRule.launchActivity(intent);
+        PlaybackTestFragment fragment = mActivity.getPlaybackFragment();
+        assertTrue(fragment.getView().hasFocus());
+
+        OnItemViewSelectedListener selectedListener = Mockito.mock(
+                OnItemViewSelectedListener.class);
+        fragment.setOnItemViewSelectedListener(selectedListener);
+
+
+        PlaybackControlsRow controlsRow = fragment.getGlue().getControlsRow();
+        SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
+                controlsRow.getPrimaryActionsAdapter();
+
+        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+        PlaybackControlsRow.MultiAction rewind = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_REWIND);
+
+        PlaybackControlsRow.MultiAction thumbsUp = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST);
+
+        ArgumentCaptor<Presenter.ViewHolder> itemVHCaptor =
+                ArgumentCaptor.forClass(Presenter.ViewHolder.class);
+        ArgumentCaptor<Object> itemCaptor = ArgumentCaptor.forClass(Object.class);
+        ArgumentCaptor<RowPresenter.ViewHolder> rowVHCaptor =
+                ArgumentCaptor.forClass(RowPresenter.ViewHolder.class);
+        ArgumentCaptor<Row> rowCaptor = ArgumentCaptor.forClass(Row.class);
+
+
+        // First navigate left within PlaybackControlsRow items.
+        verify(selectedListener, times(0)).onItemSelected(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(selectedListener, times(1)).onItemSelected(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The selected action should be rewind", rewind, itemCaptor.getValue());
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(selectedListener, times(2)).onItemSelected(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The selected action should be thumbsUp", thumbsUp, itemCaptor.getValue());
+
+        // Now navigate down to a ListRow item.
+        ListRow listRow0 = (ListRow) fragment.getAdapter().get(1);
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(selectedListener, times(3)).onItemSelected(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same list row should be passed to the listener", listRow0,
+                rowCaptor.getValue());
+        // Depending on the focusSearch algorithm, one of the items in the first ListRow must be
+        // selected.
+        boolean listRowItemPassed = (itemCaptor.getValue() == listRow0.getAdapter().get(0)
+                || itemCaptor.getValue() == listRow0.getAdapter().get(1));
+        assertTrue("None of the items in the first ListRow are passed to the selected listener.",
+                listRowItemPassed);
+    }
+
+    @Test
+    public void testClickedListener() throws Throwable {
+        Intent intent = new Intent();
+        mActivity = activityTestRule.launchActivity(intent);
+        PlaybackTestFragment fragment = mActivity.getPlaybackFragment();
+        assertTrue(fragment.getView().hasFocus());
+
+        OnItemViewClickedListener clickedListener = Mockito.mock(OnItemViewClickedListener.class);
+        fragment.setOnItemViewClickedListener(clickedListener);
+
+
+        PlaybackControlsRow controlsRow = fragment.getGlue().getControlsRow();
+        SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
+                controlsRow.getPrimaryActionsAdapter();
+
+        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+        PlaybackControlsRow.MultiAction rewind = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_REWIND);
+
+        PlaybackControlsRow.MultiAction thumbsUp = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST);
+
+        ArgumentCaptor<Presenter.ViewHolder> itemVHCaptor =
+                ArgumentCaptor.forClass(Presenter.ViewHolder.class);
+        ArgumentCaptor<Object> itemCaptor = ArgumentCaptor.forClass(Object.class);
+        ArgumentCaptor<RowPresenter.ViewHolder> rowVHCaptor =
+                ArgumentCaptor.forClass(RowPresenter.ViewHolder.class);
+        ArgumentCaptor<Row> rowCaptor = ArgumentCaptor.forClass(Row.class);
+
+
+        // First navigate left within PlaybackControlsRow items.
+        verify(clickedListener, times(0)).onItemClicked(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(clickedListener, times(1)).onItemClicked(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The clicked action should be playPause", playPause, itemCaptor.getValue());
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(clickedListener, times(1)).onItemClicked(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(clickedListener, times(2)).onItemClicked(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The clicked action should be rewind", rewind, itemCaptor.getValue());
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(clickedListener, times(2)).onItemClicked(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(clickedListener, times(3)).onItemClicked(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The clicked action should be thumbsUp", thumbsUp, itemCaptor.getValue());
+
+        // Now navigate down to a ListRow item.
+        ListRow listRow0 = (ListRow) fragment.getAdapter().get(1);
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(clickedListener, times(3)).onItemClicked(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(clickedListener, times(4)).onItemClicked(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same list row should be passed to the listener", listRow0,
+                rowCaptor.getValue());
+        boolean listRowItemPassed = (itemCaptor.getValue() == listRow0.getAdapter().get(0)
+                || itemCaptor.getValue() == listRow0.getAdapter().get(1));
+        assertTrue("None of the items in the first ListRow are passed to the click listener.",
+                listRowItemPassed);
+    }
+
+    private void sendKeys(int ...keys) {
+        for (int i = 0; i < keys.length; i++) {
+            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
+        }
+    }
+
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportFragmentTest.java
new file mode 100644
index 0000000..fdba125
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportFragmentTest.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.media.PlaybackControlGlue;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.view.KeyEvent;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class PlaybackSupportFragmentTest {
+
+    private static final String TAG = "PlaybackSupportFragmentTest";
+    private static final long TRANSITION_LENGTH = 1000;
+
+    @Rule
+    public ActivityTestRule<PlaybackSupportTestActivity> activityTestRule =
+            new ActivityTestRule<>(PlaybackSupportTestActivity.class, false, false);
+    private PlaybackSupportTestActivity mActivity;
+
+    @Test
+    public void testSelectedListener() throws Throwable {
+        Intent intent = new Intent();
+        mActivity = activityTestRule.launchActivity(intent);
+        PlaybackSupportTestFragment fragment = mActivity.getPlaybackFragment();
+        assertTrue(fragment.getView().hasFocus());
+
+        OnItemViewSelectedListener selectedListener = Mockito.mock(
+                OnItemViewSelectedListener.class);
+        fragment.setOnItemViewSelectedListener(selectedListener);
+
+
+        PlaybackControlsRow controlsRow = fragment.getGlue().getControlsRow();
+        SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
+                controlsRow.getPrimaryActionsAdapter();
+
+        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+        PlaybackControlsRow.MultiAction rewind = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_REWIND);
+
+        PlaybackControlsRow.MultiAction thumbsUp = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST);
+
+        ArgumentCaptor<Presenter.ViewHolder> itemVHCaptor =
+                ArgumentCaptor.forClass(Presenter.ViewHolder.class);
+        ArgumentCaptor<Object> itemCaptor =
+                ArgumentCaptor.forClass(Object.class);
+        ArgumentCaptor<RowPresenter.ViewHolder> rowVHCaptor =
+                ArgumentCaptor.forClass(RowPresenter.ViewHolder.class);
+        ArgumentCaptor<Row> rowCaptor = ArgumentCaptor.forClass(Row.class);
+
+
+        // First navigate left within PlaybackControlsRow items.
+        verify(selectedListener, times(0)).onItemSelected(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(selectedListener, times(1)).onItemSelected(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The selected action should be rewind", rewind, itemCaptor.getValue());
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(selectedListener, times(2)).onItemSelected(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The selected action should be thumbsUp", thumbsUp, itemCaptor.getValue());
+
+        // Now navigate down to a ListRow item.
+        ListRow listRow0 = (ListRow) fragment.getAdapter().get(1);
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(selectedListener, times(3)).onItemSelected(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same list row should be passed to the listener", listRow0,
+                rowCaptor.getValue());
+        // Depending on the focusSearch algorithm, one of the items in the first ListRow must be
+        // selected.
+        boolean listRowItemPassed = (itemCaptor.getValue() == listRow0.getAdapter().get(0)
+                || itemCaptor.getValue() == listRow0.getAdapter().get(1));
+        assertTrue("None of the items in the first ListRow are passed to the selected listener.",
+                listRowItemPassed);
+    }
+
+    @Test
+    public void testClickedListener() throws Throwable {
+        Intent intent = new Intent();
+        mActivity = activityTestRule.launchActivity(intent);
+        PlaybackSupportTestFragment fragment = mActivity.getPlaybackFragment();
+        assertTrue(fragment.getView().hasFocus());
+
+        OnItemViewClickedListener clickedListener = Mockito.mock(OnItemViewClickedListener.class);
+        fragment.setOnItemViewClickedListener(clickedListener);
+
+
+        PlaybackControlsRow controlsRow = fragment.getGlue().getControlsRow();
+        SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
+                controlsRow.getPrimaryActionsAdapter();
+
+        PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_PLAY_PAUSE);
+
+        PlaybackControlsRow.MultiAction rewind = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_REWIND);
+
+        PlaybackControlsRow.MultiAction thumbsUp = (PlaybackControlsRow.MultiAction)
+                primaryActionsAdapter.lookup(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST);
+
+        ArgumentCaptor<Presenter.ViewHolder> itemVHCaptor =
+                ArgumentCaptor.forClass(Presenter.ViewHolder.class);
+        ArgumentCaptor<Object> itemCaptor = ArgumentCaptor.forClass(Object.class);
+        ArgumentCaptor<RowPresenter.ViewHolder> rowVHCaptor =
+                ArgumentCaptor.forClass(RowPresenter.ViewHolder.class);
+        ArgumentCaptor<Row> rowCaptor = ArgumentCaptor.forClass(Row.class);
+
+
+        // First navigate left within PlaybackControlsRow items.
+        verify(clickedListener, times(0)).onItemClicked(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(clickedListener, times(1)).onItemClicked(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The clicked action should be playPause", playPause, itemCaptor.getValue());
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(clickedListener, times(1)).onItemClicked(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(clickedListener, times(2)).onItemClicked(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The clicked action should be rewind", rewind, itemCaptor.getValue());
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(clickedListener, times(2)).onItemClicked(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(clickedListener, times(3)).onItemClicked(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same controls row should be passed to the listener", controlsRow,
+                rowCaptor.getValue());
+        assertSame("The clicked action should be thumbsUp", thumbsUp, itemCaptor.getValue());
+
+        // Now navigate down to a ListRow item.
+        ListRow listRow0 = (ListRow) fragment.getAdapter().get(1);
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(clickedListener, times(3)).onItemClicked(any(Presenter.ViewHolder.class),
+                any(Object.class), any(RowPresenter.ViewHolder.class), any(Row.class));
+        sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+        Thread.sleep(TRANSITION_LENGTH);
+        verify(clickedListener, times(4)).onItemClicked(itemVHCaptor.capture(),
+                itemCaptor.capture(), rowVHCaptor.capture(), rowCaptor.capture());
+        assertSame("Same list row should be passed to the listener", listRow0,
+                rowCaptor.getValue());
+        boolean listRowItemPassed = (itemCaptor.getValue() == listRow0.getAdapter().get(0)
+                || itemCaptor.getValue() == listRow0.getAdapter().get(1));
+        assertTrue("None of the items in the first ListRow are passed to the click listener.",
+                listRowItemPassed);
+    }
+
+    private void sendKeys(int ...keys) {
+        for (int i = 0; i < keys.length; i++) {
+            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
+        }
+    }
+
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportTestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportTestActivity.java
new file mode 100644
index 0000000..c85fe83
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportTestActivity.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.os.Bundle;
+import android.support.v17.leanback.test.R;
+import android.support.v4.app.FragmentActivity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PlaybackSupportTestActivity extends FragmentActivity {
+    private List<PictureInPictureListener> mListeners = new ArrayList<>();
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.playback_support_controls);
+    }
+
+    @Override
+    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+        for (PictureInPictureListener listener : mListeners) {
+            listener.onPictureInPictureModeChanged(isInPictureInPictureMode);
+        }
+    }
+
+    public void registerPictureInPictureListener(PictureInPictureListener listener) {
+        mListeners.add(listener);
+    }
+
+    public void unregisterPictureInPictureListener(PictureInPictureListener listener) {
+        mListeners.remove(listener);
+    }
+
+    public interface PictureInPictureListener {
+        void onPictureInPictureModeChanged(boolean isInPictureInPictureMode);
+    }
+
+    public PlaybackSupportTestFragment getPlaybackFragment() {
+        return (PlaybackSupportTestFragment) getSupportFragmentManager().findFragmentById(
+                R.id.playback_controls_fragment);
+    }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportTestFragment.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportTestFragment.java
new file mode 100644
index 0000000..4a07a60
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackSupportTestFragment.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+
+import android.support.v17.leanback.media.PlaybackControlGlue;
+import android.support.v17.leanback.test.R;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.Toast;
+
+public class PlaybackSupportTestFragment extends PlaybackSupportFragment {
+    private static final String TAG = "PlaybackTestFragment";
+
+    /**
+     * Change this to choose a different overlay background.
+     */
+    private static final int BACKGROUND_TYPE = PlaybackFragment.BG_LIGHT;
+
+    private static final int ROW_CONTROLS = 0;
+
+    /**
+     * Change this to select hidden
+     */
+    private static final boolean SECONDARY_HIDDEN = false;
+
+    /**
+     * Change the number of related content rows.
+     */
+    private static final int RELATED_CONTENT_ROWS = 3;
+
+    private android.support.v17.leanback.media.PlaybackControlGlue mGlue;
+    private ListRowPresenter mListRowPresenter;
+
+    public SparseArrayObjectAdapter getAdapter() {
+        return (SparseArrayObjectAdapter) super.getAdapter();
+    }
+
+    private OnItemViewClickedListener mOnItemViewClickedListener = new OnItemViewClickedListener() {
+        @Override
+        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                                  RowPresenter.ViewHolder rowViewHolder, Row row) {
+            Log.d(TAG, "onItemClicked: " + item + " row " + row);
+        }
+    };
+
+    private OnItemViewSelectedListener mOnItemViewSelectedListener =
+            new OnItemViewSelectedListener() {
+                @Override
+                public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                                           RowPresenter.ViewHolder rowViewHolder, Row row) {
+                    Log.d(TAG, "onItemSelected: " + item + " row " + row);
+                }
+            };
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate");
+        super.onCreate(savedInstanceState);
+
+        setBackgroundType(BACKGROUND_TYPE);
+        // setOnItemViewSelectedListener(mOnItemViewSelectedListener);
+
+        createComponents(getActivity());
+        setOnItemViewClickedListener(mOnItemViewClickedListener);
+    }
+
+    private void createComponents(Context context) {
+        mGlue = new PlaybackControlHelper(context) {
+            @Override
+            public int getUpdatePeriod() {
+                int totalTime = getControlsRow().getTotalTime();
+                if (getView() == null || getView().getWidth() == 0 || totalTime <= 0) {
+                    return 1000;
+                }
+                return Math.max(16, totalTime / getView().getWidth());
+            }
+
+            @Override
+            public void onActionClicked(Action action) {
+                if (action.getId() == R.id.lb_control_picture_in_picture) {
+                    getActivity().enterPictureInPictureMode();
+                    return;
+                }
+                super.onActionClicked(action);
+            }
+
+            @Override
+            protected void onCreateControlsRowAndPresenter() {
+                super.onCreateControlsRowAndPresenter();
+                getControlsRowPresenter().setSecondaryActionsHidden(SECONDARY_HIDDEN);
+            }
+        };
+
+        mGlue.setHost(new PlaybackSupportFragmentGlueHost(this));
+        //  mGlue.setOnI
+        mListRowPresenter = new ListRowPresenter();
+
+        setAdapter(new SparseArrayObjectAdapter(new PresenterSelector() {
+            @Override
+            public Presenter getPresenter(Object object) {
+                if (object instanceof PlaybackControlsRow) {
+                    return mGlue.getControlsRowPresenter();
+                } else if (object instanceof ListRow) {
+                    return mListRowPresenter;
+                }
+                throw new IllegalArgumentException("Unhandled object: " + object);
+            }
+        }));
+
+        // Add the controls row
+        getAdapter().set(ROW_CONTROLS, mGlue.getControlsRow());
+
+        // Add related content rows
+        for (int i = 0; i < RELATED_CONTENT_ROWS; ++i) {
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new StringPresenter());
+            listRowAdapter.add("Some related content");
+            listRowAdapter.add("Other related content");
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            getAdapter().set(ROW_CONTROLS + 1 + i, new ListRow(header, listRowAdapter));
+        }
+    }
+
+    public PlaybackControlGlue getGlue() {
+        return mGlue;
+    }
+
+    abstract static class PlaybackControlHelper extends PlaybackControlGlue {
+        /**
+         * Change the location of the thumbs up/down controls
+         */
+        private static final boolean THUMBS_PRIMARY = true;
+
+        private static final String FAUX_TITLE = "A short song of silence";
+        private static final String FAUX_SUBTITLE = "2014";
+        private static final int FAUX_DURATION = 33 * 1000;
+
+        // These should match the playback service FF behavior
+        private static int[] sFastForwardSpeeds = { 2, 3, 4, 5 };
+
+        private boolean mIsPlaying;
+        private int mSpeed = PLAYBACK_SPEED_PAUSED;
+        private long mStartTime;
+        private long mStartPosition = 0;
+
+        private PlaybackControlsRow.RepeatAction mRepeatAction;
+        private PlaybackControlsRow.ThumbsUpAction mThumbsUpAction;
+        private PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
+        private PlaybackControlsRow.PictureInPictureAction mPipAction;
+        private static Handler sProgressHandler = new Handler();
+
+        private final Runnable mUpdateProgressRunnable = new Runnable() {
+            @Override
+            public void run() {
+                updateProgress();
+                sProgressHandler.postDelayed(this, getUpdatePeriod());
+            }
+        };
+
+        PlaybackControlHelper(Context context) {
+            super(context, sFastForwardSpeeds);
+            mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context);
+            mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsUpAction.OUTLINE);
+            mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context);
+            mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsDownAction.OUTLINE);
+            mRepeatAction = new PlaybackControlsRow.RepeatAction(context);
+            mPipAction = new PlaybackControlsRow.PictureInPictureAction(context);
+        }
+
+        @Override
+        protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
+                PresenterSelector presenterSelector) {
+            SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
+            if (THUMBS_PRIMARY) {
+                adapter.set(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction);
+                adapter.set(PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction);
+            }
+            return adapter;
+        }
+
+        @Override
+        public void onActionClicked(Action action) {
+            if (shouldDispatchAction(action)) {
+                dispatchAction(action);
+                return;
+            }
+            super.onActionClicked(action);
+        }
+
+        @Override
+        public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
+            if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+                Action action = getControlsRow().getActionForKeyCode(keyEvent.getKeyCode());
+                if (shouldDispatchAction(action)) {
+                    dispatchAction(action);
+                    return true;
+                }
+            }
+            return super.onKey(view, keyCode, keyEvent);
+        }
+
+        private boolean shouldDispatchAction(Action action) {
+            return action == mRepeatAction || action == mThumbsUpAction
+                    || action == mThumbsDownAction;
+        }
+
+        private void dispatchAction(Action action) {
+            Toast.makeText(getContext(), action.toString(), Toast.LENGTH_SHORT).show();
+            PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action;
+            multiAction.nextIndex();
+            notifyActionChanged(multiAction);
+        }
+
+        private void notifyActionChanged(PlaybackControlsRow.MultiAction action) {
+            int index;
+            index = getPrimaryActionsAdapter().indexOf(action);
+            if (index >= 0) {
+                getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+            } else {
+                index = getSecondaryActionsAdapter().indexOf(action);
+                if (index >= 0) {
+                    getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+                }
+            }
+        }
+
+        private SparseArrayObjectAdapter getPrimaryActionsAdapter() {
+            return (SparseArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter();
+        }
+
+        private ArrayObjectAdapter getSecondaryActionsAdapter() {
+            return (ArrayObjectAdapter) getControlsRow().getSecondaryActionsAdapter();
+        }
+
+        @Override
+        public boolean hasValidMedia() {
+            return true;
+        }
+
+        @Override
+        public boolean isMediaPlaying() {
+            return mIsPlaying;
+        }
+
+        @Override
+        public CharSequence getMediaTitle() {
+            return FAUX_TITLE;
+        }
+
+        @Override
+        public CharSequence getMediaSubtitle() {
+            return FAUX_SUBTITLE;
+        }
+
+        @Override
+        public int getMediaDuration() {
+            return FAUX_DURATION;
+        }
+
+        @Override
+        public Drawable getMediaArt() {
+            return null;
+        }
+
+        @Override
+        public long getSupportedActions() {
+            return ACTION_PLAY_PAUSE | ACTION_FAST_FORWARD | ACTION_REWIND;
+        }
+
+        @Override
+        public int getCurrentSpeedId() {
+            return mSpeed;
+        }
+
+        @Override
+        public int getCurrentPosition() {
+            int speed;
+            if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
+                speed = 0;
+            } else if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_NORMAL) {
+                speed = 1;
+            } else if (mSpeed >= PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
+                int index = mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
+                speed = getFastForwardSpeeds()[index];
+            } else if (mSpeed <= -PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
+                int index = -mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
+                speed = -getRewindSpeeds()[index];
+            } else {
+                return -1;
+            }
+            long position = mStartPosition + (System.currentTimeMillis() - mStartTime) * speed;
+            if (position > getMediaDuration()) {
+                position = getMediaDuration();
+                onPlaybackComplete(true);
+            } else if (position < 0) {
+                position = 0;
+                onPlaybackComplete(false);
+            }
+            return (int) position;
+        }
+
+        void onPlaybackComplete(final boolean ended) {
+            sProgressHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.NONE) {
+                        pause();
+                    } else {
+                        play(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL);
+                    }
+                    mStartPosition = 0;
+                    onStateChanged();
+                }
+            });
+        }
+
+        @Override
+        public void play(int speed) {
+            if (speed == mSpeed) {
+                return;
+            }
+            mStartPosition = getCurrentPosition();
+            mSpeed = speed;
+            mIsPlaying = true;
+            mStartTime = System.currentTimeMillis();
+        }
+
+        @Override
+        public void pause() {
+            if (mSpeed == PLAYBACK_SPEED_PAUSED) {
+                return;
+            }
+            mStartPosition = getCurrentPosition();
+            mSpeed = PLAYBACK_SPEED_PAUSED;
+            mIsPlaying = false;
+        }
+
+        @Override
+        public void next() {
+            // Not supported
+        }
+
+        @Override
+        public void previous() {
+            // Not supported
+        }
+
+        @Override
+        public void enableProgressUpdating(boolean enable) {
+            sProgressHandler.removeCallbacks(mUpdateProgressRunnable);
+            if (enable) {
+                mUpdateProgressRunnable.run();
+            }
+        }
+    }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestActivity.java
new file mode 100644
index 0000000..ff840ec
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestActivity.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v17.leanback.test.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PlaybackTestActivity extends Activity {
+    private List<PictureInPictureListener> mListeners = new ArrayList<>();
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.playback_controls);
+    }
+
+    @Override
+    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+        for (PictureInPictureListener listener : mListeners) {
+            listener.onPictureInPictureModeChanged(isInPictureInPictureMode);
+        }
+    }
+
+    public void registerPictureInPictureListener(PictureInPictureListener listener) {
+        mListeners.add(listener);
+    }
+
+    public void unregisterPictureInPictureListener(PictureInPictureListener listener) {
+        mListeners.remove(listener);
+    }
+
+    public interface PictureInPictureListener {
+        void onPictureInPictureModeChanged(boolean isInPictureInPictureMode);
+    }
+
+    public PlaybackTestFragment getPlaybackFragment() {
+        return (PlaybackTestFragment) getFragmentManager().findFragmentById(
+                R.id.playback_controls_fragment);
+    }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestFragment.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestFragment.java
new file mode 100644
index 0000000..043d73e
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackTestFragment.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+
+import android.support.v17.leanback.media.PlaybackControlGlue;
+import android.support.v17.leanback.test.R;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SparseArrayObjectAdapter;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.Toast;
+
+public class PlaybackTestFragment extends PlaybackFragment {
+    private static final String TAG = "PlaybackTestFragment";
+
+    /**
+     * Change this to choose a different overlay background.
+     */
+    private static final int BACKGROUND_TYPE = PlaybackFragment.BG_LIGHT;
+
+    private static final int ROW_CONTROLS = 0;
+
+    /**
+     * Change this to select hidden
+     */
+    private static final boolean SECONDARY_HIDDEN = false;
+
+    /**
+     * Change the number of related content rows.
+     */
+    private static final int RELATED_CONTENT_ROWS = 3;
+
+    private android.support.v17.leanback.media.PlaybackControlGlue mGlue;
+    private ListRowPresenter mListRowPresenter;
+
+    public SparseArrayObjectAdapter getAdapter() {
+        return (SparseArrayObjectAdapter) super.getAdapter();
+    }
+
+    private OnItemViewClickedListener mOnItemViewClickedListener = new OnItemViewClickedListener() {
+        @Override
+        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                                  RowPresenter.ViewHolder rowViewHolder, Row row) {
+            Log.d(TAG, "onItemClicked: " + item + " row " + row);
+        }
+    };
+
+    private OnItemViewSelectedListener mOnItemViewSelectedListener =
+            new OnItemViewSelectedListener() {
+                @Override
+                public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                                           RowPresenter.ViewHolder rowViewHolder, Row row) {
+                    Log.d(TAG, "onItemSelected: " + item + " row " + row);
+                }
+            };
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate");
+        super.onCreate(savedInstanceState);
+
+        setBackgroundType(BACKGROUND_TYPE);
+       // setOnItemViewSelectedListener(mOnItemViewSelectedListener);
+
+        createComponents(getActivity());
+        setOnItemViewClickedListener(mOnItemViewClickedListener);
+    }
+
+    private void createComponents(Context context) {
+        mGlue = new PlaybackControlHelper(context) {
+            @Override
+            public int getUpdatePeriod() {
+                int totalTime = getControlsRow().getTotalTime();
+                if (getView() == null || getView().getWidth() == 0 || totalTime <= 0) {
+                    return 1000;
+                }
+                return Math.max(16, totalTime / getView().getWidth());
+            }
+
+            @Override
+            public void onActionClicked(Action action) {
+                if (action.getId() == R.id.lb_control_picture_in_picture) {
+                    getActivity().enterPictureInPictureMode();
+                    return;
+                }
+                super.onActionClicked(action);
+            }
+
+            @Override
+            protected void onCreateControlsRowAndPresenter() {
+                super.onCreateControlsRowAndPresenter();
+                getControlsRowPresenter().setSecondaryActionsHidden(SECONDARY_HIDDEN);
+            }
+        };
+
+        mGlue.setHost(new PlaybackFragmentGlueHost(this));
+       //  mGlue.setOnI
+        mListRowPresenter = new ListRowPresenter();
+
+        setAdapter(new SparseArrayObjectAdapter(new PresenterSelector() {
+            @Override
+            public Presenter getPresenter(Object object) {
+                if (object instanceof PlaybackControlsRow) {
+                    return mGlue.getControlsRowPresenter();
+                } else if (object instanceof ListRow) {
+                    return mListRowPresenter;
+                }
+                throw new IllegalArgumentException("Unhandled object: " + object);
+            }
+        }));
+
+        // Add the controls row
+        getAdapter().set(ROW_CONTROLS, mGlue.getControlsRow());
+
+        // Add related content rows
+        for (int i = 0; i < RELATED_CONTENT_ROWS; ++i) {
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new StringPresenter());
+            listRowAdapter.add("Some related content");
+            listRowAdapter.add("Other related content");
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            getAdapter().set(ROW_CONTROLS + 1 + i, new ListRow(header, listRowAdapter));
+        }
+    }
+
+    public PlaybackControlGlue getGlue() {
+        return mGlue;
+    }
+
+    abstract static class PlaybackControlHelper extends PlaybackControlGlue {
+        /**
+         * Change the location of the thumbs up/down controls
+         */
+        private static final boolean THUMBS_PRIMARY = true;
+
+        private static final String FAUX_TITLE = "A short song of silence";
+        private static final String FAUX_SUBTITLE = "2014";
+        private static final int FAUX_DURATION = 33 * 1000;
+
+        // These should match the playback service FF behavior
+        private static int[] sFastForwardSpeeds = { 2, 3, 4, 5 };
+
+        private boolean mIsPlaying;
+        private int mSpeed = PLAYBACK_SPEED_PAUSED;
+        private long mStartTime;
+        private long mStartPosition = 0;
+
+        private PlaybackControlsRow.RepeatAction mRepeatAction;
+        private PlaybackControlsRow.ThumbsUpAction mThumbsUpAction;
+        private PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
+        private PlaybackControlsRow.PictureInPictureAction mPipAction;
+        private static Handler sProgressHandler = new Handler();
+
+        private final Runnable mUpdateProgressRunnable = new Runnable() {
+            @Override
+            public void run() {
+                updateProgress();
+                sProgressHandler.postDelayed(this, getUpdatePeriod());
+            }
+        };
+
+        PlaybackControlHelper(Context context) {
+            super(context, sFastForwardSpeeds);
+            mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(context);
+            mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsUpAction.OUTLINE);
+            mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(context);
+            mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsDownAction.OUTLINE);
+            mRepeatAction = new PlaybackControlsRow.RepeatAction(context);
+            mPipAction = new PlaybackControlsRow.PictureInPictureAction(context);
+        }
+
+        @Override
+        protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
+                PresenterSelector presenterSelector) {
+            SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
+            if (THUMBS_PRIMARY) {
+                adapter.set(PlaybackControlGlue.ACTION_CUSTOM_LEFT_FIRST, mThumbsUpAction);
+                adapter.set(PlaybackControlGlue.ACTION_CUSTOM_RIGHT_FIRST, mThumbsDownAction);
+            }
+            return adapter;
+        }
+
+        @Override
+        public void onActionClicked(Action action) {
+            if (shouldDispatchAction(action)) {
+                dispatchAction(action);
+                return;
+            }
+            super.onActionClicked(action);
+        }
+
+        @Override
+        public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
+            if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+                Action action = getControlsRow().getActionForKeyCode(keyEvent.getKeyCode());
+                if (shouldDispatchAction(action)) {
+                    dispatchAction(action);
+                    return true;
+                }
+            }
+            return super.onKey(view, keyCode, keyEvent);
+        }
+
+        private boolean shouldDispatchAction(Action action) {
+            return action == mRepeatAction || action == mThumbsUpAction
+                    || action == mThumbsDownAction;
+        }
+
+        private void dispatchAction(Action action) {
+            Toast.makeText(getContext(), action.toString(), Toast.LENGTH_SHORT).show();
+            PlaybackControlsRow.MultiAction multiAction = (PlaybackControlsRow.MultiAction) action;
+            multiAction.nextIndex();
+            notifyActionChanged(multiAction);
+        }
+
+        private void notifyActionChanged(PlaybackControlsRow.MultiAction action) {
+            int index;
+            index = getPrimaryActionsAdapter().indexOf(action);
+            if (index >= 0) {
+                getPrimaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+            } else {
+                index = getSecondaryActionsAdapter().indexOf(action);
+                if (index >= 0) {
+                    getSecondaryActionsAdapter().notifyArrayItemRangeChanged(index, 1);
+                }
+            }
+        }
+
+        private SparseArrayObjectAdapter getPrimaryActionsAdapter() {
+            return (SparseArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter();
+        }
+
+        private ArrayObjectAdapter getSecondaryActionsAdapter() {
+            return (ArrayObjectAdapter) getControlsRow().getSecondaryActionsAdapter();
+        }
+
+        @Override
+        public boolean hasValidMedia() {
+            return true;
+        }
+
+        @Override
+        public boolean isMediaPlaying() {
+            return mIsPlaying;
+        }
+
+        @Override
+        public CharSequence getMediaTitle() {
+            return FAUX_TITLE;
+        }
+
+        @Override
+        public CharSequence getMediaSubtitle() {
+            return FAUX_SUBTITLE;
+        }
+
+        @Override
+        public int getMediaDuration() {
+            return FAUX_DURATION;
+        }
+
+        @Override
+        public Drawable getMediaArt() {
+            return null;
+        }
+
+        @Override
+        public long getSupportedActions() {
+            return ACTION_PLAY_PAUSE | ACTION_FAST_FORWARD | ACTION_REWIND;
+        }
+
+        @Override
+        public int getCurrentSpeedId() {
+            return mSpeed;
+        }
+
+        @Override
+        public int getCurrentPosition() {
+            int speed;
+            if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED) {
+                speed = 0;
+            } else if (mSpeed == PlaybackControlGlue.PLAYBACK_SPEED_NORMAL) {
+                speed = 1;
+            } else if (mSpeed >= PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
+                int index = mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
+                speed = getFastForwardSpeeds()[index];
+            } else if (mSpeed <= -PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
+                int index = -mSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
+                speed = -getRewindSpeeds()[index];
+            } else {
+                return -1;
+            }
+            long position = mStartPosition + (System.currentTimeMillis() - mStartTime) * speed;
+            if (position > getMediaDuration()) {
+                position = getMediaDuration();
+                onPlaybackComplete(true);
+            } else if (position < 0) {
+                position = 0;
+                onPlaybackComplete(false);
+            }
+            return (int) position;
+        }
+
+        void onPlaybackComplete(final boolean ended) {
+            sProgressHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.NONE) {
+                        pause();
+                    } else {
+                        play(PlaybackControlGlue.PLAYBACK_SPEED_NORMAL);
+                    }
+                    mStartPosition = 0;
+                    onStateChanged();
+                }
+            });
+        }
+
+        @Override
+        public void play(int speed) {
+            if (speed == mSpeed) {
+                return;
+            }
+            mStartPosition = getCurrentPosition();
+            mSpeed = speed;
+            mIsPlaying = true;
+            mStartTime = System.currentTimeMillis();
+        }
+
+        @Override
+        public void pause() {
+            if (mSpeed == PLAYBACK_SPEED_PAUSED) {
+                return;
+            }
+            mStartPosition = getCurrentPosition();
+            mSpeed = PLAYBACK_SPEED_PAUSED;
+            mIsPlaying = false;
+        }
+
+        @Override
+        public void next() {
+            // Not supported
+        }
+
+        @Override
+        public void previous() {
+            // Not supported
+        }
+
+        @Override
+        public void enableProgressUpdating(boolean enable) {
+            sProgressHandler.removeCallbacks(mUpdateProgressRunnable);
+            if (enable) {
+                mUpdateProgressRunnable.run();
+            }
+        }
+    }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTest.java
new file mode 100644
index 0000000..50d5f24
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.support.test.filters.MediumTest;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.testutils.PollingCheck;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.view.KeyEvent;
+import android.view.View;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class RowsFragmentTest {
+
+    static final long ACTIVITY_LOAD_DELAY = 2000;
+
+    @Rule
+    public ActivityTestRule<RowsFragmentTestActivity> activityTestRule =
+            new ActivityTestRule<>(RowsFragmentTestActivity.class, false, false);
+    private RowsFragmentTestActivity mActivity;
+
+    @After
+    public void afterTest() throws Throwable {
+        activityTestRule.runOnUiThread(new Runnable() {
+            public void run() {
+                if (mActivity != null) {
+                    mActivity.finish();
+                    mActivity = null;
+                }
+            }
+        });
+    }
+
+    private void sendKeys(int ...keys) {
+        for (int i = 0; i < keys.length; i++) {
+            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
+        }
+    }
+
+    void launchAndWaitActivity(Intent intent) {
+        mActivity = activityTestRule.launchActivity(intent);
+        SystemClock.sleep(ACTIVITY_LOAD_DELAY);
+    }
+
+    @Test
+    public void defaultAlignment() throws InterruptedException {
+        Intent intent = new Intent();
+        intent.putExtra(RowsFragmentTestActivity.EXTRA_NUM_ROWS, 10);
+        intent.putExtra(RowsFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, 1l);
+        launchAndWaitActivity(intent);
+
+        final Rect rect = new Rect();
+
+        final VerticalGridView gridView = mActivity.getRowsTestFragment().getVerticalGridView();
+        View row0 = gridView.findViewHolderForAdapterPosition(0).itemView;
+        rect.set(0, 0, row0.getWidth(), row0.getHeight());
+        gridView.offsetDescendantRectToMyCoords(row0, rect);
+        assertEquals("First row is initially aligned to top of screen", 0, rect.top);
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+        View row1 = gridView.findViewHolderForAdapterPosition(1).itemView;
+        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(row1));
+
+        rect.set(0, 0, row1.getWidth(), row1.getHeight());
+        gridView.offsetDescendantRectToMyCoords(row1, rect);
+        assertTrue("Second row should not be aligned to top of screen", rect.top > 0);
+    }
+
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTestActivity.java
new file mode 100644
index 0000000..fe2dd25
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsFragmentTestActivity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.app.Activity;
+import android.app.FragmentTransaction;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v17.leanback.test.R;
+
+public class RowsFragmentTestActivity extends Activity {
+
+    public static final String EXTRA_NUM_ROWS = "numRows";
+    public static final String EXTRA_REPEAT_PER_ROW = "repeatPerRow";
+    public static final String EXTRA_LOAD_DATA_DELAY = "loadDataDelay";
+    public final static String EXTRA_SET_ADAPTER_AFTER_DATA_LOAD = "set_adapter_after_data_load";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent intent = getIntent();
+
+        setContentView(R.layout.rows);
+        if (savedInstanceState == null) {
+            RowsTestFragment fragment = new RowsTestFragment();
+            Bundle arguments = new Bundle();
+            if (intent.getExtras() != null) {
+                arguments.putAll(intent.getExtras());
+            }
+            fragment.setArguments(arguments);
+            FragmentTransaction ft = getFragmentManager().beginTransaction();
+            ft.replace(R.id.main_frame, fragment);
+            ft.commit();
+        }
+    }
+
+    public RowsTestFragment getRowsTestFragment() {
+        return (RowsTestFragment) getFragmentManager().findFragmentById(R.id.main_frame);
+    }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTest.java
new file mode 100644
index 0000000..c024b6c
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTest.java
@@ -0,0 +1,99 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from RowsFragmentTest.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.support.test.filters.MediumTest;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v17.leanback.testutils.PollingCheck;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.view.KeyEvent;
+import android.view.View;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class RowsSupportFragmentTest {
+
+    static final long ACTIVITY_LOAD_DELAY = 2000;
+
+    @Rule
+    public ActivityTestRule<RowsSupportFragmentTestActivity> activityTestRule =
+            new ActivityTestRule<>(RowsSupportFragmentTestActivity.class, false, false);
+    private RowsSupportFragmentTestActivity mActivity;
+
+    @After
+    public void afterTest() throws Throwable {
+        activityTestRule.runOnUiThread(new Runnable() {
+            public void run() {
+                if (mActivity != null) {
+                    mActivity.finish();
+                    mActivity = null;
+                }
+            }
+        });
+    }
+
+    private void sendKeys(int ...keys) {
+        for (int i = 0; i < keys.length; i++) {
+            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keys[i]);
+        }
+    }
+
+    void launchAndWaitActivity(Intent intent) {
+        mActivity = activityTestRule.launchActivity(intent);
+        SystemClock.sleep(ACTIVITY_LOAD_DELAY);
+    }
+
+    @Test
+    public void defaultAlignment() throws InterruptedException {
+        Intent intent = new Intent();
+        intent.putExtra(RowsSupportFragmentTestActivity.EXTRA_NUM_ROWS, 10);
+        intent.putExtra(RowsSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, 1l);
+        launchAndWaitActivity(intent);
+
+        final Rect rect = new Rect();
+
+        final VerticalGridView gridView = mActivity.getRowsTestSupportFragment().getVerticalGridView();
+        View row0 = gridView.findViewHolderForAdapterPosition(0).itemView;
+        rect.set(0, 0, row0.getWidth(), row0.getHeight());
+        gridView.offsetDescendantRectToMyCoords(row0, rect);
+        assertEquals("First row is initially aligned to top of screen", 0, rect.top);
+
+        sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+        View row1 = gridView.findViewHolderForAdapterPosition(1).itemView;
+        PollingCheck.waitFor(new PollingCheck.ViewStableOnScreen(row1));
+
+        rect.set(0, 0, row1.getWidth(), row1.getHeight());
+        gridView.offsetDescendantRectToMyCoords(row1, rect);
+        assertTrue("Second row should not be aligned to top of screen", rect.top > 0);
+    }
+
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTestActivity.java
new file mode 100644
index 0000000..d736458
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsSupportFragmentTestActivity.java
@@ -0,0 +1,56 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from RowsFragmentTestActivity.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentTransaction;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v17.leanback.test.R;
+
+public class RowsSupportFragmentTestActivity extends FragmentActivity {
+
+    public static final String EXTRA_NUM_ROWS = "numRows";
+    public static final String EXTRA_REPEAT_PER_ROW = "repeatPerRow";
+    public static final String EXTRA_LOAD_DATA_DELAY = "loadDataDelay";
+    public final static String EXTRA_SET_ADAPTER_AFTER_DATA_LOAD = "set_adapter_after_data_load";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent intent = getIntent();
+
+        setContentView(R.layout.rows);
+        if (savedInstanceState == null) {
+            RowsTestSupportFragment fragment = new RowsTestSupportFragment();
+            Bundle arguments = new Bundle();
+            if (intent.getExtras() != null) {
+                arguments.putAll(intent.getExtras());
+            }
+            fragment.setArguments(arguments);
+            FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+            ft.replace(R.id.main_frame, fragment);
+            ft.commit();
+        }
+    }
+
+    public RowsTestSupportFragment getRowsTestSupportFragment() {
+        return (RowsTestSupportFragment) getSupportFragmentManager().findFragmentById(R.id.main_frame);
+    }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/RowsTestFragment.java b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsTestFragment.java
new file mode 100644
index 0000000..d1f71db
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsTestFragment.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.util.Log;
+
+import static android.support.v17.leanback.app.RowsFragmentTestActivity.EXTRA_LOAD_DATA_DELAY;
+import static android.support.v17.leanback.app.RowsFragmentTestActivity.EXTRA_NUM_ROWS;
+import static android.support.v17.leanback.app.RowsFragmentTestActivity.EXTRA_REPEAT_PER_ROW;
+import static android.support.v17.leanback.app.RowsFragmentTestActivity.EXTRA_SET_ADAPTER_AFTER_DATA_LOAD;
+
+public class RowsTestFragment extends RowsFragment {
+    private static final String TAG = "RowsTestFragment";
+
+    final static int DEFAULT_NUM_ROWS = 100;
+    final static int DEFAULT_REPEAT_PER_ROW = 20;
+    final static long DEFAULT_LOAD_DATA_DELAY = 2000;
+    final static boolean DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD = false;
+
+    private ArrayObjectAdapter mRowsAdapter;
+
+    // For good performance, it's important to use a single instance of
+    // a card presenter for all rows using that presenter.
+    final static StringPresenter sCardPresenter = new StringPresenter();
+
+    int NUM_ROWS;
+    int REPEAT_PER_ROW;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate");
+        super.onCreate(savedInstanceState);
+
+        Bundle arguments = getArguments();
+        NUM_ROWS = arguments.getInt(EXTRA_NUM_ROWS, RowsTestFragment.DEFAULT_NUM_ROWS);
+        REPEAT_PER_ROW = arguments.getInt(EXTRA_REPEAT_PER_ROW,
+                DEFAULT_REPEAT_PER_ROW);
+        long LOAD_DATA_DELAY = arguments.getLong(EXTRA_LOAD_DATA_DELAY,
+                DEFAULT_LOAD_DATA_DELAY);
+        final boolean SET_ADAPTER_AFTER_DATA_LOAD = arguments.getBoolean(
+                EXTRA_SET_ADAPTER_AFTER_DATA_LOAD,
+                DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD);
+
+        if (!SET_ADAPTER_AFTER_DATA_LOAD) {
+            setupRows();
+        }
+
+        setOnItemViewClickedListener(new ItemViewClickedListener());
+        setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
+            @Override
+            public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                    RowPresenter.ViewHolder rowViewHolder, Row row) {
+                Log.i(TAG, "onItemSelected: " + item + " row " + row.getHeaderItem().getName()
+                        + " " + rowViewHolder
+                        + " " + ((ListRowPresenter.ViewHolder) rowViewHolder).getGridView());
+            }
+        });
+        // simulates in a real world use case  data being loaded two seconds later
+        new Handler().postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                if (getActivity() == null || getActivity().isDestroyed()) {
+                    return;
+                }
+                if (SET_ADAPTER_AFTER_DATA_LOAD) {
+                    setupRows();
+                }
+                loadData();
+            }
+        }, LOAD_DATA_DELAY);
+    }
+
+    private void setupRows() {
+        ListRowPresenter lrp = new ListRowPresenter();
+
+        mRowsAdapter = new ArrayObjectAdapter(lrp);
+
+        setAdapter(mRowsAdapter);
+    }
+
+    private void loadData() {
+        for (int i = 0; i < NUM_ROWS; ++i) {
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(sCardPresenter);
+            int index = 0;
+            for (int j = 0; j < REPEAT_PER_ROW; ++j) {
+                listRowAdapter.add("Hello world-" + (index++));
+                listRowAdapter.add("This is a test-" + (index++));
+                listRowAdapter.add("Android TV-" + (index++));
+                listRowAdapter.add("Leanback-" + (index++));
+                listRowAdapter.add("Hello world-" + (index++));
+                listRowAdapter.add("Android TV-" + (index++));
+                listRowAdapter.add("Leanback-" + (index++));
+                listRowAdapter.add("GuidedStepFragment-" + (index++));
+            }
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            mRowsAdapter.add(new ListRow(header, listRowAdapter));
+        }
+    }
+
+    private final class ItemViewClickedListener implements OnItemViewClickedListener {
+        @Override
+        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                RowPresenter.ViewHolder rowViewHolder, Row row) {
+            Log.i(TAG, "onItemClicked: " + item + " row " + row);
+        }
+    }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/RowsTestSupportFragment.java b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsTestSupportFragment.java
new file mode 100644
index 0000000..e095f94
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/RowsTestSupportFragment.java
@@ -0,0 +1,132 @@
+// CHECKSTYLE:OFF Generated code
+/* This file is auto-generated from RowsTestFragment.java.  DO NOT MODIFY. */
+
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.util.Log;
+
+import static android.support.v17.leanback.app.RowsSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY;
+import static android.support.v17.leanback.app.RowsSupportFragmentTestActivity.EXTRA_NUM_ROWS;
+import static android.support.v17.leanback.app.RowsSupportFragmentTestActivity.EXTRA_REPEAT_PER_ROW;
+import static android.support.v17.leanback.app.RowsSupportFragmentTestActivity.EXTRA_SET_ADAPTER_AFTER_DATA_LOAD;
+
+public class RowsTestSupportFragment extends RowsSupportFragment {
+    private static final String TAG = "RowsTestSupportFragment";
+
+    final static int DEFAULT_NUM_ROWS = 100;
+    final static int DEFAULT_REPEAT_PER_ROW = 20;
+    final static long DEFAULT_LOAD_DATA_DELAY = 2000;
+    final static boolean DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD = false;
+
+    private ArrayObjectAdapter mRowsAdapter;
+
+    // For good performance, it's important to use a single instance of
+    // a card presenter for all rows using that presenter.
+    final static StringPresenter sCardPresenter = new StringPresenter();
+
+    int NUM_ROWS;
+    int REPEAT_PER_ROW;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "onCreate");
+        super.onCreate(savedInstanceState);
+
+        Bundle arguments = getArguments();
+        NUM_ROWS = arguments.getInt(EXTRA_NUM_ROWS, RowsTestSupportFragment.DEFAULT_NUM_ROWS);
+        REPEAT_PER_ROW = arguments.getInt(EXTRA_REPEAT_PER_ROW,
+                DEFAULT_REPEAT_PER_ROW);
+        long LOAD_DATA_DELAY = arguments.getLong(EXTRA_LOAD_DATA_DELAY,
+                DEFAULT_LOAD_DATA_DELAY);
+        final boolean SET_ADAPTER_AFTER_DATA_LOAD = arguments.getBoolean(
+                EXTRA_SET_ADAPTER_AFTER_DATA_LOAD,
+                DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD);
+
+        if (!SET_ADAPTER_AFTER_DATA_LOAD) {
+            setupRows();
+        }
+
+        setOnItemViewClickedListener(new ItemViewClickedListener());
+        setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
+            @Override
+            public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                    RowPresenter.ViewHolder rowViewHolder, Row row) {
+                Log.i(TAG, "onItemSelected: " + item + " row " + row.getHeaderItem().getName()
+                        + " " + rowViewHolder
+                        + " " + ((ListRowPresenter.ViewHolder) rowViewHolder).getGridView());
+            }
+        });
+        // simulates in a real world use case  data being loaded two seconds later
+        new Handler().postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                if (getActivity() == null || getActivity().isDestroyed()) {
+                    return;
+                }
+                if (SET_ADAPTER_AFTER_DATA_LOAD) {
+                    setupRows();
+                }
+                loadData();
+            }
+        }, LOAD_DATA_DELAY);
+    }
+
+    private void setupRows() {
+        ListRowPresenter lrp = new ListRowPresenter();
+
+        mRowsAdapter = new ArrayObjectAdapter(lrp);
+
+        setAdapter(mRowsAdapter);
+    }
+
+    private void loadData() {
+        for (int i = 0; i < NUM_ROWS; ++i) {
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(sCardPresenter);
+            int index = 0;
+            for (int j = 0; j < REPEAT_PER_ROW; ++j) {
+                listRowAdapter.add("Hello world-" + (index++));
+                listRowAdapter.add("This is a test-" + (index++));
+                listRowAdapter.add("Android TV-" + (index++));
+                listRowAdapter.add("Leanback-" + (index++));
+                listRowAdapter.add("Hello world-" + (index++));
+                listRowAdapter.add("Android TV-" + (index++));
+                listRowAdapter.add("Leanback-" + (index++));
+                listRowAdapter.add("GuidedStepSupportFragment-" + (index++));
+            }
+            HeaderItem header = new HeaderItem(i, "Row " + i);
+            mRowsAdapter.add(new ListRow(header, listRowAdapter));
+        }
+    }
+
+    private final class ItemViewClickedListener implements OnItemViewClickedListener {
+        @Override
+        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+                RowPresenter.ViewHolder rowViewHolder, Row row) {
+            Log.i(TAG, "onItemClicked: " + item + " row " + row);
+        }
+    }
+}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/PresenterTest.java b/v17/leanback/tests/java/android/support/v17/leanback/widget/PresenterTest.java
index 8923d36..738c3e7 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/widget/PresenterTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/widget/PresenterTest.java
@@ -107,6 +107,30 @@
     }
 
     @Test
+    public void testRowHeaderPresenter() {
+        RowHeaderPresenter p = new RowHeaderPresenter();
+        p.setNullItemVisibilityGone(true);
+        RowHeaderPresenter.ViewHolder vh = (RowHeaderPresenter.ViewHolder)
+                p.onCreateViewHolder(new FrameLayout(mContext));
+        p.onBindViewHolder(vh, null);
+        assertEquals("Header visibility", View.GONE, vh.view.getVisibility());
+        p.onBindViewHolder(vh, new Row(null));
+        assertEquals("Header visibility", View.GONE, vh.view.getVisibility());
+
+        p.onBindViewHolder(vh, new Row(new HeaderItem("")));
+        assertEquals("Header visibility", View.VISIBLE, vh.view.getVisibility());
+        assertEquals("Header Description visibility", View.GONE,
+                vh.mDescriptionView.getVisibility());
+
+        HeaderItem item = new HeaderItem("");
+        item.setDescription("description");
+        p.onBindViewHolder(vh, new Row(item));
+        assertEquals("Header visibility", View.VISIBLE, vh.view.getVisibility());
+        assertEquals("Header Description visibility", View.VISIBLE,
+                vh.mDescriptionView.getVisibility());
+    }
+
+    @Test
     public void testPlaybackControlsRowPresenter() {
         Context context = new ContextThemeWrapper(mContext, R.style.Theme_Leanback);
         Presenter detailsPresenter = new AbstractDetailsDescriptionPresenter() {
diff --git a/v17/leanback/tests/res/layout/playback_controls.xml b/v17/leanback/tests/res/layout/playback_controls.xml
new file mode 100644
index 0000000..7f8910f
--- /dev/null
+++ b/v17/leanback/tests/res/layout/playback_controls.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent" >
+
+    <fragment
+            android:id="@+id/playback_controls_fragment"
+            android:name="android.support.v17.leanback.app.PlaybackTestFragment"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/v17/leanback/tests/res/layout/playback_support_controls.xml b/v17/leanback/tests/res/layout/playback_support_controls.xml
new file mode 100644
index 0000000..9e0e092
--- /dev/null
+++ b/v17/leanback/tests/res/layout/playback_support_controls.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent" >
+
+    <fragment
+            android:id="@+id/playback_controls_fragment"
+            android:name="android.support.v17.leanback.app.PlaybackSupportTestFragment"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/samples/SupportLeanbackDemos/res/layout/legacy_details.xml b/v17/leanback/tests/res/layout/rows.xml
similarity index 70%
copy from samples/SupportLeanbackDemos/res/layout/legacy_details.xml
copy to v17/leanback/tests/res/layout/rows.xml
index 4af4e6a..7a7c0b0 100644
--- a/samples/SupportLeanbackDemos/res/layout/legacy_details.xml
+++ b/v17/leanback/tests/res/layout/rows.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-     Copyright (C) 2014 The Android Open Source Project
+     Copyright (C) 2016 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -15,9 +15,9 @@
      limitations under the License.
 -->
 
-<fragment xmlns:android="http://schemas.android.com/apk/res/android"
-    android:name="com.example.android.leanback.DetailsFragment"
-    android:id="@+id/details_fragment"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/main_frame"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-/>
+    android:layout_height="match_parent">
+
+</FrameLayout>
diff --git a/v17/preference-leanback/build.gradle b/v17/preference-leanback/build.gradle
index 9bfd0f3..e58fa8b 100644
--- a/v17/preference-leanback/build.gradle
+++ b/v17/preference-leanback/build.gradle
@@ -36,28 +36,11 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/v4/AndroidManifest.xml b/v4/AndroidManifest.xml
index d76c581..cecc743 100644
--- a/v4/AndroidManifest.xml
+++ b/v4/AndroidManifest.xml
@@ -17,5 +17,6 @@
           xmlns:tools="http://schemas.android.com/tools"
           package="android.support.v4">
     <uses-sdk android:minSdkVersion="9" tools:overrideLibrary="android.support.v4"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application />
 </manifest>
diff --git a/v4/build.gradle b/v4/build.gradle
index f0226e3..f226a87 100644
--- a/v4/build.gradle
+++ b/v4/build.gradle
@@ -1,6 +1,6 @@
 apply plugin: 'com.android.library'
-
 archivesBaseName = 'support-v4'
+
 dependencies {
     compile project(':support-compat')
     compile project(':support-media-compat')
diff --git a/v7/appcompat/AndroidManifest.xml b/v7/appcompat/AndroidManifest.xml
index 99b77ee..d5858d1 100644
--- a/v7/appcompat/AndroidManifest.xml
+++ b/v7/appcompat/AndroidManifest.xml
@@ -18,5 +18,6 @@
           package="android.support.v7.appcompat">
     <uses-sdk android:minSdkVersion="9"
               tools:overrideLibrary="android.support.graphics.drawable.animated"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application/>
 </manifest>
diff --git a/v7/appcompat/build.gradle b/v7/appcompat/build.gradle
index aa62632..4935085 100644
--- a/v7/appcompat/build.gradle
+++ b/v7/appcompat/build.gradle
@@ -1,5 +1,4 @@
 apply plugin: 'com.android.library'
-
 archivesBaseName = 'appcompat-v7'
 
 dependencies {
@@ -61,28 +60,11 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/v7/appcompat/res-public/values/public_attrs.xml b/v7/appcompat/res-public/values/public_attrs.xml
index 6ffa864..3961f49 100644
--- a/v7/appcompat/res-public/values/public_attrs.xml
+++ b/v7/appcompat/res-public/values/public_attrs.xml
@@ -66,7 +66,7 @@
      <public type="attr" name="buttonBarNeutralButtonStyle"/>
      <public type="attr" name="buttonBarPositiveButtonStyle"/>
      <public type="attr" name="buttonBarStyle"/>
-     <public type="attr" name="buttonGravity" />
+     <public type="attr" name="buttonGravity"/>
      <public type="attr" name="buttonStyle"/>
      <public type="attr" name="buttonStyleSmall"/>
      <public type="attr" name="buttonTint"/>
@@ -131,7 +131,7 @@
      <public type="attr" name="listPreferredItemPaddingRight"/>
      <public type="attr" name="logo"/>
      <public type="attr" name="logoDescription"/>
-     <public type="attr" name="maxButtonHeight" />
+     <public type="attr" name="maxButtonHeight"/>
      <public type="attr" name="measureWithLargestChild"/>
      <public type="attr" name="navigationContentDescription"/>
      <public type="attr" name="navigationIcon"/>
@@ -166,6 +166,7 @@
      <public type="attr" name="spinnerStyle"/>
      <public type="attr" name="splitTrack"/>
      <public type="attr" name="srcCompat"/>
+     <public type="attr" name="state_above_anchor"/>
      <public type="attr" name="submitBackground"/>
      <public type="attr" name="subtitle"/>
      <public type="attr" name="subtitleTextAppearance"/>
@@ -188,13 +189,15 @@
      <public type="attr" name="theme"/>
      <public type="attr" name="thickness"/>
      <public type="attr" name="thumbTextPadding"/>
+     <public type="attr" name="thumbTint"/>
+     <public type="attr" name="thumbTintMode"/>
      <public type="attr" name="tickMark"/>
      <public type="attr" name="tickMarkTint"/>
      <public type="attr" name="tickMarkTintMode"/>
      <public type="attr" name="title"/>
+     <public type="attr" name="titleMargin"/>
      <public type="attr" name="titleMarginBottom"/>
      <public type="attr" name="titleMarginEnd"/>
-     <public type="attr" name="titleMargin"/>
      <public type="attr" name="titleMargins"/>
      <public type="attr" name="titleMarginStart"/>
      <public type="attr" name="titleMarginTop"/>
@@ -204,6 +207,8 @@
      <public type="attr" name="toolbarNavigationButtonStyle"/>
      <public type="attr" name="toolbarStyle"/>
      <public type="attr" name="track"/>
+     <public type="attr" name="trackTint"/>
+     <public type="attr" name="trackTintMode"/>
      <public type="attr" name="voiceIcon"/>
      <public type="attr" name="windowActionBar"/>
      <public type="attr" name="windowActionBarOverlay"/>
diff --git a/v7/appcompat/res-public/values/public_styles.xml b/v7/appcompat/res-public/values/public_styles.xml
index 38efc46..a9956ad 100644
--- a/v7/appcompat/res-public/values/public_styles.xml
+++ b/v7/appcompat/res-public/values/public_styles.xml
@@ -36,6 +36,12 @@
     <public type="style" name="TextAppearance.AppCompat.Medium"/>
     <public type="style" name="TextAppearance.AppCompat.Medium.Inverse"/>
     <public type="style" name="TextAppearance.AppCompat.Menu"/>
+    <public type="style" name="TextAppearance.AppCompat.Notification"/>
+    <public type="style" name="TextAppearance.AppCompat.Notification.Info"/>
+    <public type="style" name="TextAppearance.AppCompat.Notification.Line2"/>
+    <public type="style" name="TextAppearance.AppCompat.Notification.Media"/>
+    <public type="style" name="TextAppearance.AppCompat.Notification.Time"/>
+    <public type="style" name="TextAppearance.AppCompat.Notification.Title"/>
     <public type="style" name="TextAppearance.AppCompat.SearchResult.Subtitle"/>
     <public type="style" name="TextAppearance.AppCompat.SearchResult.Title"/>
     <public type="style" name="TextAppearance.AppCompat.Small"/>
diff --git a/v7/appcompat/res/layout/abc_action_mode_close_item_material.xml b/v7/appcompat/res/layout/abc_action_mode_close_item_material.xml
index b3babb2..118ce2b 100644
--- a/v7/appcompat/res/layout/abc_action_mode_close_item_material.xml
+++ b/v7/appcompat/res/layout/abc_action_mode_close_item_material.xml
@@ -14,14 +14,17 @@
      limitations under the License.
 -->
 
-<ImageView
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:app="http://schemas.android.com/apk/res-auto"
-        android:id="@+id/action_mode_close_button"
-        android:contentDescription="@string/abc_action_mode_done"
-        android:focusable="true"
-        android:clickable="true"
-        app:srcCompat="?attr/actionModeCloseDrawable"
-        style="?attr/actionModeCloseButtonStyle"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"/>
\ No newline at end of file
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/action_mode_close_button"
+    style="?attr/actionModeCloseButtonStyle"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:layout_marginEnd="16dip"
+    android:layout_marginRight="16dip"
+    android:clickable="true"
+    android:contentDescription="@string/abc_action_mode_done"
+    android:focusable="true"
+    android:paddingLeft="8dp"
+    android:paddingStart="8dp"
+    app:srcCompat="?attr/actionModeCloseDrawable" />
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/layout/toolbar_decor_content.xml b/v7/appcompat/tests/res/layout/toolbar_decor_content.xml
index db9935d..c271f68 100644
--- a/v7/appcompat/tests/res/layout/toolbar_decor_content.xml
+++ b/v7/appcompat/tests/res/layout/toolbar_decor_content.xml
@@ -29,11 +29,11 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent">
 
-        <EditText
+        <View
                 android:id="@+id/editText"
                 android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:imeOptions="flagNoExtractUi"/>
+                android:layout_height="20dip"
+                android:focusable="true" />
 
     </android.support.v7.custom.FitWindowsContentLayout>
 
diff --git a/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java
index 3390da2..3fe899d 100755
--- a/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java
@@ -270,10 +270,17 @@
             // specified, so it should have its height reduced by the height of the system status
             // bar.
 
+            final int[] contentViewLocationOnScreen = new int[2];
+            mContentView.getLocationOnScreen(contentViewLocationOnScreen);
+            final int statusBarHeight = contentViewLocationOnScreen[1];
             // Get the system window top inset that was propagated to the top-level DrawerLayout
             // during its layout.
             int drawerTopInset = mDrawerLayout.getSystemWindowInsetTop();
-            assertTrue("Drawer top inset is positive on L+", drawerTopInset > 0);
+            if (statusBarHeight > 0) {
+                assertEquals("Drawer top inset is positive on L+", statusBarHeight, drawerTopInset);
+            } else {
+                assertEquals("Drawer top inset 0 due to no status bar", 0, drawerTopInset);
+            }
             assertEquals("Drawer layout and drawer heights on L+",
                     drawerLayoutHeight - drawerTopInset, contentHeight);
         }
diff --git a/v7/cardview/AndroidManifest.xml b/v7/cardview/AndroidManifest.xml
index c35e369..e85003c 100644
--- a/v7/cardview/AndroidManifest.xml
+++ b/v7/cardview/AndroidManifest.xml
@@ -16,5 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.v7.cardview">
     <uses-sdk android:minSdkVersion="9"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application />
 </manifest>
diff --git a/v7/cardview/build.gradle b/v7/cardview/build.gradle
index b88aad7..ce3f28d 100644
--- a/v7/cardview/build.gradle
+++ b/v7/cardview/build.gradle
@@ -38,28 +38,11 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/v7/gridlayout/AndroidManifest.xml b/v7/gridlayout/AndroidManifest.xml
index d2cc627..dfcc942 100644
--- a/v7/gridlayout/AndroidManifest.xml
+++ b/v7/gridlayout/AndroidManifest.xml
@@ -16,5 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.v7.gridlayout">
     <uses-sdk android:minSdkVersion="9"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application />
 </manifest>
diff --git a/v7/gridlayout/build.gradle b/v7/gridlayout/build.gradle
index 542d5a7..56f320f 100644
--- a/v7/gridlayout/build.gradle
+++ b/v7/gridlayout/build.gradle
@@ -1,5 +1,4 @@
 apply plugin: 'com.android.library'
-
 archivesBaseName = 'gridlayout-v7'
 
 dependencies {
@@ -53,28 +52,11 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/v7/mediarouter/AndroidManifest.xml b/v7/mediarouter/AndroidManifest.xml
index 59d9f99..bbebdfe 100644
--- a/v7/mediarouter/AndroidManifest.xml
+++ b/v7/mediarouter/AndroidManifest.xml
@@ -16,5 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.v7.mediarouter">
     <uses-sdk android:minSdkVersion="9"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application />
 </manifest>
diff --git a/v7/mediarouter/build.gradle b/v7/mediarouter/build.gradle
index 6b63089..7796565 100644
--- a/v7/mediarouter/build.gradle
+++ b/v7/mediarouter/build.gradle
@@ -4,6 +4,9 @@
 dependencies {
     compile project(":support-appcompat-v7")
     compile project(":support-palette-v7")
+
+    androidTestCompile ("com.android.support.test:runner:${project.rootProject.ext.testRunnerVersion}")
+    testCompile 'junit:junit:4.12'
 }
 
 android {
@@ -11,6 +14,8 @@
 
     defaultConfig {
         minSdkVersion 9
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
     }
 
     sourceSets {
@@ -23,6 +28,11 @@
                 'src'
         ]
         main.res.srcDir 'res'
+
+        androidTest.setRoot('tests')
+        androidTest.java.srcDir 'tests/src'
+        androidTest.res.srcDir 'tests/res'
+        androidTest.manifest.srcFile 'tests/AndroidManifest.xml'
     }
 
     compileOptions {
@@ -39,28 +49,11 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/v7/mediarouter/jellybean/android/support/v7/media/MediaRouterJellybean.java b/v7/mediarouter/jellybean/android/support/v7/media/MediaRouterJellybean.java
index 85071a4..7ade21d 100644
--- a/v7/mediarouter/jellybean/android/support/v7/media/MediaRouterJellybean.java
+++ b/v7/mediarouter/jellybean/android/support/v7/media/MediaRouterJellybean.java
@@ -121,9 +121,9 @@
             Field globalRouterField = routerObj.getClass().getDeclaredField("sStatic");
             globalRouterField.setAccessible(true);
             Object globalRouterObj = globalRouterField.get(null);
-            Method method = globalRouterObj.getClass().getDeclaredMethod("isBluetoothA2dpOn", null);
+            Method method = globalRouterObj.getClass().getDeclaredMethod("isBluetoothA2dpOn");
             method.setAccessible(true);
-            Object result = method.invoke(globalRouterObj, null);
+            Object result = method.invoke(globalRouterObj);
             return (Boolean) result;
         } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException
                 | NoSuchMethodException | InvocationTargetException e) {
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
index ac79f2d..2e5b983 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
@@ -378,12 +378,12 @@
         }
     }
 
-    private static final class RouteComparator implements Comparator<MediaRouter.RouteInfo> {
+    static final class RouteComparator implements Comparator<MediaRouter.RouteInfo> {
         public static final RouteComparator sInstance = new RouteComparator();
 
         @Override
         public int compare(MediaRouter.RouteInfo lhs, MediaRouter.RouteInfo rhs) {
-            return lhs.getName().compareTo(rhs.getName());
+            return lhs.getName().compareToIgnoreCase(rhs.getName());
         }
     }
 }
diff --git a/v7/mediarouter/tests/AndroidManifest.xml b/v7/mediarouter/tests/AndroidManifest.xml
new file mode 100644
index 0000000..87c4d65
--- /dev/null
+++ b/v7/mediarouter/tests/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   Copyright (C) 2016 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
+          package="android.support.v7.mediarouter.test">
+
+    <uses-sdk
+            android:minSdkVersion="9"
+            android:targetSdkVersion="23"
+            tools:overrideLibrary="android.support.test, android.app, android.support.test.rule,
+                      android.support.test.espresso, android.support.test.espresso.idling"/>
+
+    <application android:supportsRtl="true">
+
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation
+            android:name="android.test.InstrumentationTestRunner"
+            android:targetPackage="android.support.v7.mediarouter.test"/>
+
+</manifest>
diff --git a/v7/mediarouter/tests/NO_DOCS b/v7/mediarouter/tests/NO_DOCS
new file mode 100644
index 0000000..092a39c
--- /dev/null
+++ b/v7/mediarouter/tests/NO_DOCS
@@ -0,0 +1,17 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+Having this file, named NO_DOCS, in a directory will prevent
+Android javadocs from being generated for java files under
+the directory. This is especially useful for test projects.
diff --git a/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTest.java b/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTest.java
new file mode 100644
index 0000000..7a21cdb
--- /dev/null
+++ b/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.app;
+
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.media.MediaRouter.RouteInfo;
+import android.support.v7.media.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaRouteChooserDialogTest {
+    private MediaRouteChooserDialog.RouteComparator mComparator;
+
+    @Before
+    public void setup() {
+        mComparator = new MediaRouteChooserDialog.RouteComparator();
+    }
+
+    @Test
+    public void testRouteComparatorWithSameRouteName() {
+        RouteInfo routeInfo1 = TestUtils.createRouteInfo("ROUTE_ID_1", "ROUTE_NAME_1");
+        RouteInfo routeInfo2 = TestUtils.createRouteInfo("ROUTE_ID_2", "ROUTE_NAME_1");
+        int result = mComparator.compare(routeInfo1, routeInfo2);
+        assertTrue(result == 0);
+    }
+
+    @Test
+    public void testRouteComparatorWithCommonComparison() {
+        RouteInfo routeInfo1 = TestUtils.createRouteInfo("ROUTE_ID_1", "Route ABC");
+        RouteInfo routeInfo2 = TestUtils.createRouteInfo("ROUTE_ID_2", "Route XYZ");
+        int result = mComparator.compare(routeInfo1, routeInfo2);
+        assertTrue(result < 0);
+    }
+
+    @Test
+    public void testRouteComparatorWithCaseInsensitiveComparison() {
+        RouteInfo routeInfo1 = TestUtils.createRouteInfo("ROUTE_ID_1", "living room abc");
+        RouteInfo routeInfo2 = TestUtils.createRouteInfo("ROUTE_ID_2", "LIVING ROOM XYZ");
+        int result1 = mComparator.compare(routeInfo1, routeInfo2);
+        assertTrue(result1 < 0);
+
+        RouteInfo routeInfo3 = TestUtils.createRouteInfo("ROUTE_ID_3", "LIVING ROOM ABC");
+        RouteInfo routeInfo4 = TestUtils.createRouteInfo("ROUTE_ID_4", "living room xyz");
+        int result2 = mComparator.compare(routeInfo3, routeInfo4);
+        assertTrue(result2 < 0);
+    }
+}
diff --git a/v7/mediarouter/tests/src/android/support/v7/media/TestUtils.java b/v7/mediarouter/tests/src/android/support/v7/media/TestUtils.java
new file mode 100644
index 0000000..f92a4d2
--- /dev/null
+++ b/v7/mediarouter/tests/src/android/support/v7/media/TestUtils.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.media;
+
+public class TestUtils {
+    public static MediaRouter.RouteInfo createRouteInfo(String id, String name) {
+        MediaRouteDescriptor descriptor = new MediaRouteDescriptor.Builder(id, name).build();
+        MediaRouter.RouteInfo routeInfo = new MediaRouter.RouteInfo(null, null, null);
+        routeInfo.updateDescriptor(descriptor);
+        return routeInfo;
+    }
+}
diff --git a/v7/palette/build.gradle b/v7/palette/build.gradle
index 2494370..686fe71 100644
--- a/v7/palette/build.gradle
+++ b/v7/palette/build.gradle
@@ -1,5 +1,4 @@
 apply plugin: 'com.android.library'
-
 archivesBaseName = 'palette-v7'
 
 dependencies {
@@ -34,28 +33,11 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/v7/palette/src/main/AndroidManifest.xml b/v7/palette/src/main/AndroidManifest.xml
index c44818a..52e90a2 100644
--- a/v7/palette/src/main/AndroidManifest.xml
+++ b/v7/palette/src/main/AndroidManifest.xml
@@ -16,6 +16,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.v7.palette">
     <uses-sdk android:minSdkVersion="9"/>
-    <application>
-    </application>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    <application />
 </manifest>
diff --git a/v7/palette/src/main/java/android/support/v7/graphics/Palette.java b/v7/palette/src/main/java/android/support/v7/graphics/Palette.java
index 93570de..d092587 100644
--- a/v7/palette/src/main/java/android/support/v7/graphics/Palette.java
+++ b/v7/palette/src/main/java/android/support/v7/graphics/Palette.java
@@ -84,7 +84,7 @@
         void onGenerated(Palette palette);
     }
 
-    static final int DEFAULT_RESIZE_BITMAP_AREA = 160 * 160;
+    static final int DEFAULT_RESIZE_BITMAP_AREA = 112 * 112;
     static final int DEFAULT_CALCULATE_NUMBER_COLORS = 16;
 
     static final float MIN_CONTRAST_TITLE_TEXT = 3.0f;
@@ -909,7 +909,7 @@
             if (mResizeArea > 0) {
                 final int bitmapArea = bitmap.getWidth() * bitmap.getHeight();
                 if (bitmapArea > mResizeArea) {
-                    scaleRatio = mResizeArea / (double) bitmapArea;
+                    scaleRatio = Math.sqrt(mResizeArea / (double) bitmapArea);
                 }
             } else if (mResizeMaxDimension > 0) {
                 final int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
diff --git a/v7/preference/AndroidManifest.xml b/v7/preference/AndroidManifest.xml
index da9c80e..19e6215 100644
--- a/v7/preference/AndroidManifest.xml
+++ b/v7/preference/AndroidManifest.xml
@@ -1,8 +1,21 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.support.v7.preference"
-    android:versionCode="1"
-    android:versionName="1.0">
+    package="android.support.v7.preference">
     <uses-sdk android:minSdkVersion="9" />
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
     <application />
 </manifest>
diff --git a/v7/preference/build.gradle b/v7/preference/build.gradle
index a63cc9e..b5fd656 100644
--- a/v7/preference/build.gradle
+++ b/v7/preference/build.gradle
@@ -15,7 +15,6 @@
  */
 
 apply plugin: 'com.android.library'
-
 archivesBaseName = 'preference-v7'
 
 dependencies {
@@ -80,28 +79,11 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/v7/recyclerview/AndroidManifest.xml b/v7/recyclerview/AndroidManifest.xml
index d1c1489..f4f010d 100644
--- a/v7/recyclerview/AndroidManifest.xml
+++ b/v7/recyclerview/AndroidManifest.xml
@@ -16,4 +16,5 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.v7.recyclerview">
     <uses-sdk android:minSdkVersion="9"/>
+    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
 </manifest>
diff --git a/v7/recyclerview/build.gradle b/v7/recyclerview/build.gradle
index 11a098e..e262ec7 100644
--- a/v7/recyclerview/build.gradle
+++ b/v7/recyclerview/build.gradle
@@ -1,5 +1,4 @@
 apply plugin: 'com.android.library'
-
 archivesBaseName = 'recyclerview-v7'
 
 dependencies {
@@ -47,10 +46,6 @@
         targetCompatibility JavaVersion.VERSION_1_7
     }
 
-    packagingOptions {
-        exclude 'LICENSE.txt'
-    }
-
     testOptions {
         unitTests.returnDefaultValues = true
     }
@@ -68,28 +63,11 @@
     }
     def suffix = name.capitalize()
 
-    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
-        dependsOn variant.javaCompile
-        from variant.javaCompile.destinationDir
-        from 'LICENSE.txt'
-    }
-    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
-        source android.sourceSets.main.java
-        classpath = files(variant.javaCompile.classpath.files) + files(
-                "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
-    }
-
-    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
-        classifier = 'javadoc'
-        from 'build/docs/javadoc'
-    }
-
     def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
 
-    artifacts.add('archives', javadocJarTask);
     artifacts.add('archives', sourcesJarTask);
 }
 
diff --git a/v7/recyclerview/src/android/support/v7/widget/GapWorker.java b/v7/recyclerview/src/android/support/v7/widget/GapWorker.java
index 87bc8c4..797ace3 100644
--- a/v7/recyclerview/src/android/support/v7/widget/GapWorker.java
+++ b/v7/recyclerview/src/android/support/v7/widget/GapWorker.java
@@ -15,6 +15,7 @@
  */
 package android.support.v7.widget;
 
+import android.support.annotation.Nullable;
 import android.support.v4.os.TraceCompat;
 import android.view.View;
 
@@ -97,6 +98,7 @@
 
                 if (mCount > layout.mPrefetchMaxCountObserved) {
                     layout.mPrefetchMaxCountObserved = mCount;
+                    layout.mPrefetchMaxObservedInInitialPrefetch = nested;
                     view.mRecycler.updateViewCacheSize();
                 }
             }
@@ -257,7 +259,7 @@
         return false;
     }
 
-    private RecyclerView.ViewHolder flushWorkWithDeadline(RecyclerView view,
+    private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view,
             int position, long deadlineNs) {
         if (isPrefetchPositionAttached(view, position)) {
             // don't attempt to prefetch attached views
@@ -284,38 +286,45 @@
         return holder;
     }
 
+    private void prefetchInnerRecyclerViewWithDeadline(@Nullable RecyclerView innerView,
+            long deadlineNs) {
+        if (innerView == null) {
+            return;
+        }
+
+        if (innerView.mDataSetHasChangedAfterLayout
+                && innerView.mChildHelper.getUnfilteredChildCount() != 0) {
+            // RecyclerView has new data, but old attached views. Clear everything, so that
+            // we can prefetch without partially stale data.
+            innerView.removeAndRecycleViews();
+        }
+
+        // do nested prefetch!
+        final LayoutPrefetchRegistryImpl innerPrefetchRegistry = innerView.mPrefetchRegistry;
+        innerPrefetchRegistry.collectPrefetchPositionsFromView(innerView, true);
+
+        if (innerPrefetchRegistry.mCount != 0) {
+            try {
+                TraceCompat.beginSection(RecyclerView.TRACE_NESTED_PREFETCH_TAG);
+                innerView.mState.prepareForNestedPrefetch(innerView.mAdapter);
+                for (int i = 0; i < innerPrefetchRegistry.mCount * 2; i += 2) {
+                    // Note that we ignore immediate flag for inner items because
+                    // we have lower confidence they're needed next frame.
+                    final int innerPosition = innerPrefetchRegistry.mPrefetchArray[i];
+                    prefetchPositionWithDeadline(innerView, innerPosition, deadlineNs);
+                }
+            } finally {
+                TraceCompat.endSection();
+            }
+        }
+    }
+
     private void flushTaskWithDeadline(Task task, long deadlineNs) {
         long taskDeadlineNs = task.immediate ? RecyclerView.FOREVER_NS : deadlineNs;
-        RecyclerView.ViewHolder holder = flushWorkWithDeadline(task.view,
+        RecyclerView.ViewHolder holder = prefetchPositionWithDeadline(task.view,
                 task.position, taskDeadlineNs);
         if (holder != null && holder.mNestedRecyclerView != null) {
-            final RecyclerView innerView = holder.mNestedRecyclerView;
-
-            if (innerView.mDataSetHasChangedAfterLayout
-                    && innerView.mChildHelper.getUnfilteredChildCount() != 0) {
-                // RecyclerView has new data, but old attached views. Clear everything, so that
-                // we can prefetch without partially stale data.
-                innerView.removeAndRecycleViews();
-            }
-
-            // do nested prefetch!
-            final LayoutPrefetchRegistryImpl innerPrefetchRegistry = innerView.mPrefetchRegistry;
-            innerPrefetchRegistry.collectPrefetchPositionsFromView(innerView, true);
-
-            if (innerPrefetchRegistry.mCount != 0) {
-                try {
-                    TraceCompat.beginSection(RecyclerView.TRACE_NESTED_PREFETCH_TAG);
-                    innerView.mState.prepareForNestedPrefetch(innerView.mAdapter);
-                    for (int i = 0; i < innerPrefetchRegistry.mCount * 2; i += 2) {
-                        // Note that we ignore immediate flag for inner items because
-                        // we have lower confidence they're needed next frame.
-                        final int innerPosition = innerPrefetchRegistry.mPrefetchArray[i];
-                        flushWorkWithDeadline(innerView, innerPosition, deadlineNs);
-                    }
-                } finally {
-                    TraceCompat.endSection();
-                }
-            }
+            prefetchInnerRecyclerViewWithDeadline(holder.mNestedRecyclerView.get(), deadlineNs);
         }
     }
 
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
index 9297522..78a31b4 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
@@ -413,14 +413,22 @@
     }
 
     /**
-     * <p>Returns the amount of extra space that should be laid out by LayoutManager.
-     * By default, {@link android.support.v7.widget.LinearLayoutManager} lays out 1 extra page of
-     * items while smooth scrolling and 0 otherwise. You can override this method to implement your
-     * custom layout pre-cache logic.</p>
-     * <p>Laying out invisible elements will eventually come with performance cost. On the other
-     * hand, in places like smooth scrolling to an unknown location, this extra content helps
-     * LayoutManager to calculate a much smoother scrolling; which improves user experience.</p>
-     * <p>You can also use this if you are trying to pre-layout your upcoming views.</p>
+     * <p>Returns the amount of extra space that should be laid out by LayoutManager.</p>
+     *
+     * <p>By default, {@link android.support.v7.widget.LinearLayoutManager} lays out 1 extra page
+     * of items while smooth scrolling and 0 otherwise. You can override this method to implement
+     * your custom layout pre-cache logic.</p>
+     *
+     * <p><strong>Note:</strong>Laying out invisible elements generally comes with significant
+     * performance cost. It's typically only desirable in places like smooth scrolling to an unknown
+     * location, where 1) the extra content helps LinearLayoutManager know in advance when its
+     * target is approaching, so it can decelerate early and smoothly and 2) while motion is
+     * continuous.</p>
+     *
+     * <p>Extending the extra layout space is especially expensive if done while the user may change
+     * scrolling direction. Changing direction will cause the extra layout space to swap to the
+     * opposite side of the viewport, incurring many rebinds/recycles, unless the cache is large
+     * enough to handle it.</p>
      *
      * @return The extra space that should be laid out (in pixels).
      */
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index d192f4d..7027acb 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -74,6 +74,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
@@ -188,6 +189,16 @@
      */
     private static final boolean FORCE_ABS_FOCUS_SEARCH_DIRECTION = Build.VERSION.SDK_INT <= 15;
 
+    /**
+     * on API 15-, a focused child can still be considered a focused child of RV even after
+     * it's being removed or its focusable flag is set to false. This is because when this focused
+     * child is detached, the reference to this child is not removed in clearFocus. API 16 and above
+     * properly handle this case by calling ensureInputFocusOnFirstFocusable or rootViewRequestFocus
+     * to request focus on a new child, which will clear the focus on the old (detached) child as a
+     * side-effect.
+     */
+    private static final boolean IGNORE_DETACHED_FOCUSED_CHILD = Build.VERSION.SDK_INT <= 15;
+
     static final boolean DISPATCH_TEMP_DETACH = false;
     public static final int HORIZONTAL = 0;
     public static final int VERTICAL = 1;
@@ -3340,9 +3351,28 @@
         // only recover focus if RV itself has the focus or the focused view is hidden
         if (!isFocused()) {
             final View focusedChild = getFocusedChild();
-            if (!mChildHelper.isHidden(focusedChild)
-                    // on API 15, this happens :/.
-                    && focusedChild.getParent() == this && focusedChild.hasFocus()) {
+            if (IGNORE_DETACHED_FOCUSED_CHILD
+                    && (focusedChild.getParent() == null || !focusedChild.hasFocus())) {
+                // Special handling of API 15-. A focused child can be invalid because mFocus is not
+                // cleared when the child is detached (mParent = null),
+                // This happens because clearFocus on API 15- does not invalidate mFocus of its
+                // parent when this child is detached.
+                // For API 16+, this is not an issue because requestFocus takes care of clearing the
+                // prior detached focused child. For API 15- the problem happens in 2 cases because
+                // clearChild does not call clearChildFocus on RV: 1. setFocusable(false) is called
+                // for the current focused item which calls clearChild or 2. when the prior focused
+                // child is removed, removeDetachedView called in layout step 3 which calls
+                // clearChild. We should ignore this invalid focused child in all our calculations
+                // for the next view to receive focus, and apply the focus recovery logic instead.
+                if (mChildHelper.getChildCount() == 0) {
+                    // No children left. Request focus on the RV itself since one of its children
+                    // was holding focus previously.
+                    requestFocus();
+                    return;
+                }
+            } else if (!mChildHelper.isHidden(focusedChild)) {
+                // If the currently focused child is hidden, apply the focus recovery logic.
+                // Otherwise return, i.e. the currently (unhidden) focused child is good enough :/.
                 return;
             }
         }
@@ -3585,6 +3615,14 @@
         if (mRecycler.mChangedScrap != null) {
             mRecycler.mChangedScrap.clear();
         }
+        if (mLayout.mPrefetchMaxObservedInInitialPrefetch) {
+            // Initial prefetch has expanded cache, so reset until next prefetch.
+            // This prevents initial prefetches from expanding the cache permanently.
+            mLayout.mPrefetchMaxCountObserved = 0;
+            mLayout.mPrefetchMaxObservedInInitialPrefetch = false;
+            mRecycler.updateViewCacheSize();
+        }
+
         mLayout.onLayoutCompleted(mState);
         onExitLayoutOrScroll();
         resumeRequestLayout(false);
@@ -4992,7 +5030,7 @@
             if (oldAverage == 0) {
                 return newValue;
             }
-            return (oldAverage / 8 * 7) + (newValue / 8);
+            return (oldAverage / 4 * 3) + (newValue / 4);
         }
 
         void factorInCreateTime(int viewType, long createTimeNs) {
@@ -5088,7 +5126,7 @@
      */
     static void clearNestedRecyclerViewIfNotNested(@NonNull ViewHolder holder) {
         if (holder.mNestedRecyclerView != null) {
-            View item = holder.mNestedRecyclerView;
+            View item = holder.mNestedRecyclerView.get();
             while (item != null) {
                 if (item == holder.itemView) {
                     return; // match found, don't need to clear
@@ -5469,7 +5507,10 @@
                     holder = mAdapter.createViewHolder(RecyclerView.this, type);
                     if (ALLOW_THREAD_GAP_WORK) {
                         // only bother finding nested RV if prefetching
-                        holder.mNestedRecyclerView = findNestedRecyclerView(holder.itemView);
+                        RecyclerView innerView = findNestedRecyclerView(holder.itemView);
+                        if (innerView != null) {
+                            holder.mNestedRecyclerView = new WeakReference<>(innerView);
+                        }
                     }
 
                     long end = getNanoTime();
@@ -5704,7 +5745,12 @@
                     recycled = true;
                 }
             } else {
-                holder.mNestedRecyclerView = null;
+                // NOTE: A view can fail to be recycled when it is scrolled off while an animation
+                // runs. In this case, the item is eventually recycled by
+                // ItemAnimatorRestoreListener#onAnimationFinished.
+
+                // TODO: consider cancelling an animation when an item is removed scrollBy,
+                // to return it to the pool faster
                 if (DEBUG) {
                     Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
                             + "re-visit here. We are still removing it from animation lists");
@@ -6789,10 +6835,19 @@
          * Written by {@link GapWorker} when prefetches occur to track largest number of view ever
          * requested by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)} or
          * {@link #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry)} call.
+         *
+         * If expanded by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)},
+         * will be reset upon layout to prevent initial prefetches (often large, since they're
+         * proportional to expected child count) from expanding cache permanently.
          */
         int mPrefetchMaxCountObserved;
 
         /**
+         * If true, mPrefetchMaxCountObserved is only valid until next layout, and should be reset.
+         */
+        boolean mPrefetchMaxObservedInInitialPrefetch;
+
+        /**
          * These measure specs might be the measure specs that were passed into RecyclerView's
          * onMeasure method OR fake measure specs created by the RecyclerView.
          * For example, when a layout is run, RecyclerView always sets these specs to be
@@ -9860,7 +9915,7 @@
      */
     public static abstract class ViewHolder {
         public final View itemView;
-        RecyclerView mNestedRecyclerView;
+        WeakReference<RecyclerView> mNestedRecyclerView;
         int mPosition = NO_POSITION;
         int mOldPosition = NO_POSITION;
         long mItemId = NO_ID;
diff --git a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
index adbe4dd..19f31f8 100644
--- a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -2097,15 +2097,23 @@
         if (mPrefetchDistances == null || mPrefetchDistances.length < mSpanCount) {
             mPrefetchDistances = new int[mSpanCount];
         }
+
+        int itemPrefetchCount = 0;
         for (int i = 0; i < mSpanCount; i++) {
-            mPrefetchDistances[i] = mLayoutState.mItemDirection == LAYOUT_START
+            // compute number of pixels past the edge of the viewport that the current span extends
+            int distance = mLayoutState.mItemDirection == LAYOUT_START
                     ? mLayoutState.mStartLine - mSpans[i].getStartLine(mLayoutState.mStartLine)
                     : mSpans[i].getEndLine(mLayoutState.mEndLine) - mLayoutState.mEndLine;
+            if (distance >= 0) {
+                // span extends to the edge, so prefetch next item
+                mPrefetchDistances[itemPrefetchCount] = distance;
+                itemPrefetchCount++;
+            }
         }
-        Arrays.sort(mPrefetchDistances, 0, mSpanCount);
+        Arrays.sort(mPrefetchDistances, 0, itemPrefetchCount);
 
         // then assign them in order to the next N views (where N = span count)
-        for (int i = 0; i < mSpanCount && mLayoutState.hasMore(state); i++) {
+        for (int i = 0; i < itemPrefetchCount && mLayoutState.hasMore(state); i++) {
             layoutPrefetchRegistry.addPosition(mLayoutState.mCurrentPosition, mPrefetchDistances[i]);
             mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
         }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
index deac38c..9fbc967 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
@@ -47,6 +47,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
@@ -441,21 +442,21 @@
 
         // verify trivial noop case
         RecyclerView.ViewHolder holder = new RecyclerView.ViewHolder(recyclerView) {};
-        holder.mNestedRecyclerView = recyclerView;
+        holder.mNestedRecyclerView = new WeakReference<>(recyclerView);
         RecyclerView.clearNestedRecyclerViewIfNotNested(holder);
-        assertEquals(recyclerView, holder.mNestedRecyclerView);
+        assertEquals(recyclerView, holder.mNestedRecyclerView.get());
 
         // verify clear case
         holder = new RecyclerView.ViewHolder(new View(getContext())) {};
-        holder.mNestedRecyclerView = recyclerView;
+        holder.mNestedRecyclerView = new WeakReference<>(recyclerView);
         RecyclerView.clearNestedRecyclerViewIfNotNested(holder);
         assertNull(holder.mNestedRecyclerView);
 
         // verify more deeply nested case
         holder = new RecyclerView.ViewHolder(grandParent) {};
-        holder.mNestedRecyclerView = recyclerView;
+        holder.mNestedRecyclerView = new WeakReference<>(recyclerView);
         RecyclerView.clearNestedRecyclerViewIfNotNested(holder);
-        assertEquals(recyclerView, holder.mNestedRecyclerView);
+        assertEquals(recyclerView, holder.mNestedRecyclerView.get());
     }
 
     static class MockLayoutManager extends RecyclerView.LayoutManager {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
index f011440..7ab616f 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
@@ -515,6 +515,50 @@
     }
 
     @Test
+    public void prefetchStaggeredPastBoundary() {
+        StaggeredGridLayoutManager sglm =
+                new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
+        mRecyclerView.setLayoutManager(sglm);
+        mRecyclerView.setAdapter(new RecyclerView.Adapter() {
+            @Override
+            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+                return new RecyclerView.ViewHolder(new View(getContext())) {};
+            }
+
+            @Override
+            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+                holder.itemView.setMinimumWidth(100);
+                holder.itemView.setMinimumHeight(position == 0 ? 100 : 200);
+            }
+
+            @Override
+            public int getItemCount() {
+                return 2;
+            }
+        });
+
+        layout(200, 100);
+        mRecyclerView.scrollBy(0, 50);
+
+        /* Each row is 50 pixels:
+         * ------------- *
+         *___0___|___1___*
+         *   0   |   1   *
+         *_______|___1___*
+         *       |   1   *
+         */
+        assertEquals(2, mRecyclerView.getChildCount());
+        assertEquals(0, sglm.getFirstChildPosition());
+        assertEquals(1, sglm.getLastChildPosition());
+
+        // prefetch upward gets nothing
+        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10);
+
+        // prefetch downward gets nothing (and doesn't crash...)
+        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10);
+    }
+
+    @Test
     public void prefetchItemsSkipAnimations() {
         LinearLayoutManager llm = new LinearLayoutManager(getContext());
         mRecyclerView.setLayoutManager(llm);
@@ -608,7 +652,7 @@
                     @Override
                     public boolean matches(Object item) {
                         RecyclerView.ViewHolder holder = (RecyclerView.ViewHolder) item;
-                        return holder.itemView == holder.mNestedRecyclerView;
+                        return holder.itemView == holder.mNestedRecyclerView.get();
                     }
 
                     @Override
@@ -651,7 +695,6 @@
     }
 
     class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> {
-        private static final int OUTER_ITEM_COUNT = 10;
 
         private boolean mReverseInner;
 
@@ -672,13 +715,24 @@
         }
 
         OuterAdapter(boolean reverseInner) {
+            this(reverseInner, 10);
+        }
+
+        OuterAdapter(boolean reverseInner, int itemCount) {
             mReverseInner = reverseInner;
-            for (int i = 0; i <= OUTER_ITEM_COUNT; i++) {
+            for (int i = 0; i < itemCount; i++) {
                 mAdapters.add(new InnerAdapter());
                 mSavedStates.add(null);
             }
         }
 
+        void addItem() {
+            int index = getItemCount();
+            mAdapters.add(new InnerAdapter());
+            mSavedStates.add(null);
+            notifyItemInserted(index);
+        }
+
         @Override
         public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
             mRecyclerView.registerTimePassingMs(5);
@@ -714,7 +768,7 @@
 
         @Override
         public int getItemCount() {
-            return OUTER_ITEM_COUNT;
+            return mAdapters.size();
         }
     }
 
@@ -734,13 +788,14 @@
         RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
         assertNotNull(holder);
         assertNotNull(holder.mNestedRecyclerView);
-        CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView, 0, 1);
+        RecyclerView innerView = holder.mNestedRecyclerView.get();
+        CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1);
 
         // prefetch 4
-        ((LinearLayoutManager) holder.mNestedRecyclerView.getLayoutManager())
+        ((LinearLayoutManager) innerView.getLayoutManager())
                 .setInitialPrefetchItemCount(4);
         mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
-        CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView, 0, 1, 2, 3);
+        CacheUtils.verifyCacheContainsPrefetchedPositions(innerView, 0, 1, 2, 3);
     }
 
     @Test
@@ -755,7 +810,7 @@
         RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
 
         // anchor from right side, should see last two positions
-        CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView, 18, 19);
+        CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView.get(), 18, 19);
     }
 
     @Test
@@ -805,7 +860,7 @@
         assertEquals(0, outerAdapter.mAdapters.get(2).mItemsBound);
         mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
         RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
-        RecyclerView innerRecyclerView = holder.mNestedRecyclerView;
+        RecyclerView innerRecyclerView = holder.mNestedRecyclerView.get();
 
         assertNotNull(innerRecyclerView);
         CacheUtils.verifyCacheContainsPrefetchedPositions(innerRecyclerView, 0, 1);
@@ -841,7 +896,7 @@
         // item 0 is cached
         assertEquals(2, outerAdapter.mAdapters.get(0).mItemsBound);
         RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 0);
-        validateRvChildrenValid(holder.mNestedRecyclerView, 2);
+        validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2);
 
         // try and prefetch it
         mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, -1);
@@ -849,7 +904,92 @@
 
         // make sure cache's inner items aren't rebound unnecessarily
         assertEquals(2, outerAdapter.mAdapters.get(0).mItemsBound);
-        validateRvChildrenValid(holder.mNestedRecyclerView, 2);
+        validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2);
+    }
+
+    @Test
+    public void nestedRemoveAnimatingView() {
+        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+        OuterAdapter outerAdapter = new OuterAdapter(false, 1);
+        mRecyclerView.setAdapter(outerAdapter);
+        mRecyclerView.getItemAnimator().setAddDuration(TimeUnit.MILLISECONDS.toNanos(30));
+
+        layout(200, 200);
+
+        // Insert 3 items - only first one in viewport, so only it animates
+        for (int i = 0; i < 3; i++) {
+            outerAdapter.addItem();
+        }
+        layout(200, 200); // layout again to kick off animation
+
+
+        // item 1 is animating, so scroll it out of viewport
+        mRecyclerView.scrollBy(0, 200);
+
+        // 2 items attached, 1 cached (pos 0), but item animating pos 1 not accounted for...
+        assertEquals(2, mRecyclerView.mChildHelper.getUnfilteredChildCount());
+        assertEquals(1, mRecycler.mCachedViews.size());
+        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 0);
+        assertEquals(0, mRecyclerView.getRecycledViewPool().getRecycledViewCount(0));
+
+        // until animation ends
+        mRecyclerView.getItemAnimator().endAnimations();
+        assertEquals(2, mRecyclerView.mChildHelper.getUnfilteredChildCount());
+        assertEquals(2, mRecycler.mCachedViews.size());
+        CacheUtils.verifyCacheContainsPositions(mRecyclerView, 0, 1);
+        assertEquals(0, mRecyclerView.getRecycledViewPool().getRecycledViewCount(0));
+
+        for (RecyclerView.ViewHolder viewHolder : mRecycler.mCachedViews) {
+            assertNotNull(viewHolder.mNestedRecyclerView);
+        }
+    }
+
+    @Test
+    public void nestedExpandCacheCorrectly() {
+        final int DEFAULT_CACHE_SIZE = RecyclerView.Recycler.DEFAULT_CACHE_SIZE;
+
+        mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+        OuterAdapter outerAdapter = new OuterAdapter();
+        mRecyclerView.setAdapter(outerAdapter);
+
+        layout(200, 200);
+
+        mRecyclerView.mPrefetchRegistry.setPrefetchVector(0, 1);
+        mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
+
+        // after initial prefetch, view cache max expanded by number of inner items prefetched (2)
+        RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
+        RecyclerView innerView = holder.mNestedRecyclerView.get();
+        assertTrue(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch);
+        assertEquals(2, innerView.getLayoutManager().mPrefetchMaxCountObserved);
+        assertEquals(2 + DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax);
+
+        try {
+            // Note: As a hack, we not only must manually dispatch attachToWindow(), but we
+            // also have to be careful to call innerView.mGapWorker below. mRecyclerView.mGapWorker
+            // is registered to the wrong thread, since @setup is called on a different thread
+            // from @Test. Assert this, so this test can be fixed when setup == test thread.
+            assertEquals(1, mRecyclerView.mGapWorker.mRecyclerViews.size());
+            assertFalse(innerView.isAttachedToWindow());
+            innerView.onAttachedToWindow();
+
+            // bring prefetch view into viewport, at which point it shouldn't have cache expanded...
+            mRecyclerView.scrollBy(0, 100);
+            assertFalse(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch);
+            assertEquals(0, innerView.getLayoutManager().mPrefetchMaxCountObserved);
+            assertEquals(DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax);
+
+            // until a valid horizontal prefetch caches an item, and expands view count by one
+            innerView.mPrefetchRegistry.setPrefetchVector(1, 0);
+            innerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS); // NB: must be innerView.mGapWorker
+            assertFalse(innerView.getLayoutManager().mPrefetchMaxObservedInInitialPrefetch);
+            assertEquals(1, innerView.getLayoutManager().mPrefetchMaxCountObserved);
+            assertEquals(1 + DEFAULT_CACHE_SIZE, innerView.mRecycler.mViewCacheMax);
+        } finally {
+            if (innerView.isAttachedToWindow()) {
+                innerView.onDetachedFromWindow();
+            }
+        }
     }
 
     /**
@@ -949,8 +1089,8 @@
         for (RecyclerView.ViewHolder holder : mRecycler.mRecyclerPool.mScrap.get(0).mScrapHeap) {
             // verify that children are attached and valid, since the RVs haven't been rebound
             assertNotNull(holder.mNestedRecyclerView);
-            assertFalse(holder.mNestedRecyclerView.mDataSetHasChangedAfterLayout);
-            validateRvChildrenValid(holder.mNestedRecyclerView, 2);
+            assertFalse(holder.mNestedRecyclerView.get().mDataSetHasChangedAfterLayout);
+            validateRvChildrenValid(holder.mNestedRecyclerView.get(), 2);
         }
 
         // prefetch the outer item bind, but without enough time to do any inner binds
@@ -963,11 +1103,11 @@
         RecyclerView.ViewHolder holder = CacheUtils.peekAtCachedViewForPosition(mRecyclerView, 2);
         assertNotNull(holder);
         assertNotNull(holder.mNestedRecyclerView);
-        assertEquals(0, holder.mNestedRecyclerView.mChildHelper.getUnfilteredChildCount());
-        assertEquals(0, holder.mNestedRecyclerView.mRecycler.mCachedViews.size());
+        assertEquals(0, holder.mNestedRecyclerView.get().mChildHelper.getUnfilteredChildCount());
+        assertEquals(0, holder.mNestedRecyclerView.get().mRecycler.mCachedViews.size());
 
         // but if we give it more time to bind items, it'll now acquire its inner items
         mRecyclerView.mGapWorker.prefetch(RecyclerView.FOREVER_NS);
-        CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView, 0, 1);
+        CacheUtils.verifyCacheContainsPrefetchedPositions(holder.mNestedRecyclerView.get(), 0, 1);
     }
 }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewFocusRecoveryTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewFocusRecoveryTest.java
index 0c324a9..0354d53 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewFocusRecoveryTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewFocusRecoveryTest.java
@@ -421,6 +421,11 @@
      */
     private void assertFocusAfterLayout(int focusedChildIndexWhenRecoveryEnabled,
                                         int focusedChildIndexWhenRecoveryDisabled) {
+        if (mDisableAnimation && mDisableRecovery) {
+            // This case is not quite handled properly at the moment. For now, RV may become focused
+            // without re-delivering the focus down to the children. Skip the checks for now.
+            return;
+        }
         if (mRecyclerView.getChildCount() == 0) {
             assertThat("RV should have focus when it has no children",
                     mRecyclerView.hasFocus(), is(true));
@@ -428,11 +433,6 @@
                     mRecyclerView.isFocused(), is(true));
             return;
         }
-        if (mDisableAnimation && mDisableRecovery) {
-            // This case is not quite handled properly at the moment. For now, RV may become focused
-            // without re-delivering the focus down to the children. Skip the checks for now.
-            return;
-        }
 
         assertThat("RV should still have focus after layout", mRecyclerView.hasFocus(), is(true));
         if ((mDisableRecovery && focusedChildIndexWhenRecoveryDisabled == -1)