Merge "Fix up small issues in support-transition."
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index b24c09a..3e9e37b 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -5,4 +5,4 @@
     <mapping directory="$PROJECT_DIR$/../../external/doclava" vcs="Git" />
     <mapping directory="$PROJECT_DIR$/../../external/jdiff" vcs="Git" />
   </component>
-</project>
\ No newline at end of file
+</project>
diff --git a/api/26.0.0.txt b/api/26.0.0.txt
index b58bf3c..5b98882 100644
--- a/api/26.0.0.txt
+++ b/api/26.0.0.txt
@@ -1317,6 +1317,7 @@
     method public int getTransportStreamId();
     method public java.lang.String getType();
     method public java.lang.String getVideoFormat();
+    method public boolean isBrowsable();
     method public boolean isSearchable();
     method public android.content.ContentValues toContentValues();
   }
@@ -1351,6 +1352,13 @@
     method public android.support.media.tv.Channel.Builder setVideoFormat(java.lang.String);
   }
 
+  public class ChannelLogoUtils {
+    ctor public ChannelLogoUtils();
+    method public static android.graphics.Bitmap loadChannelLogo(android.content.Context, long);
+    method public static boolean storeChannelLogo(android.content.Context, long, android.net.Uri);
+    method public static boolean storeChannelLogo(android.content.Context, long, android.graphics.Bitmap);
+  }
+
   public final class PreviewProgram extends android.support.media.tv.BasePreviewProgram {
     method public static android.support.media.tv.PreviewProgram fromCursor(android.database.Cursor);
     method public long getChannelId();
@@ -1879,6 +1887,24 @@
     method public void setResizeClip(boolean);
   }
 
+  public class ChangeClipBounds extends android.support.transition.Transition {
+    ctor public ChangeClipBounds();
+    ctor public ChangeClipBounds(android.content.Context, android.util.AttributeSet);
+    method public void captureEndValues(android.support.transition.TransitionValues);
+    method public void captureStartValues(android.support.transition.TransitionValues);
+  }
+
+  public class CircularPropagation extends android.support.transition.VisibilityPropagation {
+    ctor public CircularPropagation();
+    method public long getStartDelay(android.view.ViewGroup, android.support.transition.Transition, android.support.transition.TransitionValues, android.support.transition.TransitionValues);
+    method public void setPropagationSpeed(float);
+  }
+
+  public class Explode extends android.support.transition.Visibility {
+    ctor public Explode();
+    ctor public Explode(android.content.Context, android.util.AttributeSet);
+  }
+
   public class Fade extends android.support.transition.Visibility {
     ctor public Fade(int);
     ctor public Fade();
@@ -1931,9 +1957,12 @@
     method public android.support.transition.Transition excludeTarget(java.lang.String, boolean);
     method public android.support.transition.Transition excludeTarget(java.lang.Class, boolean);
     method public long getDuration();
+    method public android.graphics.Rect getEpicenter();
+    method public android.support.transition.Transition.EpicenterCallback getEpicenterCallback();
     method public android.animation.TimeInterpolator getInterpolator();
     method public java.lang.String getName();
     method public android.support.transition.PathMotion getPathMotion();
+    method public android.support.transition.TransitionPropagation getPropagation();
     method public long getStartDelay();
     method public java.util.List<java.lang.Integer> getTargetIds();
     method public java.util.List<java.lang.String> getTargetNames();
@@ -1947,9 +1976,11 @@
     method public android.support.transition.Transition removeTarget(java.lang.String);
     method public android.support.transition.Transition removeTarget(java.lang.Class);
     method public android.support.transition.Transition setDuration(long);
+    method public void setEpicenterCallback(android.support.transition.Transition.EpicenterCallback);
     method public android.support.transition.Transition setInterpolator(android.animation.TimeInterpolator);
     method public void setMatchOrder(int...);
     method public void setPathMotion(android.support.transition.PathMotion);
+    method public void setPropagation(android.support.transition.TransitionPropagation);
     method public android.support.transition.Transition setStartDelay(long);
     field public static final int MATCH_ID = 3; // 0x3
     field public static final int MATCH_INSTANCE = 1; // 0x1
@@ -1957,6 +1988,11 @@
     field public static final int MATCH_NAME = 2; // 0x2
   }
 
+  public static abstract class Transition.EpicenterCallback {
+    ctor public Transition.EpicenterCallback();
+    method public abstract android.graphics.Rect onGetEpicenter(android.support.transition.Transition);
+  }
+
   public static abstract interface Transition.TransitionListener {
     method public abstract void onTransitionCancel(android.support.transition.Transition);
     method public abstract void onTransitionEnd(android.support.transition.Transition);
@@ -1982,6 +2018,13 @@
     method public void transitionTo(android.support.transition.Scene);
   }
 
+  public abstract class TransitionPropagation {
+    ctor public TransitionPropagation();
+    method public abstract void captureValues(android.support.transition.TransitionValues);
+    method public abstract java.lang.String[] getPropagationProperties();
+    method public abstract long getStartDelay(android.view.ViewGroup, android.support.transition.Transition, android.support.transition.TransitionValues, android.support.transition.TransitionValues);
+  }
+
   public class TransitionSet extends android.support.transition.Transition {
     ctor public TransitionSet();
     ctor public TransitionSet(android.content.Context, android.util.AttributeSet);
@@ -2019,6 +2062,15 @@
     field public static final int MODE_OUT = 2; // 0x2
   }
 
+  public abstract class VisibilityPropagation extends android.support.transition.TransitionPropagation {
+    ctor public VisibilityPropagation();
+    method public void captureValues(android.support.transition.TransitionValues);
+    method public java.lang.String[] getPropagationProperties();
+    method public int getViewVisibility(android.support.transition.TransitionValues);
+    method public int getViewX(android.support.transition.TransitionValues);
+    method public int getViewY(android.support.transition.TransitionValues);
+  }
+
 }
 
 package android.support.v13.app {
@@ -6927,7 +6979,6 @@
     method public java.lang.Object getRemoteControlClient();
     method public android.support.v4.media.session.MediaSessionCompat.Token getSessionToken();
     method public boolean isActive();
-    method public static deprecated android.support.v4.media.session.MediaSessionCompat obtain(android.content.Context, java.lang.Object);
     method public void release();
     method public void removeOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener);
     method public void sendSessionEvent(java.lang.String, android.os.Bundle);
@@ -6998,7 +7049,6 @@
     method public android.support.v4.media.MediaDescriptionCompat getDescription();
     method public long getQueueId();
     method public java.lang.Object getQueueItem();
-    method public static deprecated android.support.v4.media.session.MediaSessionCompat.QueueItem obtain(java.lang.Object);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.support.v4.media.session.MediaSessionCompat.QueueItem> CREATOR;
     field public static final int UNKNOWN_ID = -1; // 0xffffffff
@@ -7503,7 +7553,7 @@
   public class SimpleArrayMap<K, V> {
     ctor public SimpleArrayMap();
     ctor public SimpleArrayMap(int);
-    ctor public SimpleArrayMap(android.support.v4.util.SimpleArrayMap);
+    ctor public SimpleArrayMap(android.support.v4.util.SimpleArrayMap<K, V>);
     method public void clear();
     method public boolean containsKey(java.lang.Object);
     method public boolean containsValue(java.lang.Object);
diff --git a/api/current.txt b/api/current.txt
index e2873c4..8757fb5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1352,6 +1352,13 @@
     method public android.support.media.tv.Channel.Builder setVideoFormat(java.lang.String);
   }
 
+  public class ChannelLogoUtils {
+    ctor public ChannelLogoUtils();
+    method public static android.graphics.Bitmap loadChannelLogo(android.content.Context, long);
+    method public static boolean storeChannelLogo(android.content.Context, long, android.net.Uri);
+    method public static boolean storeChannelLogo(android.content.Context, long, android.graphics.Bitmap);
+  }
+
   public final class PreviewProgram extends android.support.media.tv.BasePreviewProgram {
     method public static android.support.media.tv.PreviewProgram fromCursor(android.database.Cursor);
     method public long getChannelId();
@@ -1671,13 +1678,13 @@
 
 package android.support.percent {
 
-  public class PercentFrameLayout extends android.widget.FrameLayout {
+  public deprecated class PercentFrameLayout extends android.widget.FrameLayout {
     ctor public PercentFrameLayout(android.content.Context);
     ctor public PercentFrameLayout(android.content.Context, android.util.AttributeSet);
     ctor public PercentFrameLayout(android.content.Context, android.util.AttributeSet, int);
   }
 
-  public static class PercentFrameLayout.LayoutParams extends android.widget.FrameLayout.LayoutParams implements android.support.percent.PercentLayoutHelper.PercentLayoutParams {
+  public static deprecated class PercentFrameLayout.LayoutParams extends android.widget.FrameLayout.LayoutParams implements android.support.percent.PercentLayoutHelper.PercentLayoutParams {
     ctor public PercentFrameLayout.LayoutParams(android.content.Context, android.util.AttributeSet);
     ctor public PercentFrameLayout.LayoutParams(int, int);
     ctor public PercentFrameLayout.LayoutParams(int, int, int);
@@ -1688,7 +1695,7 @@
     method public android.support.percent.PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo();
   }
 
-  public class PercentLayoutHelper {
+  public deprecated class PercentLayoutHelper {
     ctor public PercentLayoutHelper(android.view.ViewGroup);
     method public void adjustChildren(int, int);
     method public static void fetchWidthAndHeight(android.view.ViewGroup.LayoutParams, android.content.res.TypedArray, int, int);
@@ -1697,7 +1704,7 @@
     method public void restoreOriginalParams();
   }
 
-  public static class PercentLayoutHelper.PercentLayoutInfo {
+  public static deprecated class PercentLayoutHelper.PercentLayoutInfo {
     ctor public PercentLayoutHelper.PercentLayoutInfo();
     method public void fillLayoutParams(android.view.ViewGroup.LayoutParams, int, int);
     method public deprecated void fillMarginLayoutParams(android.view.ViewGroup.MarginLayoutParams, int, int);
@@ -1715,17 +1722,17 @@
     field public float widthPercent;
   }
 
-  public static abstract interface PercentLayoutHelper.PercentLayoutParams {
+  public static abstract deprecated interface PercentLayoutHelper.PercentLayoutParams {
     method public abstract android.support.percent.PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo();
   }
 
-  public class PercentRelativeLayout extends android.widget.RelativeLayout {
+  public deprecated class PercentRelativeLayout extends android.widget.RelativeLayout {
     ctor public PercentRelativeLayout(android.content.Context);
     ctor public PercentRelativeLayout(android.content.Context, android.util.AttributeSet);
     ctor public PercentRelativeLayout(android.content.Context, android.util.AttributeSet, int);
   }
 
-  public static class PercentRelativeLayout.LayoutParams extends android.widget.RelativeLayout.LayoutParams implements android.support.percent.PercentLayoutHelper.PercentLayoutParams {
+  public static deprecated class PercentRelativeLayout.LayoutParams extends android.widget.RelativeLayout.LayoutParams implements android.support.percent.PercentLayoutHelper.PercentLayoutParams {
     ctor public PercentRelativeLayout.LayoutParams(android.content.Context, android.util.AttributeSet);
     ctor public PercentRelativeLayout.LayoutParams(int, int);
     ctor public PercentRelativeLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
@@ -1880,6 +1887,24 @@
     method public void setResizeClip(boolean);
   }
 
+  public class ChangeClipBounds extends android.support.transition.Transition {
+    ctor public ChangeClipBounds();
+    ctor public ChangeClipBounds(android.content.Context, android.util.AttributeSet);
+    method public void captureEndValues(android.support.transition.TransitionValues);
+    method public void captureStartValues(android.support.transition.TransitionValues);
+  }
+
+  public class CircularPropagation extends android.support.transition.VisibilityPropagation {
+    ctor public CircularPropagation();
+    method public long getStartDelay(android.view.ViewGroup, android.support.transition.Transition, android.support.transition.TransitionValues, android.support.transition.TransitionValues);
+    method public void setPropagationSpeed(float);
+  }
+
+  public class Explode extends android.support.transition.Visibility {
+    ctor public Explode();
+    ctor public Explode(android.content.Context, android.util.AttributeSet);
+  }
+
   public class Fade extends android.support.transition.Visibility {
     ctor public Fade(int);
     ctor public Fade();
@@ -1932,9 +1957,12 @@
     method public android.support.transition.Transition excludeTarget(java.lang.String, boolean);
     method public android.support.transition.Transition excludeTarget(java.lang.Class, boolean);
     method public long getDuration();
+    method public android.graphics.Rect getEpicenter();
+    method public android.support.transition.Transition.EpicenterCallback getEpicenterCallback();
     method public android.animation.TimeInterpolator getInterpolator();
     method public java.lang.String getName();
     method public android.support.transition.PathMotion getPathMotion();
+    method public android.support.transition.TransitionPropagation getPropagation();
     method public long getStartDelay();
     method public java.util.List<java.lang.Integer> getTargetIds();
     method public java.util.List<java.lang.String> getTargetNames();
@@ -1948,9 +1976,11 @@
     method public android.support.transition.Transition removeTarget(java.lang.String);
     method public android.support.transition.Transition removeTarget(java.lang.Class);
     method public android.support.transition.Transition setDuration(long);
+    method public void setEpicenterCallback(android.support.transition.Transition.EpicenterCallback);
     method public android.support.transition.Transition setInterpolator(android.animation.TimeInterpolator);
     method public void setMatchOrder(int...);
     method public void setPathMotion(android.support.transition.PathMotion);
+    method public void setPropagation(android.support.transition.TransitionPropagation);
     method public android.support.transition.Transition setStartDelay(long);
     field public static final int MATCH_ID = 3; // 0x3
     field public static final int MATCH_INSTANCE = 1; // 0x1
@@ -1958,6 +1988,11 @@
     field public static final int MATCH_NAME = 2; // 0x2
   }
 
+  public static abstract class Transition.EpicenterCallback {
+    ctor public Transition.EpicenterCallback();
+    method public abstract android.graphics.Rect onGetEpicenter(android.support.transition.Transition);
+  }
+
   public static abstract interface Transition.TransitionListener {
     method public abstract void onTransitionCancel(android.support.transition.Transition);
     method public abstract void onTransitionEnd(android.support.transition.Transition);
@@ -1983,6 +2018,13 @@
     method public void transitionTo(android.support.transition.Scene);
   }
 
+  public abstract class TransitionPropagation {
+    ctor public TransitionPropagation();
+    method public abstract void captureValues(android.support.transition.TransitionValues);
+    method public abstract java.lang.String[] getPropagationProperties();
+    method public abstract long getStartDelay(android.view.ViewGroup, android.support.transition.Transition, android.support.transition.TransitionValues, android.support.transition.TransitionValues);
+  }
+
   public class TransitionSet extends android.support.transition.Transition {
     ctor public TransitionSet();
     ctor public TransitionSet(android.content.Context, android.util.AttributeSet);
@@ -2020,6 +2062,15 @@
     field public static final int MODE_OUT = 2; // 0x2
   }
 
+  public abstract class VisibilityPropagation extends android.support.transition.TransitionPropagation {
+    ctor public VisibilityPropagation();
+    method public void captureValues(android.support.transition.TransitionValues);
+    method public java.lang.String[] getPropagationProperties();
+    method public int getViewVisibility(android.support.transition.TransitionValues);
+    method public int getViewX(android.support.transition.TransitionValues);
+    method public int getViewY(android.support.transition.TransitionValues);
+  }
+
 }
 
 package android.support.v13.app {
@@ -2814,6 +2865,9 @@
     method protected abstract int getPageCount();
     method protected abstract java.lang.CharSequence getPageDescription(int);
     method protected abstract java.lang.CharSequence getPageTitle(int);
+    method protected final boolean isLogoAnimationFinished();
+    method protected void moveToNextPage();
+    method protected void moveToPreviousPage();
     method protected abstract android.view.View onCreateBackgroundView(android.view.LayoutInflater, android.view.ViewGroup);
     method protected abstract android.view.View onCreateContentView(android.view.LayoutInflater, android.view.ViewGroup);
     method protected android.animation.Animator onCreateDescriptionAnimator();
@@ -2822,6 +2876,7 @@
     method protected android.animation.Animator onCreateLogoAnimation();
     method protected android.animation.Animator onCreateTitleAnimator();
     method protected void onFinishFragment();
+    method protected void onLogoAnimationFinished();
     method protected void onPageChanged(int, int);
     method public int onProvideTheme();
     method public final void setIconResouceId(int);
@@ -2836,6 +2891,9 @@
     method protected abstract int getPageCount();
     method protected abstract java.lang.CharSequence getPageDescription(int);
     method protected abstract java.lang.CharSequence getPageTitle(int);
+    method protected final boolean isLogoAnimationFinished();
+    method protected void moveToNextPage();
+    method protected void moveToPreviousPage();
     method protected abstract android.view.View onCreateBackgroundView(android.view.LayoutInflater, android.view.ViewGroup);
     method protected abstract android.view.View onCreateContentView(android.view.LayoutInflater, android.view.ViewGroup);
     method protected android.animation.Animator onCreateDescriptionAnimator();
@@ -2844,6 +2902,7 @@
     method protected android.animation.Animator onCreateLogoAnimation();
     method protected android.animation.Animator onCreateTitleAnimator();
     method protected void onFinishFragment();
+    method protected void onLogoAnimationFinished();
     method protected void onPageChanged(int, int);
     method public int onProvideTheme();
     method public final void setIconResouceId(int);
@@ -5883,6 +5942,7 @@
     ctor public NotificationCompat.MessagingStyle.Message(java.lang.CharSequence, long, java.lang.CharSequence);
     method public java.lang.String getDataMimeType();
     method public android.net.Uri getDataUri();
+    method public android.os.Bundle getExtras();
     method public java.lang.CharSequence getSender();
     method public java.lang.CharSequence getText();
     method public long getTimestamp();
@@ -6928,7 +6988,6 @@
     method public java.lang.Object getRemoteControlClient();
     method public android.support.v4.media.session.MediaSessionCompat.Token getSessionToken();
     method public boolean isActive();
-    method public static deprecated android.support.v4.media.session.MediaSessionCompat obtain(android.content.Context, java.lang.Object);
     method public void release();
     method public void removeOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener);
     method public void sendSessionEvent(java.lang.String, android.os.Bundle);
@@ -6999,7 +7058,6 @@
     method public android.support.v4.media.MediaDescriptionCompat getDescription();
     method public long getQueueId();
     method public java.lang.Object getQueueItem();
-    method public static deprecated android.support.v4.media.session.MediaSessionCompat.QueueItem obtain(java.lang.Object);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.support.v4.media.session.MediaSessionCompat.QueueItem> CREATOR;
     field public static final int UNKNOWN_ID = -1; // 0xffffffff
@@ -7504,7 +7562,7 @@
   public class SimpleArrayMap<K, V> {
     ctor public SimpleArrayMap();
     ctor public SimpleArrayMap(int);
-    ctor public SimpleArrayMap(android.support.v4.util.SimpleArrayMap);
+    ctor public SimpleArrayMap(android.support.v4.util.SimpleArrayMap<K, V>);
     method public void clear();
     method public boolean containsKey(java.lang.Object);
     method public boolean containsValue(java.lang.Object);
diff --git a/buildSrc/diff_and_docs.gradle b/buildSrc/diff_and_docs.gradle
index 9ad419d..2034c15 100644
--- a/buildSrc/diff_and_docs.gradle
+++ b/buildSrc/diff_and_docs.gradle
@@ -25,10 +25,51 @@
 
 import groovy.io.FileType
 
+// Set up platform API files for federation.
+if (project.androidApiTxt != null) {
+    task generateSdkApi(type: Copy) {
+        description = 'Copies the API files for the current SDK.'
+
+        // Export the API files so this looks like a DoclavaTask.
+        ext.apiFile = new File(project.docsDir, 'release/sdk_current.txt')
+        ext.removedApiFile = new File(project.docsDir, 'release/sdk_removed.txt')
+
+        from project.androidApiTxt.absolutePath
+        into apiFile.parent
+        rename { apiFile.name }
+
+        // Register the fake removed file as an output.
+        outputs.file removedApiFile
+
+        doLast {
+            removedApiFile.createNewFile()
+        }
+    }
+} else {
+    task generateSdkApi(type: DoclavaTask, dependsOn: [configurations.doclava]) {
+        description = 'Generates API files for the current SDK.'
+
+        docletpath = configurations.doclava.resolve()
+        destinationDir = project.docsDir
+
+        classpath = project.androidJar
+        source zipTree(project.androidSrcJar)
+
+        apiFile = new File(project.docsDir, 'release/sdk_current.txt')
+        removedApiFile = new File(project.docsDir, 'release/sdk_removed.txt')
+        generateDocs = false
+
+        options {
+            addStringOption "stubpackages", "android.*"
+        }
+    }
+}
+
 // Generates online docs.
-task generateDocs(type: DoclavaTask, dependsOn: configurations.doclava) {
+task generateDocs(type: DoclavaTask, dependsOn: [configurations.doclava, generateSdkApi]) {
     group = JavaBasePlugin.DOCUMENTATION_GROUP
-    description = 'Generates d.android.com style documentation.'
+    description = 'Generates d.android.com-style documentation.'
+
     docletpath = configurations.doclava.resolve()
     destinationDir = new File(project.docsDir, "online")
 
@@ -40,6 +81,12 @@
             ['android.whichdoc', 'online'],
             ['android.hasSamples', 'true'])
 
+    def federateOption = new DoclavaMultilineJavadocOptionFileOption('federate')
+    federateOption.add(['Android', 'https://developer.android.com'])
+
+    def federationapiOption = new DoclavaMultilineJavadocOptionFileOption('federationapi')
+    federationapiOption.add(['Android', generateSdkApi.apiFile.absolutePath])
+
     // Track API change history.
     def apiFilePattern = /(\d+\.\d+\.\d).txt/
     def sinceOption = new DoclavaMultilineJavadocOptionFileOption('since')
@@ -60,9 +107,10 @@
     options {
         addStringOption "templatedir",
                 "${supportRootFolder}/../../external/doclava/res/assets/templates-sdk"
-        addStringOption "federate Android", "http://developer.android.com"
         addStringOption "stubpackages", "android.support.*"
         addStringOption "samplesdir", "${supportRootFolder}/samples"
+        addOption federateOption
+        addOption federationapiOption
         addOption hdfOption
         addOption sinceOption
     }
@@ -70,21 +118,18 @@
     exclude '**/BuildConfig.java'
 }
 
-JDiffTask createApiDiffsTask(String taskName, File oldApiXml, File newApiXml, File outDir,
-                             Configuration jdiff, Task... dependencies) {
-    return tasks.create(name: taskName, type: JDiffTask.class) {
-        dependsOn jdiff
-        dependsOn dependencies
+// Generates a distribution artifact for online docs.
+task distDocs(type: Zip, dependsOn: generateDocs) {
+    group = JavaBasePlugin.DOCUMENTATION_GROUP
+    description = 'Generates distribution artifact for d.android.com-style documentation.'
 
-        docletpath = jdiff.resolve()
+    from generateDocs.destinationDir
+    destinationDir project.distDir
+    baseName = "android-support-docs"
+    version = project.buildNumber
 
-        oldApiXmlFile oldApiXml
-        newApiXmlFile newApiXml
-        destinationDir = outDir
-
-        // This prefix is based on the assumption that the output diffs will
-        // ultimately land in frameworks/base/docs/html/sdk/support_api_diff/.
-        newJavadocPrefix = "../reference/"
+    doLast {
+        logger.lifecycle('Wrote API reference to ${archivePath}')
     }
 }
 
@@ -103,7 +148,6 @@
     options {
         addStringOption "templatedir",
                 "${supportRootFolder}/../../external/doclava/res/assets/templates-sdk"
-        addStringOption "federate Android", "http://developer.android.com"
         addStringOption "stubpackages", "android.support.*"
     }
     exclude '**/BuildConfig.java'
diff --git a/buildSrc/init.gradle b/buildSrc/init.gradle
index 25281df..7337c53 100644
--- a/buildSrc/init.gradle
+++ b/buildSrc/init.gradle
@@ -80,8 +80,11 @@
     final String fullSdkPath = "${init.prebuiltsRoot}/fullsdk-${platform}"
     if (file(fullSdkPath).exists()) {
         gradle.ext.currentSdk = 26
-        project.ext.androidJar = files("${fullSdkPath}/platforms/android-${gradle.ext.currentSdk}" +
-                "/android.jar")
+        project.ext.androidJar =
+                files("${fullSdkPath}/platforms/android-${gradle.currentSdk}/android.jar")
+        project.ext.androidSrcJar =
+                file("${fullSdkPath}/platforms/android-${gradle.currentSdk}/android-stubs-src.jar")
+        project.ext.androidApiTxt = null
         System.setProperty('android.home', "${init.prebuiltsRoot}/fullsdk-${platform}")
         File props = file("local.properties")
         props.write "sdk.dir=${fullSdkPath}"
@@ -89,6 +92,8 @@
     } else {
         gradle.ext.currentSdk = 'current'
         project.ext.androidJar = files("${init.prebuiltsRoot}/sdk/current/android.jar")
+        project.ext.androidSrcJar = null
+        project.ext.androidApiTxt = file("${init.prebuiltsRoot}/sdk/api/26.txt")
         File props = file("local.properties")
         props.write "android.dir=../../"
         ext.usingFullSdk = false
diff --git a/buildSrc/src/main/groovy/android/support/SupportLibraryExtension.groovy b/buildSrc/src/main/groovy/android/support/SupportLibraryExtension.groovy
index b3b5783..f9a2581 100644
--- a/buildSrc/src/main/groovy/android/support/SupportLibraryExtension.groovy
+++ b/buildSrc/src/main/groovy/android/support/SupportLibraryExtension.groovy
@@ -16,11 +16,38 @@
 
 package android.support;
 
+import org.gradle.api.Project
+
 /**
  * Extension for {@link SupportLibraryPlugin}.
  */
 class SupportLibraryExtension {
+    Project project
     String name;
     String description;
     String inceptionYear;
-}
+    Collection<License> licenses = [];
+
+    SupportLibraryExtension(Project project) {
+        this.project = project
+    }
+
+    License license(Closure closure) {
+        def license = project.configure(new License(), closure)
+        licenses.add(license)
+        return license
+    }
+
+    class License {
+        String name;
+        String url;
+
+        void url(String p) {
+            url = p
+        }
+
+        void name(String p) {
+            name = p
+        }
+    }
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/android/support/SupportLibraryPlugin.groovy b/buildSrc/src/main/groovy/android/support/SupportLibraryPlugin.groovy
index 4c9f797..87a5abf 100644
--- a/buildSrc/src/main/groovy/android/support/SupportLibraryPlugin.groovy
+++ b/buildSrc/src/main/groovy/android/support/SupportLibraryPlugin.groovy
@@ -42,7 +42,7 @@
     @Override
     public void apply(Project project) {
         SupportLibraryExtension supportLibraryExtension =
-                project.getExtensions().create("supportLibrary", SupportLibraryExtension);
+                project.getExtensions().create("supportLibrary", SupportLibraryExtension, project);
 
         project.apply(ImmutableMap.of("plugin", "com.android.library"));
         project.apply(ImmutableMap.of("plugin", ErrorProneBasePlugin.class));
@@ -142,6 +142,15 @@
                                 url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
                                 distribution 'repo'
                             }
+
+                            supportLibraryExtension.getLicenses().each {
+                                SupportLibraryExtension.License supportLicense ->
+                                    license {
+                                        name supportLicense.name
+                                        url supportLicense.url
+                                        distribution 'repo'
+                                    }
+                            }
                         }
 
                         scm {
diff --git a/buildSrc/src/main/groovy/android/support/doclava/DoclavaJavadocOptionFileOption.java b/buildSrc/src/main/groovy/android/support/doclava/DoclavaJavadocOptionFileOption.java
index 4bd67a3..db3f318 100644
--- a/buildSrc/src/main/groovy/android/support/doclava/DoclavaJavadocOptionFileOption.java
+++ b/buildSrc/src/main/groovy/android/support/doclava/DoclavaJavadocOptionFileOption.java
@@ -61,9 +61,14 @@
      */
     public DoclavaJavadocOptionFileOption duplicate() {
         final Iterable<String> value = getValue();
-        final ArrayList<String> valueCopy = new ArrayList<>();
-        for (String item : value) {
-            valueCopy.add(item);
+        final ArrayList<String> valueCopy;
+        if (value != null) {
+            valueCopy = new ArrayList<>();
+            for (String item : value) {
+                valueCopy.add(item);
+            }
+        } else {
+            valueCopy = null;
         }
         return new DoclavaJavadocOptionFileOption(getOption(), valueCopy);
     }
diff --git a/buildSrc/versions.gradle b/buildSrc/versions.gradle
index 33cd0a0..8152a02 100644
--- a/buildSrc/versions.gradle
+++ b/buildSrc/versions.gradle
@@ -17,10 +17,10 @@
 ext['android.builder.sdkDownload'] = false
 
 // Version code of the support library components.
-ext.supportVersion = "26.0.0-SNAPSHOT"
+ext.supportVersion = "26.0.0-alpha1"
 
 // This number gets incremented for each public release.
-ext.extraVersion = 44
+ext.extraVersion = 46
 
 // Enforce the use of prebuilt dependencies in all sub-projects. This is
 // required for the doclava dependency.
diff --git a/compat/AndroidManifest.xml b/compat/AndroidManifest.xml
index 6a9f6a6..09383ee 100644
--- a/compat/AndroidManifest.xml
+++ b/compat/AndroidManifest.xml
@@ -17,6 +17,7 @@
           xmlns:tools="http://schemas.android.com/tools"
           package="android.support.compat">
     <uses-sdk android:minSdkVersion="14" tools:overrideLibrary="android.support.compat"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/compat/ics/android/support/v4/app/ShareCompatICS.java b/compat/ics/android/support/v4/app/ShareCompatICS.java
deleted file mode 100644
index f635e73..0000000
--- a/compat/ics/android/support/v4/app/ShareCompatICS.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.app;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.support.annotation.RequiresApi;
-import android.view.ActionProvider;
-import android.view.MenuItem;
-import android.widget.ShareActionProvider;
-
-@RequiresApi(14)
-class ShareCompatICS {
-    private static final String HISTORY_FILENAME_PREFIX = ".sharecompat_";
-
-    public static void configureMenuItem(MenuItem item, Activity callingActivity, Intent intent) {
-        ActionProvider itemProvider = item.getActionProvider();
-        ShareActionProvider provider = null;
-        if (!(itemProvider instanceof ShareActionProvider)) {
-            provider = new ShareActionProvider(callingActivity);
-        } else {
-            provider = (ShareActionProvider) itemProvider;
-        }
-        provider.setShareHistoryFileName(HISTORY_FILENAME_PREFIX +
-                callingActivity.getClass().getName());
-        provider.setShareIntent(intent);
-        item.setActionProvider(provider);
-    }
-}
diff --git a/compat/java/android/support/v4/app/ActivityCompat.java b/compat/java/android/support/v4/app/ActivityCompat.java
index 303c4c3..e5c057f 100644
--- a/compat/java/android/support/v4/app/ActivityCompat.java
+++ b/compat/java/android/support/v4/app/ActivityCompat.java
@@ -74,7 +74,7 @@
 
     /**
      * This class should not be instantiated, but the constructor must be
-     * visible for the class to be extended (ex. in support-v13).
+     * visible for the class to be extended (as in support-v13).
      */
     protected ActivityCompat() {
         // Not publicly instantiable, but may be extended.
diff --git a/compat/java/android/support/v4/app/NotificationCompat.java b/compat/java/android/support/v4/app/NotificationCompat.java
index 5d43871..f8edf58 100644
--- a/compat/java/android/support/v4/app/NotificationCompat.java
+++ b/compat/java/android/support/v4/app/NotificationCompat.java
@@ -20,7 +20,6 @@
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
-import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.Notification;
 import android.app.PendingIntent;
@@ -2298,11 +2297,13 @@
             static final String KEY_SENDER = "sender";
             static final String KEY_DATA_MIME_TYPE = "type";
             static final String KEY_DATA_URI= "uri";
+            static final String KEY_EXTRAS_BUNDLE = "extras";
 
             private final CharSequence mText;
             private final long mTimestamp;
             private final CharSequence mSender;
 
+            private Bundle mExtras = new Bundle();
             private String mDataMimeType;
             private Uri mDataUri;
 
@@ -2371,6 +2372,13 @@
             }
 
             /**
+             * Get the extras Bundle for this message.
+             */
+            public Bundle getExtras() {
+                return mExtras;
+            }
+
+            /**
              * Get the text used to display the contact's name in the messaging experience
              */
             public CharSequence getSender() {
@@ -2407,6 +2415,9 @@
                 if (mDataUri != null) {
                     bundle.putParcelable(KEY_DATA_URI, mDataUri);
                 }
+                if (mExtras != null) {
+                    bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras);
+                }
                 return bundle;
             }
 
@@ -2441,10 +2452,12 @@
                                 bundle.getLong(KEY_TIMESTAMP), bundle.getCharSequence(KEY_SENDER));
                         if (bundle.containsKey(KEY_DATA_MIME_TYPE) &&
                                 bundle.containsKey(KEY_DATA_URI)) {
-
                             message.setData(bundle.getString(KEY_DATA_MIME_TYPE),
                                     (Uri) bundle.getParcelable(KEY_DATA_URI));
                         }
+                        if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) {
+                            message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE));
+                        }
                         return message;
                     }
                 } catch (ClassCastException e) {
diff --git a/compat/java/android/support/v4/app/ShareCompat.java b/compat/java/android/support/v4/app/ShareCompat.java
index 1f1c776..b0c6e62 100644
--- a/compat/java/android/support/v4/app/ShareCompat.java
+++ b/compat/java/android/support/v4/app/ShareCompat.java
@@ -16,7 +16,8 @@
 
 package android.support.v4.app;
 
-import android.support.annotation.RequiresApi;
+import static android.os.Build.VERSION.SDK_INT;
+
 import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -24,14 +25,15 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
-import android.os.Build;
 import android.support.annotation.StringRes;
 import android.support.v4.content.IntentCompat;
 import android.text.Html;
 import android.text.Spanned;
 import android.util.Log;
+import android.view.ActionProvider;
 import android.view.Menu;
 import android.view.MenuItem;
+import android.widget.ShareActionProvider;
 
 import java.util.ArrayList;
 
@@ -75,94 +77,7 @@
     public static final String EXTRA_CALLING_ACTIVITY =
             "android.support.v4.app.EXTRA_CALLING_ACTIVITY";
 
-    /**
-     * Compatibility shims for sharing operations
-     */
-    interface ShareCompatImpl {
-        void configureMenuItem(MenuItem item, IntentBuilder shareIntent);
-        String escapeHtml(CharSequence text);
-    }
-
-    static class ShareCompatImplBase implements ShareCompatImpl {
-        @Override
-        public void configureMenuItem(MenuItem item, IntentBuilder shareIntent) {
-            item.setIntent(shareIntent.createChooserIntent());
-        }
-
-        @Override
-        public String escapeHtml(CharSequence text) {
-            StringBuilder out = new StringBuilder();
-            withinStyle(out, text, 0, text.length());
-            return out.toString();
-        }
-
-        private static void withinStyle(StringBuilder out, CharSequence text,
-                int start, int end) {
-            for (int i = start; i < end; i++) {
-                char c = text.charAt(i);
-
-                if (c == '<') {
-                    out.append("&lt;");
-                } else if (c == '>') {
-                    out.append("&gt;");
-                } else if (c == '&') {
-                    out.append("&amp;");
-                } else if (c > 0x7E || c < ' ') {
-                    out.append("&#" + ((int) c) + ";");
-                } else if (c == ' ') {
-                    while (i + 1 < end && text.charAt(i + 1) == ' ') {
-                        out.append("&nbsp;");
-                        i++;
-                    }
-
-                    out.append(' ');
-                } else {
-                    out.append(c);
-                }
-            }
-        }
-    }
-
-    @RequiresApi(14)
-    static class ShareCompatImplICS extends ShareCompatImplBase {
-        @Override
-        public void configureMenuItem(MenuItem item, IntentBuilder shareIntent) {
-            ShareCompatICS.configureMenuItem(item, shareIntent.getActivity(),
-                    shareIntent.getIntent());
-            if (shouldAddChooserIntent(item)) {
-                item.setIntent(shareIntent.createChooserIntent());
-            }
-        }
-
-        boolean shouldAddChooserIntent(MenuItem item) {
-            return !item.hasSubMenu();
-        }
-    }
-
-    @RequiresApi(16)
-    static class ShareCompatImplJB extends ShareCompatImplICS {
-        @Override
-        public String escapeHtml(CharSequence html) {
-            return ShareCompatJB.escapeHtml(html);
-        }
-
-        @Override
-        boolean shouldAddChooserIntent(MenuItem item) {
-            return false;
-        }
-    }
-
-    static ShareCompatImpl IMPL;
-
-    static {
-        if (Build.VERSION.SDK_INT >= 16) {
-            IMPL = new ShareCompatImplJB();
-        } else if (Build.VERSION.SDK_INT >= 14) {
-            IMPL = new ShareCompatImplICS();
-        } else {
-            IMPL = new ShareCompatImplBase();
-        }
-    }
+    private static final String HISTORY_FILENAME_PREFIX = ".sharecompat_";
 
     private ShareCompat() {}
 
@@ -209,22 +124,18 @@
     /**
      * Configure a {@link MenuItem} to act as a sharing action.
      *
-     * <p>If the app is running on API level 14 or higher (Android 4.0/Ice Cream Sandwich)
-     * this method will configure a ShareActionProvider to provide a more robust UI
+     * <p>This method will configure a ShareActionProvider to provide a more robust UI
      * for selecting the target of the share. History will be tracked for each calling
      * activity in a file named with the prefix ".sharecompat_" in the application's
      * private data directory. If the application wishes to set this MenuItem to show
      * as an action in the Action Bar it should use {@link MenuItem#setShowAsAction(int)} to request
      * that behavior in addition to calling this method.</p>
      *
-     * <p>If the app is running on an older platform version this method will configure
-     * a standard activity chooser dialog for the menu item.</p>
-     *
      * <p>During the calling activity's lifecycle, if data within the share intent must
      * change the app should change that state in one of several ways:</p>
      * <ul>
-     * <li>Call {@link ActivityCompat#invalidateOptionsMenu(Activity)}. If the app is running
-     * on API level 11 or above and uses the Action Bar its menu will be recreated and rebuilt.
+     * <li>Call {@link ActivityCompat#invalidateOptionsMenu(Activity)}. If the app uses the
+     * Action Bar its menu will be recreated and rebuilt.
      * If not, the activity will receive a call to {@link Activity#onPrepareOptionsMenu(Menu)}
      * the next time the user presses the menu key to open the options menu panel. The activity
      * can then call configureMenuItem again with a new or altered IntentBuilder to reconfigure
@@ -237,7 +148,23 @@
      * @param shareIntent IntentBuilder with data about the content to share
      */
     public static void configureMenuItem(MenuItem item, IntentBuilder shareIntent) {
-        IMPL.configureMenuItem(item, shareIntent);
+        ActionProvider itemProvider = item.getActionProvider();
+        ShareActionProvider provider;
+        if (!(itemProvider instanceof ShareActionProvider)) {
+            provider = new ShareActionProvider(shareIntent.getActivity());
+        } else {
+            provider = (ShareActionProvider) itemProvider;
+        }
+        provider.setShareHistoryFileName(HISTORY_FILENAME_PREFIX
+                + shareIntent.getActivity().getClass().getName());
+        provider.setShareIntent(shareIntent.getIntent());
+        item.setActionProvider(provider);
+
+        if (SDK_INT < 16) {
+            if (!item.hasSubMenu()) {
+                item.setIntent(shareIntent.createChooserIntent());
+            }
+        }
     }
 
     /**
@@ -251,8 +178,8 @@
     public static void configureMenuItem(Menu menu, int menuItemId, IntentBuilder shareIntent) {
         MenuItem item = menu.findItem(menuItemId);
         if (item == null) {
-            throw new IllegalArgumentException("Could not find menu item with id " + menuItemId +
-                    " in the supplied menu");
+            throw new IllegalArgumentException("Could not find menu item with id " + menuItemId
+                    + " in the supplied menu");
         }
         configureMenuItem(item, shareIntent);
     }
@@ -760,12 +687,44 @@
                 if (text instanceof Spanned) {
                     result = Html.toHtml((Spanned) text);
                 } else if (text != null) {
-                    result = IMPL.escapeHtml(text);
+                    if (SDK_INT >= 16) {
+                        result = Html.escapeHtml(text);
+                    } else {
+                        StringBuilder out = new StringBuilder();
+                        withinStyle(out, text, 0, text.length());
+                        result = out.toString();
+                    }
                 }
             }
             return result;
         }
 
+        private static void withinStyle(StringBuilder out, CharSequence text,
+                int start, int end) {
+            for (int i = start; i < end; i++) {
+                char c = text.charAt(i);
+
+                if (c == '<') {
+                    out.append("&lt;");
+                } else if (c == '>') {
+                    out.append("&gt;");
+                } else if (c == '&') {
+                    out.append("&amp;");
+                } else if (c > 0x7E || c < ' ') {
+                    out.append("&#" + ((int) c) + ";");
+                } else if (c == ' ') {
+                    while (i + 1 < end && text.charAt(i + 1) == ' ') {
+                        out.append("&nbsp;");
+                        i++;
+                    }
+
+                    out.append(' ');
+                } else {
+                    out.append(c);
+                }
+            }
+        }
+
         /**
          * Get a URI referring to a data stream shared with the target activity.
          *
@@ -800,8 +759,8 @@
             if (index == 0) {
                 return mIntent.getParcelableExtra(Intent.EXTRA_STREAM);
             }
-            throw new IndexOutOfBoundsException("Stream items available: " + getStreamCount() +
-                    " index requested: " + index);
+            throw new IndexOutOfBoundsException("Stream items available: " + getStreamCount()
+                    + " index requested: " + index);
         }
 
         /**
diff --git a/compat/java/android/support/v4/content/ModernAsyncTask.java b/compat/java/android/support/v4/content/ModernAsyncTask.java
index db07ee5..8e2991a 100644
--- a/compat/java/android/support/v4/content/ModernAsyncTask.java
+++ b/compat/java/android/support/v4/content/ModernAsyncTask.java
@@ -178,7 +178,7 @@
 
     Result postResult(Result result) {
         Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
-                new AsyncTaskResult<Result>(this, result));
+                new AsyncTaskResult<>(this, result));
         message.sendToTarget();
         return result;
     }
diff --git a/compat/java/android/support/v4/util/SimpleArrayMap.java b/compat/java/android/support/v4/util/SimpleArrayMap.java
index c7d4b5d..1e520e5 100644
--- a/compat/java/android/support/v4/util/SimpleArrayMap.java
+++ b/compat/java/android/support/v4/util/SimpleArrayMap.java
@@ -227,7 +227,7 @@
     /**
      * Create a new ArrayMap with the mappings from the given ArrayMap.
      */
-    public SimpleArrayMap(SimpleArrayMap map) {
+    public SimpleArrayMap(SimpleArrayMap<K, V> map) {
         this();
         if (map != null) {
             putAll(map);
diff --git a/compat/java/android/support/v4/view/ViewCompat.java b/compat/java/android/support/v4/view/ViewCompat.java
index 54c2730..60f8ba7 100644
--- a/compat/java/android/support/v4/view/ViewCompat.java
+++ b/compat/java/android/support/v4/view/ViewCompat.java
@@ -19,7 +19,6 @@
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
 import android.animation.ValueAnimator;
-import android.support.annotation.RequiresApi;
 import android.content.ClipData;
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -35,6 +34,7 @@
 import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
 import android.support.annotation.RestrictTo;
 import android.support.v4.os.BuildCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
@@ -468,11 +468,11 @@
         }
 
         public void postInvalidateOnAnimation(View view) {
-            view.invalidate();
+            view.postInvalidate();
         }
 
         public void postInvalidateOnAnimation(View view, int left, int top, int right, int bottom) {
-            view.invalidate(left, top, right, bottom);
+            view.postInvalidate(left, top, right, bottom);
         }
 
         public void postOnAnimation(View view, Runnable action) {
diff --git a/compat/tests/java/android/support/v4/app/NotificationCompatTest.java b/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
index 3b953ff..4c7bb0e 100644
--- a/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
+++ b/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
@@ -25,6 +25,7 @@
 import android.annotation.TargetApi;
 import android.app.Notification;
 import android.content.Context;
+import android.os.Bundle;
 import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -35,6 +36,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -170,6 +173,25 @@
         verifyRemoteInputArrayHasSingleResult(a.getDataOnlyRemoteInputs(), DATA_RESULT_KEY);
     }
 
+    @Test
+    public void testMessage_setAndGetExtras() throws Throwable {
+        String extraKey = "extra_key";
+        CharSequence extraValue = "extra_value";
+        NotificationCompat.MessagingStyle.Message m =
+                new NotificationCompat.MessagingStyle.Message("text", 0 /*timestamp */, "sender");
+        m.getExtras().putCharSequence(extraKey, extraValue);
+        assertEquals(extraValue, m.getExtras().getCharSequence(extraKey));
+
+        ArrayList<NotificationCompat.MessagingStyle.Message> messages = new ArrayList<>(1);
+        messages.add(m);
+        Bundle[] bundleArray =
+                NotificationCompat.MessagingStyle.Message.getBundleArrayForMessages(messages);
+        assertEquals(1, bundleArray.length);
+        NotificationCompat.MessagingStyle.Message fromBundle =
+                NotificationCompat.MessagingStyle.Message.getMessageFromBundle(bundleArray[0]);
+        assertEquals(extraValue, fromBundle.getExtras().getCharSequence(extraKey));
+    }
+
     private static RemoteInput newDataOnlyRemoteInput() {
         return new RemoteInput.Builder(DATA_RESULT_KEY)
             .setAllowFreeFormInput(false)
diff --git a/compat/tests/res/values-hdpi/dimens.xml b/compat/tests/res/values-hdpi/dimens.xml
index eb1ff54..3126a6e 100755
--- a/compat/tests/res/values-hdpi/dimens.xml
+++ b/compat/tests/res/values-hdpi/dimens.xml
@@ -13,6 +13,6 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
     <dimen name="density_aware_size">14dip</dimen>
 </resources>
\ No newline at end of file
diff --git a/compat/tests/res/values-mdpi/dimens.xml b/compat/tests/res/values-mdpi/dimens.xml
index 5766379..ada2cae 100755
--- a/compat/tests/res/values-mdpi/dimens.xml
+++ b/compat/tests/res/values-mdpi/dimens.xml
@@ -13,6 +13,6 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
     <dimen name="density_aware_size">12dip</dimen>
 </resources>
\ No newline at end of file
diff --git a/compat/tests/res/values-xhdpi/dimens.xml b/compat/tests/res/values-xhdpi/dimens.xml
index a25d23d..21125b8 100755
--- a/compat/tests/res/values-xhdpi/dimens.xml
+++ b/compat/tests/res/values-xhdpi/dimens.xml
@@ -13,6 +13,6 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
     <dimen name="density_aware_size">16dip</dimen>
 </resources>
\ No newline at end of file
diff --git a/compat/tests/res/values-xxhdpi/dimens.xml b/compat/tests/res/values-xxhdpi/dimens.xml
index 399cde1..aaee862 100755
--- a/compat/tests/res/values-xxhdpi/dimens.xml
+++ b/compat/tests/res/values-xxhdpi/dimens.xml
@@ -13,6 +13,6 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
     <dimen name="density_aware_size">18dip</dimen>
 </resources>
\ No newline at end of file
diff --git a/compat/tests/res/values/attrs.xml b/compat/tests/res/values/attrs.xml
index 36d1e9e..eb82fb3 100644
--- a/compat/tests/res/values/attrs.xml
+++ b/compat/tests/res/values/attrs.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
     <attr name="theme_color_default" format="reference" />
     <attr name="theme_color_focused" format="reference" />
     <attr name="theme_color_pressed" format="reference" />
diff --git a/compat/tests/res/values/colors.xml b/compat/tests/res/values/colors.xml
index 15158cf..0902707 100644
--- a/compat/tests/res/values/colors.xml
+++ b/compat/tests/res/values/colors.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
     <color name="text_color">#FF8090</color>
 
     <color name="selector_color_default">#70A0C0</color>
diff --git a/compat/tests/res/values/dimens.xml b/compat/tests/res/values/dimens.xml
index 5c82462..5c47040 100644
--- a/compat/tests/res/values/dimens.xml
+++ b/compat/tests/res/values/dimens.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
     <dimen name="text_medium_size">20sp</dimen>
 
     <dimen name="drawable_small_size">12dip</dimen>
diff --git a/compat/tests/res/values/strings.xml b/compat/tests/res/values/strings.xml
index 4aebfae..d43d30e 100644
--- a/compat/tests/res/values/strings.xml
+++ b/compat/tests/res/values/strings.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
     <!-- Short text for testing. -->
     <string name="test_text_short">Lorem ipsum</string>
     <!-- Medium text for testing. -->
diff --git a/compat/tests/res/values/styles.xml b/compat/tests/res/values/styles.xml
index 447d5ec..b74bacc 100644
--- a/compat/tests/res/values/styles.xml
+++ b/compat/tests/res/values/styles.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
     <style name="TestActivityTheme">
         <item name="android:windowAnimationStyle">@null</item>
     </style>
diff --git a/core-ui/AndroidManifest.xml b/core-ui/AndroidManifest.xml
index 61eba66..b952b5e 100644
--- a/core-ui/AndroidManifest.xml
+++ b/core-ui/AndroidManifest.xml
@@ -17,6 +17,7 @@
           xmlns:tools="http://schemas.android.com/tools"
           package="android.support.coreui">
     <uses-sdk android:minSdkVersion="14" tools:overrideLibrary="android.support.coreui"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/core-ui/tests/res/values/colors.xml b/core-ui/tests/res/values/colors.xml
index d0d5309..3c7bf7c 100644
--- a/core-ui/tests/res/values/colors.xml
+++ b/core-ui/tests/res/values/colors.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
     <color name="text_color">#FF8090</color>
 
     <color name="test_red">#FF6030</color>
diff --git a/core-ui/tests/res/values/dimens.xml b/core-ui/tests/res/values/dimens.xml
index c3617a9..d473645 100644
--- a/core-ui/tests/res/values/dimens.xml
+++ b/core-ui/tests/res/values/dimens.xml
@@ -13,6 +13,6 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
     <dimen name="text_medium_size">20sp</dimen>
 </resources>
\ No newline at end of file
diff --git a/core-ui/tests/res/values/strings.xml b/core-ui/tests/res/values/strings.xml
index 2fa1430..e605c2c 100644
--- a/core-ui/tests/res/values/strings.xml
+++ b/core-ui/tests/res/values/strings.xml
@@ -13,6 +13,6 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
     <string name="hello">Hello World</string>
 </resources>
\ No newline at end of file
diff --git a/core-ui/tests/res/values/styles.xml b/core-ui/tests/res/values/styles.xml
index e530e60..047e2d0 100644
--- a/core-ui/tests/res/values/styles.xml
+++ b/core-ui/tests/res/values/styles.xml
@@ -17,9 +17,4 @@
     <style name="TestActivityTheme" parent="android:Theme.Holo">
         <item name="android:windowAnimationStyle">@null</item>
     </style>
-    <style name="TextMediumStyle" parent="@android:style/TextAppearance.Medium">
-        <item name="android:textSize">@dimen/text_medium_size</item>
-        <item name="android:textColor">@color/text_color</item>
-        <item name="android:textStyle">italic</item>
-    </style>
 </resources>
\ No newline at end of file
diff --git a/core-utils/AndroidManifest.xml b/core-utils/AndroidManifest.xml
index b3b404b..5c10a5b 100644
--- a/core-utils/AndroidManifest.xml
+++ b/core-utils/AndroidManifest.xml
@@ -17,6 +17,7 @@
           xmlns:tools="http://schemas.android.com/tools"
           package="android.support.coreutils">
     <uses-sdk android:minSdkVersion="14" tools:overrideLibrary="android.support.coreutils"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/customtabs/AndroidManifest.xml b/customtabs/AndroidManifest.xml
index 19a1d54..4069dae 100644
--- a/customtabs/AndroidManifest.xml
+++ b/customtabs/AndroidManifest.xml
@@ -16,6 +16,7 @@
 <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 />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/design/AndroidManifest.xml b/design/AndroidManifest.xml
index e4449de..afae16e 100644
--- a/design/AndroidManifest.xml
+++ b/design/AndroidManifest.xml
@@ -18,6 +18,7 @@
           package="android.support.design">
     <uses-sdk android:minSdkVersion="14"
               tools:overrideLibrary="android.support.transition"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/design/src/.readme b/design/src/.readme
deleted file mode 100644
index 4bcebad..0000000
--- a/design/src/.readme
+++ /dev/null
@@ -1,2 +0,0 @@
-This hidden file is there to ensure there is an src folder.
-Once we support binary library this will go away.
\ No newline at end of file
diff --git a/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
index af88e9d..a9fa46a 100644
--- a/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
+++ b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
@@ -21,9 +21,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
-import android.os.SystemClock;
 import android.support.design.test.R;
 import android.support.test.filters.LargeTest;
+import android.support.testutils.PollingCheck;
 import android.widget.ImageView;
 
 import org.junit.Test;
@@ -387,8 +387,13 @@
         // this test needs to be tied to the internal implementation details of running animation
         // that scales the FAB to 0/0 scales and interpolates its alpha to 0. Since that animation
         // starts running partway through our swipe gesture and may complete a bit later then
-        // the swipe gesture, sleep for a bit to catch the "final" state of the FAB.
-        SystemClock.sleep(200);
+        // the swipe gesture, poll to catch the "final" state of the FAB.
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return fab.getScaleX() == 0.0f;
+            }
+        });
 
         // At this point the FAB should be scaled to 0/0 and set at alpha 0. Since the relevant
         // getter methods are only available on v11+, wrap the asserts with build version check.
@@ -403,9 +408,13 @@
                 originalAppbarBottom,
                 longSwipeAmount);
 
-        // Same as for swipe-up gesture - sleep for a bit to catch the "final" visible state of
-        // the FAB.
-        SystemClock.sleep(200);
+        // Same as for swipe-up gesture.
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return fab.getScaleX() == 1.0f;
+            }
+        });
 
         // At this point the FAB should be scaled back to its original size and be at full opacity.
         assertEquals(1.0f, fab.getScaleX(), 0.0f);
diff --git a/design/tests/src/android/support/design/widget/AppBarWithToolbarAndTabsTest.java b/design/tests/src/android/support/design/widget/AppBarWithToolbarAndTabsTest.java
index c745f17..23ef7c5 100644
--- a/design/tests/src/android/support/design/widget/AppBarWithToolbarAndTabsTest.java
+++ b/design/tests/src/android/support/design/widget/AppBarWithToolbarAndTabsTest.java
@@ -27,7 +27,9 @@
 import android.support.annotation.StringRes;
 import android.support.design.test.R;
 import android.support.design.testutils.Cheeses;
+import android.support.test.filters.FlakyTest;
 import android.support.test.filters.LargeTest;
+import android.support.test.filters.Suppress;
 
 import org.junit.Test;
 
@@ -232,6 +234,8 @@
         assertAppBarElevation(mDefaultElevationValue);
     }
 
+    @Suppress
+    @FlakyTest(bugId = 30701044)
     @LargeTest
     @Test
     public void testSnappingToolbarAndSnappingTabs() throws Throwable {
diff --git a/dynamic-animation/AndroidManifest.xml b/dynamic-animation/AndroidManifest.xml
index 9e83183..f818402 100644
--- a/dynamic-animation/AndroidManifest.xml
+++ b/dynamic-animation/AndroidManifest.xml
@@ -16,5 +16,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.dynamicanimation">
     <uses-sdk android:minSdkVersion="16"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/dynamic-animation/src/android/support/animation/DynamicAnimation.java b/dynamic-animation/src/android/support/animation/DynamicAnimation.java
index 7285943..cb21683 100644
--- a/dynamic-animation/src/android/support/animation/DynamicAnimation.java
+++ b/dynamic-animation/src/android/support/animation/DynamicAnimation.java
@@ -332,6 +332,12 @@
      * to pixel/second based on the density of the screen to achieve a consistent look across
      * different screens.
      *
+     * <p>To convert from dp/second to pixel/second:
+     * <pre class="prettyprint">
+     * float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond,
+     *         getResources().getDisplayMetrics());
+     * </pre>
+     *
      * @param startVelocity start velocity of the animation in pixel/second
      * @return the Animation whose start velocity is being set
      */
diff --git a/dynamic-animation/src/android/support/animation/SpringAnimation.java b/dynamic-animation/src/android/support/animation/SpringAnimation.java
index 70f939b..47096df 100644
--- a/dynamic-animation/src/android/support/animation/SpringAnimation.java
+++ b/dynamic-animation/src/android/support/animation/SpringAnimation.java
@@ -27,12 +27,39 @@
  * The animation will continue to run until the spring force reaches equilibrium. If the spring used
  * in the animation is undamped, the animation will never reach equilibrium. Instead, it will
  * oscillate forever.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * </div>
+ *
+ * <p>To create a simple {@link SpringAnimation} that uses the default {@link SpringForce}:</p>
+ * <pre class="prettyprint">
+ * // Create an animation to animate view's X property, set the rest position of the
+ * // default spring to 0, and start the animation with a starting velocity of 5000 (pixel/s).
+ * final SpringAnimation anim = new SpringAnimation(view, DynamicAnimation.X, 0)
+ *         .setSpring(spring).setStartVelocity(5000).start();
+ * </pre>
+ *
+ * <p>Alternatively, a {@link SpringAnimation} can take a pre-configured {@link SpringForce}, and
+ * use that to drive the animation. </p>
+ * <pre class="prettyprint">
+ * // Create a low stiffness, low bounce spring at position 0.
+ * SpringForce spring = new SpringForce(0)
+ *         .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+ *         .setStiffness(SpringForce.STIFFNESS_LOW);
+ * // Create an animation to animate view's scaleY property, and start the animation using
+ * // the spring above and a starting value of 0.5. Additionally, constrain the range of value for
+ * // the animation to be non-negative, effectively preventing any spring overshoot.
+ * final SpringAnimation anim = new SpringAnimation(view, DynamicAnimation.SCALE_Y)
+ *         .setMinValue(0).setSpring(spring).setStartValue(1).start();
+ * </pre>
  */
 public final class SpringAnimation extends DynamicAnimation<SpringAnimation> {
 
     private SpringForce mSpring = null;
     private float mPendingPosition = UNSET;
     private static final float UNSET = Float.MAX_VALUE;
+    private boolean mEndRequested = false;
 
     /**
      * This creates a SpringAnimation that animates the property of the given view.
@@ -132,13 +159,7 @@
             throw new AndroidRuntimeException("Animations may only be started on the main thread");
         }
         if (mRunning) {
-            if (mPendingPosition != UNSET) {
-                mSpring.setFinalPosition(mPendingPosition);
-                mPendingPosition = UNSET;
-            }
-            mValue = mSpring.getFinalPosition();
-            mVelocity = 0;
-            cancel();
+            mEndRequested = true;
         }
     }
 
@@ -183,6 +204,19 @@
 
     @Override
     boolean updateValueAndVelocity(long deltaT) {
+        // If user had requested end, then update the value and velocity to end state and consider
+        // animation done.
+        if (mEndRequested) {
+            if (mPendingPosition != UNSET) {
+                mSpring.setFinalPosition(mPendingPosition);
+                mPendingPosition = UNSET;
+            }
+            mValue = mSpring.getFinalPosition();
+            mVelocity = 0;
+            mEndRequested = false;
+            return true;
+        }
+
         if (mPendingPosition != UNSET) {
             double lastPosition = mSpring.getFinalPosition();
             // Approximate by considering half of the time spring position stayed at the old
diff --git a/dynamic-animation/tests/src/android/support/dynamicanimation/tests/SpringTests.java b/dynamic-animation/tests/src/android/support/dynamicanimation/tests/SpringTests.java
index c87bf50..d716fb8 100644
--- a/dynamic-animation/tests/src/android/support/dynamicanimation/tests/SpringTests.java
+++ b/dynamic-animation/tests/src/android/support/dynamicanimation/tests/SpringTests.java
@@ -437,22 +437,48 @@
      */
     @Test
     public void testSkipToEnd() {
-        final SpringAnimation anim = new SpringAnimation(mView1, DynamicAnimation.SCROLL_X, 0.0f);
+        final float finalPosition = 10f;
+        final SpringAnimation anim = new SpringAnimation(mView1, DynamicAnimation.SCROLL_X,
+                finalPosition);
+        final DynamicAnimation.OnAnimationEndListener mockListener =
+                mock(DynamicAnimation.OnAnimationEndListener.class);
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                anim.setStartValue(200).start();
+                anim.addEndListener(mockListener).setStartValue(200).start();
             }
         });
         assertTrue(anim.isRunning());
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
+                int scrollX = mView1.getScrollX();
                 anim.skipToEnd();
+                // Expect no change in the animation values until next frame.
+                assertEquals(scrollX, mView1.getScrollX());
+                assertTrue(anim.isRunning());
             }
         });
-        assertFalse(anim.isRunning());
-        assertEquals(0, mView1.getScrollX());
+        verify(mockListener, timeout(100).times(1)).onAnimationEnd(anim, false, finalPosition, 0);
+
+        // Also make sure the skipToEnd() call doesn't affect next animation run.
+        final DynamicAnimation.OnAnimationEndListener mockListener2 =
+                mock(DynamicAnimation.OnAnimationEndListener.class);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                anim.addEndListener(mockListener2);
+                anim.animateToFinalPosition(finalPosition + 1000f);
+            }
+        });
+        // Verify that the animation doesn't finish right away
+        verify(mockListener2, timeout(300).times(0)).onAnimationEnd(any(DynamicAnimation.class),
+                any(boolean.class), any(float.class), any(float.class));
+
+        // But the animation should eventually finish.
+        verify(mockListener, timeout(1000).times(1)).onAnimationEnd(anim, false,
+                finalPosition + 1000f, 0);
+
     }
 
     /**
diff --git a/emoji/Android.mk b/emoji/Android.mk
new file mode 100644
index 0000000..337f5b9
--- /dev/null
+++ b/emoji/Android.mk
@@ -0,0 +1,16 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/emoji/appcompat/Android.mk b/emoji/appcompat/Android.mk
new file mode 100644
index 0000000..54f6b14
--- /dev/null
+++ b/emoji/appcompat/Android.mk
@@ -0,0 +1,41 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+
+# Here is the final static library that apps can link against.
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-support-emoji-appcompat \
+#       android-support-v7-appcompat \
+#       android-support-emoji \
+#       android-support-compat
+#
+# in their makefiles to include the resources and their dependencies in their package.
+#
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_MODULE := android-support-emoji-appcompat
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-annotations \
+    android-support-v7-appcompat \
+    android-support-emoji \
+    android-support-compat
+LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/emoji/appcompat/AndroidManifest.xml b/emoji/appcompat/AndroidManifest.xml
index 81bc0f8..56d8ab5 100644
--- a/emoji/appcompat/AndroidManifest.xml
+++ b/emoji/appcompat/AndroidManifest.xml
@@ -17,6 +17,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.text.emoji.appcompat">
     <uses-sdk android:minSdkVersion="19"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/emoji/bundled-typeface/Android.mk b/emoji/bundled-typeface/Android.mk
new file mode 100644
index 0000000..324801e
--- /dev/null
+++ b/emoji/bundled-typeface/Android.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+
+# Here is the final static library that apps can link against.
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-support-emoji-typeface \
+#       android-support-emoji \
+#       android-support-compat
+#
+# in their makefiles to include the resources and their dependencies in their package.
+#
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_MODULE := android-support-emoji-typeface
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-annotations \
+    android-support-emoji \
+    android-support-compat
+LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/emoji/bundled-typeface/AndroidManifest.xml b/emoji/bundled-typeface/AndroidManifest.xml
index 2c5bc46..5bb7578 100644
--- a/emoji/bundled-typeface/AndroidManifest.xml
+++ b/emoji/bundled-typeface/AndroidManifest.xml
@@ -16,6 +16,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.text.emoji.typeface">
     <uses-sdk android:minSdkVersion="19"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}"/>
-    <application/>
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
\ No newline at end of file
diff --git a/emoji/bundled-typeface/build.gradle b/emoji/bundled-typeface/build.gradle
index 5014649..ab45916 100644
--- a/emoji/bundled-typeface/build.gradle
+++ b/emoji/bundled-typeface/build.gradle
@@ -22,4 +22,14 @@
     name 'Android Emoji Compat'
     inceptionYear '2017'
     description 'Library bundled with assets to enable emoji compatibility in Kitkat and newer devices to avoid the empty emoji characters.'
+
+    license {
+        name 'SIL Open Font License, Version 1.1'
+        url  'http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web'
+    }
+
+    license {
+        name 'Unicode, Inc. License'
+        url 'http://www.unicode.org/copyright.html#License'
+    }
 }
\ No newline at end of file
diff --git a/emoji/core/Android.mk b/emoji/core/Android.mk
new file mode 100644
index 0000000..35fb551
--- /dev/null
+++ b/emoji/core/Android.mk
@@ -0,0 +1,54 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+
+# Here is the final static library that apps can link against.
+# Applications that use this library must specify
+#
+#   LOCAL_STATIC_ANDROID_LIBRARIES := \
+#       android-support-emoji \
+#       android-support-compat
+#
+# in their makefiles to include the resources and their dependencies in their package.
+#
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_MODULE := android-support-emoji
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-emoji-flatbuffers-jarjar
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+    android-support-annotations \
+    android-support-compat
+LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+#############################################################
+# Pre-built dependency jars
+#############################################################
+
+include $(CLEAR_VARS)
+LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
+    android-support-emoji-flatbuffers:third_party/flatbuffers/flatbuffers-java-1.6.0.jar
+include $(BUILD_MULTI_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-emoji-flatbuffers
+LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
+LOCAL_MODULE := android-support-emoji-flatbuffers-jarjar
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/emoji/core/AndroidManifest.xml b/emoji/core/AndroidManifest.xml
index b288921..815858d 100644
--- a/emoji/core/AndroidManifest.xml
+++ b/emoji/core/AndroidManifest.xml
@@ -16,6 +16,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.text.emoji">
     <uses-sdk android:minSdkVersion="19"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/emoji/core/build.gradle b/emoji/core/build.gradle
index cc389d1..0e94d4e 100644
--- a/emoji/core/build.gradle
+++ b/emoji/core/build.gradle
@@ -8,7 +8,7 @@
     compile jarjar.repackage {
         from files('third_party/flatbuffers/flatbuffers-java-1.6.0.jar')
         destinationName 'flatbuffers-java-1.6.0-repacked.jar'
-        classRename 'com.google.flatbuffers.**', 'com.google.flatbuffers.emojicompat.@1'
+        classRename 'com.google.flatbuffers.**', 'android.support.text.emoji.flatbuffer.@1'
     }
 
     androidTestCompile (libs.test_runner) {
@@ -20,6 +20,7 @@
     androidTestCompile libs.mockito_core
     androidTestCompile libs.dexmaker
     androidTestCompile libs.dexmaker_mockito
+    androidTestCompile project(path: ':support-testutils')
 }
 
 android {
@@ -38,4 +39,14 @@
     name 'Android Emoji Compat'
     inceptionYear '2017'
     description 'Core library to enable emoji compatibility in Kitkat and newer devices to avoid the empty emoji characters.'
-}
\ No newline at end of file
+
+    license {
+        name 'SIL Open Font License, Version 1.1'
+        url  'http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web'
+    }
+
+    license {
+        name 'Unicode, Inc. License'
+        url 'http://www.unicode.org/copyright.html#License'
+    }
+}
diff --git a/emoji/core/jarjar-rules.txt b/emoji/core/jarjar-rules.txt
new file mode 100644
index 0000000..e6b4b3e
--- /dev/null
+++ b/emoji/core/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.google.flatbuffers.** android.support.text.emoji.flatbuffer.@1
\ No newline at end of file
diff --git a/emoji/core/src/android/support/text/emoji/EmojiProcessor.java b/emoji/core/src/android/support/text/emoji/EmojiProcessor.java
index 9712b80..9275a6e 100644
--- a/emoji/core/src/android/support/text/emoji/EmojiProcessor.java
+++ b/emoji/core/src/android/support/text/emoji/EmojiProcessor.java
@@ -457,9 +457,10 @@
      */
     private boolean hasGlyph(final CharSequence charSequence, int start, final int end,
             final EmojiMetadata metadata) {
-        // if the emoji was added to OS at a later version we know that the system cannot render it.
-        // without this check pre M devices result in more false positives.
-        if (metadata.getSdkAdded() > Build.VERSION.SDK_INT) {
+        // For pre M devices, heuristic in PaintCompat can result in false positives. we are
+        // adding another heuristic using the sdkAdded field. if the emoji was added to OS
+        // at a later version we assume that the system probably cannot render it.
+        if (Build.VERSION.SDK_INT < 23 && metadata.getSdkAdded() > Build.VERSION.SDK_INT) {
             return false;
         }
 
diff --git a/emoji/core/src/android/support/text/emoji/flatbuffer/MetadataItem.java b/emoji/core/src/android/support/text/emoji/flatbuffer/MetadataItem.java
index f0466ea..ea21821 100644
--- a/emoji/core/src/android/support/text/emoji/flatbuffer/MetadataItem.java
+++ b/emoji/core/src/android/support/text/emoji/flatbuffer/MetadataItem.java
@@ -21,9 +21,6 @@
 
 import android.support.annotation.RestrictTo;
 
-import com.google.flatbuffers.emojicompat.FlatBufferBuilder;
-import com.google.flatbuffers.emojicompat.Table;
-
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 
diff --git a/emoji/core/src/android/support/text/emoji/flatbuffer/MetadataList.java b/emoji/core/src/android/support/text/emoji/flatbuffer/MetadataList.java
index 4de07d7..257b243 100644
--- a/emoji/core/src/android/support/text/emoji/flatbuffer/MetadataList.java
+++ b/emoji/core/src/android/support/text/emoji/flatbuffer/MetadataList.java
@@ -21,9 +21,6 @@
 
 import android.support.annotation.RestrictTo;
 
-import com.google.flatbuffers.emojicompat.FlatBufferBuilder;
-import com.google.flatbuffers.emojicompat.Table;
-
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 
diff --git a/emoji/core/tests/java/android/support/text/emoji/EmojiInstrumentationTest.java b/emoji/core/tests/java/android/support/text/emoji/EmojiInstrumentationTest.java
index eb0ba3a..22ee475 100644
--- a/emoji/core/tests/java/android/support/text/emoji/EmojiInstrumentationTest.java
+++ b/emoji/core/tests/java/android/support/text/emoji/EmojiInstrumentationTest.java
@@ -34,16 +34,15 @@
 import android.support.test.filters.LargeTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.testutils.PollingCheck;
 import android.support.text.emoji.test.R;
 import android.support.text.emoji.util.KeyboardUtil;
 import android.support.text.emoji.util.TestString;
 import android.text.Editable;
-import android.text.Selection;
 import android.text.Spannable;
 import android.text.Spanned;
 import android.text.style.RelativeSizeSpan;
 import android.util.TypedValue;
-import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.widget.EditText;
 import android.widget.TextView;
@@ -102,7 +101,8 @@
 
         // cover the charsequence with RelativeSizeSpan which will triple the size of the
         // characters.
-        final RelativeSizeSpan sizeSpan = new RelativeSizeSpan(3.0f);
+        final float multiplier = 3.0f;
+        final RelativeSizeSpan sizeSpan = new RelativeSizeSpan(multiplier);
         spanned.setSpan(sizeSpan, 0, charSequence.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
         // set the new text
         mInstrumentation.runOnMainSync(new Runnable() {
@@ -128,18 +128,13 @@
         final EditText editText = (EditText) activity.findViewById(R.id.editText);
         final TestString string = new TestString(EMOJI_WITH_ZWJ).withPrefix()
                 .withSuffix();
-        final InputConnection inputConnection = editText.onCreateInputConnection(new EditorInfo());
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                KeyboardUtil.setComposingTextInBatch(inputConnection, string.toString());
-            }
-        });
-        mInstrumentation.waitForIdleSync();
 
+        final InputConnection inputConnection = KeyboardUtil.initTextViewForSimulatedIme(
+                mInstrumentation, editText);
+        KeyboardUtil.setComposingTextInBatch(mInstrumentation, inputConnection,
+                string.toString());
         Editable editable = editText.getEditableText();
 
-        // 0xf0950 is the remapped codepoint for WOMEN_WITH_BALL
         assertThat(editable, hasEmojiAt(EMOJI_WITH_ZWJ, string.emojiStartIndex(),
                 string.emojiEndIndex()));
     }
@@ -150,25 +145,19 @@
         final EditText editText = (EditText) activity.findViewById(R.id.editText);
         final TestString string = new TestString(EMOJI_WITH_ZWJ).withPrefix()
                 .withSuffix();
-        final InputConnection inputConnection = editText.onCreateInputConnection(new EditorInfo());
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                KeyboardUtil.setComposingTextInBatch(inputConnection, string.toString());
-                Selection.setSelection(editText.getEditableText(), string.emojiEndIndex());
-            }
-        });
-        mInstrumentation.waitForIdleSync();
+        final InputConnection inputConnection = KeyboardUtil.initTextViewForSimulatedIme(
+                mInstrumentation, editText);
+        KeyboardUtil.setComposingTextInBatch(mInstrumentation, inputConnection, string.toString());
+
+        // assert that emoji is there
         final Editable editable = editText.getEditableText();
         assertThat(editable, hasEmoji());
 
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                KeyboardUtil.deleteSurrondingText(inputConnection, 1, 0);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
+        // put selection at the end of emoji and back delete
+        KeyboardUtil.setSelection(mInstrumentation, editText.getEditableText(),
+                string.emojiEndIndex());
+        KeyboardUtil.deleteSurroundingText(mInstrumentation, inputConnection, 1, 0);
+
         assertThat(editable, not(hasEmoji()));
     }
 
@@ -178,25 +167,20 @@
         final EditText editText = (EditText) activity.findViewById(R.id.editText);
         final TestString string = new TestString(EMOJI_WITH_ZWJ).withPrefix()
                 .withSuffix();
-        final InputConnection inputConnection = editText.onCreateInputConnection(new EditorInfo());
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                KeyboardUtil.setComposingTextInBatch(inputConnection, string.toString());
-                Selection.setSelection(editText.getEditableText(), string.emojiStartIndex());
-            }
-        });
-        mInstrumentation.waitForIdleSync();
+        final InputConnection inputConnection = KeyboardUtil.initTextViewForSimulatedIme(
+                mInstrumentation, editText);
+        KeyboardUtil.setComposingTextInBatch(mInstrumentation, inputConnection, string.toString());
+
+        // assert that emoji is there
         final Editable editable = editText.getEditableText();
         assertThat(editable, hasEmoji());
 
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                KeyboardUtil.deleteSurrondingText(inputConnection, 0, 1);
-            }
-        });
-        mInstrumentation.waitForIdleSync();
+        // put selection at the begining of emoji and forward delete
+        KeyboardUtil.setSelection(mInstrumentation, editText.getEditableText(),
+                string.emojiStartIndex());
+        KeyboardUtil.deleteSurroundingText(mInstrumentation, inputConnection, 0, 1);
+
+
         assertThat(editable, not(hasEmoji()));
     }
 
@@ -206,21 +190,26 @@
         final EditText editText = (EditText) activity.findViewById(R.id.editText);
         final TestString string = new TestString(EMOJI_WITH_ZWJ).withPrefix()
                 .withSuffix();
-        final InputConnection inputConnection = editText.onCreateInputConnection(new EditorInfo());
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                KeyboardUtil.setComposingTextInBatch(inputConnection, string.toString());
-                Selection.setSelection(editText.getEditableText(), string.emojiEndIndex());
-            }
-        });
-        mInstrumentation.waitForIdleSync();
+        final InputConnection inputConnection = KeyboardUtil.initTextViewForSimulatedIme(
+                mInstrumentation, editText);
+        KeyboardUtil.setComposingTextInBatch(mInstrumentation, inputConnection, string.toString());
+
+        // assert that emoji is there
         final Editable editable = editText.getEditableText();
         assertThat(editable, hasEmoji());
 
-
+        // put selection at the end of emoji and back delete
+        KeyboardUtil.setSelection(mInstrumentation, editText.getEditableText(),
+                string.emojiEndIndex());
         mInstrumentation.sendKeySync(del());
         mInstrumentation.waitForIdleSync();
+
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return not(hasEmoji()).matches(true);
+            }
+        });
         assertThat(editable, not(hasEmoji()));
     }
 
@@ -230,20 +219,26 @@
         final EditText editText = (EditText) activity.findViewById(R.id.editText);
         final TestString string = new TestString(EMOJI_WITH_ZWJ).withPrefix()
                 .withSuffix();
-        final InputConnection inputConnection = editText.onCreateInputConnection(new EditorInfo());
-        mInstrumentation.runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                KeyboardUtil.setComposingTextInBatch(inputConnection, string.toString());
-                Selection.setSelection(editText.getEditableText(), string.emojiStartIndex());
-            }
-        });
-        mInstrumentation.waitForIdleSync();
+        final InputConnection inputConnection = KeyboardUtil.initTextViewForSimulatedIme(
+                mInstrumentation, editText);
+        KeyboardUtil.setComposingTextInBatch(mInstrumentation, inputConnection, string.toString());
+
+        // assert that emoji is there
         final Editable editable = editText.getEditableText();
         assertThat(editable, hasEmoji());
 
+        // put selection at the begining of emoji and forward delete
+        KeyboardUtil.setSelection(mInstrumentation, editText.getEditableText(),
+                string.emojiStartIndex());
         mInstrumentation.sendKeySync(forwardDel());
         mInstrumentation.waitForIdleSync();
+
+        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+            @Override
+            public boolean canProceed() {
+                return not(hasEmoji()).matches(true);
+            }
+        });
         assertThat(editable, not(hasEmoji()));
     }
 }
diff --git a/emoji/core/tests/java/android/support/text/emoji/util/KeyboardUtil.java b/emoji/core/tests/java/android/support/text/emoji/util/KeyboardUtil.java
index 4764455..0c89b2d 100644
--- a/emoji/core/tests/java/android/support/text/emoji/util/KeyboardUtil.java
+++ b/emoji/core/tests/java/android/support/text/emoji/util/KeyboardUtil.java
@@ -15,8 +15,17 @@
  */
 package android.support.text.emoji.util;
 
+import android.app.Instrumentation;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.method.QwertyKeyListener;
+import android.text.method.TextKeyListener;
 import android.view.KeyEvent;
+import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
+import android.widget.TextView;
+
+import java.util.concurrent.CountDownLatch;
 
 /**
  * Utility class for KeyEvents
@@ -65,15 +74,74 @@
         return new KeyEvent(currentTime, currentTime, KeyEvent.ACTION_DOWN, keycode, 0);
     }
 
-    public static void setComposingTextInBatch(InputConnection input, CharSequence text) {
-        input.beginBatchEdit();
-        input.setComposingText(text, 1);
-        input.endBatchEdit();
+    public static void setComposingTextInBatch(final Instrumentation instrumentation,
+            final InputConnection inputConnection, final CharSequence text)
+            throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        instrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                inputConnection.beginBatchEdit();
+                inputConnection.setComposingText(text, 1);
+                inputConnection.endBatchEdit();
+                latch.countDown();
+            }
+        });
+
+        latch.await();
+        instrumentation.waitForIdleSync();
     }
 
-    public static void deleteSurrondingText(InputConnection input, int before, int after) {
-        input.beginBatchEdit();
-        input.deleteSurroundingText(before, after);
-        input.endBatchEdit();
+    public static void deleteSurroundingText(final Instrumentation instrumentation,
+            final InputConnection inputConnection, final int before, final int after)
+            throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        instrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                inputConnection.beginBatchEdit();
+                inputConnection.deleteSurroundingText(before, after);
+                inputConnection.endBatchEdit();
+                latch.countDown();
+            }
+        });
+        latch.await();
+        instrumentation.waitForIdleSync();
+    }
+
+    public static void setSelection(Instrumentation instrumentation, final Spannable spannable,
+            final int start) throws InterruptedException {
+        setSelection(instrumentation, spannable, start, start);
+    }
+
+    public static void setSelection(Instrumentation instrumentation, final Spannable spannable,
+            final int start, final int end) throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        instrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                Selection.setSelection(spannable, start, end);
+                latch.countDown();
+            }
+        });
+        latch.await();
+        instrumentation.waitForIdleSync();
+    }
+
+    public static InputConnection initTextViewForSimulatedIme(Instrumentation instrumentation,
+            final TextView textView) throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        instrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                textView.setKeyListener(
+                        QwertyKeyListener.getInstance(false, TextKeyListener.Capitalize.NONE));
+                textView.setText("", TextView.BufferType.EDITABLE);
+                latch.countDown();
+            }
+        });
+        latch.await();
+        instrumentation.waitForIdleSync();
+        return textView.onCreateInputConnection(new EditorInfo());
     }
 }
diff --git a/emoji/core/tests/res/layout/activity_default.xml b/emoji/core/tests/res/layout/activity_default.xml
index b9a09ca..c8341b8 100644
--- a/emoji/core/tests/res/layout/activity_default.xml
+++ b/emoji/core/tests/res/layout/activity_default.xml
@@ -9,7 +9,8 @@
     <TextView
         android:id="@+id/text"
         android:layout_width="wrap_content"
-        android:layout_height="wrap_content"/>
+        android:layout_height="wrap_content"
+        android:textSize="8sp"/>
 
     <android.support.text.emoji.widget.EmojiEditText
         android:id="@+id/editText"
diff --git a/emoji/core/third_party/flatbuffers/FLATBUFFERS_LICENSE.txt b/emoji/core/third_party/flatbuffers/LICENSE
similarity index 100%
rename from emoji/core/third_party/flatbuffers/FLATBUFFERS_LICENSE.txt
rename to emoji/core/third_party/flatbuffers/LICENSE
diff --git a/emoji/core/third_party/flatbuffers/README.android b/emoji/core/third_party/flatbuffers/README.android
new file mode 100644
index 0000000..4317ba1
--- /dev/null
+++ b/emoji/core/third_party/flatbuffers/README.android
@@ -0,0 +1,10 @@
+URL: https://github.com/google/flatbuffers/archive/v1.6.0.zip
+Version: 1.6.0
+License: Apache License, Version 2.0
+License File: LICENSE
+
+Description:
+Cross platform serialization library. Used to read the metadata in the font.
+
+Local Modifications:
+No modifications.
\ No newline at end of file
diff --git a/emoji/core/third_party/notocoloremoji/LICENSE b/emoji/core/third_party/notocoloremoji/LICENSE
new file mode 100644
index 0000000..d952d62
--- /dev/null
+++ b/emoji/core/third_party/notocoloremoji/LICENSE
@@ -0,0 +1,92 @@
+This Font Software is licensed under the SIL Open Font License,
+Version 1.1.
+
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font
+creation efforts of academic and linguistic communities, and to
+provide a free and open framework in which fonts may be shared and
+improved in partnership with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply to
+any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software
+components as distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to,
+deleting, or substituting -- in part or in whole -- any of the
+components of the Original Version, by changing formats or by porting
+the Font Software to a new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed,
+modify, redistribute, and sell modified and unmodified copies of the
+Font Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components, in
+Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the
+corresponding Copyright Holder. This restriction only applies to the
+primary font name as presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created using
+the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/emoji/core/third_party/unicode/LICENSE b/emoji/core/third_party/unicode/LICENSE
new file mode 100644
index 0000000..3a6d4f2
--- /dev/null
+++ b/emoji/core/third_party/unicode/LICENSE
@@ -0,0 +1,37 @@
+COPYRIGHT AND PERMISSION NOTICE
+
+Copyright © 1991-2016 Unicode, Inc. All rights reserved.
+Distributed under the Terms of Use in 
+http://www.unicode.org/copyright.html.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Unicode data files and any associated documentation
+(the "Data Files") or Unicode software and any associated documentation
+(the "Software") to deal in the Data Files or Software
+without restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, and/or sell copies of
+the Data Files or Software, and to permit persons to whom the Data Files
+or Software are furnished to do so, provided that
+(a) this copyright and permission notice appear with all copies 
+of the Data Files or Software,
+(b) this copyright and permission notice appear in associated 
+documentation, and
+(c) there is clear notice in each modified Data File or in the Software
+as well as in the documentation associated with the Data File(s) or
+Software that the data or software has been modified.
+
+THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS
+NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
+DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THE DATA FILES OR SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder
+shall not be used in advertising or otherwise to promote the sale,
+use or other dealings in these Data Files or Software without prior
+written authorization of the copyright holder.
\ No newline at end of file
diff --git a/exifinterface/AndroidManifest.xml b/exifinterface/AndroidManifest.xml
index 94b3300..344b99b 100644
--- a/exifinterface/AndroidManifest.xml
+++ b/exifinterface/AndroidManifest.xml
@@ -16,6 +16,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.exifinterface">
     <uses-sdk android:minSdkVersion="14"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/fragment/AndroidManifest.xml b/fragment/AndroidManifest.xml
index c6fdcc4..725fe43 100644
--- a/fragment/AndroidManifest.xml
+++ b/fragment/AndroidManifest.xml
@@ -17,6 +17,7 @@
           xmlns:tools="http://schemas.android.com/tools"
           package="android.support.fragment">
     <uses-sdk android:minSdkVersion="14" tools:overrideLibrary="android.support.fragment"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/fragment/tests/java/android/support/v4/app/LoaderTest.java b/fragment/tests/java/android/support/v4/app/LoaderTest.java
index fde9d63..9ae0723 100644
--- a/fragment/tests/java/android/support/v4/app/LoaderTest.java
+++ b/fragment/tests/java/android/support/v4/app/LoaderTest.java
@@ -59,7 +59,19 @@
             new ActivityTestRule(LoaderActivity.class);
 
     @After
-    public void clearActivity() {
+    public void resetActivity() {
+        final LoaderActivity activity = LoaderActivity.sActivity;
+        final int unspecifiedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+        if (activity != null && activity.getRequestedOrientation() != unspecifiedOrientation) {
+            LoaderActivity.sResumed = new CountDownLatch(1);
+            activity.setRequestedOrientation(unspecifiedOrientation);
+            // Wait for the orientation change to settle, if there was a change
+            try {
+                LoaderActivity.sResumed.await(1, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                // I guess there wasn't a change in orientation after all
+            }
+        }
         LoaderActivity.clearState();
     }
 
diff --git a/fragment/tests/res/values/colors.xml b/fragment/tests/res/values/colors.xml
index d0d5309..3c7bf7c 100644
--- a/fragment/tests/res/values/colors.xml
+++ b/fragment/tests/res/values/colors.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
     <color name="text_color">#FF8090</color>
 
     <color name="test_red">#FF6030</color>
diff --git a/fragment/tests/res/values/dimens.xml b/fragment/tests/res/values/dimens.xml
index c3617a9..d473645 100644
--- a/fragment/tests/res/values/dimens.xml
+++ b/fragment/tests/res/values/dimens.xml
@@ -13,6 +13,6 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
     <dimen name="text_medium_size">20sp</dimen>
 </resources>
\ No newline at end of file
diff --git a/fragment/tests/res/values/strings.xml b/fragment/tests/res/values/strings.xml
index 2fa1430..e605c2c 100644
--- a/fragment/tests/res/values/strings.xml
+++ b/fragment/tests/res/values/strings.xml
@@ -13,6 +13,6 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
     <string name="hello">Hello World</string>
 </resources>
\ No newline at end of file
diff --git a/fragment/tests/res/values/styles.xml b/fragment/tests/res/values/styles.xml
index ae6325b..3e8e01c 100644
--- a/fragment/tests/res/values/styles.xml
+++ b/fragment/tests/res/values/styles.xml
@@ -13,13 +13,8 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
     <style name="TestActivityTheme">
         <item name="android:windowAnimationStyle">@null</item>
     </style>
-    <style name="TextMediumStyle" parent="@android:style/TextAppearance.Medium">
-        <item name="android:textSize">@dimen/text_medium_size</item>
-        <item name="android:textColor">@color/text_color</item>
-        <item name="android:textStyle">italic</item>
-    </style>
 </resources>
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 84939b4..5bc6b35 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=../../../../tools/external/gradle/gradle-3.3-bin.zip
+distributionUrl=../../../../tools/external/gradle/gradle-3.4-bin.zip
diff --git a/graphics/drawable/animated/AndroidManifest.xml b/graphics/drawable/animated/AndroidManifest.xml
index 58017dc..b59fa47 100644
--- a/graphics/drawable/animated/AndroidManifest.xml
+++ b/graphics/drawable/animated/AndroidManifest.xml
@@ -16,6 +16,7 @@
   -->
 <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/>
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/graphics/drawable/static/AndroidManifest.xml b/graphics/drawable/static/AndroidManifest.xml
index 0383e4c..14821a8 100644
--- a/graphics/drawable/static/AndroidManifest.xml
+++ b/graphics/drawable/static/AndroidManifest.xml
@@ -16,6 +16,7 @@
   -->
 <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}" />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/instantvideo/AndroidManifest.xml b/instantvideo/AndroidManifest.xml
index 08ebcbe..26b46c0 100644
--- a/instantvideo/AndroidManifest.xml
+++ b/instantvideo/AndroidManifest.xml
@@ -16,6 +16,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.media.instantvideo">
     <uses-sdk android:minSdkVersion="14"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/media-compat/AndroidManifest.xml b/media-compat/AndroidManifest.xml
index 4e73a70..f5e74a2 100644
--- a/media-compat/AndroidManifest.xml
+++ b/media-compat/AndroidManifest.xml
@@ -17,6 +17,7 @@
           xmlns:tools="http://schemas.android.com/tools"
           package="android.support.mediacompat">
     <uses-sdk android:minSdkVersion="14" tools:overrideLibrary="android.support.mediacompat"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
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 fdea9cf..56a0b1b 100644
--- a/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -703,22 +703,6 @@
      * @param context The context to use to create the session.
      * @param mediaSession A {@link android.media.session.MediaSession} object.
      * @return An equivalent {@link MediaSessionCompat} object, or null if none.
-     * @deprecated Use {@link #fromMediaSession(Context, Object)} instead.
-     */
-    @Deprecated
-    public static MediaSessionCompat obtain(Context context, Object mediaSession) {
-        return fromMediaSession(context, mediaSession);
-    }
-
-    /**
-     * Creates an instance from a framework {@link android.media.session.MediaSession} object.
-     * <p>
-     * This method is only supported on API 21+. On API 20 and below, it returns null.
-     * </p>
-     *
-     * @param context The context to use to create the session.
-     * @param mediaSession A {@link android.media.session.MediaSession} object.
-     * @return An equivalent {@link MediaSessionCompat} object, or null if none.
      */
     public static MediaSessionCompat fromMediaSession(Context context, Object mediaSession) {
         if (context != null && mediaSession != null && Build.VERSION.SDK_INT >= 21) {
@@ -1446,22 +1430,6 @@
          *
          * @param queueItem A {@link android.media.session.MediaSession.QueueItem} object.
          * @return An equivalent {@link QueueItem} object, or null if none.
-         * @deprecated Use {@link #fromQueueItem(Object)} instead.
-         */
-        @Deprecated
-        public static QueueItem obtain(Object queueItem) {
-            return fromQueueItem(queueItem);
-        }
-
-        /**
-         * Creates an instance from a framework {@link android.media.session.MediaSession.QueueItem}
-         * object.
-         * <p>
-         * This method is only supported on API 21+. On API 20 and below, it returns null.
-         * </p>
-         *
-         * @param queueItem A {@link android.media.session.MediaSession.QueueItem} object.
-         * @return An equivalent {@link QueueItem} object, or null if none.
          */
         public static QueueItem fromQueueItem(Object queueItem) {
             if (queueItem == null || Build.VERSION.SDK_INT < 21) {
diff --git a/pathmap.mk b/pathmap.mk
index fd29ee1..1d8a47c 100644
--- a/pathmap.mk
+++ b/pathmap.mk
@@ -20,26 +20,29 @@
 FRAMEWORKS_SUPPORT_SUBDIRS := \
     annotations \
     compat \
-    media-compat \
-    fragment \
     core-ui \
     core-utils \
-    v7/gridlayout \
-    v7/cardview \
-    v7/mediarouter \
-    v7/palette \
-    v13 \
-    v17/leanback \
+    customtabs \
     design \
+    dynamic-animation \
+    exifinterface \
+    fragment \
+    instantvideo \
+    media-compat \
     percent \
     recommendation \
     transition \
+    tv-provider \
+    v7/cardview \
+    v7/gridlayout \
+    v7/mediarouter \
+    v7/palette \
     v7/preference \
+    v13 \
     v14/preference \
+    v17/leanback \
     v17/preference-leanback \
-    customtabs \
-    exifinterface \
-    dynamic-animation
+    wearable
 
 #
 # A version of FRAMEWORKS_SUPPORT_SUBDIRS that is expanded to full paths from
@@ -50,15 +53,20 @@
     frameworks/support/graphics/drawable/animated \
     frameworks/support/graphics/drawable/static \
     frameworks/support/v7/appcompat/src \
-    frameworks/support/v7/recyclerview/src
+    frameworks/support/v7/recyclerview/src \
+    frameworks/support/emoji/core/src \
+    frameworks/support/emoji/appcompat/src \
+    frameworks/support/emoji/bundled-typeface/src
 
 #
 # A list of support library modules.
 #
 FRAMEWORKS_SUPPORT_JAVA_LIBRARIES := \
     $(foreach dir,$(FRAMEWORKS_SUPPORT_SUBDIRS),android-support-$(subst /,-,$(dir))) \
-    android-support-v4 \
     android-support-vectordrawable \
     android-support-animatedvectordrawable \
     android-support-v7-appcompat \
-    android-support-v7-recyclerview
+    android-support-v7-recyclerview \
+    android-support-emoji \
+    android-support-emoji-appcompat \
+    android-support-emoji-typeface
diff --git a/percent/AndroidManifest.xml b/percent/AndroidManifest.xml
index fd770ab..58eebfe 100644
--- a/percent/AndroidManifest.xml
+++ b/percent/AndroidManifest.xml
@@ -16,6 +16,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.percent">
     <uses-sdk android:minSdkVersion="14"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/percent/build.gradle b/percent/build.gradle
index 1dc9490..90c2962 100644
--- a/percent/build.gradle
+++ b/percent/build.gradle
@@ -3,7 +3,6 @@
 
 dependencies {
     compile project(':support-compat')
-
     androidTestCompile (libs.test_runner) {
         exclude module: 'support-annotations'
     }
diff --git a/percent/src/.readme b/percent/src/.readme
deleted file mode 100644
index 4bcebad..0000000
--- a/percent/src/.readme
+++ /dev/null
@@ -1,2 +0,0 @@
-This hidden file is there to ensure there is an src folder.
-Once we support binary library this will go away.
\ No newline at end of file
diff --git a/percent/src/android/support/percent/PercentFrameLayout.java b/percent/src/android/support/percent/PercentFrameLayout.java
index 9dce2bb..b9abd39 100644
--- a/percent/src/android/support/percent/PercentFrameLayout.java
+++ b/percent/src/android/support/percent/PercentFrameLayout.java
@@ -74,7 +74,60 @@
  * </pre>
  * This will make the aspect ratio 16:9 (1.78:1) with the width fixed at 300dp and height adjusted
  * accordingly.
+ *
+ * @deprecated consider using ConstraintLayout and associated layouts instead. The following shows
+ * how to replicate the functionality of percentage layouts with a ConstraintLayout. The Guidelines
+ * are used to define each percentage break point, and then a Button view is stretched to fill
+ * the gap:
+ *
+ * <pre class="prettyprint">
+ * &lt;android.support.constraint.ConstraintLayout
+ *         xmlns:android="http://schemas.android.com/apk/res/android"
+ *         xmlns:app="http://schemas.android.com/apk/res-auto"
+ *         android:layout_width="match_parent"
+ *         android:layout_height="match_parent"&gt
+ *
+ *     &lt;android.support.constraint.Guideline
+ *         android:layout_width="wrap_content"
+ *         android:layout_height="wrap_content"
+ *         android:id="@+id/left_guideline"
+ *         app:layout_constraintGuide_percent=".15"
+ *         android:orientation="vertical"/&gt
+ *
+ *     &lt;android.support.constraint.Guideline
+ *         android:layout_width="wrap_content"
+ *         android:layout_height="wrap_content"
+ *         android:id="@+id/right_guideline"
+ *         app:layout_constraintGuide_percent=".85"
+ *         android:orientation="vertical"/&gt
+ *
+ *     &lt;android.support.constraint.Guideline
+ *         android:layout_width="wrap_content"
+ *         android:layout_height="wrap_content"
+ *         android:id="@+id/top_guideline"
+ *         app:layout_constraintGuide_percent=".15"
+ *         android:orientation="horizontal"/&gt
+ *
+ *     &lt;android.support.constraint.Guideline
+ *         android:layout_width="wrap_content"
+ *         android:layout_height="wrap_content"
+ *         android:id="@+id/bottom_guideline"
+ *         app:layout_constraintGuide_percent=".85"
+ *         android:orientation="horizontal"/&gt
+ *
+ *     &lt;Button
+ *         android:text="Button"
+ *         android:layout_width="0dp"
+ *         android:layout_height="0dp"
+ *         android:id="@+id/button"
+ *         app:layout_constraintLeft_toLeftOf="@+id/left_guideline"
+ *         app:layout_constraintRight_toRightOf="@+id/right_guideline"
+ *         app:layout_constraintTop_toTopOf="@+id/top_guideline"
+ *         app:layout_constraintBottom_toBottomOf="@+id/bottom_guideline" /&gt
+ *
+ * &lt;/android.support.constraint.ConstraintLayout&gt
  */
+@Deprecated
 public class PercentFrameLayout extends FrameLayout {
     private final PercentLayoutHelper mHelper = new PercentLayoutHelper(this);
 
@@ -115,6 +168,10 @@
         mHelper.restoreOriginalParams();
     }
 
+    /**
+     * @deprecated this class is deprecated along with its parent class.
+     */
+    @Deprecated
     public static class LayoutParams extends FrameLayout.LayoutParams
             implements PercentLayoutHelper.PercentLayoutParams {
         private PercentLayoutHelper.PercentLayoutInfo mPercentLayoutInfo;
diff --git a/percent/src/android/support/percent/PercentLayoutHelper.java b/percent/src/android/support/percent/PercentLayoutHelper.java
index d681244..44a76c7 100644
--- a/percent/src/android/support/percent/PercentLayoutHelper.java
+++ b/percent/src/android/support/percent/PercentLayoutHelper.java
@@ -67,7 +67,59 @@
  * }
  * </pre>
  * </ol>
+ * @deprecated consider using ConstraintLayout and associated layouts instead. The following shows
+ * how to replicate the functionality of percentage layouts with a ConstraintLayout. The Guidelines
+ * are used to define each percentage break point, and then a Button view is stretched to fill
+ * the gap:
+ *
+ * <pre class="prettyprint">
+ * &lt;android.support.constraint.ConstraintLayout
+ *         xmlns:android="http://schemas.android.com/apk/res/android"
+ *         xmlns:app="http://schemas.android.com/apk/res-auto"
+ *         android:layout_width="match_parent"
+ *         android:layout_height="match_parent"&gt
+ *
+ *     &lt;android.support.constraint.Guideline
+ *         android:layout_width="wrap_content"
+ *         android:layout_height="wrap_content"
+ *         android:id="@+id/left_guideline"
+ *         app:layout_constraintGuide_percent=".15"
+ *         android:orientation="vertical"/&gt
+ *
+ *     &lt;android.support.constraint.Guideline
+ *         android:layout_width="wrap_content"
+ *         android:layout_height="wrap_content"
+ *         android:id="@+id/right_guideline"
+ *         app:layout_constraintGuide_percent=".85"
+ *         android:orientation="vertical"/&gt
+ *
+ *     &lt;android.support.constraint.Guideline
+ *         android:layout_width="wrap_content"
+ *         android:layout_height="wrap_content"
+ *         android:id="@+id/top_guideline"
+ *         app:layout_constraintGuide_percent=".15"
+ *         android:orientation="horizontal"/&gt
+ *
+ *     &lt;android.support.constraint.Guideline
+ *         android:layout_width="wrap_content"
+ *         android:layout_height="wrap_content"
+ *         android:id="@+id/bottom_guideline"
+ *         app:layout_constraintGuide_percent=".85"
+ *         android:orientation="horizontal"/&gt
+ *
+ *     &lt;Button
+ *         android:text="Button"
+ *         android:layout_width="0dp"
+ *         android:layout_height="0dp"
+ *         android:id="@+id/button"
+ *         app:layout_constraintLeft_toLeftOf="@+id/left_guideline"
+ *         app:layout_constraintRight_toRightOf="@+id/right_guideline"
+ *         app:layout_constraintTop_toTopOf="@+id/top_guideline"
+ *         app:layout_constraintBottom_toBottomOf="@+id/bottom_guideline" /&gt
+ *
+ * &lt;/android.support.constraint.ConstraintLayout&gt
  */
+@Deprecated
 public class PercentLayoutHelper {
     private static final String TAG = "PercentLayout";
 
@@ -344,7 +396,10 @@
     /**
      * Container for information about percentage dimensions and margins. It acts as an extension
      * for {@code LayoutParams}.
+     *
+     * @deprecated use ConstraintLayout and Guidelines for layout support.
      */
+    @Deprecated
     public static class PercentLayoutInfo {
         /** The decimal value of the percentage-based width. */
         public float widthPercent;
@@ -555,7 +610,10 @@
      *
      * Your {@code LayoutParams} subclass should contain an instance of {@code PercentLayoutInfo}
      * and the implementation of this interface should be a simple accessor.
+     *
+     * @deprecated this class is deprecated along with its parent class.
      */
+    @Deprecated
     public interface PercentLayoutParams {
         PercentLayoutInfo getPercentLayoutInfo();
     }
diff --git a/percent/src/android/support/percent/PercentRelativeLayout.java b/percent/src/android/support/percent/PercentRelativeLayout.java
index f068d6b..5b10349 100644
--- a/percent/src/android/support/percent/PercentRelativeLayout.java
+++ b/percent/src/android/support/percent/PercentRelativeLayout.java
@@ -73,7 +73,60 @@
  * </pre>
  * This will make the aspect ratio 16:9 (1.78:1) with the width fixed at 300dp and height adjusted
  * accordingly.
+ *
+ * @deprecated consider using ConstraintLayout and associated layouts instead. The following shows
+ * how to replicate the functionality of percentage layouts with a ConstraintLayout. The Guidelines
+ * are used to define each percentage break point, and then a Button view is stretched to fill
+ * the gap:
+ *
+ * <pre class="prettyprint">
+ * &lt;android.support.constraint.ConstraintLayout
+ *         xmlns:android="http://schemas.android.com/apk/res/android"
+ *         xmlns:app="http://schemas.android.com/apk/res-auto"
+ *         android:layout_width="match_parent"
+ *         android:layout_height="match_parent"&gt
+ *
+ *     &lt;android.support.constraint.Guideline
+ *         android:layout_width="wrap_content"
+ *         android:layout_height="wrap_content"
+ *         android:id="@+id/left_guideline"
+ *         app:layout_constraintGuide_percent=".15"
+ *         android:orientation="vertical"/&gt
+ *
+ *     &lt;android.support.constraint.Guideline
+ *         android:layout_width="wrap_content"
+ *         android:layout_height="wrap_content"
+ *         android:id="@+id/right_guideline"
+ *         app:layout_constraintGuide_percent=".85"
+ *         android:orientation="vertical"/&gt
+ *
+ *     &lt;android.support.constraint.Guideline
+ *         android:layout_width="wrap_content"
+ *         android:layout_height="wrap_content"
+ *         android:id="@+id/top_guideline"
+ *         app:layout_constraintGuide_percent=".15"
+ *         android:orientation="horizontal"/&gt
+ *
+ *     &lt;android.support.constraint.Guideline
+ *         android:layout_width="wrap_content"
+ *         android:layout_height="wrap_content"
+ *         android:id="@+id/bottom_guideline"
+ *         app:layout_constraintGuide_percent=".85"
+ *         android:orientation="horizontal"/&gt
+ *
+ *     &lt;Button
+ *         android:text="Button"
+ *         android:layout_width="0dp"
+ *         android:layout_height="0dp"
+ *         android:id="@+id/button"
+ *         app:layout_constraintLeft_toLeftOf="@+id/left_guideline"
+ *         app:layout_constraintRight_toRightOf="@+id/right_guideline"
+ *         app:layout_constraintTop_toTopOf="@+id/top_guideline"
+ *         app:layout_constraintBottom_toBottomOf="@+id/bottom_guideline" /&gt
+ *
+ * &lt;/android.support.constraint.ConstraintLayout&gt
  */
+@Deprecated
 public class PercentRelativeLayout extends RelativeLayout {
     private final PercentLayoutHelper mHelper = new PercentLayoutHelper(this);
 
@@ -114,6 +167,10 @@
         mHelper.restoreOriginalParams();
     }
 
+    /**
+     * @deprecated this class is deprecated along with its parent class.
+     */
+    @Deprecated
     public static class LayoutParams extends RelativeLayout.LayoutParams
             implements PercentLayoutHelper.PercentLayoutParams {
         private PercentLayoutHelper.PercentLayoutInfo mPercentLayoutInfo;
diff --git a/recommendation/Android.mk b/recommendation/Android.mk
index 2a68de6..4c27532 100644
--- a/recommendation/Android.mk
+++ b/recommendation/Android.mk
@@ -33,38 +33,3 @@
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
 include $(BUILD_STATIC_JAVA_LIBRARY)
-
-# ===========================================================
-# Common Droiddoc vars
-recommendation.docs.src_files := \
-    $(call all-java-files-under, src) \
-    $(call all-html-files-under, src)
-recommendation.docs.java_libraries := \
-    android-support-v4 \
-    android-support-recommendation
-
-# Documentation
-# ===========================================================
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := android-support-recommendation
-LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := $(recommendation.docs.src_files)
-
-LOCAL_SDK_VERSION := 21
-LOCAL_IS_HOST_MODULE := false
-LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR := external/doclava/res/assets/templates-sdk
-
-LOCAL_SHARED_ANDROID_LIBRARIES := $(recommendation.docs.java_libraries)
-
-LOCAL_DROIDDOC_OPTIONS := \
-    -offlinemode \
-    -hdf android.whichdoc offline \
-    -federate Android http://developer.android.com \
-    -federationapi Android prebuilts/sdk/api/21.txt \
-    -hide 113
-
-include $(BUILD_DROIDDOC)
-
diff --git a/recommendation/AndroidManifest.xml b/recommendation/AndroidManifest.xml
index e36c822..09017e0 100644
--- a/recommendation/AndroidManifest.xml
+++ b/recommendation/AndroidManifest.xml
@@ -16,6 +16,7 @@
 <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 />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/samples/Support13Demos/res/values/colors.xml b/samples/Support13Demos/res/values/colors.xml
index a1daf63..e50c7a0 100644
--- a/samples/Support13Demos/res/values/colors.xml
+++ b/samples/Support13Demos/res/values/colors.xml
@@ -18,5 +18,5 @@
     <drawable name="red">#7f00</drawable>
     <drawable name="blue">#770000ff</drawable>
     <drawable name="green">#7700ff00</drawable>
-	<drawable name="yellow">#77ffff00</drawable>
+    <drawable name="yellow">#77ffff00</drawable>
 </resources>
diff --git a/samples/Support13Demos/res/values/strings.xml b/samples/Support13Demos/res/values/strings.xml
index 30b6a18..b0950b2 100644
--- a/samples/Support13Demos/res/values/strings.xml
+++ b/samples/Support13Demos/res/values/strings.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
     <string name="activity_sample_code">Support v13 Demos</string>
 
     <string name="hello_world"><b>Hello, <i>World!</i></b></string>
diff --git a/samples/Support4Demos/res/values/strings.xml b/samples/Support4Demos/res/values/strings.xml
index 24d88d6..053dd3a 100644
--- a/samples/Support4Demos/res/values/strings.xml
+++ b/samples/Support4Demos/res/values/strings.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
     <string name="activity_sample_code">Support v4 Demos</string>
 
     <string name="hello_world"><b>Hello, <i>World!</i></b></string>
@@ -24,9 +24,9 @@
     </string>
     <string name="alert_dialog_ok">OK</string>
     <string name="alert_dialog_cancel">Cancel</string>
-    
+
     <string name="initial_text">Initial text.</string>
-    
+
     <string name="pick_result">Pick a result to send, or BACK to cancel.</string>
     <string name="corky">Corky</string>
     <string name="violet">Violet</string>
@@ -86,7 +86,7 @@
     <string name="home">Go home</string>
     <string name="new_fragment">Add new</string>
     <string name="delete_fragment">Pop top</string>
-    
+
     <string name="fragment_tabs">Fragment/Tabs</string>
 
     <string name="fragment_tabs_pager">Fragment/Tabs and Pager</string>
diff --git a/samples/Support7Demos/AndroidManifest.xml b/samples/Support7Demos/AndroidManifest.xml
index 0ba2e6a..0635231 100644
--- a/samples/Support7Demos/AndroidManifest.xml
+++ b/samples/Support7Demos/AndroidManifest.xml
@@ -254,6 +254,15 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".app.AppCompatWidgetsTextViews"
+                  android:label="@string/appcompat_widgets_text_views"
+                  android:theme="@style/Theme.Custom.TextLink">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.example.android.supportv7.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
         <activity android:name=".app.ToolbarUsage"
                   android:label="@string/toolbar_usage"
                   android:theme="@style/Theme.Custom.NoActionBar">
@@ -537,6 +546,16 @@
                 <category android:name="com.example.android.supportv7.SAMPLE_CODE"/>
             </intent-filter>
         </activity>
+
+        <!-- ListView styling activity -->
+        <activity android:name=".widget.ListViewActivity"
+                  android:label="@string/list_view_activity"
+                  android:theme="@style/Theme.AppCompat.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="com.example.android.supportv7.SAMPLE_CODE"/>
+            </intent-filter>
+        </activity>
     </application>
 
 
diff --git a/samples/Support7Demos/res/color/link_color.xml b/samples/Support7Demos/res/color/link_color.xml
new file mode 100644
index 0000000..6d71ed9
--- /dev/null
+++ b/samples/Support7Demos/res/color/link_color.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:alpha="?android:disabledAlpha"
+          android:color="?colorPrimary"
+          android:state_enabled="false"/>
+    <item android:color="?colorPrimary"/>
+</selector>
\ No newline at end of file
diff --git a/samples/Support7Demos/res/layout/appcompat_widgets_text_views.xml b/samples/Support7Demos/res/layout/appcompat_widgets_text_views.xml
new file mode 100644
index 0000000..4aafaa1
--- /dev/null
+++ b/samples/Support7Demos/res/layout/appcompat_widgets_text_views.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 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.
+  -->
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:padding="16dp">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/text_plain_enabled"/>
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:enabled="false"
+            android:text="@string/text_plain_disabled"/>
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/text_link_enabled"/>
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:enabled="false"
+            android:text="@string/text_link_disabled"/>
+
+    </LinearLayout>
+
+</ScrollView>
\ No newline at end of file
diff --git a/samples/Support7Demos/res/layout/list_view_activity.xml b/samples/Support7Demos/res/layout/list_view_activity.xml
new file mode 100644
index 0000000..aed3ea6
--- /dev/null
+++ b/samples/Support7Demos/res/layout/list_view_activity.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 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.
+  -->
+
+<ListView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/list_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" />
+
diff --git a/samples/Support7Demos/res/layout/list_view_item.xml b/samples/Support7Demos/res/layout/list_view_item.xml
new file mode 100644
index 0000000..4a97d34
--- /dev/null
+++ b/samples/Support7Demos/res/layout/list_view_item.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 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="wrap_content"
+    android:padding="12dp"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textAppearance="?attr/textAppearanceListItem" />
+
+    <TextView
+        android:id="@+id/subtitle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textAppearance="?attr/textAppearanceListItemSecondary" />
+
+</LinearLayout>
diff --git a/samples/Support7Demos/res/layout/popup_menu_activity.xml b/samples/Support7Demos/res/layout/popup_menu_activity.xml
index c3ed4f5..552a996 100644
--- a/samples/Support7Demos/res/layout/popup_menu_activity.xml
+++ b/samples/Support7Demos/res/layout/popup_menu_activity.xml
@@ -27,6 +27,16 @@
         android:layout_centerHorizontal="true"
         android:text="@string/popup_menu_button" />
 
+    <android.support.v7.widget.SwitchCompat
+        android:id="@+id/elevation_toggle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/test_button"
+        android:layout_marginLeft="8dp"
+        android:layout_marginRight="8dp"
+        android:checked="true"
+        android:text="@string/popup_default_elevation" />
+
     <TextView
         android:id="@+id/log"
         android:layout_width="match_parent"
diff --git a/samples/Support7Demos/res/values-v21/styles.xml b/samples/Support7Demos/res/values-v21/styles.xml
index d5b1764..f75030d 100644
--- a/samples/Support7Demos/res/values-v21/styles.xml
+++ b/samples/Support7Demos/res/values-v21/styles.xml
@@ -24,4 +24,8 @@
         <item name="windowActionModeOverlay">true</item>
         <item name="android:windowContentOverlay">@null</item>
     </style>
-</resources>
\ No newline at end of file
+
+    <style name="CustomPopupNoElevation" parent="@style/Widget.AppCompat.Light.PopupMenu">
+        <item name="android:popupElevation">2dp</item>
+    </style>
+</resources>
diff --git a/samples/Support7Demos/res/values/strings.xml b/samples/Support7Demos/res/values/strings.xml
index fe325a1..c5ffe46 100644
--- a/samples/Support7Demos/res/values/strings.xml
+++ b/samples/Support7Demos/res/values/strings.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+<resources>
     <string name="activity_sample_code">Support v7 Demos</string>
 
     <!-- MediaRouter -->
@@ -68,6 +68,7 @@
     <string name="appcompat_widgets_buttons">AppCompat/Widgets/Buttons</string>
     <string name="appcompat_widgets_spinners">AppCompat/Widgets/Spinners</string>
     <string name="appcompat_widgets_text_input">AppCompat/Widgets/Text Input</string>
+    <string name="appcompat_widgets_text_views">AppCompat/Widgets/Text Views</string>
 
     <string name="action_bar_search">Search</string>
     <string name="action_bar_add">Add</string>
@@ -217,6 +218,7 @@
     <string name="popup_menu_activity">AppCompat/Popup menu</string>
     <string name="popup_menu_summary">This activity illustrates the use of popup menus. The popup menu is shown by clicking the button above. The text area below logs various events.</string>
     <string name="popup_menu_button">Show popup!</string>
+    <string name="popup_default_elevation">Use default elevation on popup</string>
     <string name="popup_menu_highlight">Highlight</string>
     <string name="popup_menu_highlight_description">Highlight description</string>
     <string name="popup_menu_highlight_tooltip">Highlight tooltip</string>
@@ -230,9 +232,16 @@
     <string name="popup_menu_share_circles">To my circles</string>
     <string name="popup_menu_print">Print</string>
 
+    <string name="list_view_activity">AppCompat/ListView styling</string>
+
     <string name="appcompat_vector_disabled">AnimatedVectorDrawableCompat does not work on devices running API v10 or below</string>
     <string name="appcompat_vector_title">AppCompat/Integrations/AnimatedVectorDrawable</string>
 
     <string name="night_mode">DAY</string>
+
+    <string name="text_plain_enabled">Plain enabled</string>
+    <string name="text_plain_disabled">Plain disabled</string>
+    <string name="text_link_enabled">With <a href="http://www.google.com">link</a> enabled</string>
+    <string name="text_link_disabled">With <a href="http://www.google.com">link</a> disabled</string>
 </resources>
 
diff --git a/samples/Support7Demos/res/values/styles.xml b/samples/Support7Demos/res/values/styles.xml
index 3395c69..9c022d3 100644
--- a/samples/Support7Demos/res/values/styles.xml
+++ b/samples/Support7Demos/res/values/styles.xml
@@ -29,6 +29,10 @@
         <item name="colorAccent">#ffff00</item>
     </style>
 
+    <style name="Theme.Custom.TextLink">
+        <item name="android:textColorLink">@color/link_color</item>
+    </style>
+
     <style name="Theme.SampleMediaRouter" parent="Theme.AppCompat">
         <item name="colorPrimary">#fff44336</item>
         <item name="colorPrimaryDark">#d32f2f</item>
@@ -52,4 +56,6 @@
         <item name="windowActionModeOverlay">true</item>
         <item name="android:windowContentOverlay">@null</item>
     </style>
+
+    <style name="CustomPopupNoElevation" parent="@style/Widget.AppCompat.Light.PopupMenu" />
 </resources>
diff --git a/samples/Support7Demos/src/com/example/android/supportv7/app/AppCompatWidgetsTextViews.java b/samples/Support7Demos/src/com/example/android/supportv7/app/AppCompatWidgetsTextViews.java
new file mode 100644
index 0000000..1480509
--- /dev/null
+++ b/samples/Support7Demos/src/com/example/android/supportv7/app/AppCompatWidgetsTextViews.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 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.supportv7.app;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+
+import com.example.android.supportv7.R;
+
+/**
+ * This demonstrates the styled {@link android.widget.TextView} widgets in AppCompat.
+ */
+public class AppCompatWidgetsTextViews extends AppCompatActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.appcompat_widgets_text_views);
+    }
+
+}
diff --git a/samples/Support7Demos/src/com/example/android/supportv7/widget/ListViewActivity.java b/samples/Support7Demos/src/com/example/android/supportv7/widget/ListViewActivity.java
new file mode 100644
index 0000000..cb0c2e2
--- /dev/null
+++ b/samples/Support7Demos/src/com/example/android/supportv7/widget/ListViewActivity.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 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.supportv7.widget;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.example.android.supportv7.R;
+
+/**
+ * Sample activity for the demo of list item styles.
+ */
+public class ListViewActivity extends AppCompatActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.list_view_activity);
+        ListView listView = (ListView) findViewById(R.id.list_view);
+        listView.setAdapter(new BaseAdapter() {
+            @Override
+            public int getCount() {
+                return 100;
+            }
+
+            @Override
+            public Object getItem(int i) {
+                return null;
+            }
+
+            @Override
+            public long getItemId(int i) {
+                return i;
+            }
+
+            @Override
+            public View getView(int i, View view, ViewGroup viewGroup) {
+                // this is just a demo, so not using view holders
+                if (view == null) {
+                    view = LayoutInflater.from(viewGroup.getContext()).inflate(
+                            R.layout.list_view_item, viewGroup, false);
+                }
+                TextView title = (TextView) view.findViewById(R.id.title);
+                TextView subtitle = (TextView) view.findViewById(R.id.subtitle);
+                title.setText("Title for #" + i);
+                subtitle.setText("Subtitle for #" + i);
+                return view;
+            }
+        });
+    }
+}
diff --git a/samples/Support7Demos/src/com/example/android/supportv7/widget/PopupMenuActivity.java b/samples/Support7Demos/src/com/example/android/supportv7/widget/PopupMenuActivity.java
index bb7cd86..3adf840 100644
--- a/samples/Support7Demos/src/com/example/android/supportv7/widget/PopupMenuActivity.java
+++ b/samples/Support7Demos/src/com/example/android/supportv7/widget/PopupMenuActivity.java
@@ -20,26 +20,23 @@
 import android.support.v4.view.MenuItemCompat;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.widget.PopupMenu;
+import android.support.v7.widget.SwitchCompat;
+import android.view.Gravity;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
 import android.widget.TextView;
+
 import com.example.android.supportv7.R;
 
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
 public class PopupMenuActivity extends AppCompatActivity {
-    private ViewGroup mContainer;
-
     private TextView mLog;
 
-    private Button mButton;
-
-    private PopupMenu mPopupMenu;
-
     private SimpleDateFormat mDateFormat;
 
     @Override
@@ -50,17 +47,29 @@
 
         mDateFormat = new SimpleDateFormat("HH:mm:ss.SSS");
 
-        mContainer = (ViewGroup) findViewById(R.id.container);
-        mLog = (TextView) mContainer.findViewById(R.id.log);
-        mButton = (Button) mContainer.findViewById(R.id.test_button);
+        final ViewGroup container = (ViewGroup) findViewById(R.id.container);
+        mLog = (TextView) container.findViewById(R.id.log);
 
-        mButton.setOnClickListener(new View.OnClickListener() {
+        final SwitchCompat elevationToggle = (SwitchCompat) container.findViewById(
+                R.id.elevation_toggle);
+        final Button button = (Button) container.findViewById(R.id.test_button);
+        button.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                mPopupMenu = new PopupMenu(mContainer.getContext(), mButton);
-                final MenuInflater menuInflater = mPopupMenu.getMenuInflater();
-                menuInflater.inflate(R.menu.popup_menu, mPopupMenu.getMenu());
-                final MenuItem editItem = mPopupMenu.getMenu().findItem(R.id.action_edit);
+                // Do we need to use a custom style that removes elevation?
+                boolean useDefaultElevation = elevationToggle.isChecked();
+
+                PopupMenu popupMenu = null;
+                if (useDefaultElevation) {
+                    popupMenu = new PopupMenu(container.getContext(), button);
+                } else {
+                    popupMenu = new PopupMenu(container.getContext(), button, Gravity.NO_GRAVITY,
+                            0, R.style.CustomPopupNoElevation);
+                }
+
+                final MenuInflater menuInflater = popupMenu.getMenuInflater();
+                menuInflater.inflate(R.menu.popup_menu, popupMenu.getMenu());
+                final MenuItem editItem = popupMenu.getMenu().findItem(R.id.action_edit);
                 MenuItemCompat.setContentDescription(editItem,
                         getString(R.string.popup_menu_edit_description));
                 MenuItemCompat.setTooltipText(editItem,
@@ -68,7 +77,7 @@
 
                 // Register a listener to be notified when a menu item in our popup menu has
                 // been clicked.
-                mPopupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+                popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
                     @Override
                     public boolean onMenuItemClick(MenuItem item) {
                         addToLog("Item '"+ item.getTitle() + "' clicked");
@@ -77,7 +86,7 @@
                 });
 
                 // Register a listener to be notified when our popup menu is dismissed.
-                mPopupMenu.setOnDismissListener(new PopupMenu.OnDismissListener() {
+                popupMenu.setOnDismissListener(new PopupMenu.OnDismissListener() {
                     @Override
                     public void onDismiss(PopupMenu menu) {
                         addToLog("Popup menu dismissed");
@@ -85,7 +94,7 @@
                 });
 
                 // Show the popup menu
-                mPopupMenu.show();
+                popupMenu.show();
             }
         });
     }
diff --git a/samples/SupportTransitionDemos/AndroidManifest.xml b/samples/SupportTransitionDemos/AndroidManifest.xml
index 083f898..6a08d2c 100644
--- a/samples/SupportTransitionDemos/AndroidManifest.xml
+++ b/samples/SupportTransitionDemos/AndroidManifest.xml
@@ -72,5 +72,23 @@
                 <category android:name="com.example.android.support.transition.SAMPLE_CODE" />
             </intent-filter>
         </activity>
+
+        <activity android:name=".widget.ExplodeUsage"
+                  android:label="@string/explode"
+                  android:theme="@style/Theme.Transition">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.example.android.support.transition.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".widget.ChangeClipBoundsUsage"
+                  android:label="@string/clipBounds"
+                  android:theme="@style/Theme.Transition">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.example.android.support.transition.SAMPLE_CODE" />
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/samples/SupportTransitionDemos/res/layout/clip_bounds.xml b/samples/SupportTransitionDemos/res/layout/clip_bounds.xml
new file mode 100644
index 0000000..2e98f32
--- /dev/null
+++ b/samples/SupportTransitionDemos/res/layout/clip_bounds.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2017 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:id="@+id/root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <Button
+        android:id="@+id/toggle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/toggle"/>
+
+    <ImageView
+        android:id="@+id/photo"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:contentDescription="Photo"
+        android:src="@drawable/photo"/>
+
+</LinearLayout>
diff --git a/samples/SupportTransitionDemos/res/layout/explode.xml b/samples/SupportTransitionDemos/res/layout/explode.xml
new file mode 100644
index 0000000..a612910
--- /dev/null
+++ b/samples/SupportTransitionDemos/res/layout/explode.xml
@@ -0,0 +1,63 @@
+<?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:id="@+id/root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <View
+        android:id="@+id/view_1"
+        android:layout_width="64dp"
+        android:layout_height="64dp"
+        android:layout_gravity="start|top"
+        android:layout_margin="64dp"
+        android:background="#E91E63"/>
+
+    <View
+        android:id="@+id/view_2"
+        android:layout_width="64dp"
+        android:layout_height="64dp"
+        android:layout_gravity="end|top"
+        android:layout_margin="64dp"
+        android:background="#673AB7"/>
+
+    <View
+        android:id="@+id/view_3"
+        android:layout_width="64dp"
+        android:layout_height="64dp"
+        android:layout_gravity="start|bottom"
+        android:layout_margin="64dp"
+        android:background="#009688"/>
+
+    <View
+        android:id="@+id/view_4"
+        android:layout_width="64dp"
+        android:layout_height="64dp"
+        android:layout_gravity="end|bottom"
+        android:layout_margin="64dp"
+        android:background="#FF5722"/>
+
+    <Button
+        android:id="@+id/toggle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal|bottom"
+        android:layout_margin="64dp"
+        android:text="@string/toggle"/>
+
+</FrameLayout>
diff --git a/samples/SupportTransitionDemos/res/values/strings.xml b/samples/SupportTransitionDemos/res/values/strings.xml
index de69ee1..73ed883 100644
--- a/samples/SupportTransitionDemos/res/values/strings.xml
+++ b/samples/SupportTransitionDemos/res/values/strings.xml
@@ -20,6 +20,8 @@
     <string name="custom">Custom Transition</string>
     <string name="beginDelayed">Begin Delayed Transition</string>
     <string name="arcMotion">Arc Motion</string>
+    <string name="explode">Explode</string>
+    <string name="clipBounds">Change Clip Bounds</string>
     <string name="toggle">Toggle</string>
     <string name="begin">Begin</string>
     <string name="hello_world">Hello, world!</string>
diff --git a/samples/SupportTransitionDemos/src/com/example/android/support/transition/widget/ChangeClipBoundsUsage.java b/samples/SupportTransitionDemos/src/com/example/android/support/transition/widget/ChangeClipBoundsUsage.java
new file mode 100644
index 0000000..8455404
--- /dev/null
+++ b/samples/SupportTransitionDemos/src/com/example/android/support/transition/widget/ChangeClipBoundsUsage.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 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.support.transition.widget;
+
+
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.support.transition.ChangeClipBounds;
+import android.support.transition.TransitionManager;
+import android.support.v4.view.ViewCompat;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import com.example.android.support.transition.R;
+
+/**
+ * This demonstrates usage of {@link ChangeClipBounds}.
+ */
+public class ChangeClipBoundsUsage extends TransitionUsageBase {
+
+    private static final Rect BOUNDS = new Rect(20, 20, 100, 100);
+
+    private final ChangeClipBounds mChangeClipBounds = new ChangeClipBounds();
+    private ViewGroup mRoot;
+    private ImageView mPhoto;
+
+    @Override
+    int getLayoutResId() {
+        return R.layout.clip_bounds;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mRoot = (ViewGroup) findViewById(R.id.root);
+        mPhoto = (ImageView) findViewById(R.id.photo);
+        findViewById(R.id.toggle).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                toggle();
+            }
+        });
+    }
+
+    void toggle() {
+        TransitionManager.beginDelayedTransition(mRoot, mChangeClipBounds);
+        if (BOUNDS.equals(ViewCompat.getClipBounds(mPhoto))) {
+            ViewCompat.setClipBounds(mPhoto, null);
+        } else {
+            ViewCompat.setClipBounds(mPhoto, BOUNDS);
+        }
+    }
+
+}
diff --git a/samples/SupportTransitionDemos/src/com/example/android/support/transition/widget/ExplodeUsage.java b/samples/SupportTransitionDemos/src/com/example/android/support/transition/widget/ExplodeUsage.java
new file mode 100644
index 0000000..c7a4fd1
--- /dev/null
+++ b/samples/SupportTransitionDemos/src/com/example/android/support/transition/widget/ExplodeUsage.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 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.support.transition.widget;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.support.transition.Explode;
+import android.support.transition.Transition;
+import android.support.transition.TransitionManager;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.example.android.support.transition.R;
+
+import java.util.ArrayList;
+
+/**
+ * This demonstrates usage of {@link Explode} Transition type.
+ */
+public class ExplodeUsage extends TransitionUsageBase {
+
+    private FrameLayout mRoot;
+    private final ArrayList<View> mViews = new ArrayList<>();
+    private final Explode mExplode = new Explode();
+
+    final Rect mRect = new Rect();
+
+    @Override
+    int getLayoutResId() {
+        return R.layout.explode;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mExplode.setEpicenterCallback(new Transition.EpicenterCallback() {
+            @Override
+            public Rect onGetEpicenter(Transition transition) {
+                return mRect;
+            }
+        });
+        mRoot = (FrameLayout) findViewById(R.id.root);
+        if (mViews.isEmpty()) {
+            mViews.add(findViewById(R.id.view_1));
+            mViews.add(findViewById(R.id.view_2));
+            mViews.add(findViewById(R.id.view_3));
+            mViews.add(findViewById(R.id.view_4));
+        }
+        findViewById(R.id.toggle).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                v.getGlobalVisibleRect(mRect);
+                TransitionManager.beginDelayedTransition(mRoot, mExplode);
+                int vis = mViews.get(0).getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE;
+                for (View view : mViews) {
+                    view.setVisibility(vis);
+                }
+            }
+        });
+    }
+
+}
diff --git a/transition/AndroidManifest.xml b/transition/AndroidManifest.xml
index 1059f63..309b695 100644
--- a/transition/AndroidManifest.xml
+++ b/transition/AndroidManifest.xml
@@ -16,6 +16,7 @@
 <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 />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/transition/res/values/ids.xml b/transition/res/values/ids.xml
index 071cf2a..5fc44dc 100644
--- a/transition/res/values/ids.xml
+++ b/transition/res/values/ids.xml
@@ -18,4 +18,5 @@
     <item name="transition_scene_layoutid_cache" type="id"/>
     <item name="transition_current_scene" type="id"/>
     <item name="transition_layout_save" type="id"/>
+    <item name="transition_position" type="id"/>
 </resources>
diff --git a/transition/src/android/support/transition/ChangeClipBounds.java b/transition/src/android/support/transition/ChangeClipBounds.java
new file mode 100644
index 0000000..7fd1023
--- /dev/null
+++ b/transition/src/android/support/transition/ChangeClipBounds.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 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.transition;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.annotation.NonNull;
+import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * ChangeClipBounds captures the {@link android.view.View#getClipBounds()} before and after the
+ * scene change and animates those changes during the transition.
+ *
+ * <p>Prior to API 18 this does nothing.</p>
+ */
+public class ChangeClipBounds extends Transition {
+
+    private static final String PROPNAME_CLIP = "android:clipBounds:clip";
+    private static final String PROPNAME_BOUNDS = "android:clipBounds:bounds";
+
+    private static final String[] sTransitionProperties = {
+            PROPNAME_CLIP,
+    };
+
+    @Override
+    public String[] getTransitionProperties() {
+        return sTransitionProperties;
+    }
+
+    public ChangeClipBounds() {
+    }
+
+    public ChangeClipBounds(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    private void captureValues(TransitionValues values) {
+        View view = values.view;
+        if (view.getVisibility() == View.GONE) {
+            return;
+        }
+
+        Rect clip = ViewCompat.getClipBounds(view);
+        values.values.put(PROPNAME_CLIP, clip);
+        if (clip == null) {
+            Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight());
+            values.values.put(PROPNAME_BOUNDS, bounds);
+        }
+    }
+
+    @Override
+    public void captureStartValues(@NonNull TransitionValues transitionValues) {
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public void captureEndValues(@NonNull TransitionValues transitionValues) {
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public Animator createAnimator(@NonNull final ViewGroup sceneRoot, TransitionValues startValues,
+            TransitionValues endValues) {
+        if (startValues == null || endValues == null
+                || !startValues.values.containsKey(PROPNAME_CLIP)
+                || !endValues.values.containsKey(PROPNAME_CLIP)) {
+            return null;
+        }
+        Rect start = (Rect) startValues.values.get(PROPNAME_CLIP);
+        Rect end = (Rect) endValues.values.get(PROPNAME_CLIP);
+        if (start == null && end == null) {
+            return null; // No animation required since there is no clip.
+        }
+
+        if (start == null) {
+            start = (Rect) startValues.values.get(PROPNAME_BOUNDS);
+        } else if (end == null) {
+            end = (Rect) endValues.values.get(PROPNAME_BOUNDS);
+        }
+        if (start.equals(end)) {
+            return null;
+        }
+
+        ViewCompat.setClipBounds(endValues.view, start);
+        RectEvaluator evaluator = new RectEvaluator(new Rect());
+        return ObjectAnimator.ofObject(endValues.view, ViewUtils.CLIP_BOUNDS, evaluator,
+                start, end);
+    }
+}
diff --git a/transition/src/android/support/transition/CircularPropagation.java b/transition/src/android/support/transition/CircularPropagation.java
new file mode 100644
index 0000000..ced4223
--- /dev/null
+++ b/transition/src/android/support/transition/CircularPropagation.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 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.transition;
+
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A propagation that varies with the distance to the epicenter of the Transition
+ * or center of the scene if no epicenter exists. When a View is visible in the
+ * start of the transition, Views farther from the epicenter will transition
+ * sooner than Views closer to the epicenter. When a View is not in the start
+ * of the transition or is not visible at the start of the transition, it will
+ * transition sooner when closer to the epicenter and later when farther from
+ * the epicenter. This is the default TransitionPropagation used with
+ * {@link Explode}.
+ */
+public class CircularPropagation extends VisibilityPropagation {
+
+    private float mPropagationSpeed = 3.0f;
+
+    /**
+     * Sets the speed at which transition propagation happens, relative to the duration of the
+     * Transition. A <code>propagationSpeed</code> of 1 means that a View centered farthest from
+     * the epicenter and View centered at the epicenter will have a difference
+     * in start delay of approximately the duration of the Transition. A speed of 2 means the
+     * start delay difference will be approximately half of the duration of the transition. A
+     * value of 0 is illegal, but negative values will invert the propagation.
+     *
+     * @param propagationSpeed The speed at which propagation occurs, relative to the duration
+     *                         of the transition. A speed of 4 means it works 4 times as fast
+     *                         as the duration of the transition. May not be 0.
+     */
+    public void setPropagationSpeed(float propagationSpeed) {
+        if (propagationSpeed == 0) {
+            throw new IllegalArgumentException("propagationSpeed may not be 0");
+        }
+        mPropagationSpeed = propagationSpeed;
+    }
+
+    @Override
+    public long getStartDelay(ViewGroup sceneRoot, Transition transition,
+            TransitionValues startValues, TransitionValues endValues) {
+        if (startValues == null && endValues == null) {
+            return 0;
+        }
+        int directionMultiplier = 1;
+        TransitionValues positionValues;
+        if (endValues == null || getViewVisibility(startValues) == View.VISIBLE) {
+            positionValues = startValues;
+            directionMultiplier = -1;
+        } else {
+            positionValues = endValues;
+        }
+
+        int viewCenterX = getViewX(positionValues);
+        int viewCenterY = getViewY(positionValues);
+
+        Rect epicenter = transition.getEpicenter();
+        int epicenterX;
+        int epicenterY;
+        if (epicenter != null) {
+            epicenterX = epicenter.centerX();
+            epicenterY = epicenter.centerY();
+        } else {
+            int[] loc = new int[2];
+            sceneRoot.getLocationOnScreen(loc);
+            epicenterX = Math.round(loc[0] + (sceneRoot.getWidth() / 2)
+                    + sceneRoot.getTranslationX());
+            epicenterY = Math.round(loc[1] + (sceneRoot.getHeight() / 2)
+                    + sceneRoot.getTranslationY());
+        }
+        float distance = distance(viewCenterX, viewCenterY, epicenterX, epicenterY);
+        float maxDistance = distance(0, 0, sceneRoot.getWidth(), sceneRoot.getHeight());
+        float distanceFraction = distance / maxDistance;
+
+        long duration = transition.getDuration();
+        if (duration < 0) {
+            duration = 300;
+        }
+
+        return Math.round(duration * directionMultiplier / mPropagationSpeed * distanceFraction);
+    }
+
+    private static float distance(float x1, float y1, float x2, float y2) {
+        float x = x2 - x1;
+        float y = y2 - y1;
+        return (float) Math.sqrt((x * x) + (y * y));
+    }
+
+}
diff --git a/transition/src/android/support/transition/Explode.java b/transition/src/android/support/transition/Explode.java
new file mode 100644
index 0000000..38d0fb7
--- /dev/null
+++ b/transition/src/android/support/transition/Explode.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2017 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.transition;
+
+import android.animation.Animator;
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+/**
+ * This transition tracks changes to the visibility of target views in the
+ * start and end scenes and moves views in or out from the edges of the
+ * scene. Visibility is determined by both the
+ * {@link View#setVisibility(int)} state of the view as well as whether it
+ * is parented in the current view hierarchy. Disappearing Views are
+ * limited as described in {@link Visibility#onDisappear(android.view.ViewGroup,
+ * TransitionValues, int, TransitionValues, int)}.
+ * <p>Views move away from the focal View or the center of the Scene if
+ * no epicenter was provided.</p>
+ */
+public class Explode extends Visibility {
+
+    private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
+    private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
+    private static final String PROPNAME_SCREEN_BOUNDS = "android:explode:screenBounds";
+
+    private int[] mTempLoc = new int[2];
+
+    public Explode() {
+        setPropagation(new CircularPropagation());
+    }
+
+    public Explode(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setPropagation(new CircularPropagation());
+    }
+
+    private void captureValues(TransitionValues transitionValues) {
+        View view = transitionValues.view;
+        view.getLocationOnScreen(mTempLoc);
+        int left = mTempLoc[0];
+        int top = mTempLoc[1];
+        int right = left + view.getWidth();
+        int bottom = top + view.getHeight();
+        transitionValues.values.put(PROPNAME_SCREEN_BOUNDS, new Rect(left, top, right, bottom));
+    }
+
+    @Override
+    public void captureStartValues(@NonNull TransitionValues transitionValues) {
+        super.captureStartValues(transitionValues);
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public void captureEndValues(@NonNull TransitionValues transitionValues) {
+        super.captureEndValues(transitionValues);
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public Animator onAppear(ViewGroup sceneRoot, View view,
+            TransitionValues startValues, TransitionValues endValues) {
+        if (endValues == null) {
+            return null;
+        }
+        Rect bounds = (Rect) endValues.values.get(PROPNAME_SCREEN_BOUNDS);
+        float endX = view.getTranslationX();
+        float endY = view.getTranslationY();
+        calculateOut(sceneRoot, bounds, mTempLoc);
+        float startX = endX + mTempLoc[0];
+        float startY = endY + mTempLoc[1];
+
+        return TranslationAnimationCreator.createAnimation(view, endValues, bounds.left, bounds.top,
+                startX, startY, endX, endY, sDecelerate);
+    }
+
+    @Override
+    public Animator onDisappear(ViewGroup sceneRoot, View view,
+            TransitionValues startValues, TransitionValues endValues) {
+        if (startValues == null) {
+            return null;
+        }
+        Rect bounds = (Rect) startValues.values.get(PROPNAME_SCREEN_BOUNDS);
+        int viewPosX = bounds.left;
+        int viewPosY = bounds.top;
+        float startX = view.getTranslationX();
+        float startY = view.getTranslationY();
+        float endX = startX;
+        float endY = startY;
+        int[] interruptedPosition = (int[]) startValues.view.getTag(R.id.transition_position);
+        if (interruptedPosition != null) {
+            // We want to have the end position relative to the interrupted position, not
+            // the position it was supposed to start at.
+            endX += interruptedPosition[0] - bounds.left;
+            endY += interruptedPosition[1] - bounds.top;
+            bounds.offsetTo(interruptedPosition[0], interruptedPosition[1]);
+        }
+        calculateOut(sceneRoot, bounds, mTempLoc);
+        endX += mTempLoc[0];
+        endY += mTempLoc[1];
+
+        return TranslationAnimationCreator.createAnimation(view, startValues,
+                viewPosX, viewPosY, startX, startY, endX, endY, sAccelerate);
+    }
+
+    private void calculateOut(View sceneRoot, Rect bounds, int[] outVector) {
+        sceneRoot.getLocationOnScreen(mTempLoc);
+        int sceneRootX = mTempLoc[0];
+        int sceneRootY = mTempLoc[1];
+        int focalX;
+        int focalY;
+
+        Rect epicenter = getEpicenter();
+        if (epicenter == null) {
+            focalX = sceneRootX + (sceneRoot.getWidth() / 2)
+                    + Math.round(sceneRoot.getTranslationX());
+            focalY = sceneRootY + (sceneRoot.getHeight() / 2)
+                    + Math.round(sceneRoot.getTranslationY());
+        } else {
+            focalX = epicenter.centerX();
+            focalY = epicenter.centerY();
+        }
+
+        int centerX = bounds.centerX();
+        int centerY = bounds.centerY();
+        float xVector = centerX - focalX;
+        float yVector = centerY - focalY;
+
+        if (xVector == 0 && yVector == 0) {
+            // Random direction when View is centered on focal View.
+            xVector = (float) (Math.random() * 2) - 1;
+            yVector = (float) (Math.random() * 2) - 1;
+        }
+        float vectorSize = calculateDistance(xVector, yVector);
+        xVector /= vectorSize;
+        yVector /= vectorSize;
+
+        float maxDistance =
+                calculateMaxDistance(sceneRoot, focalX - sceneRootX, focalY - sceneRootY);
+
+        outVector[0] = Math.round(maxDistance * xVector);
+        outVector[1] = Math.round(maxDistance * yVector);
+    }
+
+    private static float calculateMaxDistance(View sceneRoot, int focalX, int focalY) {
+        int maxX = Math.max(focalX, sceneRoot.getWidth() - focalX);
+        int maxY = Math.max(focalY, sceneRoot.getHeight() - focalY);
+        return calculateDistance(maxX, maxY);
+    }
+
+    private static float calculateDistance(float x, float y) {
+        return (float) Math.sqrt((x * x) + (y * y));
+    }
+
+}
diff --git a/transition/src/android/support/transition/Transition.java b/transition/src/android/support/transition/Transition.java
index 17b23ab..807e353 100644
--- a/transition/src/android/support/transition/Transition.java
+++ b/transition/src/android/support/transition/Transition.java
@@ -25,6 +25,7 @@
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.graphics.Path;
+import android.graphics.Rect;
 import android.support.annotation.IdRes;
 import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
@@ -37,6 +38,7 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.view.InflateException;
 import android.view.SurfaceView;
 import android.view.TextureView;
@@ -234,6 +236,13 @@
     // to be run in runAnimators()
     private ArrayList<Animator> mAnimators = new ArrayList<>();
 
+    // The function for calculating the Animation start delay.
+    TransitionPropagation mPropagation;
+
+    // The rectangular region for Transitions like Explode and TransitionPropagations
+    // like CircularPropagation
+    private EpicenterCallback mEpicenterCallback;
+
     // For Fragment shared element transitions, linking views explicitly by mismatching
     // transitionNames.
     private ArrayMap<String, String> mNameOverrides;
@@ -694,6 +703,8 @@
             Log.d(LOG_TAG, "createAnimators() for " + this);
         }
         ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
+        long minStartDelay = Long.MAX_VALUE;
+        SparseIntArray startDelays = new SparseIntArray();
         int startValuesListCount = startValuesList.size();
         for (int i = 0; i < startValuesListCount; ++i) {
             TransitionValues start = startValuesList.get(i);
@@ -764,6 +775,11 @@
                         view = start.view;
                     }
                     if (animator != null) {
+                        if (mPropagation != null) {
+                            long delay = mPropagation.getStartDelay(sceneRoot, this, start, end);
+                            startDelays.put(mAnimators.size(), (int) delay);
+                            minStartDelay = Math.min(delay, minStartDelay);
+                        }
                         AnimationInfo info = new AnimationInfo(view, getName(), this,
                                 ViewUtils.getWindowId(sceneRoot), infoValues);
                         runningAnimators.put(animator, info);
@@ -772,6 +788,14 @@
                 }
             }
         }
+        if (minStartDelay != 0) {
+            for (int i = 0; i < startDelays.size(); i++) {
+                int index = startDelays.keyAt(i);
+                Animator animator = mAnimators.get(index);
+                long delay = startDelays.valueAt(i) - minStartDelay + animator.getStartDelay();
+                animator.setStartDelay(delay);
+            }
+        }
     }
 
     /**
@@ -1455,6 +1479,7 @@
                         captureEndValues(values);
                     }
                     values.mTargetedTransitions.add(this);
+                    capturePropagationValues(values);
                     if (start) {
                         addViewValues(mStartValues, view, values);
                     } else {
@@ -1472,6 +1497,7 @@
                     captureEndValues(values);
                 }
                 values.mTargetedTransitions.add(this);
+                capturePropagationValues(values);
                 if (start) {
                     addViewValues(mStartValues, view, values);
                 } else {
@@ -1594,6 +1620,7 @@
                 captureEndValues(values);
             }
             values.mTargetedTransitions.add(this);
+            capturePropagationValues(values);
             if (start) {
                 addViewValues(mStartValues, view, values);
             } else {
@@ -1993,6 +2020,7 @@
     }
 
     /**
+     * <<<<<<< HEAD
      * Sets the algorithm used to calculate two-dimensional interpolation.
      * <p>
      * Transitions such as {@link android.transition.ChangeBounds} move Views, typically
@@ -2029,6 +2057,106 @@
         return mPathMotion;
     }
 
+    /**
+     * Sets the callback to use to find the epicenter of a Transition. A null value indicates
+     * that there is no epicenter in the Transition and onGetEpicenter() will return null.
+     * Transitions like {@link android.transition.Explode} use a point or Rect to orient
+     * the direction of travel. This is called the epicenter of the Transition and is
+     * typically centered on a touched View. The
+     * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
+     * dynamically retrieve the epicenter during a Transition.
+     *
+     * @param epicenterCallback The callback to use to find the epicenter of the Transition.
+     */
+    public void setEpicenterCallback(@Nullable EpicenterCallback epicenterCallback) {
+        mEpicenterCallback = epicenterCallback;
+    }
+
+    /**
+     * Returns the callback used to find the epicenter of the Transition.
+     * Transitions like {@link android.transition.Explode} use a point or Rect to orient
+     * the direction of travel. This is called the epicenter of the Transition and is
+     * typically centered on a touched View. The
+     * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
+     * dynamically retrieve the epicenter during a Transition.
+     *
+     * @return the callback used to find the epicenter of the Transition.
+     */
+    @Nullable
+    public EpicenterCallback getEpicenterCallback() {
+        return mEpicenterCallback;
+    }
+
+    /**
+     * Returns the epicenter as specified by the
+     * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
+     *
+     * @return the epicenter as specified by the
+     * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
+     * @see #setEpicenterCallback(EpicenterCallback)
+     */
+    @Nullable
+    public Rect getEpicenter() {
+        if (mEpicenterCallback == null) {
+            return null;
+        }
+        return mEpicenterCallback.onGetEpicenter(this);
+    }
+
+    /**
+     * Sets the method for determining Animator start delays.
+     * When a Transition affects several Views like {@link android.transition.Explode} or
+     * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
+     * such that the Animator start delay depends on position of the View. The
+     * TransitionPropagation specifies how the start delays are calculated.
+     *
+     * @param transitionPropagation The class used to determine the start delay of
+     *                              Animators created by this Transition. A null value
+     *                              indicates that no delay should be used.
+     */
+    public void setPropagation(TransitionPropagation transitionPropagation) {
+        mPropagation = transitionPropagation;
+    }
+
+    /**
+     * Returns the {@link android.transition.TransitionPropagation} used to calculate Animator
+     * start
+     * delays.
+     * When a Transition affects several Views like {@link android.transition.Explode} or
+     * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
+     * such that the Animator start delay depends on position of the View. The
+     * TransitionPropagation specifies how the start delays are calculated.
+     *
+     * @return the {@link android.transition.TransitionPropagation} used to calculate Animator start
+     * delays. This is null by default.
+     */
+    public TransitionPropagation getPropagation() {
+        return mPropagation;
+    }
+
+    /**
+     * Captures TransitionPropagation values for the given view and the
+     * hierarchy underneath it.
+     */
+    void capturePropagationValues(TransitionValues transitionValues) {
+        if (mPropagation != null && !transitionValues.values.isEmpty()) {
+            String[] propertyNames = mPropagation.getPropagationProperties();
+            if (propertyNames == null) {
+                return;
+            }
+            boolean containsAll = true;
+            for (int i = 0; i < propertyNames.length; i++) {
+                if (!transitionValues.values.containsKey(propertyNames[i])) {
+                    containsAll = false;
+                    break;
+                }
+            }
+            if (!containsAll) {
+                mPropagation.captureValues(transitionValues);
+            }
+        }
+    }
+
     Transition setSceneRoot(ViewGroup sceneRoot) {
         mSceneRoot = sceneRoot;
         return this;
@@ -2273,4 +2401,29 @@
         }
     }
 
+    /**
+     * Class to get the epicenter of Transition. Use
+     * {@link #setEpicenterCallback(EpicenterCallback)} to set the callback used to calculate the
+     * epicenter of the Transition. Override {@link #getEpicenter()} to return the rectangular
+     * region in screen coordinates of the epicenter of the transition.
+     *
+     * @see #setEpicenterCallback(EpicenterCallback)
+     */
+    public abstract static class EpicenterCallback {
+
+        /**
+         * Implementers must override to return the epicenter of the Transition in screen
+         * coordinates. Transitions like {@link android.transition.Explode} depend upon
+         * an epicenter for the Transition. In Explode, Views move toward or away from the
+         * center of the epicenter Rect along the vector between the epicenter and the center
+         * of the View appearing and disappearing. Some Transitions, such as
+         * {@link android.transition.Fade} pay no attention to the epicenter.
+         *
+         * @param transition The transition for which the epicenter applies.
+         * @return The Rect region of the epicenter of <code>transition</code> or null if
+         * there is no epicenter.
+         */
+        public abstract Rect onGetEpicenter(Transition transition);
+    }
+
 }
diff --git a/transition/src/android/support/transition/TransitionInflater.java b/transition/src/android/support/transition/TransitionInflater.java
index 9bdfdf7..e1da808 100644
--- a/transition/src/android/support/transition/TransitionInflater.java
+++ b/transition/src/android/support/transition/TransitionInflater.java
@@ -134,6 +134,10 @@
                 transition = new Fade(mContext, attrs);
             } else if ("changeBounds".equals(name)) {
                 transition = new ChangeBounds(mContext, attrs);
+            } else if ("explode".equals(name)) {
+                transition = new Explode(mContext, attrs);
+            } else if ("changeClipBounds".equals(name)) {
+                transition = new ChangeClipBounds(mContext, attrs);
             } else if ("autoTransition".equals(name)) {
                 transition = new AutoTransition(mContext, attrs);
             } else if ("transitionSet".equals(name)) {
diff --git a/transition/src/android/support/transition/TransitionPropagation.java b/transition/src/android/support/transition/TransitionPropagation.java
new file mode 100644
index 0000000..76ce9b2
--- /dev/null
+++ b/transition/src/android/support/transition/TransitionPropagation.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 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.transition;
+
+import android.view.ViewGroup;
+
+/**
+ * Extend <code>TransitionPropagation</code> to customize start delays for Animators created
+ * in {@link Transition#createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
+ * A Transition such as {@link Explode} defaults to using {@link CircularPropagation} and Views
+ * closer to the epicenter will move out of the scene later and into the scene sooner than Views
+ * farther from the epicenter, giving the appearance of inertia. With no TransitionPropagation, all
+ * Views will react simultaneously to the start of the transition.
+ *
+ * @see Transition#setPropagation(TransitionPropagation)
+ * @see Transition#getEpicenter()
+ */
+public abstract class TransitionPropagation {
+
+    /**
+     * Called by Transition to alter the Animator start delay. All start delays will be adjusted
+     * such that the minimum becomes zero.
+     *
+     * @param sceneRoot   The root of the View hierarchy running the transition.
+     * @param transition  The transition that created the Animator
+     * @param startValues The values for a specific target in the start scene.
+     * @param endValues   The values for the target in the end scene.
+     * @return A start delay to use with the Animator created by <code>transition</code>. The
+     * delay will be offset by the minimum delay of all <code>TransitionPropagation</code>s
+     * used in the Transition so that the smallest delay will be 0. Returned values may be
+     * negative.
+     */
+    public abstract long getStartDelay(ViewGroup sceneRoot, Transition transition,
+            TransitionValues startValues, TransitionValues endValues);
+
+    /**
+     * Captures the values in the start or end scene for the properties that this
+     * transition propagation monitors. These values are then passed as the startValues
+     * or endValues structure in a later call to
+     * {@link #getStartDelay(ViewGroup, Transition, TransitionValues, TransitionValues)}.
+     * The main concern for an implementation is what the
+     * properties are that the transition cares about and what the values are
+     * for all of those properties. The start and end values will be compared
+     * later during the
+     * {@link #getStartDelay(ViewGroup, Transition, TransitionValues, TransitionValues)}.
+     * method to determine the start delay.
+     *
+     * <p>Subclasses must implement this method. The method should only be called by the
+     * transition system; it is not intended to be called from external classes.</p>
+     *
+     * @param transitionValues The holder for any values that the Transition
+     *                         wishes to store. Values are stored in the <code>values</code> field
+     *                         of this TransitionValues object and are keyed from
+     *                         a String value. For example, to store a view's rotation value,
+     *                         a transition might call
+     *                         <code>transitionValues.values.put("appname:transitionname:rotation",
+     *                         view.getRotation())</code>. The target view will already be stored
+     *                         in
+     *                         the transitionValues structure when this method is called.
+     */
+    public abstract void captureValues(TransitionValues transitionValues);
+
+    /**
+     * Returns the set of property names stored in the {@link TransitionValues}
+     * object passed into {@link #captureValues(TransitionValues)} that
+     * this transition propagation cares about for the purposes of preventing
+     * duplicate capturing of property values.
+     *
+     * <p>A <code>TransitionPropagation</code> must override this method to prevent
+     * duplicate capturing of values and must contain at least one </p>
+     *
+     * @return An array of property names as described in the class documentation for
+     * {@link TransitionValues}.
+     */
+    public abstract String[] getPropagationProperties();
+
+}
diff --git a/transition/src/android/support/transition/TransitionSet.java b/transition/src/android/support/transition/TransitionSet.java
index f1d83e0..df63414 100644
--- a/transition/src/android/support/transition/TransitionSet.java
+++ b/transition/src/android/support/transition/TransitionSet.java
@@ -488,6 +488,15 @@
         }
     }
 
+    @Override
+    void capturePropagationValues(TransitionValues transitionValues) {
+        super.capturePropagationValues(transitionValues);
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; ++i) {
+            mTransitions.get(i).capturePropagationValues(transitionValues);
+        }
+    }
+
     /** @hide */
     @RestrictTo(LIBRARY_GROUP)
     @Override
@@ -541,6 +550,15 @@
     }
 
     @Override
+    public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
+        super.setEpicenterCallback(epicenterCallback);
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; ++i) {
+            mTransitions.get(i).setEpicenterCallback(epicenterCallback);
+        }
+    }
+
+    @Override
     String toString(String indent) {
         String result = super.toString(indent);
         for (int i = 0; i < mTransitions.size(); ++i) {
diff --git a/transition/src/android/support/transition/TranslationAnimationCreator.java b/transition/src/android/support/transition/TranslationAnimationCreator.java
new file mode 100644
index 0000000..7f2fd1d
--- /dev/null
+++ b/transition/src/android/support/transition/TranslationAnimationCreator.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 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.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.TimeInterpolator;
+import android.view.View;
+
+/**
+ * This class is used by Slide and Explode to create an animator that goes from the start
+ * position to the end position. It takes into account the canceled position so that it
+ * will not blink out or shift suddenly when the transition is interrupted.
+ */
+class TranslationAnimationCreator {
+
+    /**
+     * Creates an animator that can be used for x and/or y translations. When interrupted,
+     * it sets a tag to keep track of the position so that it may be continued from position.
+     *
+     * @param view         The view being moved. This may be in the overlay for onDisappear.
+     * @param values       The values containing the view in the view hierarchy.
+     * @param viewPosX     The x screen coordinate of view
+     * @param viewPosY     The y screen coordinate of view
+     * @param startX       The start translation x of view
+     * @param startY       The start translation y of view
+     * @param endX         The end translation x of view
+     * @param endY         The end translation y of view
+     * @param interpolator The interpolator to use with this animator.
+     * @return An animator that moves from (startX, startY) to (endX, endY) unless there was
+     * a previous interruption, in which case it moves from the current position to (endX, endY).
+     */
+    static Animator createAnimation(View view, TransitionValues values, int viewPosX, int viewPosY,
+            float startX, float startY, float endX, float endY, TimeInterpolator interpolator) {
+        float terminalX = view.getTranslationX();
+        float terminalY = view.getTranslationY();
+        int[] startPosition = (int[]) values.view.getTag(R.id.transition_position);
+        if (startPosition != null) {
+            startX = startPosition[0] - viewPosX + terminalX;
+            startY = startPosition[1] - viewPosY + terminalY;
+        }
+        // Initial position is at translation startX, startY, so position is offset by that amount
+        int startPosX = viewPosX + Math.round(startX - terminalX);
+        int startPosY = viewPosY + Math.round(startY - terminalY);
+
+        view.setTranslationX(startX);
+        view.setTranslationY(startY);
+        if (startX == endX && startY == endY) {
+            return null;
+        }
+        ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(view,
+                PropertyValuesHolder.ofFloat(View.TRANSLATION_X, startX, endX),
+                PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, startY, endY));
+
+        TransitionPositionListener listener = new TransitionPositionListener(view, values.view,
+                startPosX, startPosY, terminalX, terminalY);
+        anim.addListener(listener);
+        AnimatorUtils.addPauseListener(anim, listener);
+        anim.setInterpolator(interpolator);
+        return anim;
+    }
+
+    private static class TransitionPositionListener extends AnimatorListenerAdapter {
+
+        private final View mViewInHierarchy;
+        private final View mMovingView;
+        private final int mStartX;
+        private final int mStartY;
+        private int[] mTransitionPosition;
+        private float mPausedX;
+        private float mPausedY;
+        private final float mTerminalX;
+        private final float mTerminalY;
+
+        private TransitionPositionListener(View movingView, View viewInHierarchy,
+                int startX, int startY, float terminalX, float terminalY) {
+            mMovingView = movingView;
+            mViewInHierarchy = viewInHierarchy;
+            mStartX = startX - Math.round(mMovingView.getTranslationX());
+            mStartY = startY - Math.round(mMovingView.getTranslationY());
+            mTerminalX = terminalX;
+            mTerminalY = terminalY;
+            mTransitionPosition = (int[]) mViewInHierarchy.getTag(R.id.transition_position);
+            if (mTransitionPosition != null) {
+                mViewInHierarchy.setTag(R.id.transition_position, null);
+            }
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            if (mTransitionPosition == null) {
+                mTransitionPosition = new int[2];
+            }
+            mTransitionPosition[0] = Math.round(mStartX + mMovingView.getTranslationX());
+            mTransitionPosition[1] = Math.round(mStartY + mMovingView.getTranslationY());
+            mViewInHierarchy.setTag(R.id.transition_position, mTransitionPosition);
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animator) {
+            mMovingView.setTranslationX(mTerminalX);
+            mMovingView.setTranslationY(mTerminalY);
+        }
+
+        @Override
+        public void onAnimationPause(Animator animator) {
+            mPausedX = mMovingView.getTranslationX();
+            mPausedY = mMovingView.getTranslationY();
+            mMovingView.setTranslationX(mTerminalX);
+            mMovingView.setTranslationY(mTerminalY);
+        }
+
+        @Override
+        public void onAnimationResume(Animator animator) {
+            mMovingView.setTranslationX(mPausedX);
+            mMovingView.setTranslationY(mPausedY);
+        }
+    }
+
+}
diff --git a/transition/src/android/support/transition/Visibility.java b/transition/src/android/support/transition/Visibility.java
index da56f91..0c6c9d9 100644
--- a/transition/src/android/support/transition/Visibility.java
+++ b/transition/src/android/support/transition/Visibility.java
@@ -50,7 +50,7 @@
  */
 public abstract class Visibility extends Transition {
 
-    private static final String PROPNAME_VISIBILITY = "android:visibility:visibility";
+    static final String PROPNAME_VISIBILITY = "android:visibility:visibility";
     private static final String PROPNAME_PARENT = "android:visibility:parent";
     private static final String PROPNAME_SCREEN_LOCATION = "android:visibility:screenLocation";
 
diff --git a/transition/src/android/support/transition/VisibilityPropagation.java b/transition/src/android/support/transition/VisibilityPropagation.java
new file mode 100644
index 0000000..c9ada2e
--- /dev/null
+++ b/transition/src/android/support/transition/VisibilityPropagation.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017 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.transition;
+
+import android.view.View;
+
+/**
+ * Base class for <code>TransitionPropagation</code>s that care about
+ * View Visibility and the center position of the View.
+ */
+public abstract class VisibilityPropagation extends TransitionPropagation {
+
+    /**
+     * The property key used for {@link android.view.View#getVisibility()}.
+     */
+    private static final String PROPNAME_VISIBILITY = "android:visibilityPropagation:visibility";
+
+    /**
+     * The property key used for the center of the View in screen coordinates. This is an
+     * int[2] with the index 0 taking the x coordinate and index 1 taking the y coordinate.
+     */
+    private static final String PROPNAME_VIEW_CENTER = "android:visibilityPropagation:center";
+
+    private static final String[] VISIBILITY_PROPAGATION_VALUES = {
+            PROPNAME_VISIBILITY,
+            PROPNAME_VIEW_CENTER,
+    };
+
+    @Override
+    public void captureValues(TransitionValues values) {
+        View view = values.view;
+        Integer visibility = (Integer) values.values.get(Visibility.PROPNAME_VISIBILITY);
+        if (visibility == null) {
+            visibility = view.getVisibility();
+        }
+        values.values.put(PROPNAME_VISIBILITY, visibility);
+        int[] loc = new int[2];
+        view.getLocationOnScreen(loc);
+        loc[0] += Math.round(view.getTranslationX());
+        loc[0] += view.getWidth() / 2;
+        loc[1] += Math.round(view.getTranslationY());
+        loc[1] += view.getHeight() / 2;
+        values.values.put(PROPNAME_VIEW_CENTER, loc);
+    }
+
+    @Override
+    public String[] getPropagationProperties() {
+        return VISIBILITY_PROPAGATION_VALUES;
+    }
+
+    /**
+     * Returns {@link android.view.View#getVisibility()} for the View at the time the values
+     * were captured.
+     * @param values The TransitionValues captured at the start or end of the Transition.
+     * @return {@link android.view.View#getVisibility()} for the View at the time the values
+     * were captured.
+     */
+    public int getViewVisibility(TransitionValues values) {
+        if (values == null) {
+            return View.GONE;
+        }
+        Integer visibility = (Integer) values.values.get(PROPNAME_VISIBILITY);
+        if (visibility == null) {
+            return View.GONE;
+        }
+        return visibility;
+    }
+
+    /**
+     * Returns the View's center x coordinate, relative to the screen, at the time the values
+     * were captured.
+     * @param values The TransitionValues captured at the start or end of the Transition.
+     * @return the View's center x coordinate, relative to the screen, at the time the values
+     * were captured.
+     */
+    public int getViewX(TransitionValues values) {
+        return getViewCoordinate(values, 0);
+    }
+
+    /**
+     * Returns the View's center y coordinate, relative to the screen, at the time the values
+     * were captured.
+     * @param values The TransitionValues captured at the start or end of the Transition.
+     * @return the View's center y coordinate, relative to the screen, at the time the values
+     * were captured.
+     */
+    public int getViewY(TransitionValues values) {
+        return getViewCoordinate(values, 1);
+    }
+
+    private static int getViewCoordinate(TransitionValues values, int coordinateIndex) {
+        if (values == null) {
+            return -1;
+        }
+
+        int[] coordinates = (int[]) values.values.get(PROPNAME_VIEW_CENTER);
+        if (coordinates == null) {
+            return -1;
+        }
+
+        return coordinates[coordinateIndex];
+    }
+
+}
diff --git a/transition/tests/res/layout/scene1.xml b/transition/tests/res/layout/scene1.xml
index 4d83ebd..07e88b8 100644
--- a/transition/tests/res/layout/scene1.xml
+++ b/transition/tests/res/layout/scene1.xml
@@ -14,30 +14,29 @@
      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"
-             android:id="@+id/container">
-
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/holder"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:transitionName="holder">
     <View
-        android:id="@+id/view0"
-        android:layout_width="64dp"
-        android:layout_height="64dp"
-        android:layout_gravity="end|top"
-        android:background="#f00"/>
-
+        android:id="@+id/redSquare"
+        android:layout_width="10dp"
+        android:layout_height="10dp"
+        android:background="#F00"
+        android:transitionName="red"/>
     <View
-        android:id="@+id/view1"
-        android:layout_width="64dp"
-        android:layout_height="64dp"
-        android:layout_gravity="center"
-        android:background="#0f0"/>
-
-    <View
-        android:id="@+id/view2"
-        android:layout_width="64dp"
-        android:layout_height="64dp"
-        android:layout_gravity="end|bottom"
-        android:background="#00f"/>
-
-</FrameLayout>
+        android:id="@+id/greenSquare"
+        android:layout_width="10dp"
+        android:layout_height="10dp"
+        android:layout_below="@id/redSquare"
+        android:background="#0F0"
+        android:transitionName="green"/>
+    <TextView
+        android:id="@+id/hello"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/hello"
+        android:transitionName="hello"/>
+</RelativeLayout>
diff --git a/transition/tests/res/layout/scene10.xml b/transition/tests/res/layout/scene10.xml
new file mode 100644
index 0000000..7e4c804
--- /dev/null
+++ b/transition/tests/res/layout/scene10.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2017 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.
+-->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:transitionGroup="false"
+    android:transitionName="holder"
+    android:id="@+id/holder">
+    <View
+        android:layout_width="10dp"
+        android:layout_height="10dp"
+        android:background="#F00"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true"
+        android:id="@+id/redSquare"/>
+    <View
+        android:layout_width="10dp"
+        android:layout_height="10dp"
+        android:background="#0F0"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentTop="true"
+        android:id="@+id/greenSquare"/>
+    <View
+        android:layout_width="10dp"
+        android:layout_height="10dp"
+        android:background="#00F"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentBottom="true"
+        android:id="@+id/blueSquare"/>
+    <View
+        android:layout_width="10dp"
+        android:layout_height="10dp"
+        android:background="#FF0"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentBottom="true"
+        android:id="@+id/yellowSquare"/>
+</RelativeLayout>
diff --git a/transition/tests/res/layout/support_scene1.xml b/transition/tests/res/layout/support_scene1.xml
new file mode 100644
index 0000000..1c6ff90
--- /dev/null
+++ b/transition/tests/res/layout/support_scene1.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2017 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"
+             android:id="@+id/container">
+
+    <View
+        android:id="@+id/view0"
+        android:layout_width="64dp"
+        android:layout_height="64dp"
+        android:layout_gravity="end|top"
+        android:background="#f00"/>
+
+    <View
+        android:id="@+id/view1"
+        android:layout_width="64dp"
+        android:layout_height="64dp"
+        android:layout_gravity="center"
+        android:background="#0f0"/>
+
+    <View
+        android:id="@+id/view2"
+        android:layout_width="64dp"
+        android:layout_height="64dp"
+        android:layout_gravity="end|bottom"
+        android:background="#00f"/>
+
+</FrameLayout>
diff --git a/transition/tests/res/transition/change_clip_bounds.xml b/transition/tests/res/transition/change_clip_bounds.xml
new file mode 100644
index 0000000..e917c54
--- /dev/null
+++ b/transition/tests/res/transition/change_clip_bounds.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2017 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.
+-->
+<changeClipBounds/>
diff --git a/transition/tests/res/transition/explode.xml b/transition/tests/res/transition/explode.xml
new file mode 100644
index 0000000..71e0d26
--- /dev/null
+++ b/transition/tests/res/transition/explode.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2017 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.
+-->
+<explode xmlns:android="http://schemas.android.com/apk/res/android"
+         android:transitionVisibilityMode="mode_in"/>
diff --git a/transition/tests/res/values/strings.xml b/transition/tests/res/values/strings.xml
new file mode 100644
index 0000000..741c335
--- /dev/null
+++ b/transition/tests/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2017 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.
+-->
+<resources>
+    <string name="hello">Hello</string>
+</resources>
diff --git a/transition/tests/src/android/support/transition/BaseTest.java b/transition/tests/src/android/support/transition/BaseTest.java
index ddf43c3..4ffb2f9 100644
--- a/transition/tests/src/android/support/transition/BaseTest.java
+++ b/transition/tests/src/android/support/transition/BaseTest.java
@@ -22,7 +22,6 @@
 import org.junit.Rule;
 import org.junit.runner.RunWith;
 
-@SuppressWarnings("unchecked")
 @RunWith(AndroidJUnit4.class)
 public abstract class BaseTest {
 
@@ -30,7 +29,7 @@
     public final ActivityTestRule<TransitionActivity> rule;
 
     BaseTest() {
-        rule = new ActivityTestRule(TransitionActivity.class);
+        rule = new ActivityTestRule<>(TransitionActivity.class);
     }
 
 }
diff --git a/transition/tests/src/android/support/transition/BaseTransitionTest.java b/transition/tests/src/android/support/transition/BaseTransitionTest.java
new file mode 100644
index 0000000..81a9526
--- /dev/null
+++ b/transition/tests/src/android/support/transition/BaseTransitionTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2017 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.transition;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.support.test.InstrumentationRegistry;
+import android.support.transition.test.R;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import org.junit.Before;
+
+import java.util.ArrayList;
+
+public abstract class BaseTransitionTest extends BaseTest {
+
+    ArrayList<View> mTransitionTargets = new ArrayList<>();
+    LinearLayout mRoot;
+    Transition mTransition;
+    Transition.TransitionListener mListener;
+    float mAnimatedValue;
+
+    @Before
+    public void setUp() {
+        InstrumentationRegistry.getInstrumentation().setInTouchMode(false);
+        mRoot = (LinearLayout) rule.getActivity().findViewById(R.id.root);
+        mTransitionTargets.clear();
+        mTransition = createTransition();
+        mListener = mock(Transition.TransitionListener.class);
+        mTransition.addListener(mListener);
+    }
+
+    Transition createTransition() {
+        return new TestTransition();
+    }
+
+    void waitForStart() {
+        verify(mListener, timeout(3000)).onTransitionStart(any(Transition.class));
+    }
+
+    void waitForEnd() {
+        verify(mListener, timeout(3000)).onTransitionEnd(any(Transition.class));
+    }
+
+    Scene loadScene(final int layoutId) throws Throwable {
+        final Scene[] scene = new Scene[1];
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                scene[0] = Scene.getSceneForLayout(mRoot, layoutId, rule.getActivity());
+            }
+        });
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        return scene[0];
+    }
+
+    void enterScene(final int layoutId) throws Throwable {
+        enterScene(loadScene(layoutId));
+    }
+
+    void enterScene(final Scene scene) throws Throwable {
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                scene.enter();
+            }
+        });
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    void resetListener() {
+        mTransition.removeListener(mListener);
+        mListener = mock(Transition.TransitionListener.class);
+        mTransition.addListener(mListener);
+    }
+
+    void setAnimatedValue(float animatedValue) {
+        mAnimatedValue = animatedValue;
+    }
+
+    public class TestTransition extends Visibility {
+
+        @Override
+        public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues,
+                TransitionValues endValues) {
+            mTransitionTargets.add(endValues.view);
+            return ObjectAnimator.ofFloat(BaseTransitionTest.this, "animatedValue", 0, 1);
+        }
+
+        @Override
+        public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues,
+                TransitionValues endValues) {
+            mTransitionTargets.add(startValues.view);
+            return ObjectAnimator.ofFloat(BaseTransitionTest.this, "animatedValue", 1, 0);
+        }
+
+    }
+
+}
diff --git a/transition/tests/src/android/support/transition/ChangeClipBoundsTest.java b/transition/tests/src/android/support/transition/ChangeClipBoundsTest.java
new file mode 100644
index 0000000..fb94b25
--- /dev/null
+++ b/transition/tests/src/android/support/transition/ChangeClipBoundsTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 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.transition;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Rect;
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.SdkSuppress;
+import android.support.transition.test.R;
+import android.support.v4.view.ViewCompat;
+import android.view.View;
+
+import org.junit.Test;
+
+@MediumTest
+public class ChangeClipBoundsTest extends BaseTransitionTest {
+
+    @Override
+    Transition createTransition() {
+        return new ChangeClipBounds();
+    }
+
+    @SdkSuppress(minSdkVersion = 18)
+    @Test
+    public void testChangeClipBounds() throws Throwable {
+        enterScene(R.layout.scene1);
+
+        final View redSquare = rule.getActivity().findViewById(R.id.redSquare);
+        final Rect newClip = new Rect(redSquare.getLeft() + 10, redSquare.getTop() + 10,
+                redSquare.getRight() - 10, redSquare.getBottom() - 10);
+
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertNull(ViewCompat.getClipBounds(redSquare));
+                TransitionManager.beginDelayedTransition(mRoot, mTransition);
+                ViewCompat.setClipBounds(redSquare, newClip);
+            }
+        });
+        waitForStart();
+        Thread.sleep(150);
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Rect midClip = ViewCompat.getClipBounds(redSquare);
+                assertNotNull(midClip);
+                assertTrue(midClip.left > 0 && midClip.left < newClip.left);
+                assertTrue(midClip.top > 0 && midClip.top < newClip.top);
+                assertTrue(midClip.right < redSquare.getRight() && midClip.right > newClip.right);
+                assertTrue(midClip.bottom < redSquare.getBottom()
+                        && midClip.bottom > newClip.bottom);
+            }
+        });
+        waitForEnd();
+
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                final Rect endRect = ViewCompat.getClipBounds(redSquare);
+                assertNotNull(endRect);
+                assertEquals(newClip, endRect);
+            }
+        });
+
+        resetListener();
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.beginDelayedTransition(mRoot, mTransition);
+                ViewCompat.setClipBounds(redSquare, null);
+            }
+        });
+        waitForStart();
+        Thread.sleep(150);
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Rect midClip = ViewCompat.getClipBounds(redSquare);
+                assertNotNull(midClip);
+                assertTrue(midClip.left > 0 && midClip.left < newClip.left);
+                assertTrue(midClip.top > 0 && midClip.top < newClip.top);
+                assertTrue(midClip.right < redSquare.getRight() && midClip.right > newClip.right);
+                assertTrue(midClip.bottom < redSquare.getBottom()
+                        && midClip.bottom > newClip.bottom);
+            }
+        });
+        waitForEnd();
+
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertNotNull(ViewCompat.getClipBounds(redSquare));
+            }
+        });
+
+    }
+
+    @Test
+    public void dummy() {
+        // Avoid "No tests found" on older devices
+    }
+
+}
diff --git a/transition/tests/src/android/support/transition/ExplodeTest.java b/transition/tests/src/android/support/transition/ExplodeTest.java
new file mode 100644
index 0000000..b421537
--- /dev/null
+++ b/transition/tests/src/android/support/transition/ExplodeTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2017 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.transition;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.transition.test.R;
+import android.view.View;
+
+import org.junit.Test;
+
+@MediumTest
+public class ExplodeTest extends BaseTransitionTest {
+
+    @Override
+    Transition createTransition() {
+        return new Explode();
+    }
+
+    @Test
+    public void testExplode() throws Throwable {
+        enterScene(R.layout.scene10);
+        final View redSquare = rule.getActivity().findViewById(R.id.redSquare);
+        final View greenSquare = rule.getActivity().findViewById(R.id.greenSquare);
+        final View blueSquare = rule.getActivity().findViewById(R.id.blueSquare);
+        final View yellowSquare = rule.getActivity().findViewById(R.id.yellowSquare);
+
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.beginDelayedTransition(mRoot, mTransition);
+                redSquare.setVisibility(View.INVISIBLE);
+                greenSquare.setVisibility(View.INVISIBLE);
+                blueSquare.setVisibility(View.INVISIBLE);
+                yellowSquare.setVisibility(View.INVISIBLE);
+            }
+        });
+        waitForStart();
+        verify(mListener, never()).onTransitionEnd(any(Transition.class));
+        assertEquals(View.VISIBLE, redSquare.getVisibility());
+        assertEquals(View.VISIBLE, greenSquare.getVisibility());
+        assertEquals(View.VISIBLE, blueSquare.getVisibility());
+        assertEquals(View.VISIBLE, yellowSquare.getVisibility());
+        float redStartX = redSquare.getTranslationX();
+        float redStartY = redSquare.getTranslationY();
+
+        SystemClock.sleep(100);
+        verifyTranslation(redSquare, true, true);
+        verifyTranslation(greenSquare, false, true);
+        verifyTranslation(blueSquare, false, false);
+        verifyTranslation(yellowSquare, true, false);
+        assertTrue(redStartX > redSquare.getTranslationX()); // moving left
+        assertTrue(redStartY > redSquare.getTranslationY()); // moving up
+        waitForEnd();
+
+        verifyNoTranslation(redSquare);
+        verifyNoTranslation(greenSquare);
+        verifyNoTranslation(blueSquare);
+        verifyNoTranslation(yellowSquare);
+        assertEquals(View.INVISIBLE, redSquare.getVisibility());
+        assertEquals(View.INVISIBLE, greenSquare.getVisibility());
+        assertEquals(View.INVISIBLE, blueSquare.getVisibility());
+        assertEquals(View.INVISIBLE, yellowSquare.getVisibility());
+    }
+
+    @Test
+    public void testImplode() throws Throwable {
+        enterScene(R.layout.scene10);
+        final View redSquare = rule.getActivity().findViewById(R.id.redSquare);
+        final View greenSquare = rule.getActivity().findViewById(R.id.greenSquare);
+        final View blueSquare = rule.getActivity().findViewById(R.id.blueSquare);
+        final View yellowSquare = rule.getActivity().findViewById(R.id.yellowSquare);
+
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                redSquare.setVisibility(View.INVISIBLE);
+                greenSquare.setVisibility(View.INVISIBLE);
+                blueSquare.setVisibility(View.INVISIBLE);
+                yellowSquare.setVisibility(View.INVISIBLE);
+            }
+        });
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.beginDelayedTransition(mRoot, mTransition);
+                redSquare.setVisibility(View.VISIBLE);
+                greenSquare.setVisibility(View.VISIBLE);
+                blueSquare.setVisibility(View.VISIBLE);
+                yellowSquare.setVisibility(View.VISIBLE);
+            }
+        });
+        waitForStart();
+
+        assertEquals(View.VISIBLE, redSquare.getVisibility());
+        assertEquals(View.VISIBLE, greenSquare.getVisibility());
+        assertEquals(View.VISIBLE, blueSquare.getVisibility());
+        assertEquals(View.VISIBLE, yellowSquare.getVisibility());
+        float redStartX = redSquare.getTranslationX();
+        float redStartY = redSquare.getTranslationY();
+
+        SystemClock.sleep(100);
+        verifyTranslation(redSquare, true, true);
+        verifyTranslation(greenSquare, false, true);
+        verifyTranslation(blueSquare, false, false);
+        verifyTranslation(yellowSquare, true, false);
+        assertTrue(redStartX < redSquare.getTranslationX()); // moving right
+        assertTrue(redStartY < redSquare.getTranslationY()); // moving down
+        waitForEnd();
+
+        verifyNoTranslation(redSquare);
+        verifyNoTranslation(greenSquare);
+        verifyNoTranslation(blueSquare);
+        verifyNoTranslation(yellowSquare);
+        assertEquals(View.VISIBLE, redSquare.getVisibility());
+        assertEquals(View.VISIBLE, greenSquare.getVisibility());
+        assertEquals(View.VISIBLE, blueSquare.getVisibility());
+        assertEquals(View.VISIBLE, yellowSquare.getVisibility());
+    }
+
+    private void verifyTranslation(View view, boolean goLeft, boolean goUp) {
+        float translationX = view.getTranslationX();
+        float translationY = view.getTranslationY();
+
+        if (goLeft) {
+            assertTrue(translationX < 0);
+        } else {
+            assertTrue(translationX > 0);
+        }
+
+        if (goUp) {
+            assertTrue(translationY < 0);
+        } else {
+            assertTrue(translationY > 0);
+        }
+    }
+
+    private void verifyNoTranslation(View view) {
+        assertEquals(0f, view.getTranslationX(), 0.0f);
+        assertEquals(0f, view.getTranslationY(), 0.0f);
+    }
+
+}
diff --git a/transition/tests/src/android/support/transition/PropagationTest.java b/transition/tests/src/android/support/transition/PropagationTest.java
new file mode 100644
index 0000000..3d57998
--- /dev/null
+++ b/transition/tests/src/android/support/transition/PropagationTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 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.transition;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Rect;
+import android.support.test.filters.MediumTest;
+import android.support.transition.test.R;
+import android.view.View;
+
+import org.junit.Test;
+
+@MediumTest
+public class PropagationTest extends BaseTransitionTest {
+
+    @Test
+    public void testCircularPropagation() throws Throwable {
+        enterScene(R.layout.scene10);
+        CircularPropagation propagation = new CircularPropagation();
+        mTransition.setPropagation(propagation);
+        final TransitionValues redValues = new TransitionValues();
+        redValues.view = mRoot.findViewById(R.id.redSquare);
+        propagation.captureValues(redValues);
+
+        // Only the reported propagation properties are set
+        for (String prop : propagation.getPropagationProperties()) {
+            assertTrue(redValues.values.keySet().contains(prop));
+        }
+        assertEquals(propagation.getPropagationProperties().length, redValues.values.size());
+
+        // check the visibility
+        assertEquals(View.VISIBLE, propagation.getViewVisibility(redValues));
+        assertEquals(View.GONE, propagation.getViewVisibility(null));
+
+        // Check the positions
+        int[] pos = new int[2];
+        redValues.view.getLocationOnScreen(pos);
+        pos[0] += redValues.view.getWidth() / 2;
+        pos[1] += redValues.view.getHeight() / 2;
+        assertEquals(pos[0], propagation.getViewX(redValues));
+        assertEquals(pos[1], propagation.getViewY(redValues));
+
+        mTransition.setEpicenterCallback(new Transition.EpicenterCallback() {
+            @Override
+            public Rect onGetEpicenter(Transition transition) {
+                return new Rect(0, 0, redValues.view.getWidth(), redValues.view.getHeight());
+            }
+        });
+
+        long redDelay = getDelay(R.id.redSquare);
+        // red square's delay should be roughly 0 since it is at the epicenter
+        assertEquals(0f, redDelay, 30f);
+
+        // The green square is on the upper-right
+        long greenDelay = getDelay(R.id.greenSquare);
+        assertTrue(greenDelay < redDelay);
+
+        // The blue square is on the lower-right
+        long blueDelay = getDelay(R.id.blueSquare);
+        assertTrue(blueDelay < greenDelay);
+
+        // Test propagation speed
+        propagation.setPropagationSpeed(1000000000f);
+        assertEquals(0, getDelay(R.id.blueSquare));
+    }
+
+    private TransitionValues capturePropagationValues(int viewId) {
+        TransitionValues transitionValues = new TransitionValues();
+        transitionValues.view = mRoot.findViewById(viewId);
+        mTransition.getPropagation().captureValues(transitionValues);
+        return transitionValues;
+    }
+
+    private long getDelay(int viewId) {
+        TransitionValues transitionValues = capturePropagationValues(viewId);
+        return mTransition.getPropagation()
+                .getStartDelay(mRoot, mTransition, transitionValues, null);
+    }
+
+}
diff --git a/transition/tests/src/android/support/transition/TransitionInflaterTest.java b/transition/tests/src/android/support/transition/TransitionInflaterTest.java
index 28c97d8..bc53dc8 100644
--- a/transition/tests/src/android/support/transition/TransitionInflaterTest.java
+++ b/transition/tests/src/android/support/transition/TransitionInflaterTest.java
@@ -51,6 +51,9 @@
         TransitionInflater inflater = TransitionInflater.from(rule.getActivity());
         // TODO: Add more Transition types
         verifyFadeProperties(inflater.inflateTransition(R.transition.fade));
+        verifyExplodeProperties(inflater.inflateTransition(R.transition.explode));
+        verifyChangeClipBoundsProperties(
+                inflater.inflateTransition(R.transition.change_clip_bounds));
         verifyAutoTransitionProperties(inflater.inflateTransition(R.transition.auto_transition));
         verifyTransitionSetProperties(inflater.inflateTransition(R.transition.transition_set));
         verifyCustomTransitionProperties(
@@ -68,6 +71,16 @@
         assertEquals(Fade.OUT, fade.getMode());
     }
 
+    private void verifyExplodeProperties(Transition transition) {
+        assertTrue(transition instanceof Explode);
+        Visibility visibility = (Visibility) transition;
+        assertEquals(Visibility.MODE_IN, visibility.getMode());
+    }
+
+    private void verifyChangeClipBoundsProperties(Transition transition) {
+        assertTrue(transition instanceof ChangeClipBounds);
+    }
+
     private void verifyAutoTransitionProperties(Transition transition) {
         assertTrue(transition instanceof AutoTransition);
     }
diff --git a/transition/tests/src/android/support/transition/TransitionTest.java b/transition/tests/src/android/support/transition/TransitionTest.java
index f9370c9..335c07b 100644
--- a/transition/tests/src/android/support/transition/TransitionTest.java
+++ b/transition/tests/src/android/support/transition/TransitionTest.java
@@ -20,6 +20,7 @@
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.hasItem;
 import static org.hamcrest.Matchers.sameInstance;
@@ -33,6 +34,7 @@
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
+import android.graphics.Rect;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.test.annotation.UiThreadTest;
@@ -289,6 +291,39 @@
         verify(animatorListener, never()).onAnimationStart(any(Animator.class));
     }
 
+    @Test
+    public void testEpicenter() throws Throwable {
+        final Transition transition = new EmptyTransition();
+        final Transition.EpicenterCallback epicenterCallback = new Transition.EpicenterCallback() {
+            private Rect mRect = new Rect();
+
+            @Override
+            public Rect onGetEpicenter(Transition t) {
+                assertThat(t, is(sameInstance(transition)));
+                mRect.set(1, 2, 3, 4);
+                return mRect;
+            }
+        };
+        transition.setEpicenterCallback(epicenterCallback);
+        assertThat(transition.getEpicenterCallback(),
+                is(sameInstance(transition.getEpicenterCallback())));
+        Rect rect = transition.getEpicenter();
+        assertNotNull(rect);
+        assertThat(rect.left, is(1));
+        assertThat(rect.top, is(2));
+        assertThat(rect.right, is(3));
+        assertThat(rect.bottom, is(4));
+    }
+
+    @Test
+    public void testSetPropagation() throws Throwable {
+        final Transition transition = new EmptyTransition();
+        assertThat(transition.getPropagation(), is(nullValue()));
+        final TransitionPropagation propagation = new CircularPropagation();
+        transition.setPropagation(propagation);
+        assertThat(propagation, is(sameInstance(propagation)));
+    }
+
     private void showInitialScene() throws Throwable {
         SyncRunnable enter0 = new SyncRunnable();
         mScenes[0].setEnterAction(enter0);
diff --git a/tv-provider/AndroidManifest.xml b/tv-provider/AndroidManifest.xml
index e479417..f07d090 100644
--- a/tv-provider/AndroidManifest.xml
+++ b/tv-provider/AndroidManifest.xml
@@ -16,6 +16,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.media.tv">
     <uses-sdk android:minSdkVersion="21"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application />
+    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
+    <uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/tv-provider/src/android/support/media/tv/BasePreviewProgram.java b/tv-provider/src/android/support/media/tv/BasePreviewProgram.java
index da43f3d..6648ea4 100644
--- a/tv-provider/src/android/support/media/tv/BasePreviewProgram.java
+++ b/tv-provider/src/android/support/media/tv/BasePreviewProgram.java
@@ -327,6 +327,18 @@
      * TV Input Framework database.
      */
     public ContentValues toContentValues() {
+        return toContentValues(false);
+    }
+
+    /**
+     * Returns fields of the BasePreviewProgram in the ContentValues format to be easily inserted
+     * into the TV Input Framework database.
+     *
+     * @param includeProtectedFields Whether the fields protected by system is included or not.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public ContentValues toContentValues(boolean includeProtectedFields) {
         ContentValues values = super.toContentValues();
         if (BuildCompat.isAtLeastO()) {
             if (!TextUtils.isEmpty(mInternalProviderId)) {
@@ -399,12 +411,16 @@
             if (!TextUtils.isEmpty(mReviewRating)) {
                 values.put(BasePreviewProgramColumns.COLUMN_REVIEW_RATING, mReviewRating);
             }
-            if (mBrowsable != INVALID_INT_VALUE) {
-                values.put(BasePreviewProgramColumns.COLUMN_BROWSABLE, mBrowsable);
-            }
             if (!TextUtils.isEmpty(mContentId)) {
                 values.put(BasePreviewProgramColumns.COLUMN_CONTENT_ID, mContentId);
             }
+            if (includeProtectedFields) {
+                if (BuildCompat.isAtLeastO()) {
+                    if (mBrowsable != INVALID_INT_VALUE) {
+                        values.put(BasePreviewProgramColumns.COLUMN_BROWSABLE, mBrowsable);
+                    }
+                }
+            }
         }
         return values;
     }
diff --git a/tv-provider/src/android/support/media/tv/Channel.java b/tv-provider/src/android/support/media/tv/Channel.java
index 795ffa6..7e68755 100644
--- a/tv-provider/src/android/support/media/tv/Channel.java
+++ b/tv-provider/src/android/support/media/tv/Channel.java
@@ -17,6 +17,7 @@
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
+import android.annotation.TargetApi;
 import android.content.ContentValues;
 import android.content.Intent;
 import android.database.Cursor;
@@ -62,6 +63,7 @@
  * }
  * </pre>
  */
+@TargetApi(21)
 public final class Channel {
     /**
      * @hide
@@ -253,7 +255,6 @@
         return Intent.parseUri(mAppLinkIntentUri.toString(), Intent.URI_INTENT_SCHEME);
     }
 
-
     /**
      * @return The value of {@link Channels#COLUMN_NETWORK_AFFILIATION} for the channel.
      */
@@ -357,6 +358,18 @@
      * TV Input Framework database.
      */
     public ContentValues toContentValues() {
+        return toContentValues(false);
+    }
+
+    /**
+     * Returns fields of the Channel in the ContentValues format to be easily inserted into the
+     * TV Input Framework database.
+     *
+     * @param includeProtectedFields Whether the fields protected by system is included or not.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public ContentValues toContentValues(boolean includeProtectedFields) {
         ContentValues values = new ContentValues();
         if (mId != INVALID_CHANNEL_ID) {
             values.put(Channels._ID, mId);
@@ -408,8 +421,6 @@
         values.put(Channels.COLUMN_NETWORK_AFFILIATION, mNetworkAffiliation);
         values.put(Channels.COLUMN_SEARCHABLE, mSearchable);
         values.put(Channels.COLUMN_SERVICE_TYPE, mServiceType);
-        values.put(Channels.COLUMN_BROWSABLE, mBrowsable);
-
 
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
             values.put(Channels.COLUMN_APP_LINK_COLOR, mAppLinkColor);
@@ -449,7 +460,13 @@
         }
         if (BuildCompat.isAtLeastO()) {
             values.put(Channels.COLUMN_TRANSIENT, mTransient);
-            values.put(Channels.COLUMN_SYSTEM_APPROVED, mSystemApproved);
+        }
+
+        if (includeProtectedFields) {
+            values.put(Channels.COLUMN_BROWSABLE, mBrowsable);
+            if (BuildCompat.isAtLeastO()) {
+                values.put(Channels.COLUMN_SYSTEM_APPROVED, mSystemApproved);
+            }
         }
         return values;
     }
diff --git a/tv-provider/src/android/support/media/tv/ChannelLogoUtils.java b/tv-provider/src/android/support/media/tv/ChannelLogoUtils.java
new file mode 100644
index 0000000..20b79f3
--- /dev/null
+++ b/tv-provider/src/android/support/media/tv/ChannelLogoUtils.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2017 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.media.tv;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.sqlite.SQLiteException;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.tv.TvContract;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.WorkerThread;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+
+/** A utility class for conveniently storing and loading channel logos. */
+@WorkerThread
+public class ChannelLogoUtils {
+    private static final String TAG = "ChannelLogoUtils";
+
+    private static final int CONNECTION_TIMEOUT_MS_FOR_URLCONNECTION = 3000;  // 3 sec
+    private static final int READ_TIMEOUT_MS_FOR_URLCONNECTION = 10000;  // 10 sec
+
+    /**
+     * Stores channel logo in the system content provider from the given URI. The method will try
+     * to fetch the image file and decode it into {@link Bitmap}. Once the image is successfully
+     * fetched, it will be stored in the system content provider and associated with the given
+     * channel ID.
+     *
+     * <p>The URI provided to this method can be a URL referring to a image file residing in some
+     * remote site/server, or a URI in one of the following formats:
+     *
+     *    <ul>
+     *        <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
+     *        <li>android.resource ({@link android.content.ContentResolver
+     *                                     #SCHEME_ANDROID_RESOURCE})</li>
+     *        <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
+     *    </ul>
+     *
+     * <p>This method should be run in a worker thread since it may require network connection,
+     * which will raise an exception if it's running in the main thread.
+     *
+     * @param context the context used to access the system content provider
+     * @param channelId the ID of the target channel with which the fetched logo should be
+     *                  associated
+     * @param logoUri the {@link Uri} of the logo file to be fetched and stored in the system
+     *                provider
+     *
+     * @return {@code true} if successfully fetched the image file referred by the give logo URI
+     *         and stored it in the system content provider, or {@code false} if failed.
+     *
+     * @see #loadChannelLogo(Context, long)
+     */
+    public static boolean storeChannelLogo(@NonNull Context context, long channelId,
+            @NonNull Uri logoUri) {
+        String scheme = logoUri.normalizeScheme().getScheme();
+        URLConnection urlConnection = null;
+        InputStream inputStream = null;
+        Bitmap fetchedLogo = null;
+        try {
+            if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)
+                    || ContentResolver.SCHEME_FILE.equals(scheme)
+                    || ContentResolver.SCHEME_CONTENT.equals(scheme)) {
+                // A local resource
+                inputStream = context.getContentResolver().openInputStream(logoUri);
+            } else {
+                // A remote resource, should be an valid URL.
+                urlConnection = getUrlConnection(logoUri.toString());
+                inputStream = urlConnection.getInputStream();
+            }
+            fetchedLogo = BitmapFactory.decodeStream(inputStream);
+        } catch (IOException e) {
+            Log.i(TAG, "Failed to get logo from the URI: " + logoUri + "\n", e);
+        } finally {
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (IOException e) {
+                    // Do nothing.
+                }
+            }
+            if (urlConnection instanceof HttpURLConnection) {
+                ((HttpURLConnection) urlConnection).disconnect();
+            }
+        }
+        return fetchedLogo != null && storeChannelLogo(context, channelId, fetchedLogo);
+    }
+
+    /**
+     * Stores the given channel logo {@link Bitmap} in the system content provider and associate
+     * it with the given channel ID.
+     *
+     * @param context the context used to access the system content provider
+     * @param channelId the ID of the target channel with which the given logo should be associated
+     * @param logo the logo image to be stored
+     *
+     * @return {@code true} if successfully stored the logo in the system content provider,
+     *         otherwise {@code false}.
+     *
+     * @see #loadChannelLogo(Context, long)
+     */
+    public static boolean storeChannelLogo(@NonNull Context context, long channelId,
+            @NonNull Bitmap logo) {
+        boolean result = false;
+        Uri localUri = TvContract.buildChannelLogoUri(channelId);
+        try (OutputStream outputStream = context.getContentResolver().openOutputStream(localUri)) {
+            result = logo.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
+            outputStream.flush();
+        } catch (SQLiteException | IOException e) {
+            Log.i(TAG, "Failed to store the logo to the system content provider.\n", e);
+        }
+        return result;
+    }
+
+    /**
+     * A convenient helper method to get the channel logo associated to the given channel ID from
+     * the system content provider.
+     *
+     * @param context the context used to access the system content provider
+     * @param channelId the ID of the channel whose logo is supposed to be loaded
+     *
+     * @return the requested channel logo in {@link Bitmap}, or {@code null} if not available.
+     *
+     * @see #storeChannelLogo(Context, long, Uri)
+     * @see #storeChannelLogo(Context, long, Bitmap)
+     */
+    public static Bitmap loadChannelLogo(@NonNull Context context, long channelId) {
+        Bitmap channelLogo = null;
+        try {
+            channelLogo = BitmapFactory.decodeStream(context.getContentResolver().openInputStream(
+                    TvContract.buildChannelLogoUri(channelId)));
+        } catch (FileNotFoundException e) {
+            // Channel logo is not found in the content provider.
+            Log.i(TAG, "Channel logo for channel (ID:" + channelId + ") not found.", e);
+        }
+        return channelLogo;
+    }
+
+    private static URLConnection getUrlConnection(String uriString) throws IOException {
+        URLConnection urlConnection = new URL(uriString).openConnection();
+        urlConnection.setConnectTimeout(CONNECTION_TIMEOUT_MS_FOR_URLCONNECTION);
+        urlConnection.setReadTimeout(READ_TIMEOUT_MS_FOR_URLCONNECTION);
+        return urlConnection;
+    }
+}
diff --git a/tv-provider/src/android/support/media/tv/PreviewProgram.java b/tv-provider/src/android/support/media/tv/PreviewProgram.java
index 5a34ed0..6de7cf2 100644
--- a/tv-provider/src/android/support/media/tv/PreviewProgram.java
+++ b/tv-provider/src/android/support/media/tv/PreviewProgram.java
@@ -116,7 +116,19 @@
      * TV Input Framework database.
      */
     public ContentValues toContentValues() {
-        ContentValues values = super.toContentValues();
+        return toContentValues(false);
+    }
+
+    /**
+     * Returns fields of the PreviewProgram in the ContentValues format to be easily inserted
+     * into the TV Input Framework database.
+     *
+     * @param includeProtectedFields Whether the fields protected by system is included or not.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public ContentValues toContentValues(boolean includeProtectedFields) {
+        ContentValues values = super.toContentValues(includeProtectedFields);
         if (mChannelId != INVALID_LONG_VALUE) {
             values.put(PreviewPrograms.COLUMN_CHANNEL_ID, mChannelId);
         } else {
diff --git a/tv-provider/src/android/support/media/tv/Program.java b/tv-provider/src/android/support/media/tv/Program.java
index b8e1861..d1b6cce 100644
--- a/tv-provider/src/android/support/media/tv/Program.java
+++ b/tv-provider/src/android/support/media/tv/Program.java
@@ -17,6 +17,7 @@
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
+import android.annotation.TargetApi;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.os.Build;
@@ -57,6 +58,7 @@
  * }
  * </pre>
  */
+@TargetApi(21)
 public final class Program extends BaseProgram implements Comparable<Program> {
     /**
      * @hide
diff --git a/tv-provider/src/android/support/media/tv/WatchNextProgram.java b/tv-provider/src/android/support/media/tv/WatchNextProgram.java
index 664c0f9..1e48c2b 100644
--- a/tv-provider/src/android/support/media/tv/WatchNextProgram.java
+++ b/tv-provider/src/android/support/media/tv/WatchNextProgram.java
@@ -120,7 +120,19 @@
      * TV Input Framework database.
      */
     public ContentValues toContentValues() {
-        ContentValues values = super.toContentValues();
+        return toContentValues(false);
+    }
+
+    /**
+     * Returns fields of the WatchNextProgram in the ContentValues format to be easily inserted
+     * into the TV Input Framework database.
+     *
+     * @param includeProtectedFields Whether the fields protected by system is included or not.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public ContentValues toContentValues(boolean includeProtectedFields) {
+        ContentValues values = super.toContentValues(includeProtectedFields);
         if (!TextUtils.isEmpty(mWatchNextType)) {
             values.put(WatchNextPrograms.COLUMN_WATCH_NEXT_TYPE, mWatchNextType);
         }
diff --git a/tv-provider/tests/res/drawable/test_icon.png b/tv-provider/tests/res/drawable/test_icon.png
new file mode 100644
index 0000000..d6a6ea4
--- /dev/null
+++ b/tv-provider/tests/res/drawable/test_icon.png
Binary files differ
diff --git a/tv-provider/tests/src/android/support/media/tv/ChannelLogoUtilsTest.java b/tv-provider/tests/src/android/support/media/tv/ChannelLogoUtilsTest.java
new file mode 100644
index 0000000..cd6a0e9
--- /dev/null
+++ b/tv-provider/tests/src/android/support/media/tv/ChannelLogoUtilsTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 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.media.tv;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.tv.TvContract;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.support.media.tv.test.R;
+import android.test.AndroidTestCase;
+
+public class ChannelLogoUtilsTest extends AndroidTestCase {
+    private static final String FAKE_INPUT_ID = "ChannelLogoUtils.test";
+
+    private ContentResolver mContentResolver;
+    private Uri mChannelUri;
+    private long mChannelId;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContentResolver = getContext().getContentResolver();
+        ContentValues contentValues = new Channel.Builder()
+                .setInputId(FAKE_INPUT_ID)
+                .setType(TvContractCompat.Channels.TYPE_OTHER).build().toContentValues();
+        mChannelUri = mContentResolver.insert(TvContract.Channels.CONTENT_URI, contentValues);
+        mChannelId = ContentUris.parseId(mChannelUri);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mContentResolver.delete(mChannelUri, null, null);
+    }
+
+    public void testStoreChannelLogo_fromBitmap() {
+        assertNull(ChannelLogoUtils.loadChannelLogo(getContext(), mChannelId));
+        Bitmap logo = BitmapFactory.decodeResource(getContext().getResources(),
+                R.drawable.test_icon);
+        assertNotNull(logo);
+        assertTrue(ChannelLogoUtils.storeChannelLogo(getContext(), mChannelId, logo));
+        // Workaround: the file status is not consistent between openInputStream/openOutputStream,
+        // wait 10 secs to make sure that the logo file is written into the disk.
+        SystemClock.sleep(10000);
+        assertNotNull(ChannelLogoUtils.loadChannelLogo(getContext(), mChannelId));
+    }
+
+    public void testStoreChannelLogo_fromResUri() {
+        assertNull(ChannelLogoUtils.loadChannelLogo(getContext(), mChannelId));
+        int resId = R.drawable.test_icon;
+        Resources res = getContext().getResources();
+        Uri logoUri = new Uri.Builder()
+                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+                .authority(res.getResourcePackageName(resId))
+                .appendPath(res.getResourceTypeName(resId))
+                .appendPath(res.getResourceEntryName(resId))
+                .build();
+        assertTrue(ChannelLogoUtils.storeChannelLogo(getContext(), mChannelId, logoUri));
+        // Workaround: the file status is not consistent between openInputStream/openOutputStream,
+        // wait 10 secs to make sure that the logo file is written into the disk.
+        SystemClock.sleep(10000);
+        assertNotNull(ChannelLogoUtils.loadChannelLogo(getContext(), mChannelId));
+    }
+}
diff --git a/tv-provider/tests/src/android/support/media/tv/ChannelTest.java b/tv-provider/tests/src/android/support/media/tv/ChannelTest.java
index 6783148..cf085b1 100644
--- a/tv-provider/tests/src/android/support/media/tv/ChannelTest.java
+++ b/tv-provider/tests/src/android/support/media/tv/ChannelTest.java
@@ -15,11 +15,15 @@
  */
 package android.support.media.tv;
 
+import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Intent;
+import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.net.Uri;
 import android.os.Build;
+import android.support.media.tv.TvContractCompat.Channels;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.v4.os.BuildCompat;
@@ -33,18 +37,27 @@
  * values from them
  */
 @SmallTest
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
 public class ChannelTest extends TestCase {
     private static final String KEY_SPLASHSCREEN = "splashscreen";
     private static final String KEY_PREMIUM_CHANNEL = "premium";
     private static final String SPLASHSCREEN_URL = "http://example.com/splashscreen.jpg";
 
+    @Override
+    protected void tearDown() {
+        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+            return;
+        }
+        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
+        resolver.delete(Channels.CONTENT_URI, null, null);
+    }
+
     @Test
     public void testEmptyChannel() {
         Channel emptyChannel = new Channel.Builder()
                 .build();
-        ContentValues contentValues = emptyChannel.toContentValues();
-        compareChannel(emptyChannel, Channel.fromCursor(getChannelCursor(contentValues)));
+        ContentValues contentValues = emptyChannel.toContentValues(true);
+        compareChannel(emptyChannel, Channel.fromCursor(getChannelCursor(contentValues)), true);
     }
 
     @Test
@@ -60,16 +73,47 @@
                         Intent.URI_INTENT_SCHEME)))
                 .setOriginalNetworkId(0)
                 .build();
-        ContentValues contentValues = sampleChannel.toContentValues();
-        compareChannel(sampleChannel, Channel.fromCursor(getChannelCursor(contentValues)));
+        ContentValues contentValues = sampleChannel.toContentValues(true);
+        compareChannel(sampleChannel, Channel.fromCursor(getChannelCursor(contentValues)), true);
 
         Channel clonedSampleChannel = new Channel.Builder(sampleChannel).build();
-        compareChannel(sampleChannel, clonedSampleChannel);
+        compareChannel(sampleChannel, clonedSampleChannel, true);
     }
 
     @Test
     public void testFullyPopulatedChannel() {
-        Channel fullyPopulatedChannel = new Channel.Builder()
+        Channel fullyPopulatedChannel = createFullyPopulatedChannel();
+        ContentValues contentValues = fullyPopulatedChannel.toContentValues(true);
+        compareChannel(fullyPopulatedChannel, Channel.fromCursor(getChannelCursor(contentValues)),
+                true);
+
+        Channel clonedFullyPopulatedChannel = new Channel.Builder(fullyPopulatedChannel).build();
+        compareChannel(fullyPopulatedChannel, clonedFullyPopulatedChannel, true);
+    }
+
+    @Test
+    public void testChannelWithSystemContentProvider() {
+        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+            return;
+        }
+        Channel fullyPopulatedChannel = createFullyPopulatedChannel();
+        ContentValues contentValues = fullyPopulatedChannel.toContentValues();
+        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
+        Uri channelUri = resolver.insert(Channels.CONTENT_URI, contentValues);
+        assertNotNull(channelUri);
+
+        Channel channelFromSystemDb;
+        try (Cursor cursor = resolver.query(channelUri, Channel.PROJECTION, null, null, null)) {
+            assertNotNull(cursor);
+            assertEquals(1, cursor.getCount());
+            cursor.moveToNext();
+            channelFromSystemDb = Channel.fromCursor(cursor);
+        }
+        compareChannel(fullyPopulatedChannel, channelFromSystemDb, false);
+    }
+
+    private static Channel createFullyPopulatedChannel() {
+        return new Channel.Builder()
                 .setAppLinkColor(0x00FF0000)
                 .setAppLinkIconUri(Uri.parse("http://example.com/icon.png"))
                 .setAppLinkIntent(new Intent())
@@ -81,7 +125,7 @@
                 .setInputId("TestInputService")
                 .setNetworkAffiliation("Network Affiliation")
                 .setOriginalNetworkId(2)
-                .setPackageName("com.example.android.sampletvinput")
+                .setPackageName("android.support.media.tv.test")
                 .setSearchable(false)
                 .setServiceId(3)
                 .setTransportStreamId(4)
@@ -96,19 +140,14 @@
                 .setBrowsable(true)
                 .setSystemApproved(true)
                 .build();
-        ContentValues contentValues = fullyPopulatedChannel.toContentValues();
-        compareChannel(fullyPopulatedChannel, Channel.fromCursor(getChannelCursor(contentValues)));
-
-        Channel clonedFullyPopulatedChannel = new Channel.Builder(fullyPopulatedChannel).build();
-        compareChannel(fullyPopulatedChannel, clonedFullyPopulatedChannel);
     }
 
-    private static void compareChannel(Channel channelA, Channel channelB) {
+    private static void compareChannel(Channel channelA, Channel channelB,
+            boolean includeIdAndProtectedFields) {
         assertEquals(channelA.isSearchable(), channelB.isSearchable());
         assertEquals(channelA.getDescription(), channelB.getDescription());
         assertEquals(channelA.getDisplayName(), channelB.getDisplayName());
         assertEquals(channelA.getDisplayNumber(), channelB.getDisplayNumber());
-        assertEquals(channelA.getId(), channelB.getId());
         assertEquals(channelA.getInputId(), channelB.getInputId());
         assertEquals(channelA.getNetworkAffiliation(), channelB.getNetworkAffiliation());
         assertEquals(channelA.getOriginalNetworkId(), channelB.getOriginalNetworkId());
@@ -118,7 +157,6 @@
         assertEquals(channelA.getTransportStreamId(), channelB.getTransportStreamId());
         assertEquals(channelA.getType(), channelB.getType());
         assertEquals(channelA.getVideoFormat(), channelB.getVideoFormat());
-        assertEquals(channelA.isBrowsable(), channelB.isBrowsable());
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
             assertEquals(channelA.getAppLinkColor(), channelB.getAppLinkColor());
             assertEquals(channelA.getAppLinkIconUri(), channelB.getAppLinkIconUri());
@@ -128,10 +166,19 @@
         }
         if (BuildCompat.isAtLeastO()) {
             assertEquals(channelA.isTransient(), channelB.isTransient());
-            assertEquals(channelA.isSystemApproved(), channelB.isSystemApproved());
-            assertEquals(channelA.toContentValues(), channelB.toContentValues());
         }
-        assertEquals(channelA.toString(), channelB.toString());
+        if (includeIdAndProtectedFields) {
+            // Skip row ID since the one from system DB has the valid ID while the other does not.
+            assertEquals(channelA.getId(), channelB.getId());
+            // When we insert a channel using toContentValues() to the system, we drop some
+            // protected fields since they only can be modified by system apps.
+            assertEquals(channelA.isBrowsable(), channelB.isBrowsable());
+            if (BuildCompat.isAtLeastO()) {
+                assertEquals(channelA.isSystemApproved(), channelB.isSystemApproved());
+            }
+            assertEquals(channelA.toContentValues(), channelB.toContentValues());
+            assertEquals(channelA.toString(), channelB.toString());
+        }
     }
 
     private static MatrixCursor getChannelCursor(ContentValues contentValues) {
diff --git a/tv-provider/tests/src/android/support/media/tv/PreviewProgramTest.java b/tv-provider/tests/src/android/support/media/tv/PreviewProgramTest.java
index bfacc40..20bc8ab 100644
--- a/tv-provider/tests/src/android/support/media/tv/PreviewProgramTest.java
+++ b/tv-provider/tests/src/android/support/media/tv/PreviewProgramTest.java
@@ -15,12 +15,17 @@
  */
 package android.support.media.tv;
 
+import android.content.ContentResolver;
+import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Intent;
+import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.media.tv.TvContentRating;
 import android.net.Uri;
+import android.support.media.tv.TvContractCompat.Channels;
 import android.support.media.tv.TvContractCompat.PreviewPrograms;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.v4.os.BuildCompat;
 
@@ -38,16 +43,30 @@
  */
 @SmallTest
 public class PreviewProgramTest extends TestCase {
+
+    @Override
+    protected void tearDown() {
+        // TODO: Use @SdkSuppress once Build.VERSION_CODES.O has a right value.
+        if (!BuildCompat.isAtLeastO()) {
+            return;
+        }
+        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+            return;
+        }
+        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
+        resolver.delete(Channels.CONTENT_URI, null, null);
+    }
+
     @Test
     public void testEmptyPreviewProgram() {
-        // TODO: Use @SdkSuppress once Build.VERSION_CODES.O has a right value.
         if (!BuildCompat.isAtLeastO()) {
             return;
         }
         PreviewProgram emptyProgram = new PreviewProgram.Builder().build();
         ContentValues contentValues = emptyProgram.toContentValues();
         compareProgram(emptyProgram,
-                PreviewProgram.fromCursor(getProgramCursor(Program.PROJECTION, contentValues)));
+                PreviewProgram.fromCursor(getProgramCursor(Program.PROJECTION, contentValues)),
+                true);
     }
 
     @Test
@@ -64,13 +83,13 @@
                 .setChannelId(3)
                 .setWeight(70)
                 .build();
-        ContentValues contentValues = sampleProgram.toContentValues();
+        ContentValues contentValues = sampleProgram.toContentValues(true);
         compareProgram(sampleProgram,
                 PreviewProgram.fromCursor(
-                        getProgramCursor(PreviewProgram.PROJECTION, contentValues)));
+                        getProgramCursor(PreviewProgram.PROJECTION, contentValues)), true);
 
         PreviewProgram clonedSampleProgram = new PreviewProgram.Builder(sampleProgram).build();
-        compareProgram(sampleProgram, clonedSampleProgram);
+        compareProgram(sampleProgram, clonedSampleProgram, true);
     }
 
     @Test
@@ -78,63 +97,46 @@
         if (!BuildCompat.isAtLeastO()) {
             return;
         }
-        PreviewProgram fullyPopulatedProgram = new PreviewProgram.Builder()
-                .setTitle("Google")
-                .setInternalProviderId("ID-4321")
-                .setChannelId(3)
-                .setWeight(100)
-                .setPreviewVideoUri(Uri.parse("http://example.com/preview-video.mpg"))
-                .setLastPlaybackPositionMillis(0)
-                .setDurationMillis(60 * 1000)
-                .setAppLinkIntentUri(Uri.parse(new Intent(Intent.ACTION_VIEW).toUri(
-                        Intent.URI_INTENT_SCHEME)))
-                .setTransient(false)
-                .setType(PreviewPrograms.TYPE_MOVIE)
-                .setPosterArtAspectRatio(PreviewPrograms.ASPECT_RATIO_2_3)
-                .setThumbnailAspectRatio(PreviewPrograms.ASPECT_RATIO_16_9)
-                .setLogoUri(Uri.parse("http://example.com/program-logo.mpg"))
-                .setAvailability(PreviewPrograms.AVAILABILITY_AVAILABLE)
-                .setStartingPrice("12.99 USD")
-                .setOfferPrice("4.99 USD")
-                .setReleaseDate("1997")
-                .setItemCount(3)
-                .setLive(false)
-                .setInteractionType(PreviewPrograms.INTERACTION_TYPE_LIKES)
-                .setInteractionCount(10200)
-                .setAuthor("author_name")
-                .setReviewRatingStyle(PreviewPrograms.REVIEW_RATING_STYLE_STARS)
-                .setReviewRating("4.5")
-                .setSearchable(false)
-                .setThumbnailUri(Uri.parse("http://example.com/thumbnail.png"))
-                .setAudioLanguages(new String [] {"eng", "kor"})
-                .setCanonicalGenres(new String[] {TvContractCompat.Programs.Genres.MOVIES})
-                .setContentRatings(new TvContentRating[] {
-                        TvContentRating.createRating("com.android.tv", "US_TV", "US_TV_Y7")})
-                .setDescription("This is a sample program")
-                .setEpisodeNumber("Pilot", 0)
-                .setEpisodeTitle("Hello World")
-                .setLongDescription("This is a longer description than the previous description")
-                .setPosterArtUri(Uri.parse("http://example.com/poster.png"))
-                .setSeasonNumber("The Final Season", 7)
-                .setSeasonTitle("The Final Season")
-                .setVideoHeight(1080)
-                .setVideoWidth(1920)
-                .setInternalProviderFlag1(0x4)
-                .setInternalProviderFlag2(0x3)
-                .setInternalProviderFlag3(0x2)
-                .setInternalProviderFlag4(0x1)
-                .setBrowsable(true)
-                .setContentId("CID-8642")
-                .build();
-
-        ContentValues contentValues = fullyPopulatedProgram.toContentValues();
+        PreviewProgram fullyPopulatedProgram = createFullyPopulatedPreviewProgram(3);
+        ContentValues contentValues = fullyPopulatedProgram.toContentValues(true);
         compareProgram(fullyPopulatedProgram,
                 PreviewProgram.fromCursor(
-                        getProgramCursor(PreviewProgram.PROJECTION, contentValues)));
+                        getProgramCursor(PreviewProgram.PROJECTION, contentValues)), true);
 
         PreviewProgram clonedFullyPopulatedProgram =
                 new PreviewProgram.Builder(fullyPopulatedProgram).build();
-        compareProgram(fullyPopulatedProgram, clonedFullyPopulatedProgram);
+        compareProgram(fullyPopulatedProgram, clonedFullyPopulatedProgram, true);
+    }
+
+    @Test
+    public void testChannelWithSystemContentProvider() {
+        if (!BuildCompat.isAtLeastO()) {
+            return;
+        }
+        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+            return;
+        }
+        Channel channel = new Channel.Builder()
+                .setInputId("TestInputService")
+                .setType(TvContractCompat.Channels.TYPE_PREVIEW)
+                .build();
+        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
+        Uri channelUri = resolver.insert(Channels.CONTENT_URI, channel.toContentValues());
+        assertNotNull(channelUri);
+
+        PreviewProgram fullyPopulatedProgram = createFullyPopulatedPreviewProgram(
+                ContentUris.parseId(channelUri));
+        Uri previewProgramUri = resolver.insert(PreviewPrograms.CONTENT_URI,
+                fullyPopulatedProgram.toContentValues());
+
+        PreviewProgram programFromSystemDb;
+        try (Cursor cursor = resolver.query(previewProgramUri, null, null, null, null)) {
+            assertNotNull(cursor);
+            assertEquals(1, cursor.getCount());
+            cursor.moveToNext();
+            programFromSystemDb = PreviewProgram.fromCursor(cursor);
+        }
+        compareProgram(fullyPopulatedProgram, programFromSystemDb, false);
     }
 
     @Test
@@ -204,16 +206,69 @@
                 PreviewPrograms.COLUMN_REVIEW_RATING,
         };
 
-        ContentValues contentValues = previewProgram.toContentValues();
+        ContentValues contentValues = previewProgram.toContentValues(true);
         compareProgram(previewProgram,
-                PreviewProgram.fromCursor(getProgramCursor(partialProjection, contentValues)));
+                PreviewProgram.fromCursor(getProgramCursor(partialProjection, contentValues)),
+                true);
 
         PreviewProgram clonedFullyPopulatedProgram =
                 new PreviewProgram.Builder(previewProgram).build();
-        compareProgram(previewProgram, clonedFullyPopulatedProgram);
+        compareProgram(previewProgram, clonedFullyPopulatedProgram, true);
     }
 
-    private static void compareProgram(PreviewProgram programA, PreviewProgram programB) {
+    private static PreviewProgram createFullyPopulatedPreviewProgram(long channelId) {
+        return new PreviewProgram.Builder()
+                .setTitle("Google")
+                .setInternalProviderId("ID-4321")
+                .setChannelId(channelId)
+                .setWeight(100)
+                .setPreviewVideoUri(Uri.parse("http://example.com/preview-video.mpg"))
+                .setLastPlaybackPositionMillis(0)
+                .setDurationMillis(60 * 1000)
+                .setAppLinkIntentUri(Uri.parse(new Intent(Intent.ACTION_VIEW).toUri(
+                        Intent.URI_INTENT_SCHEME)))
+                .setTransient(false)
+                .setType(PreviewPrograms.TYPE_MOVIE)
+                .setPosterArtAspectRatio(PreviewPrograms.ASPECT_RATIO_2_3)
+                .setThumbnailAspectRatio(PreviewPrograms.ASPECT_RATIO_16_9)
+                .setLogoUri(Uri.parse("http://example.com/program-logo.mpg"))
+                .setAvailability(PreviewPrograms.AVAILABILITY_AVAILABLE)
+                .setStartingPrice("12.99 USD")
+                .setOfferPrice("4.99 USD")
+                .setReleaseDate("1997")
+                .setItemCount(3)
+                .setLive(false)
+                .setInteractionType(PreviewPrograms.INTERACTION_TYPE_LIKES)
+                .setInteractionCount(10200)
+                .setAuthor("author_name")
+                .setReviewRatingStyle(PreviewPrograms.REVIEW_RATING_STYLE_STARS)
+                .setReviewRating("4.5")
+                .setSearchable(false)
+                .setThumbnailUri(Uri.parse("http://example.com/thumbnail.png"))
+                .setAudioLanguages(new String [] {"eng", "kor"})
+                .setCanonicalGenres(new String[] {TvContractCompat.Programs.Genres.MOVIES})
+                .setContentRatings(new TvContentRating[] {
+                        TvContentRating.createRating("com.android.tv", "US_TV", "US_TV_Y7")})
+                .setDescription("This is a sample program")
+                .setEpisodeNumber("Pilot", 0)
+                .setEpisodeTitle("Hello World")
+                .setLongDescription("This is a longer description than the previous description")
+                .setPosterArtUri(Uri.parse("http://example.com/poster.png"))
+                .setSeasonNumber("The Final Season", 7)
+                .setSeasonTitle("The Final Season")
+                .setVideoHeight(1080)
+                .setVideoWidth(1920)
+                .setInternalProviderFlag1(0x4)
+                .setInternalProviderFlag2(0x3)
+                .setInternalProviderFlag3(0x2)
+                .setInternalProviderFlag4(0x1)
+                .setBrowsable(true)
+                .setContentId("CID-8642")
+                .build();
+    }
+
+    private static void compareProgram(PreviewProgram programA, PreviewProgram programB,
+            boolean includeIdAndProtectedFields) {
         assertTrue(Arrays.equals(programA.getAudioLanguages(), programB.getAudioLanguages()));
         assertTrue(Arrays.deepEquals(programA.getCanonicalGenres(), programB.getCanonicalGenres()));
         assertEquals(programA.getChannelId(), programB.getChannelId());
@@ -223,7 +278,6 @@
         assertEquals(programA.getEpisodeTitle(), programB.getEpisodeTitle());
         assertEquals(programA.getLongDescription(), programB.getLongDescription());
         assertEquals(programA.getPosterArtUri(), programB.getPosterArtUri());
-        assertEquals(programA.getId(), programB.getId());
         assertEquals(programA.getSeasonNumber(), programB.getSeasonNumber());
         assertEquals(programA.getThumbnailUri(), programB.getThumbnailUri());
         assertEquals(programA.getTitle(), programB.getTitle());
@@ -258,12 +312,17 @@
         assertEquals(programA.getAuthor(), programB.getAuthor());
         assertEquals(programA.getReviewRatingStyle(), programB.getReviewRatingStyle());
         assertEquals(programA.getReviewRating(), programB.getReviewRating());
-        assertEquals(programA.isBrowsable(), programB.isBrowsable());
         assertEquals(programA.getContentId(), programB.getContentId());
-
-        assertEquals(programA.toContentValues(), programB.toContentValues());
         assertEquals(programA.toString(), programB.toString());
-        assertEquals(programA, programB);
+        if (includeIdAndProtectedFields) {
+            // Skip row ID since the one from system DB has the valid ID while the other does not.
+            assertEquals(programA.getId(), programB.getId());
+            // When we insert a channel using toContentValues() to the system, we drop some
+            // protected fields since they only can be modified by system apps.
+            assertEquals(programA.isBrowsable(), programB.isBrowsable());
+            assertEquals(programA.toContentValues(), programB.toContentValues());
+            assertEquals(programA, programB);
+        }
     }
 
     private static MatrixCursor getProgramCursor(String[] projection, ContentValues contentValues) {
diff --git a/tv-provider/tests/src/android/support/media/tv/ProgramTest.java b/tv-provider/tests/src/android/support/media/tv/ProgramTest.java
index 6d2e947..ec66068 100644
--- a/tv-provider/tests/src/android/support/media/tv/ProgramTest.java
+++ b/tv-provider/tests/src/android/support/media/tv/ProgramTest.java
@@ -15,11 +15,17 @@
  */
 package android.support.media.tv;
 
+import android.content.ContentResolver;
+import android.content.ContentUris;
 import android.content.ContentValues;
+import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.media.tv.TvContentRating;
 import android.net.Uri;
 import android.os.Build;
+import android.support.media.tv.TvContractCompat.Channels;
+import android.support.media.tv.TvContractCompat.Programs;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 
@@ -35,15 +41,24 @@
  * values from them.
  */
 @SmallTest
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
 public class ProgramTest extends TestCase {
+    @Override
+    protected void tearDown() {
+        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+            return;
+        }
+        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
+        resolver.delete(Channels.CONTENT_URI, null, null);
+    }
+
     @Test
     public void testEmptyProgram() {
         Program emptyProgram = new Program.Builder()
                 .build();
         ContentValues contentValues = emptyProgram.toContentValues();
         compareProgram(emptyProgram,
-                Program.fromCursor(getProgramCursor(Program.PROJECTION, contentValues)));
+                Program.fromCursor(getProgramCursor(Program.PROJECTION, contentValues)), true);
     }
 
     @Test
@@ -60,15 +75,54 @@
                 .build();
         ContentValues contentValues = sampleProgram.toContentValues();
         compareProgram(sampleProgram,
-                Program.fromCursor(getProgramCursor(Program.PROJECTION, contentValues)));
+                Program.fromCursor(getProgramCursor(Program.PROJECTION, contentValues)), true);
 
         Program clonedSampleProgram = new Program.Builder(sampleProgram).build();
-        compareProgram(sampleProgram, clonedSampleProgram);
+        compareProgram(sampleProgram, clonedSampleProgram, true);
     }
 
     @Test
     public void testFullyPopulatedProgram() {
-        Program fullyPopulatedProgram = new Program.Builder()
+        Program fullyPopulatedProgram = createFullyPopulatedProgram(3);
+
+        ContentValues contentValues = fullyPopulatedProgram.toContentValues();
+        compareProgram(fullyPopulatedProgram,
+                Program.fromCursor(getProgramCursor(Program.PROJECTION, contentValues)), true);
+
+        Program clonedFullyPopulatedProgram = new Program.Builder(fullyPopulatedProgram).build();
+        compareProgram(fullyPopulatedProgram, clonedFullyPopulatedProgram, true);
+    }
+
+    @Test
+    public void testChannelWithSystemContentProvider() {
+        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+            return;
+        }
+        Channel channel = new Channel.Builder()
+                .setInputId("TestInputService")
+                .setType(TvContractCompat.Channels.TYPE_OTHER)
+                .build();
+        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
+        Uri channelUri = resolver.insert(Channels.CONTENT_URI, channel.toContentValues());
+        assertNotNull(channelUri);
+
+        Program fullyPopulatedProgram =
+                createFullyPopulatedProgram(ContentUris.parseId(channelUri));
+        Uri programUri = resolver.insert(Programs.CONTENT_URI,
+                fullyPopulatedProgram.toContentValues());
+
+        Program programFromSystemDb;
+        try (Cursor cursor = resolver.query(programUri, null, null, null, null)) {
+            assertNotNull(cursor);
+            assertEquals(1, cursor.getCount());
+            cursor.moveToNext();
+            programFromSystemDb = Program.fromCursor(cursor);
+        }
+        compareProgram(fullyPopulatedProgram, programFromSystemDb, false);
+    }
+
+    private static Program createFullyPopulatedProgram(long channelId) {
+        return new Program.Builder()
                 .setSearchable(false)
                 .setThumbnailUri(Uri.parse("http://example.com/thumbnail.png"))
                 .setAudioLanguages(new String [] {"eng", "kor"})
@@ -89,22 +143,16 @@
                 .setInternalProviderFlag2(0x3)
                 .setInternalProviderFlag3(0x2)
                 .setInternalProviderFlag4(0x1)
-                .setChannelId(3)
+                .setChannelId(channelId)
                 .setStartTimeUtcMillis(0)
                 .setEndTimeUtcMillis(1000)
                 .setBroadcastGenres(new String[] {"Music", "Family"})
                 .setRecordingProhibited(false)
                 .build();
-
-        ContentValues contentValues = fullyPopulatedProgram.toContentValues();
-        compareProgram(fullyPopulatedProgram,
-                Program.fromCursor(getProgramCursor(Program.PROJECTION, contentValues)));
-
-        Program clonedFullyPopulatedProgram = new Program.Builder(fullyPopulatedProgram).build();
-        compareProgram(fullyPopulatedProgram, clonedFullyPopulatedProgram);
     }
 
-    private static void compareProgram(Program programA, Program programB) {
+    private static void compareProgram(Program programA, Program programB,
+            boolean includeIdAndProtectedFields) {
         assertTrue(Arrays.equals(programA.getAudioLanguages(), programB.getAudioLanguages()));
         assertTrue(Arrays.deepEquals(programA.getBroadcastGenres(), programB.getBroadcastGenres()));
         assertTrue(Arrays.deepEquals(programA.getCanonicalGenres(), programB.getCanonicalGenres()));
@@ -116,7 +164,6 @@
         assertEquals(programA.getEpisodeTitle(), programB.getEpisodeTitle());
         assertEquals(programA.getLongDescription(), programB.getLongDescription());
         assertEquals(programA.getPosterArtUri(), programB.getPosterArtUri());
-        assertEquals(programA.getId(), programB.getId());
         assertEquals(programA.getSeasonNumber(), programB.getSeasonNumber());
         assertEquals(programA.getStartTimeUtcMillis(), programB.getStartTimeUtcMillis());
         assertEquals(programA.getThumbnailUri(), programB.getThumbnailUri());
@@ -135,9 +182,13 @@
             assertTrue(Objects.equals(programA.isRecordingProhibited(),
                     programB.isRecordingProhibited()));
         }
-        assertEquals(programA.toContentValues(), programB.toContentValues());
         assertEquals(programA.toString(), programB.toString());
-        assertEquals(programA, programB);
+        if (includeIdAndProtectedFields) {
+            // Skip row ID since the one from system DB has the valid ID while the other does not.
+            assertEquals(programA.getId(), programB.getId());
+            assertEquals(programA.toContentValues(), programB.toContentValues());
+            assertEquals(programA, programB);
+        }
     }
 
     private static MatrixCursor getProgramCursor(String[] projection, ContentValues contentValues) {
diff --git a/compat/jellybean/android/support/v4/app/ShareCompatJB.java b/tv-provider/tests/src/android/support/media/tv/Utils.java
similarity index 60%
rename from compat/jellybean/android/support/v4/app/ShareCompatJB.java
rename to tv-provider/tests/src/android/support/media/tv/Utils.java
index e65f952..a6ff0ad 100644
--- a/compat/jellybean/android/support/v4/app/ShareCompatJB.java
+++ b/tv-provider/tests/src/android/support/media/tv/Utils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -14,14 +14,15 @@
  * limitations under the License.
  */
 
-package android.support.v4.app;
+package android.support.media.tv;
 
-import android.support.annotation.RequiresApi;
-import android.text.Html;
+import android.content.Context;
+import android.content.pm.PackageManager;
 
-@RequiresApi(16)
-class ShareCompatJB {
-    public static String escapeHtml(CharSequence html) {
-        return Html.escapeHtml(html);
+public class Utils {
+    private Utils() { }
+
+    public static boolean hasTvInputFramework(Context context) {
+        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LIVE_TV);
     }
 }
diff --git a/tv-provider/tests/src/android/support/media/tv/WatchNextProgramTest.java b/tv-provider/tests/src/android/support/media/tv/WatchNextProgramTest.java
index fa137c0..724644a 100644
--- a/tv-provider/tests/src/android/support/media/tv/WatchNextProgramTest.java
+++ b/tv-provider/tests/src/android/support/media/tv/WatchNextProgramTest.java
@@ -16,12 +16,15 @@
 
 package android.support.media.tv;
 
+import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Intent;
+import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.media.tv.TvContentRating;
 import android.net.Uri;
 import android.support.media.tv.TvContractCompat.WatchNextPrograms;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.v4.os.BuildCompat;
 
@@ -39,16 +42,30 @@
  */
 @SmallTest
 public class WatchNextProgramTest extends TestCase {
-    @Test
-    public void testEmptyPreviewProgram() {
+
+    @Override
+    protected void tearDown() {
         // TODO: Use @SdkSuppress once Build.VERSION_CODES.O has a right value.
         if (!BuildCompat.isAtLeastO()) {
             return;
         }
+        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+            return;
+        }
+        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
+        resolver.delete(WatchNextPrograms.CONTENT_URI, null, null);
+    }
+
+    @Test
+    public void testEmptyPreviewProgram() {
+        if (!BuildCompat.isAtLeastO()) {
+            return;
+        }
         WatchNextProgram emptyProgram = new WatchNextProgram.Builder().build();
-        ContentValues contentValues = emptyProgram.toContentValues();
+        ContentValues contentValues = emptyProgram.toContentValues(true);
         compareProgram(emptyProgram,
-                WatchNextProgram.fromCursor(getProgramCursor(Program.PROJECTION, contentValues)));
+                WatchNextProgram.fromCursor(getProgramCursor(Program.PROJECTION, contentValues)),
+                true);
     }
 
     @Test
@@ -63,13 +80,13 @@
                 .setSeasonNumber("The Final Season", 7)
                 .setThumbnailUri(Uri.parse("http://www.example.com/programs/poster.png"))
                 .build();
-        ContentValues contentValues = sampleProgram.toContentValues();
+        ContentValues contentValues = sampleProgram.toContentValues(true);
         compareProgram(sampleProgram,
                 WatchNextProgram.fromCursor(
-                        getProgramCursor(WatchNextProgram.PROJECTION, contentValues)));
+                        getProgramCursor(WatchNextProgram.PROJECTION, contentValues)), true);
 
         WatchNextProgram clonedSampleProgram = new WatchNextProgram.Builder(sampleProgram).build();
-        compareProgram(sampleProgram, clonedSampleProgram);
+        compareProgram(sampleProgram, clonedSampleProgram, true);
     }
 
     @Test
@@ -77,62 +94,38 @@
         if (!BuildCompat.isAtLeastO()) {
             return;
         }
-        WatchNextProgram fullyPopulatedProgram = new WatchNextProgram.Builder()
-                .setTitle("Google")
-                .setInternalProviderId("ID-4321")
-                .setPreviewVideoUri(Uri.parse("http://example.com/preview-video.mpg"))
-                .setLastPlaybackPositionMillis(0)
-                .setDurationMillis(60 * 1000)
-                .setAppLinkIntentUri(Uri.parse(new Intent(Intent.ACTION_VIEW).toUri(
-                        Intent.URI_INTENT_SCHEME)))
-                .setTransient(false)
-                .setType(WatchNextPrograms.TYPE_MOVIE)
-                .setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE)
-                .setPosterArtAspectRatio(WatchNextPrograms.ASPECT_RATIO_2_3)
-                .setThumbnailAspectRatio(WatchNextPrograms.ASPECT_RATIO_16_9)
-                .setLogoUri(Uri.parse("http://example.com/program-logo.mpg"))
-                .setAvailability(WatchNextPrograms.AVAILABILITY_AVAILABLE)
-                .setStartingPrice("12.99 USD")
-                .setOfferPrice("4.99 USD")
-                .setReleaseDate("1997")
-                .setItemCount(3)
-                .setLive(false)
-                .setInteractionType(WatchNextPrograms.INTERACTION_TYPE_LIKES)
-                .setInteractionCount(10200)
-                .setAuthor("author_name")
-                .setReviewRatingStyle(WatchNextPrograms.REVIEW_RATING_STYLE_STARS)
-                .setReviewRating("4.5")
-                .setSearchable(false)
-                .setThumbnailUri(Uri.parse("http://example.com/thumbnail.png"))
-                .setAudioLanguages(new String [] {"eng", "kor"})
-                .setCanonicalGenres(new String[] {TvContractCompat.Programs.Genres.MOVIES})
-                .setContentRatings(new TvContentRating[] {
-                        TvContentRating.createRating("com.android.tv", "US_TV", "US_TV_Y7")})
-                .setDescription("This is a sample program")
-                .setEpisodeNumber("Pilot", 0)
-                .setEpisodeTitle("Hello World")
-                .setLongDescription("This is a longer description than the previous description")
-                .setPosterArtUri(Uri.parse("http://example.com/poster.png"))
-                .setSeasonNumber("The Final Season", 7)
-                .setSeasonTitle("The Final Season")
-                .setVideoHeight(1080)
-                .setVideoWidth(1920)
-                .setInternalProviderFlag1(0x4)
-                .setInternalProviderFlag2(0x3)
-                .setInternalProviderFlag3(0x2)
-                .setInternalProviderFlag4(0x1)
-                .setBrowsable(true)
-                .setContentId("CID-8442")
-                .build();
-
-        ContentValues contentValues = fullyPopulatedProgram.toContentValues();
+        WatchNextProgram fullyPopulatedProgram = createFullyPopulatedWatchNextProgram();
+        ContentValues contentValues = fullyPopulatedProgram.toContentValues(true);
         compareProgram(fullyPopulatedProgram,
                 WatchNextProgram.fromCursor(
-                        getProgramCursor(WatchNextProgram.PROJECTION, contentValues)));
+                        getProgramCursor(WatchNextProgram.PROJECTION, contentValues)), true);
 
         WatchNextProgram clonedFullyPopulatedProgram =
                 new WatchNextProgram.Builder(fullyPopulatedProgram).build();
-        compareProgram(fullyPopulatedProgram, clonedFullyPopulatedProgram);
+        compareProgram(fullyPopulatedProgram, clonedFullyPopulatedProgram, true);
+    }
+
+    @Test
+    public void testChannelWithSystemContentProvider() {
+        if (!BuildCompat.isAtLeastO()) {
+            return;
+        }
+        if (!Utils.hasTvInputFramework(InstrumentationRegistry.getContext())) {
+            return;
+        }
+        WatchNextProgram fullyPopulatedProgram = createFullyPopulatedWatchNextProgram();
+        ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
+        Uri watchNextProgramUri = resolver.insert(WatchNextPrograms.CONTENT_URI,
+                fullyPopulatedProgram.toContentValues());
+
+        WatchNextProgram programFromSystemDb;
+        try (Cursor cursor = resolver.query(watchNextProgramUri, null, null, null, null)) {
+            assertNotNull(cursor);
+            assertEquals(1, cursor.getCount());
+            cursor.moveToNext();
+            programFromSystemDb = WatchNextProgram.fromCursor(cursor);
+        }
+        compareProgram(fullyPopulatedProgram, programFromSystemDb, false);
     }
 
     @Test
@@ -198,16 +191,68 @@
                 WatchNextPrograms.COLUMN_REVIEW_RATING,
         };
 
-        ContentValues contentValues = previewProgram.toContentValues();
+        ContentValues contentValues = previewProgram.toContentValues(true);
         compareProgram(previewProgram,
-                WatchNextProgram.fromCursor(getProgramCursor(partialProjection, contentValues)));
+                WatchNextProgram.fromCursor(getProgramCursor(partialProjection, contentValues)),
+                true);
 
         WatchNextProgram clonedFullyPopulatedProgram =
                 new WatchNextProgram.Builder(previewProgram).build();
-        compareProgram(previewProgram, clonedFullyPopulatedProgram);
+        compareProgram(previewProgram, clonedFullyPopulatedProgram, true);
     }
 
-    private static void compareProgram(WatchNextProgram programA, WatchNextProgram programB) {
+    private static WatchNextProgram createFullyPopulatedWatchNextProgram() {
+        return new WatchNextProgram.Builder()
+                .setTitle("Google")
+                .setInternalProviderId("ID-4321")
+                .setPreviewVideoUri(Uri.parse("http://example.com/preview-video.mpg"))
+                .setLastPlaybackPositionMillis(0)
+                .setDurationMillis(60 * 1000)
+                .setAppLinkIntentUri(Uri.parse(new Intent(Intent.ACTION_VIEW).toUri(
+                        Intent.URI_INTENT_SCHEME)))
+                .setTransient(false)
+                .setType(WatchNextPrograms.TYPE_MOVIE)
+                .setWatchNextType(WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE)
+                .setPosterArtAspectRatio(WatchNextPrograms.ASPECT_RATIO_2_3)
+                .setThumbnailAspectRatio(WatchNextPrograms.ASPECT_RATIO_16_9)
+                .setLogoUri(Uri.parse("http://example.com/program-logo.mpg"))
+                .setAvailability(WatchNextPrograms.AVAILABILITY_AVAILABLE)
+                .setStartingPrice("12.99 USD")
+                .setOfferPrice("4.99 USD")
+                .setReleaseDate("1997")
+                .setItemCount(3)
+                .setLive(false)
+                .setInteractionType(WatchNextPrograms.INTERACTION_TYPE_LIKES)
+                .setInteractionCount(10200)
+                .setAuthor("author_name")
+                .setReviewRatingStyle(WatchNextPrograms.REVIEW_RATING_STYLE_STARS)
+                .setReviewRating("4.5")
+                .setSearchable(false)
+                .setThumbnailUri(Uri.parse("http://example.com/thumbnail.png"))
+                .setAudioLanguages(new String [] {"eng", "kor"})
+                .setCanonicalGenres(new String[] {TvContractCompat.Programs.Genres.MOVIES})
+                .setContentRatings(new TvContentRating[] {
+                        TvContentRating.createRating("com.android.tv", "US_TV", "US_TV_Y7")})
+                .setDescription("This is a sample program")
+                .setEpisodeNumber("Pilot", 0)
+                .setEpisodeTitle("Hello World")
+                .setLongDescription("This is a longer description than the previous description")
+                .setPosterArtUri(Uri.parse("http://example.com/poster.png"))
+                .setSeasonNumber("The Final Season", 7)
+                .setSeasonTitle("The Final Season")
+                .setVideoHeight(1080)
+                .setVideoWidth(1920)
+                .setInternalProviderFlag1(0x4)
+                .setInternalProviderFlag2(0x3)
+                .setInternalProviderFlag3(0x2)
+                .setInternalProviderFlag4(0x1)
+                .setBrowsable(true)
+                .setContentId("CID-8442")
+                .build();
+    }
+
+    private static void compareProgram(WatchNextProgram programA, WatchNextProgram programB,
+            boolean includeIdAndProtectedFields) {
         assertTrue(Arrays.equals(programA.getAudioLanguages(), programB.getAudioLanguages()));
         assertTrue(Arrays.deepEquals(programA.getCanonicalGenres(), programB.getCanonicalGenres()));
         assertTrue(Arrays.deepEquals(programA.getContentRatings(), programB.getContentRatings()));
@@ -216,7 +261,6 @@
         assertEquals(programA.getEpisodeTitle(), programB.getEpisodeTitle());
         assertEquals(programA.getLongDescription(), programB.getLongDescription());
         assertEquals(programA.getPosterArtUri(), programB.getPosterArtUri());
-        assertEquals(programA.getId(), programB.getId());
         assertEquals(programA.getSeasonNumber(), programB.getSeasonNumber());
         assertEquals(programA.getThumbnailUri(), programB.getThumbnailUri());
         assertEquals(programA.getTitle(), programB.getTitle());
@@ -251,12 +295,17 @@
         assertEquals(programA.getAuthor(), programB.getAuthor());
         assertEquals(programA.getReviewRatingStyle(), programB.getReviewRatingStyle());
         assertEquals(programA.getReviewRating(), programB.getReviewRating());
-        assertEquals(programA.isBrowsable(), programB.isBrowsable());
         assertEquals(programA.getContentId(), programB.getContentId());
-
-        assertEquals(programA.toContentValues(), programB.toContentValues());
         assertEquals(programA.toString(), programB.toString());
-        assertEquals(programA, programB);
+        if (includeIdAndProtectedFields) {
+            // Skip row ID since the one from system DB has the valid ID while the other does not.
+            assertEquals(programA.getId(), programB.getId());
+            // When we insert a channel using toContentValues() to the system, we drop some
+            // protected fields since they only can be modified by system apps.
+            assertEquals(programA.isBrowsable(), programB.isBrowsable());
+            assertEquals(programA.toContentValues(), programB.toContentValues());
+            assertEquals(programA, programB);
+        }
     }
 
     private static MatrixCursor getProgramCursor(String[] projection, ContentValues contentValues) {
diff --git a/v13/AndroidManifest.xml b/v13/AndroidManifest.xml
index 65ef182..6eea520 100644
--- a/v13/AndroidManifest.xml
+++ b/v13/AndroidManifest.xml
@@ -17,6 +17,7 @@
           xmlns:tools="http://schemas.android.com/tools"
           package="android.support.v13">
     <uses-sdk android:minSdkVersion="14" tools:overrideLibrary="android.support.v13"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}"/>
-    <application />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/v13/java/android/support/v13/app/ActivityCompat.java b/v13/java/android/support/v13/app/ActivityCompat.java
index 5c8ee42..b0c3c30 100644
--- a/v13/java/android/support/v13/app/ActivityCompat.java
+++ b/v13/java/android/support/v13/app/ActivityCompat.java
@@ -38,6 +38,10 @@
         return DragAndDropPermissionsCompat.request(activity, dragEvent);
     }
 
+    /**
+     * This class should not be instantiated, but the constructor must be
+     * visible for the class to be extended.
+     */
     protected ActivityCompat() {
         // Not publicly instantiable, but may be extended.
     }
diff --git a/v14/preference/AndroidManifest.xml b/v14/preference/AndroidManifest.xml
index 74cff2e..95bab75 100644
--- a/v14/preference/AndroidManifest.xml
+++ b/v14/preference/AndroidManifest.xml
@@ -16,6 +16,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.support.v14.preference">
     <uses-sdk android:minSdkVersion="14" />
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/v14/preference/NOTICES.md b/v14/preference/NOTICES.md
deleted file mode 100644
index a390782..0000000
--- a/v14/preference/NOTICES.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# Change Log
-
-## [23.1.0](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v14/preference) (2015-09-28)
-
-**Breakage and deprecation notices:**
-
-- EditTextPreferenceDialogFragment
-  - onAddEditTextToDialogView has been removed. Any code depending on overriding this method should
-    be moved to onBindDialogView.
-  - The EditText view is now expected to be present in the dialog layout file with the id
-    @android:id/edit, and is no longer created in code.
diff --git a/v17/leanback/.classpath b/v17/leanback/.classpath
deleted file mode 100644
index f568681..0000000
--- a/v17/leanback/.classpath
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
-	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
-	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
-	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="src" path="api21"/>
-	<classpathentry kind="src" path="api23"/>
-	<classpathentry kind="src" path="jbmr2"/>
-	<classpathentry kind="src" path="common"/>
-	<classpathentry kind="src" path="kitkat"/>
-	<classpathentry kind="src" path="gen"/>
-	<classpathentry kind="output" path="bin/classes"/>
-</classpath>
diff --git a/v17/leanback/.gitignore b/v17/leanback/.gitignore
deleted file mode 100644
index c3c25e0..0000000
--- a/v17/leanback/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-.settings
-bin
-libs
-gen
diff --git a/v17/leanback/.project b/v17/leanback/.project
deleted file mode 100644
index 9d191cd..0000000
--- a/v17/leanback/.project
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>android-support-v17-leanback</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>com.android.ide.eclipse.adt.ApkBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-	</natures>
-</projectDescription>
diff --git a/v17/leanback/Android.mk b/v17/leanback/Android.mk
index 6e62436..dbfe43a 100644
--- a/v17/leanback/Android.mk
+++ b/v17/leanback/Android.mk
@@ -46,31 +46,3 @@
 LOCAL_JAVA_LANGUAGE_VERSION := 1.7
 LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
 include $(BUILD_STATIC_JAVA_LIBRARY)
-
-# Documentation
-# ===========================================================
-include $(CLEAR_VARS)
-LOCAL_USE_AAPT2 := true
-LOCAL_MODULE := android-support-v17-leanback
-LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-LOCAL_MODULE_TAGS := optional
-LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src) \
-    $(call all-html-files-under, src)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_JAVA_LIBRARIES := \
-    android-support-annotations \
-    android-support-v4 \
-    android-support-v7-recyclerview \
-    android-support-v17-leanback
-LOCAL_ADDITIONAL_JAVA_DIR := $(call intermediates-dir-for,JAVA_LIBRARIES,android-support-v17-leanback-res,,COMMON)/src
-LOCAL_IS_HOST_MODULE := false
-LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR := external/doclava/res/assets/templates-sdk
-LOCAL_DROIDDOC_OPTIONS := \
-    -offlinemode \
-    -hdf android.whichdoc offline \
-    -federate Android http://developer.android.com \
-    -federationapi Android prebuilts/sdk/api/17.txt \
-    -hide 113
-include $(BUILD_DROIDDOC)
diff --git a/v17/leanback/AndroidManifest.xml b/v17/leanback/AndroidManifest.xml
index ded4ce8..9176231 100644
--- a/v17/leanback/AndroidManifest.xml
+++ b/v17/leanback/AndroidManifest.xml
@@ -16,6 +16,7 @@
 <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 />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/v17/leanback/README.txt b/v17/leanback/README.txt
deleted file mode 100644
index f3dbe92..0000000
--- a/v17/leanback/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-Library Project including Leanback framework support.
diff --git a/v17/leanback/project.properties b/v17/leanback/project.properties
deleted file mode 100644
index b2ef7dc..0000000
--- a/v17/leanback/project.properties
+++ /dev/null
@@ -1,15 +0,0 @@
-# This file is automatically generated by Android Tools.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
-# This file must be checked in Version Control Systems.
-#
-# To customize properties used by the Ant build system edit
-# "ant.properties", and override values to adapt the script to your
-# project structure.
-#
-# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
-#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
-
-# Project target.
-target=android-23
-android.library=true
diff --git a/v17/leanback/res/layout/lb_playback_controls.xml b/v17/leanback/res/layout/lb_playback_controls.xml
index dda7e06..df2be51 100644
--- a/v17/leanback/res/layout/lb_playback_controls.xml
+++ b/v17/leanback/res/layout/lb_playback_controls.xml
@@ -30,55 +30,43 @@
         android:layoutDirection="ltr"
         android:progressDrawable="@drawable/lb_playback_progress_bar" />
 
-    <android.support.v17.leanback.widget.PersistentFocusWrapper
-        android:id="@+id/controls_container_focus_wrapper"
+    <FrameLayout
+        android:id="@+id/controls_container"
         android:layout_width="match_parent"
         android:layout_height="wrap_content" >
 
+        <android.support.v17.leanback.widget.ControlBar
+            android:id="@+id/control_bar"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layoutDirection="ltr"
+            android:layout_gravity="center_horizontal"
+            android:orientation="horizontal" />
+
         <FrameLayout
-            android:id="@+id/controls_container"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content" >
+            android:id="@+id/more_actions_dock"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="end" />
 
-            <android.support.v17.leanback.widget.PersistentFocusWrapper
-                android:id="@+id/control_bar_focus_wrapper"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center_horizontal">
+        <TextView
+            android:id="@+id/current_time"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top|left"
+            android:layout_marginStart="@dimen/lb_playback_current_time_margin_start"
+            android:paddingTop="@dimen/lb_playback_time_padding_top"
+            style="?attr/playbackControlsTimeStyle" />
 
-                <android.support.v17.leanback.widget.ControlBar
-                    android:id="@+id/control_bar"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layoutDirection="ltr"
-                    android:orientation="horizontal" />
-                </android.support.v17.leanback.widget.PersistentFocusWrapper>
+        <TextView
+            android:id="@+id/total_time"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top|right"
+            android:layout_marginEnd="@dimen/lb_playback_total_time_margin_end"
+            android:paddingTop="@dimen/lb_playback_time_padding_top"
+            style="?attr/playbackControlsTimeStyle" />
 
-            <FrameLayout
-                android:id="@+id/more_actions_dock"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="end" />
-
-            <TextView
-                android:id="@+id/current_time"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="top|left"
-                android:layout_marginStart="@dimen/lb_playback_current_time_margin_start"
-                android:paddingTop="@dimen/lb_playback_time_padding_top"
-                style="?attr/playbackControlsTimeStyle" />
-
-            <TextView
-                android:id="@+id/total_time"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="top|right"
-                android:layout_marginEnd="@dimen/lb_playback_total_time_margin_end"
-                android:paddingTop="@dimen/lb_playback_time_padding_top"
-                style="?attr/playbackControlsTimeStyle" />
-
-        </FrameLayout>
-    </android.support.v17.leanback.widget.PersistentFocusWrapper>
+    </FrameLayout>
 
 </LinearLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/values-da/strings.xml b/v17/leanback/res/values-da/strings.xml
index cf0e26e..3881fd6 100644
--- a/v17/leanback/res/values-da/strings.xml
+++ b/v17/leanback/res/values-da/strings.xml
@@ -49,7 +49,7 @@
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Deaktiver undertekster"</string>
     <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Tilføj billedet i billedtilstand"</string>
     <string name="lb_playback_controls_shown" msgid="6382160135512023238">"Knapperne til afspilning er synlige"</string>
-    <string name="lb_playback_controls_hidden" msgid="8940984081242033574">"Knapperne til afspilning er skjult. Tryk på D-pad for at se dem"</string>
+    <string name="lb_playback_controls_hidden" msgid="8940984081242033574">"Knapperne til afspilning er skjult. Tryk på D-pad\'en for at se dem"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Afslut"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Fortsæt"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-gu/strings.xml b/v17/leanback/res/values-gu/strings.xml
index bc021cb..4285f52 100644
--- a/v17/leanback/res/values-gu/strings.xml
+++ b/v17/leanback/res/values-gu/strings.xml
@@ -48,10 +48,8 @@
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"ઉપશીર્ષક સક્ષમ કરો"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"વિગતવાર ઉપશીર્ષકોને અક્ષમ કરો"</string>
     <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"ચિત્ર મોડમાં ચિત્ર દાખલ કરો"</string>
-    <!-- no translation found for lb_playback_controls_shown (6382160135512023238) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_hidden (8940984081242033574) -->
-    <skip />
+    <string name="lb_playback_controls_shown" msgid="6382160135512023238">"મીડિયા નિયંત્રણો બતાવેલા છે"</string>
+    <string name="lb_playback_controls_hidden" msgid="8940984081242033574">"મીડિયા નિયંત્રણો છુપાયેલા છે, તે બતાવવા માટે d-પૅડ દબાવો"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"સમાપ્ત કરો"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ચાલુ રાખો"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-iw/strings.xml b/v17/leanback/res/values-iw/strings.xml
index 4d6d7f4..be2740b 100644
--- a/v17/leanback/res/values-iw/strings.xml
+++ b/v17/leanback/res/values-iw/strings.xml
@@ -48,10 +48,8 @@
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"הפעל כתוביות"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"השבת כתוביות"</string>
     <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"הזן את התמונה במצב תמונה"</string>
-    <!-- no translation found for lb_playback_controls_shown (6382160135512023238) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_hidden (8940984081242033574) -->
-    <skip />
+    <string name="lb_playback_controls_shown" msgid="6382160135512023238">"פקדי המדיה מוצגים"</string>
+    <string name="lb_playback_controls_hidden" msgid="8940984081242033574">"‏פקדי המדיה מוסתרים. הקש על ה-d-pad כדי להציג אותם"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"סיום"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"המשך"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-te/strings.xml b/v17/leanback/res/values-te/strings.xml
index e1b3ba2..f9cb803 100644
--- a/v17/leanback/res/values-te/strings.xml
+++ b/v17/leanback/res/values-te/strings.xml
@@ -48,10 +48,8 @@
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"సంవృత శీర్షికలను ప్రారంభించు"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"సంవృత శీర్షికలను నిలిపివేయి"</string>
     <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"చిత్రంలో చిత్రం మోడ్‌లోకి ప్రవేశించండి"</string>
-    <!-- no translation found for lb_playback_controls_shown (6382160135512023238) -->
-    <skip />
-    <!-- no translation found for lb_playback_controls_hidden (8940984081242033574) -->
-    <skip />
+    <string name="lb_playback_controls_shown" msgid="6382160135512023238">"మీడియా నియంత్రణలు చూపబడ్డాయి"</string>
+    <string name="lb_playback_controls_hidden" msgid="8940984081242033574">"మీడియా నియంత్రణలు దాచబడ్డాయి, చూపించడానికి d-ప్యాడ్ నొక్కండి"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"ముగించు"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"కొనసాగించు"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/res/values-uz/strings.xml b/v17/leanback/res/values-uz/strings.xml
index c491358..5a4151f 100644
--- a/v17/leanback/res/values-uz/strings.xml
+++ b/v17/leanback/res/values-uz/strings.xml
@@ -48,8 +48,8 @@
     <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Taglavhalarni yoqish"</string>
     <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Taglavhalarni o‘chirib qo‘yish"</string>
     <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Tasvir ichida tasvir rejimiga kirish"</string>
-    <string name="lb_playback_controls_shown" msgid="6382160135512023238">"Boshqaruv elementlari ko‘rsatilgan"</string>
-    <string name="lb_playback_controls_hidden" msgid="8940984081242033574">"Boshqaruv elementlari yashirilgan, ko‘rsatish uchun D-pad tugmasini bosing"</string>
+    <string name="lb_playback_controls_shown" msgid="6382160135512023238">"Boshqaruv elementlari ochiq"</string>
+    <string name="lb_playback_controls_hidden" msgid="8940984081242033574">"Boshqaruv elementlari berkitilgan, ochish uchun D-pad tugmasini bosing"</string>
     <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Tugatish"</string>
     <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Davom etish"</string>
     <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
diff --git a/v17/leanback/src/.readme b/v17/leanback/src/.readme
deleted file mode 100644
index 4bcebad..0000000
--- a/v17/leanback/src/.readme
+++ /dev/null
@@ -1,2 +0,0 @@
-This hidden file is there to ensure there is an src folder.
-Once we support binary library this will go away.
\ No newline at end of file
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java
index 8ab731f..ff5ef2e 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java
@@ -26,6 +26,7 @@
 /**
  * @hide
  */
+@SuppressWarnings("FragmentNotInstantiable")
 class BaseFragment extends BrandedFragment {
 
     /**
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java
index 7d08738..62ee0d4 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java
@@ -29,6 +29,7 @@
 /**
  * @hide
  */
+@SuppressWarnings("FragmentNotInstantiable")
 class BaseSupportFragment extends BrandedSupportFragment {
 
     /**
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
index ed20153..6672398 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
@@ -1468,6 +1468,7 @@
             getChildFragmentManager().beginTransaction()
                     .replace(R.id.scale_frame, new Fragment()).commit();
             gridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+                @SuppressWarnings("ReferenceEquality")
                 @Override
                 public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                     if (newState == RecyclerView.SCROLL_STATE_IDLE) {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
index 43e10b2..5d0cee8 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -1471,6 +1471,7 @@
             getChildFragmentManager().beginTransaction()
                     .replace(R.id.scale_frame, new Fragment()).commit();
             gridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+                @SuppressWarnings("ReferenceEquality")
                 @Override
                 public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                     if (newState == RecyclerView.SCROLL_STATE_IDLE) {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java b/v17/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java
index 564c3c7..aa2a51f 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java
@@ -185,7 +185,7 @@
     // No need to save/restore the logo resource ID, because the logo animation will not appear when
     // the fragment is restored.
     private int mLogoResourceId;
-    boolean mEnterTransitionFinished;
+    boolean mLogoAnimationFinished;
     int mCurrentPageIndex;
 
     private AnimatorSet mAnimator;
@@ -193,7 +193,7 @@
     private final OnClickListener mOnClickListener = new OnClickListener() {
         @Override
         public void onClick(View view) {
-            if (!mEnterTransitionFinished) {
+            if (!mLogoAnimationFinished) {
                 // Do not change page until the enter transition finishes.
                 return;
             }
@@ -208,7 +208,7 @@
     private final OnKeyListener mOnKeyListener = new OnKeyListener() {
         @Override
         public boolean onKey(View v, int keyCode, KeyEvent event) {
-            if (!mEnterTransitionFinished) {
+            if (!mLogoAnimationFinished) {
                 // Ignore key event until the enter transition finishes.
                 return keyCode != KeyEvent.KEYCODE_BACK;
             }
@@ -241,13 +241,28 @@
         }
     };
 
-    void moveToPreviousPage() {
+    /**
+     * Navigates to the previous page.
+     */
+    protected void moveToPreviousPage() {
+        if (!mLogoAnimationFinished) {
+            // Ignore if the logo enter transition is in progress.
+            return;
+        }
         if (mCurrentPageIndex > 0) {
             --mCurrentPageIndex;
             onPageChangedInternal(mCurrentPageIndex + 1);
         }
     }
-    void moveToNextPage() {
+
+    /**
+     * Navigates to the next page.
+     */
+    protected void moveToNextPage() {
+        if (!mLogoAnimationFinished) {
+            // Ignore if the logo enter transition is in progress.
+            return;
+        }
         if (mCurrentPageIndex < getPageCount() - 1) {
             ++mCurrentPageIndex;
             onPageChangedInternal(mCurrentPageIndex - 1);
@@ -281,7 +296,7 @@
         }
         if (savedInstanceState == null) {
             mCurrentPageIndex = 0;
-            mEnterTransitionFinished = false;
+            mLogoAnimationFinished = false;
             mPageIndicator.onPageSelected(0, false);
             view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
                 @Override
@@ -294,7 +309,7 @@
                 }
             });
         } else {
-            mEnterTransitionFinished = true;
+            mLogoAnimationFinished = true;
             mCurrentPageIndex = savedInstanceState.getInt(KEY_CURRENT_PAGE_INDEX);
             initializeViews(view);
         }
@@ -463,10 +478,19 @@
         // Header views.
         mTitleView.setText(getPageTitle(mCurrentPageIndex));
         mDescriptionView.setText(getPageDescription(mCurrentPageIndex));
+        onLogoAnimationFinished();
+    }
+
+    /**
+     * Called immediately after fragment views become visible. This method gives subclasses a chance
+     * to initialize themselves. If a logo animation is specified, calling this method is delayed
+     * until after the logo animation is complete.
+     */
+    protected void onLogoAnimationFinished() {
     }
 
     void startEnterAnimation() {
-        mEnterTransitionFinished = true;
+        mLogoAnimationFinished = true;
         initializeViews(getView());
         List<Animator> animators = new ArrayList<>();
         final Context context = FragmentUtil.getContext(this);
@@ -525,6 +549,15 @@
     }
 
     /**
+     * Returns whether the logo enter transition is finished.
+     *
+     * @return {@code true} if the logo enter transition is finished, {@code false} otherwise
+     */
+    protected final boolean isLogoAnimationFinished() {
+        return mLogoAnimationFinished;
+    }
+
+    /**
      * Returns the page count.
      *
      * @return The page count.
diff --git a/v17/leanback/src/android/support/v17/leanback/app/OnboardingSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/OnboardingSupportFragment.java
index 5df8f41..59a0f5f 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/OnboardingSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/OnboardingSupportFragment.java
@@ -188,7 +188,7 @@
     // No need to save/restore the logo resource ID, because the logo animation will not appear when
     // the fragment is restored.
     private int mLogoResourceId;
-    boolean mEnterTransitionFinished;
+    boolean mLogoAnimationFinished;
     int mCurrentPageIndex;
 
     private AnimatorSet mAnimator;
@@ -196,7 +196,7 @@
     private final OnClickListener mOnClickListener = new OnClickListener() {
         @Override
         public void onClick(View view) {
-            if (!mEnterTransitionFinished) {
+            if (!mLogoAnimationFinished) {
                 // Do not change page until the enter transition finishes.
                 return;
             }
@@ -211,7 +211,7 @@
     private final OnKeyListener mOnKeyListener = new OnKeyListener() {
         @Override
         public boolean onKey(View v, int keyCode, KeyEvent event) {
-            if (!mEnterTransitionFinished) {
+            if (!mLogoAnimationFinished) {
                 // Ignore key event until the enter transition finishes.
                 return keyCode != KeyEvent.KEYCODE_BACK;
             }
@@ -244,13 +244,28 @@
         }
     };
 
-    void moveToPreviousPage() {
+    /**
+     * Navigates to the previous page.
+     */
+    protected void moveToPreviousPage() {
+        if (!mLogoAnimationFinished) {
+            // Ignore if the logo enter transition is in progress.
+            return;
+        }
         if (mCurrentPageIndex > 0) {
             --mCurrentPageIndex;
             onPageChangedInternal(mCurrentPageIndex + 1);
         }
     }
-    void moveToNextPage() {
+
+    /**
+     * Navigates to the next page.
+     */
+    protected void moveToNextPage() {
+        if (!mLogoAnimationFinished) {
+            // Ignore if the logo enter transition is in progress.
+            return;
+        }
         if (mCurrentPageIndex < getPageCount() - 1) {
             ++mCurrentPageIndex;
             onPageChangedInternal(mCurrentPageIndex - 1);
@@ -284,7 +299,7 @@
         }
         if (savedInstanceState == null) {
             mCurrentPageIndex = 0;
-            mEnterTransitionFinished = false;
+            mLogoAnimationFinished = false;
             mPageIndicator.onPageSelected(0, false);
             view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
                 @Override
@@ -297,7 +312,7 @@
                 }
             });
         } else {
-            mEnterTransitionFinished = true;
+            mLogoAnimationFinished = true;
             mCurrentPageIndex = savedInstanceState.getInt(KEY_CURRENT_PAGE_INDEX);
             initializeViews(view);
         }
@@ -466,10 +481,19 @@
         // Header views.
         mTitleView.setText(getPageTitle(mCurrentPageIndex));
         mDescriptionView.setText(getPageDescription(mCurrentPageIndex));
+        onLogoAnimationFinished();
+    }
+
+    /**
+     * Called immediately after fragment views become visible. This method gives subclasses a chance
+     * to initialize themselves. If a logo animation is specified, calling this method is delayed
+     * until after the logo animation is complete.
+     */
+    protected void onLogoAnimationFinished() {
     }
 
     void startEnterAnimation() {
-        mEnterTransitionFinished = true;
+        mLogoAnimationFinished = true;
         initializeViews(getView());
         List<Animator> animators = new ArrayList<>();
         final Context context = getContext();
@@ -528,6 +552,15 @@
     }
 
     /**
+     * Returns whether the logo enter transition is finished.
+     *
+     * @return {@code true} if the logo enter transition is finished, {@code false} otherwise
+     */
+    protected final boolean isLogoAnimationFinished() {
+        return mLogoAnimationFinished;
+    }
+
+    /**
      * Returns the page count.
      *
      * @return The page count.
diff --git a/v17/leanback/src/android/support/v17/leanback/media/MediaPlayerGlue.java b/v17/leanback/src/android/support/v17/leanback/media/MediaPlayerGlue.java
index dd0127c..204f922 100644
--- a/v17/leanback/src/android/support/v17/leanback/media/MediaPlayerGlue.java
+++ b/v17/leanback/src/android/support/v17/leanback/media/MediaPlayerGlue.java
@@ -382,17 +382,12 @@
      * @see MediaPlayer#setDataSource(String)
      */
     public boolean setMediaSource(Uri uri) {
-        if (mMediaSourceUri != null && mMediaSourceUri.equals(uri)) {
+        if (mMediaSourceUri != null ? mMediaSourceUri.equals(uri) : uri == null) {
             return false;
         }
-        if (mMediaSourceUri != null || mMediaSourcePath != null) {
-            mMediaSourceUri = uri;
-            mMediaSourcePath = null;
-            prepareMediaForPlaying();
-        } else {
-            mMediaSourceUri = uri;
-            prepareMediaForPlaying();
-        }
+        mMediaSourceUri = uri;
+        mMediaSourcePath = null;
+        prepareMediaForPlaying();
         return true;
     }
 
@@ -404,17 +399,12 @@
      * @see MediaPlayer#setDataSource(String)
      */
     public boolean setMediaSource(String path) {
-        if (mMediaSourcePath != null && mMediaSourcePath.equals(mMediaSourcePath)) {
+        if (mMediaSourcePath != null ? mMediaSourcePath.equals(path) : path == null) {
             return false;
         }
-        if (mMediaSourceUri != null || mMediaSourcePath != null) {
-            mMediaSourceUri = null;
-            mMediaSourcePath = path;
-            prepareMediaForPlaying();
-        } else {
-            mMediaSourcePath = path;
-            prepareMediaForPlaying();
-        }
+        mMediaSourceUri = null;
+        mMediaSourcePath = path;
+        prepareMediaForPlaying();
         return true;
     }
 
diff --git a/v17/leanback/src/android/support/v17/leanback/util/StateMachine.java b/v17/leanback/src/android/support/v17/leanback/util/StateMachine.java
index a00bbc5..b9d2f2d 100644
--- a/v17/leanback/src/android/support/v17/leanback/util/StateMachine.java
+++ b/v17/leanback/src/android/support/v17/leanback/util/StateMachine.java
@@ -109,11 +109,6 @@
         public final int getStatus() {
             return mStatus;
         }
-
-        @Override
-        public final boolean equals(Object other) {
-            return this == other;
-        }
     }
 
     private boolean mSorted = true;
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaItemPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaItemPresenter.java
index c607d64..196fa93 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaItemPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaItemPresenter.java
@@ -372,7 +372,7 @@
          * @param position The index of the child view to display.
          */
         public void setSelectedMediaItemNumberView(int position) {
-            if (position >= 0 & position < mMediaItemNumberViewFlipper.getChildCount()) {
+            if (position >= 0 && position < mMediaItemNumberViewFlipper.getChildCount()) {
                 mMediaItemNumberViewFlipper.setDisplayedChild(position);
             }
         }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java b/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java
index 1064c45..1ced4d4 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java
@@ -55,7 +55,7 @@
         }
     }
 
-    class OneLineActionPresenter extends Presenter {
+    static class OneLineActionPresenter extends Presenter {
         @Override
         public ViewHolder onCreateViewHolder(ViewGroup parent) {
             View v = LayoutInflater.from(parent.getContext())
@@ -77,7 +77,7 @@
         }
     }
 
-    class TwoLineActionPresenter extends Presenter {
+    static class TwoLineActionPresenter extends Presenter {
         @Override
         public ViewHolder onCreateViewHolder(ViewGroup parent) {
             View v = LayoutInflater.from(parent.getContext())
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
index 68190d4..b31d30a 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
@@ -90,7 +90,7 @@
 
     /**
      * Inserts an item into this adapter at the specified index.
-     * If the index is >= {@link #size} an exception will be thrown.
+     * If the index is > {@link #size} an exception will be thrown.
      *
      * @param index The index at which the item should be inserted.
      * @param item The item to insert into the adapter.
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ControlBar.java b/v17/leanback/src/android/support/v17/leanback/widget/ControlBar.java
index 7146371..be5fd83 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ControlBar.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ControlBar.java
@@ -17,8 +17,11 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.LinearLayout;
 
+import java.util.ArrayList;
+
 class ControlBar extends LinearLayout {
 
     public interface OnChildFocusedListener {
@@ -27,6 +30,7 @@
 
     private int mChildMarginFromCenter;
     private OnChildFocusedListener mOnChildFocusedListener;
+    int mLastFocusIndex = -1;
 
     public ControlBar(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -37,13 +41,28 @@
     }
 
     @Override
-    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
         if (getChildCount() > 0) {
-            if (getChildAt(getChildCount() / 2).requestFocus(direction, previouslyFocusedRect)) {
+            int index = mLastFocusIndex >= 0 && mLastFocusIndex < getChildCount()
+                    ? mLastFocusIndex : getChildCount() / 2;
+            if (getChildAt(index).requestFocus(direction, previouslyFocusedRect)) {
                 return true;
             }
         }
-        return super.requestFocus(direction, previouslyFocusedRect);
+        return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
+    }
+
+    @Override
+    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+        if ((direction == ViewGroup.FOCUS_UP || direction == ViewGroup.FOCUS_DOWN)) {
+            if (mLastFocusIndex >= 0 && mLastFocusIndex < getChildCount()) {
+                views.add(getChildAt(mLastFocusIndex));
+            } else if (getChildCount() > 0) {
+                views.add(getChildAt(getChildCount() / 2));
+            }
+        } else {
+            super.addFocusables(views, direction, focusableMode);
+        }
     }
 
     public void setOnChildFocusedListener(OnChildFocusedListener listener) {
@@ -57,6 +76,7 @@
     @Override
     public void requestChildFocus (View child, View focused) {
         super.requestChildFocus(child, focused);
+        mLastFocusIndex = indexOfChild(child);
         if (mOnChildFocusedListener != null) {
             mOnChildFocusedListener.onChildFocusedListener(child, focused);
         }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java
index 0bd03f1..50f0da3 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java
@@ -182,8 +182,8 @@
 
     void setSharedElementEnterTransition(Activity activity, String sharedElementName,
             long timeoutMs) {
-        if (activity == null && !TextUtils.isEmpty(sharedElementName)
-                || activity != null && TextUtils.isEmpty(sharedElementName)) {
+        if ((activity == null && !TextUtils.isEmpty(sharedElementName))
+                || (activity != null && TextUtils.isEmpty(sharedElementName))) {
             throw new IllegalArgumentException();
         }
         if (activity == mActivityToRunTransition
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
index 728d31f..988a9fc 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
@@ -251,7 +251,7 @@
             }
         }
 
-        class HeaderFocusAnimator extends FocusAnimator {
+        static class HeaderFocusAnimator extends FocusAnimator {
 
             ItemBridgeAdapter.ViewHolder mViewHolder;
             HeaderFocusAnimator(View view, float scale, int duration) {
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewSharedElementHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewSharedElementHelper.java
index 570a7f2..c2d57b6 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewSharedElementHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewSharedElementHelper.java
@@ -74,8 +74,8 @@
 
     public void setSharedElementEnterTransition(Activity activity, String sharedElementName,
             long timeoutMs) {
-        if (activity == null && !TextUtils.isEmpty(sharedElementName)
-                || activity != null && TextUtils.isEmpty(sharedElementName)) {
+        if ((activity == null && !TextUtils.isEmpty(sharedElementName))
+                || (activity != null && TextUtils.isEmpty(sharedElementName))) {
             throw new IllegalArgumentException();
         }
         if (activity == mActivityToRunTransition
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
index 9098743..c2780be 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -1655,14 +1655,14 @@
                 ? Gravity.getAbsoluteGravity(mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK,
                 View.LAYOUT_DIRECTION_RTL)
                 : mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
-        if (mOrientation == HORIZONTAL && verticalGravity == Gravity.TOP
-                || mOrientation == VERTICAL && horizontalGravity == Gravity.LEFT) {
+        if ((mOrientation == HORIZONTAL && verticalGravity == Gravity.TOP)
+                || (mOrientation == VERTICAL && horizontalGravity == Gravity.LEFT)) {
             // do nothing
-        } else if (mOrientation == HORIZONTAL && verticalGravity == Gravity.BOTTOM
-                || mOrientation == VERTICAL && horizontalGravity == Gravity.RIGHT) {
+        } else if ((mOrientation == HORIZONTAL && verticalGravity == Gravity.BOTTOM)
+                || (mOrientation == VERTICAL && horizontalGravity == Gravity.RIGHT)) {
             startSecondary += getRowSizeSecondary(rowIndex) - sizeSecondary;
-        } else if (mOrientation == HORIZONTAL && verticalGravity == Gravity.CENTER_VERTICAL
-                || mOrientation == VERTICAL && horizontalGravity == Gravity.CENTER_HORIZONTAL) {
+        } else if ((mOrientation == HORIZONTAL && verticalGravity == Gravity.CENTER_VERTICAL)
+                || (mOrientation == VERTICAL && horizontalGravity == Gravity.CENTER_HORIZONTAL)) {
             startSecondary += (getRowSizeSecondary(rowIndex) - sizeSecondary) / 2;
         }
         int left, top, right, bottom;
@@ -2418,7 +2418,7 @@
 
     public void setSelection(int position, int subposition, boolean smooth,
             int primaryScrollExtra) {
-        if (mFocusPosition != position && position != NO_POSITION
+        if ((mFocusPosition != position && position != NO_POSITION)
                 || subposition != mSubFocusPosition || primaryScrollExtra != mPrimaryScrollExtra) {
             scrollToSelection(position, subposition, smooth, primaryScrollExtra);
         }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
index 62ff2d7..632c287 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
@@ -13,15 +13,14 @@
  */
 package android.support.v17.leanback.widget;
 
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.support.annotation.DrawableRes;
 import android.support.annotation.StringRes;
 import android.support.v17.leanback.R;
 import android.support.v4.content.ContextCompat;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
 import android.text.InputType;
 
 import java.util.List;
@@ -201,10 +200,10 @@
                 mTitle = mContext.getString(R.string.lb_guidedaction_continue_title);
             } else if (id == ACTION_ID_YES) {
                 mId = ACTION_ID_YES;
-                mTitle = mContext.getString(android.R.string.yes);
+                mTitle = mContext.getString(android.R.string.ok);
             } else if (id == ACTION_ID_NO) {
                 mId = ACTION_ID_NO;
-                mTitle = mContext.getString(android.R.string.no);
+                mTitle = mContext.getString(android.R.string.cancel);
             }
             return (B) this;
         }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacetHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacetHelper.java
index 3230848..b0e681a 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacetHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacetHelper.java
@@ -55,8 +55,9 @@
                 }
             }
             if (facet.mOffsetPercent != ITEM_ALIGN_OFFSET_PERCENT_DISABLED) {
-                alignPos += ((view == itemView ? p.getOpticalWidth(view) : view.getWidth())
-                        * facet.mOffsetPercent) / 100f;
+                alignPos += Math.round(
+                        ((view == itemView ? p.getOpticalWidth(view) : view.getWidth())
+                        * facet.mOffsetPercent) / 100f);
             }
             if (itemView != view) {
                 sRect.left = alignPos;
@@ -74,8 +75,9 @@
                 }
             }
             if (facet.mOffsetPercent != ITEM_ALIGN_OFFSET_PERCENT_DISABLED) {
-                alignPos += ((view == itemView ? p.getOpticalHeight(view) : view.getHeight())
-                        * facet.mOffsetPercent) / 100f;
+                alignPos += Math.round(
+                        ((view == itemView ? p.getOpticalHeight(view) : view.getHeight())
+                        * facet.mOffsetPercent) / 100f);
             }
             if (itemView != view) {
                 sRect.top = alignPos;
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsPresenter.java
index 79d1407..01e9de3 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsPresenter.java
@@ -63,7 +63,6 @@
         StringBuilder mCurrentTimeStringBuilder = new StringBuilder();
         int mCurrentTimeMarginStart;
         int mTotalTimeMarginEnd;
-        final PersistentFocusWrapper mControlsFocusWrapper;
 
         ViewHolder(View rootView) {
             super(rootView);
@@ -91,7 +90,6 @@
                     ((MarginLayoutParams) mCurrentTime.getLayoutParams()).getMarginStart();
             mTotalTimeMarginEnd =
                     ((MarginLayoutParams) mTotalTime.getLayoutParams()).getMarginEnd();
-            mControlsFocusWrapper = (PersistentFocusWrapper) mControlBar.getParent();
         }
 
         void showMoreActions(boolean show) {
@@ -298,7 +296,6 @@
     }
 
     public void resetFocus(ViewHolder vh) {
-        vh.mControlsFocusWrapper.clearSelection();
         vh.mControlBar.requestFocus();
     }
 
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 3556057..4bc00c6 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
@@ -213,6 +213,7 @@
         return space;
     }
 
+    @SuppressWarnings("ReferenceEquality")
     protected static float getFontDescent(TextView textView, Paint fontMeasurePaint) {
         if (fontMeasurePaint.getTextSize() != textView.getTextSize()) {
             fontMeasurePaint.setTextSize(textView.getTextSize());
diff --git a/v17/leanback/tests/README.txt b/v17/leanback/tests/README.txt
deleted file mode 100644
index cf64349..0000000
--- a/v17/leanback/tests/README.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-Test project for support leanback.
-
-RUN TESTS
-Using gradle
-1. cd frameworks/support
-2. ./gradlew support-leanback-v17:connectedCheck --info
-
-Using adb
-adb shell am instrument -w android.support.v17.leanback.tests/android.test.InstrumentationTestRunner
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackOverlayFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackOverlayFragmentTest.java
index be78f79..08fa838 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackOverlayFragmentTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackOverlayFragmentTest.java
@@ -20,7 +20,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.support.test.filters.FlakyTest;
 import android.support.test.filters.MediumTest;
+import android.support.test.filters.Suppress;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v17.leanback.test.R;
 import android.view.View;
@@ -43,6 +45,8 @@
         assertTrue(fragment.getView().hasFocus());
     }
 
+    @FlakyTest
+    @Suppress
     @Test
     public void alignmentRowToBottom() throws Throwable {
         launchAndWaitActivity(PlaybackOverlayTestFragment.class,
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/ControlBarTest.java b/v17/leanback/tests/java/android/support/v17/leanback/widget/ControlBarTest.java
new file mode 100644
index 0000000..ea5b400
--- /dev/null
+++ b/v17/leanback/tests/java/android/support/v17/leanback/widget/ControlBarTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2017 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.widget;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertSame;
+import static junit.framework.Assert.assertTrue;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+
+public class ControlBarTest {
+
+    @Test
+    public void defaultFocus() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        final ControlBar bar = new ControlBar(context, null);
+        final TextView v1 = new Button(context);
+        bar.addView(v1, 100, 100);
+        final TextView v2 = new Button(context);
+        bar.addView(v2, 100, 100);
+        final TextView v3 = new Button(context);
+        bar.addView(v3, 100, 100);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        bar.requestFocus(View.FOCUS_DOWN);
+                    }
+                }
+        );
+        assertTrue(v2.hasFocus());
+    }
+
+    @Test
+    public void persistFocus() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        final LinearLayout rootView = new LinearLayout(context);
+        final ControlBar bar = new ControlBar(context, null);
+        rootView.addView(bar, 800, 100);
+        final Button barSibling = new Button(context);
+        rootView.addView(barSibling, 100, 100);
+        final TextView v1 = new Button(context);
+        bar.addView(v1, 100, 100);
+        final TextView v2 = new Button(context);
+        bar.addView(v2, 100, 100);
+        final TextView v3 = new Button(context);
+        bar.addView(v3, 100, 100);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        v3.requestFocus(View.FOCUS_DOWN);
+                    }
+                }
+        );
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        barSibling.requestFocus();
+                    }
+                }
+        );
+        assertFalse(bar.hasFocus());
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        bar.requestFocus(View.FOCUS_RIGHT);
+                    }
+                }
+        );
+        assertTrue(v3.hasFocus());
+    }
+
+    @Test
+    public void getFocusables() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        final LinearLayout rootView = new LinearLayout(context);
+        final ControlBar bar = new ControlBar(context, null);
+        rootView.addView(bar, 800, 100);
+        final Button barSibling = new Button(context);
+        rootView.addView(barSibling, 100, 100);
+        final TextView v1 = new Button(context);
+        bar.addView(v1, 100, 100);
+        final TextView v2 = new Button(context);
+        bar.addView(v2, 100, 100);
+        final TextView v3 = new Button(context);
+        bar.addView(v3, 100, 100);
+
+        ArrayList<View> focusables = new ArrayList();
+        bar.addFocusables(focusables, View.FOCUS_DOWN);
+        assertEquals(1, focusables.size());
+        assertSame(focusables.get(0), v2);
+        focusables.clear();
+        bar.addFocusables(focusables, View.FOCUS_UP);
+        assertEquals(1, focusables.size());
+        assertSame(focusables.get(0), v2);
+        focusables.clear();
+        bar.addFocusables(focusables, View.FOCUS_LEFT);
+        assertEquals(3, focusables.size());
+        focusables.clear();
+        bar.addFocusables(focusables, View.FOCUS_RIGHT);
+        assertEquals(3, focusables.size());
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        v3.requestFocus(View.FOCUS_DOWN);
+                    }
+                }
+        );
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        barSibling.requestFocus();
+                    }
+                }
+        );
+        assertFalse(bar.hasFocus());
+        focusables.clear();
+        bar.addFocusables(focusables, View.FOCUS_DOWN);
+        assertEquals(1, focusables.size());
+        assertSame(focusables.get(0), v3);
+        focusables.clear();
+        bar.addFocusables(focusables, View.FOCUS_UP);
+        assertEquals(1, focusables.size());
+        assertSame(focusables.get(0), v3);
+        focusables.clear();
+        bar.addFocusables(focusables, View.FOCUS_LEFT);
+        assertEquals(3, focusables.size());
+        focusables.clear();
+        bar.addFocusables(focusables, View.FOCUS_RIGHT);
+        assertEquals(3, focusables.size());
+
+    }
+}
diff --git a/v4/AndroidManifest.xml b/v4/AndroidManifest.xml
index 642a916..470f5c2 100644
--- a/v4/AndroidManifest.xml
+++ b/v4/AndroidManifest.xml
@@ -17,6 +17,7 @@
           xmlns:tools="http://schemas.android.com/tools"
           package="android.support.v4">
     <uses-sdk android:minSdkVersion="14" tools:overrideLibrary="android.support.v4"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/v7/appcompat/.classpath b/v7/appcompat/.classpath
deleted file mode 100644
index a4763d1..0000000
--- a/v7/appcompat/.classpath
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="src" path="gen"/>
-	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
-	<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
-	<classpathentry kind="output" path="bin/classes"/>
-</classpath>
diff --git a/v7/appcompat/.gitignore b/v7/appcompat/.gitignore
deleted file mode 100644
index c3c25e0..0000000
--- a/v7/appcompat/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-.settings
-bin
-libs
-gen
diff --git a/v7/appcompat/.project b/v7/appcompat/.project
deleted file mode 100644
index 957d33d..0000000
--- a/v7/appcompat/.project
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>android-support-v7-appcompat</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>com.android.ide.eclipse.adt.ApkBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-	</natures>
-</projectDescription>
diff --git a/v7/appcompat/AndroidManifest.xml b/v7/appcompat/AndroidManifest.xml
index c1ff659..7de91ff 100644
--- a/v7/appcompat/AndroidManifest.xml
+++ b/v7/appcompat/AndroidManifest.xml
@@ -18,6 +18,7 @@
           package="android.support.v7.appcompat">
     <uses-sdk android:minSdkVersion="14"
               tools:overrideLibrary="android.support.graphics.drawable.animated"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application/>
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/v7/appcompat/README.txt b/v7/appcompat/README.txt
deleted file mode 100644
index 8e8de05..0000000
--- a/v7/appcompat/README.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-Library Project including compatibility ActionBar.
-
-This can be used by an Android project to provide
-access to ActionBar on applications running on API 7+.
-
-There is technically no source, but the src folder is necessary
-to ensure that the build system works.  The content is actually
-located in libs/android-support-v7-appcompat.jar.
-The accompanying resources must also be included in the application.
-
diff --git a/v7/appcompat/THEMES.txt b/v7/appcompat/THEMES
similarity index 100%
rename from v7/appcompat/THEMES.txt
rename to v7/appcompat/THEMES
diff --git a/v7/appcompat/project.properties b/v7/appcompat/project.properties
deleted file mode 100644
index 91d2b02..0000000
--- a/v7/appcompat/project.properties
+++ /dev/null
@@ -1,15 +0,0 @@
-# This file is automatically generated by Android Tools.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
-# This file must be checked in Version Control Systems.
-#
-# To customize properties used by the Ant build system edit
-# "ant.properties", and override values to adapt the script to your
-# project structure.
-#
-# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
-#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
-
-# Project target.
-target=android-19
-android.library=true
diff --git a/v7/appcompat/res-public/values/public_attrs.xml b/v7/appcompat/res-public/values/public_attrs.xml
index 3961f49..2e16fe7 100644
--- a/v7/appcompat/res-public/values/public_attrs.xml
+++ b/v7/appcompat/res-public/values/public_attrs.xml
@@ -180,6 +180,7 @@
      <public type="attr" name="textAllCaps"/>
      <public type="attr" name="textAppearanceLargePopupMenu"/>
      <public type="attr" name="textAppearanceListItem"/>
+     <public type="attr" name="textAppearanceListItemSecondary"/>
      <public type="attr" name="textAppearanceListItemSmall"/>
      <public type="attr" name="textAppearancePopupMenuHeader"/>
      <public type="attr" name="textAppearanceSearchResultSubtitle"/>
diff --git a/v7/appcompat/res/values/attrs.xml b/v7/appcompat/res/values/attrs.xml
index dfefd6e..1603b90 100644
--- a/v7/appcompat/res/values/attrs.xml
+++ b/v7/appcompat/res/values/attrs.xml
@@ -280,10 +280,11 @@
 
         <!-- The preferred TextAppearance for the primary text of list items. -->
         <attr name="textAppearanceListItem" format="reference"/>
+        <!-- The preferred TextAppearance for the secondary text of list items. -->
+        <attr name="textAppearanceListItemSecondary" format="reference" />
         <!-- The preferred TextAppearance for the primary text of small list items. -->
         <attr name="textAppearanceListItemSmall" format="reference"/>
 
-
         <!-- ============ -->
         <!-- Panel styles -->
         <!-- ============ -->
@@ -1059,6 +1060,7 @@
         <attr name="android:textSize" />
         <attr name="android:textColor" />
         <attr name="android:textColorHint"/>
+        <attr name="android:textColorLink"/>
         <attr name="android:textStyle" />
         <attr name="android:typeface" />
         <attr name="textAllCaps" />
diff --git a/v7/appcompat/res/values/themes_base.xml b/v7/appcompat/res/values/themes_base.xml
index 2844d2b..dbdc605 100644
--- a/v7/appcompat/res/values/themes_base.xml
+++ b/v7/appcompat/res/values/themes_base.xml
@@ -181,6 +181,7 @@
         <!-- List attributes -->
         <item name="textAppearanceListItem">@style/TextAppearance.AppCompat.Subhead</item>
         <item name="textAppearanceListItemSmall">@style/TextAppearance.AppCompat.Subhead</item>
+        <item name="textAppearanceListItemSecondary">@style/TextAppearance.AppCompat.Body1</item>
         <item name="listPreferredItemHeight">64dp</item>
         <item name="listPreferredItemHeightSmall">48dp</item>
         <item name="listPreferredItemHeightLarge">80dp</item>
@@ -346,6 +347,7 @@
         <!-- List attributes -->
         <item name="textAppearanceListItem">@style/TextAppearance.AppCompat.Subhead</item>
         <item name="textAppearanceListItemSmall">@style/TextAppearance.AppCompat.Subhead</item>
+        <item name="textAppearanceListItemSecondary">@style/TextAppearance.AppCompat.Body1</item>
         <item name="listPreferredItemHeight">64dp</item>
         <item name="listPreferredItemHeightSmall">48dp</item>
         <item name="listPreferredItemHeightLarge">80dp</item>
diff --git a/v7/appcompat/src/.readme b/v7/appcompat/src/.readme
deleted file mode 100644
index 4bcebad..0000000
--- a/v7/appcompat/src/.readme
+++ /dev/null
@@ -1,2 +0,0 @@
-This hidden file is there to ensure there is an src folder.
-Once we support binary library this will go away.
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBar.java b/v7/appcompat/src/android/support/v7/app/ActionBar.java
index e26e813..c619596 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBar.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBar.java
@@ -900,7 +900,7 @@
      * call {@link #setHomeActionContentDescription(int) setHomeActionContentDescription()}
      * to provide a correct description of the action for accessibility support.</p>
      *
-     * @param resId Resource ID of a drawable to use for the up indicator, or 0
+     * @param resId Resource ID of a drawable to use for the up indicator, or null
      *              to use the theme's default
      *
      * @see #setDisplayOptions(int, int)
@@ -1069,6 +1069,12 @@
 
     /** @hide */
     @RestrictTo(LIBRARY_GROUP)
+    public boolean closeOptionsMenu() {
+        return false;
+    }
+
+    /** @hide */
+    @RestrictTo(LIBRARY_GROUP)
     public boolean invalidateOptionsMenu() {
         return false;
     }
@@ -1100,7 +1106,9 @@
      * Attempts to move focus to the ActionBar if it does not already contain the focus.
      *
      * @return {@code true} if focus changes or {@code false} if focus doesn't change.
+     * @hide
      */
+    @RestrictTo(LIBRARY_GROUP)
     boolean requestFocus() {
         return false;
     }
@@ -1285,7 +1293,7 @@
          * @see #setContentDescription(CharSequence)
          * @see #getContentDescription()
          */
-        public abstract Tab setContentDescription(int resId);
+        public abstract Tab setContentDescription(@StringRes int resId);
 
         /**
          * Set a description of this tab's content for use in accessibility support.
diff --git a/v7/appcompat/src/android/support/v7/app/AlertDialog.java b/v7/appcompat/src/android/support/v7/app/AlertDialog.java
index 051f5cc..c73e619 100644
--- a/v7/appcompat/src/android/support/v7/app/AlertDialog.java
+++ b/v7/appcompat/src/android/support/v7/app/AlertDialog.java
@@ -187,7 +187,9 @@
 
     /**
      * Internal api to allow hinting for the best button panel layout.
+     * @hide
      */
+    @RestrictTo(LIBRARY_GROUP)
     void setButtonPanelLayoutHint(int layoutHint) {
         mAlert.setButtonPanelLayoutHint(layoutHint);
     }
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java b/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
index 6c0f125..57e4570 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
@@ -538,6 +538,14 @@
                 return true;
             }
         }
+        // Let support action bars open menus in response to the menu key prioritized over
+        // the window handling it
+        final int keyCode = event.getKeyCode();
+        final ActionBar actionBar = getSupportActionBar();
+        if (keyCode == KeyEvent.KEYCODE_MENU
+                && actionBar != null && actionBar.onMenuKeyEvent(event)) {
+            return true;
+        }
         return super.dispatchKeyEvent(event);
     }
 
@@ -576,4 +584,22 @@
         }
         return super.onKeyDown(keyCode, event);
     }
+
+    @Override
+    public void openOptionsMenu() {
+        ActionBar actionBar = getSupportActionBar();
+        if (getWindow().hasFeature(Window.FEATURE_OPTIONS_PANEL)
+                && (actionBar == null || !actionBar.openOptionsMenu())) {
+            super.openOptionsMenu();
+        }
+    }
+
+    @Override
+    public void closeOptionsMenu() {
+        ActionBar actionBar = getSupportActionBar();
+        if (getWindow().hasFeature(Window.FEATURE_OPTIONS_PANEL)
+                && (actionBar == null || !actionBar.closeOptionsMenu())) {
+            super.closeOptionsMenu();
+        }
+    }
 }
diff --git a/v7/appcompat/src/android/support/v7/app/ToolbarActionBar.java b/v7/appcompat/src/android/support/v7/app/ToolbarActionBar.java
index c124bc9..9ac70af 100644
--- a/v7/appcompat/src/android/support/v7/app/ToolbarActionBar.java
+++ b/v7/appcompat/src/android/support/v7/app/ToolbarActionBar.java
@@ -70,9 +70,9 @@
                 }
             };
 
-    public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback callback) {
+    ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback windowCallback) {
         mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false);
-        mWindowCallback = new ToolbarCallbackWrapper(callback);
+        mWindowCallback = new ToolbarCallbackWrapper(windowCallback);
         mDecorToolbar.setWindowCallback(mWindowCallback);
         toolbar.setOnMenuItemClickListener(mMenuClicker);
         mDecorToolbar.setWindowTitle(title);
@@ -428,6 +428,11 @@
     }
 
     @Override
+    public boolean closeOptionsMenu() {
+        return mDecorToolbar.hideOverflowMenu();
+    }
+
+    @Override
     public boolean invalidateOptionsMenu() {
         mDecorToolbar.getViewGroup().removeCallbacks(mMenuInvalidator);
         ViewCompat.postOnAnimation(mDecorToolbar.getViewGroup(), mMenuInvalidator);
diff --git a/v7/appcompat/src/android/support/v7/view/ContextThemeWrapper.java b/v7/appcompat/src/android/support/v7/view/ContextThemeWrapper.java
index 44c0478..5069812 100644
--- a/v7/appcompat/src/android/support/v7/view/ContextThemeWrapper.java
+++ b/v7/appcompat/src/android/support/v7/view/ContextThemeWrapper.java
@@ -21,7 +21,9 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.res.AssetManager;
+import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.os.Build;
 import android.support.annotation.RestrictTo;
 import android.support.annotation.StyleRes;
 import android.support.v7.appcompat.R;
@@ -38,18 +40,103 @@
     private int mThemeResource;
     private Resources.Theme mTheme;
     private LayoutInflater mInflater;
+    private Configuration mOverrideConfiguration;
+    private Resources mResources;
 
+    /**
+     * Creates a new context wrapper with no theme and no base context.
+     * <p>
+     * <stong>Note:</strong> A base context <strong>must</strong> be attached
+     * using {@link #attachBaseContext(Context)} before calling any other
+     * method on the newly constructed context wrapper.
+     */
+    public ContextThemeWrapper() {
+        super(null);
+    }
+
+    /**
+     * Creates a new context wrapper with the specified theme.
+     * <p>
+     * The specified theme will be applied on top of the base context's theme.
+     * Any attributes not explicitly defined in the theme identified by
+     * <var>themeResId</var> will retain their original values.
+     *
+     * @param base the base context
+     * @param themeResId the resource ID of the theme to be applied on top of
+     *                   the base context's theme
+     */
     public ContextThemeWrapper(Context base, @StyleRes int themeResId) {
         super(base);
         mThemeResource = themeResId;
     }
 
+    /**
+     * Creates a new context wrapper with the specified theme.
+     * <p>
+     * Unlike {@link #ContextThemeWrapper(Context, int)}, the theme passed to
+     * this constructor will completely replace the base context's theme.
+     *
+     * @param base the base context
+     * @param theme the theme against which resources should be inflated
+     */
     public ContextThemeWrapper(Context base, Resources.Theme theme) {
         super(base);
         mTheme = theme;
     }
 
     @Override
+    protected void attachBaseContext(Context newBase) {
+        super.attachBaseContext(newBase);
+    }
+
+    /**
+     * Call to set an "override configuration" on this context -- this is
+     * a configuration that replies one or more values of the standard
+     * configuration that is applied to the context.  See
+     * {@link Context#createConfigurationContext(Configuration)} for more
+     * information.
+     *
+     * <p>This method can only be called once, and must be called before any
+     * calls to {@link #getResources()} or {@link #getAssets()} are made.
+     */
+    public void applyOverrideConfiguration(Configuration overrideConfiguration) {
+        if (mResources != null) {
+            throw new IllegalStateException(
+                    "getResources() or getAssets() has already been called");
+        }
+        if (mOverrideConfiguration != null) {
+            throw new IllegalStateException("Override configuration has already been set");
+        }
+        mOverrideConfiguration = new Configuration(overrideConfiguration);
+    }
+
+    /**
+     * Used by ActivityThread to apply the overridden configuration to onConfigurationChange
+     * callbacks.
+     * @hide
+     */
+    public Configuration getOverrideConfiguration() {
+        return mOverrideConfiguration;
+    }
+
+    @Override
+    public Resources getResources() {
+        return getResourcesInternal();
+    }
+
+    private Resources getResourcesInternal() {
+        if (mResources == null) {
+            if (mOverrideConfiguration == null) {
+                mResources = super.getResources();
+            } else if (Build.VERSION.SDK_INT >= 17) {
+                final Context resContext = createConfigurationContext(mOverrideConfiguration);
+                mResources = resContext.getResources();
+            }
+        }
+        return mResources;
+    }
+
+    @Override
     public void setTheme(int resid) {
         if (mThemeResource != resid) {
             mThemeResource = resid;
diff --git a/v7/appcompat/src/android/support/v7/view/menu/CascadingMenuPopup.java b/v7/appcompat/src/android/support/v7/view/menu/CascadingMenuPopup.java
index 4b3fd42..ad34238 100644
--- a/v7/appcompat/src/android/support/v7/view/menu/CascadingMenuPopup.java
+++ b/v7/appcompat/src/android/support/v7/view/menu/CascadingMenuPopup.java
@@ -228,6 +228,7 @@
         popupWindow.setAnchorView(mAnchorView);
         popupWindow.setDropDownGravity(mDropDownGravity);
         popupWindow.setModal(true);
+        popupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
         return popupWindow;
     }
 
@@ -435,9 +436,11 @@
 
         popupWindow.show();
 
+        final ListView listView = popupWindow.getListView();
+        listView.setOnKeyListener(this);
+
         // If this is the root menu, show the title if requested.
         if (parentInfo == null && mShowTitle && menu.getHeaderTitle() != null) {
-            final ListView listView = popupWindow.getListView();
             final FrameLayout titleItemView = (FrameLayout) inflater.inflate(
                     R.layout.abc_popup_menu_header_item_layout, listView, false);
             final TextView titleView = (TextView) titleItemView.findViewById(android.R.id.title);
diff --git a/v7/appcompat/src/android/support/v7/widget/ActionBarContainer.java b/v7/appcompat/src/android/support/v7/widget/ActionBarContainer.java
index eff105e..532c87d 100644
--- a/v7/appcompat/src/android/support/v7/widget/ActionBarContainer.java
+++ b/v7/appcompat/src/android/support/v7/widget/ActionBarContainer.java
@@ -211,6 +211,14 @@
         return true;
     }
 
+    @Override
+    public boolean onHoverEvent(MotionEvent ev) {
+        super.onHoverEvent(ev);
+
+        // An action bar always eats hover events.
+        return true;
+    }
+
     public void setTabContainer(ScrollingTabContainerView tabView) {
         if (mTabContainer != null) {
             removeView(mTabContainer);
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java
index 0dc5759..f63a723 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java
@@ -84,6 +84,7 @@
         boolean allCapsSet = false;
         ColorStateList textColor = null;
         ColorStateList textColorHint = null;
+        ColorStateList textColorLink = null;
 
         // First check TextAppearance's textAllCaps value
         if (ap != -1) {
@@ -102,6 +103,10 @@
                     textColorHint = a.getColorStateList(
                             R.styleable.TextAppearance_android_textColorHint);
                 }
+                if (a.hasValue(R.styleable.TextAppearance_android_textColorLink)) {
+                    textColorLink = a.getColorStateList(
+                            R.styleable.TextAppearance_android_textColorLink);
+                }
             }
             a.recycle();
         }
@@ -123,6 +128,10 @@
                 textColorHint = a.getColorStateList(
                         R.styleable.TextAppearance_android_textColorHint);
             }
+            if (a.hasValue(R.styleable.TextAppearance_android_textColorLink)) {
+                textColorLink = a.getColorStateList(
+                        R.styleable.TextAppearance_android_textColorLink);
+            }
         }
         a.recycle();
 
@@ -132,6 +141,9 @@
         if (textColorHint != null) {
             mView.setHintTextColor(textColorHint);
         }
+        if (textColorLink != null) {
+            mView.setLinkTextColor(textColorLink);
+        }
         if (!hasPwdTm && allCapsSet) {
             setAllCaps(allCaps);
         }
diff --git a/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java b/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java
index a276ec7..cc68d11 100644
--- a/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java
+++ b/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java
@@ -46,6 +46,8 @@
 
     private int mLastWidthSize = -1;
 
+    private int mMinimumHeight = 0;
+
     public ButtonBarLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
         final boolean allowStackingDefault =
@@ -148,6 +150,11 @@
         return -1;
     }
 
+    @Override
+    public int getMinimumHeight() {
+        return Math.max(mMinimumHeight, super.getMinimumHeight());
+    }
+
     private void setStacked(boolean stacked) {
         setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
         setGravity(stacked ? Gravity.RIGHT : Gravity.BOTTOM);
diff --git a/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java b/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
index a6ddb34..c363301 100644
--- a/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
+++ b/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
@@ -938,6 +938,7 @@
      * @return true if the event was handled, false if it was ignored.
      *
      * @see #setModal(boolean)
+     * @see #onKeyUp(int, KeyEvent)
      */
     public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
         // when the drop down is shown, we drive it directly
@@ -1033,6 +1034,7 @@
      * @return true if the event was handled, false if it was ignored.
      *
      * @see #setModal(boolean)
+     * @see #onKeyDown(int, KeyEvent)
      */
     public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
         if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
diff --git a/v7/appcompat/src/android/support/v7/widget/SuggestionsAdapter.java b/v7/appcompat/src/android/support/v7/widget/SuggestionsAdapter.java
index 9f2975f..24dafb3 100644
--- a/v7/appcompat/src/android/support/v7/widget/SuggestionsAdapter.java
+++ b/v7/appcompat/src/android/support/v7/widget/SuggestionsAdapter.java
@@ -110,7 +110,7 @@
      * copied to the query text field.
      * <p>
      *
-     * @param refine which queries to refine. Possible values are {@link #REFINE_NONE},
+     * @param refineWhat which queries to refine. Possible values are {@link #REFINE_NONE},
      * {@link #REFINE_BY_ENTRY}, and {@link #REFINE_ALL}.
      */
     public void setQueryRefinement(int refineWhat) {
@@ -461,6 +461,29 @@
     }
 
     /**
+     * This method is overridden purely to provide a bit of protection against
+     * flaky content providers.
+     *
+     * @see android.widget.CursorAdapter#getDropDownView(int, View, ViewGroup)
+     */
+    @Override
+    public View getDropDownView(int position, View convertView, ViewGroup parent) {
+        try {
+            return super.getDropDownView(position, convertView, parent);
+        } catch (RuntimeException e) {
+            Log.w(LOG_TAG, "Search suggestions cursor threw exception.", e);
+            // Put exception string in item title
+            final View v = newDropDownView(mContext, mCursor, parent);
+            if (v != null) {
+                final ChildViewCache views = (ChildViewCache) v.getTag();
+                final TextView tv = views.mText1;
+                tv.setText(e.toString());
+            }
+            return v;
+        }
+    }
+
+    /**
      * Gets a drawable given a value provided by a suggestion provider.
      *
      * This value could be just the string value of a resource id
diff --git a/v7/appcompat/src/android/support/v7/widget/Toolbar.java b/v7/appcompat/src/android/support/v7/widget/Toolbar.java
index 1865df1..bd770c2 100644
--- a/v7/appcompat/src/android/support/v7/widget/Toolbar.java
+++ b/v7/appcompat/src/android/support/v7/widget/Toolbar.java
@@ -2169,6 +2169,17 @@
     }
 
     /**
+     * Accessor to enable LayoutLib to get ActionMenuPresenter directly.
+     */
+    ActionMenuPresenter getOuterActionMenuPresenter() {
+        return mOuterActionMenuPresenter;
+    }
+
+    Context getPopupContext() {
+        return mPopupContext;
+    }
+
+    /**
      * Interface responsible for receiving menu item click events if the items themselves
      * do not have individual item click listeners.
      */
diff --git a/v7/appcompat/tests/res/color/color_state_list_link.xml b/v7/appcompat/tests/res/color/color_state_list_link.xml
new file mode 100644
index 0000000..36dd761
--- /dev/null
+++ b/v7/appcompat/tests/res/color/color_state_list_link.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@color/lilac_disabled"
+          android:state_enabled="false"/>
+    <item android:color="@color/lilac_default"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml b/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
index 9fd3f29..b2aafe5 100644
--- a/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
+++ b/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
@@ -104,6 +104,20 @@
             android:text="@string/sample_text2"
             android:textColor="?android:attr/textColorSecondary" />
 
+        <android.support.v7.widget.AppCompatTextView
+            android:id="@+id/view_text_link_enabled"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/text_link_enabled"
+            android:textColor="?android:attr/textColorSecondary" />
+
+        <android.support.v7.widget.AppCompatTextView
+            android:id="@+id/view_text_link_disabled"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:enabled="false"
+            android:text="@string/text_link_disabled" />
+
     </LinearLayout>
 
 </ScrollView>
diff --git a/v7/appcompat/tests/res/menu/sample_actions.xml b/v7/appcompat/tests/res/menu/sample_actions.xml
index 8b3f893..e72c4d2 100644
--- a/v7/appcompat/tests/res/menu/sample_actions.xml
+++ b/v7/appcompat/tests/res/menu/sample_actions.xml
@@ -21,7 +21,7 @@
           app:contentDescription="@string/search_menu_description"
           app:tooltipText="@string/search_menu_tooltip"
           app:showAsAction="always|collapseActionView"
-          app:actionViewClass="android.support.v7.widget.SearchView"/>
+          app:actionViewClass="android.support.v7.app.CustomCollapsibleView"/>
 
     <item android:id="@+id/action_alpha_shortcut"
           android:title="@string/alpha_menu_title"
diff --git a/v7/appcompat/tests/res/values/strings.xml b/v7/appcompat/tests/res/values/strings.xml
index fbc132b..0ded756 100644
--- a/v7/appcompat/tests/res/values/strings.xml
+++ b/v7/appcompat/tests/res/values/strings.xml
@@ -79,4 +79,7 @@
     <string name="alpha_menu_title">Alpha</string>
     <string name="alpha_menu_description">Alpha description</string>
     <string name="alpha_menu_tooltip">Alpha tooltip</string>
+
+    <string name="text_link_enabled">With <a href="http://www.google.com">link</a> enabled</string>
+    <string name="text_link_disabled">With <a href="http://www.google.com">link</a> disabled</string>
 </resources>
diff --git a/v7/appcompat/tests/res/values/styles.xml b/v7/appcompat/tests/res/values/styles.xml
index 1b416a4..5831122 100644
--- a/v7/appcompat/tests/res/values/styles.xml
+++ b/v7/appcompat/tests/res/values/styles.xml
@@ -28,6 +28,7 @@
     <style name="Theme.TextColors" parent="@style/Theme.AppCompat.Light">
         <item name="android:textColorPrimary">#FF0000FF</item>
         <item name="android:textColorSecondary">@color/color_state_list_sand</item>
+        <item name="android:textColorLink">@color/color_state_list_link</item>
     </style>
 
     <style name="TextStyleAllCapsOn" parent="@android:style/TextAppearance.Medium">
diff --git a/v7/appcompat/tests/src/android/support/v7/app/BaseBasicsTestCase.java b/v7/appcompat/tests/src/android/support/v7/app/BaseBasicsTestCase.java
index ecdc64f..6112d34 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/BaseBasicsTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/BaseBasicsTestCase.java
@@ -35,6 +35,9 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.pm.PackageManager;
 import android.support.annotation.RequiresApi;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.SdkSuppress;
@@ -84,6 +87,11 @@
                 (FitWindowsContentLayout) getActivity().findViewById(R.id.test_content);
         assertNotNull(content);
 
+        if (!canShowSystemUi(getActivity())) {
+            // Device cannot show system UI so setSystemUiVisibility will do nothing.
+            return;
+        }
+
         // Call setSystemUiVisibility with flags which will cause window insets to be dispatched
         final int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
         onView(withId(R.id.test_content)).perform(setSystemUiVisibility(flags));
@@ -98,6 +106,11 @@
         final View content = getActivity().findViewById(R.id.test_content);
         assertNotNull(content);
 
+        if (!canShowSystemUi(getActivity())) {
+            // Device cannot show system UI so setSystemUiVisibility will do nothing.
+            return;
+        }
+
         // Create a spy of one of our test listener and set it on our content
         final View.OnApplyWindowInsetsListener spyListener
                 = spy(new TestOnApplyWindowInsetsListener());
@@ -183,6 +196,15 @@
         assertNull(actionMode);
     }
 
+    @SuppressWarnings("deprecation")
+    @SuppressLint("InlinedApi")
+    private static boolean canShowSystemUi(Activity activity) {
+        PackageManager manager = activity.getPackageManager();
+        return !manager.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
+                && !manager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+                && !manager.hasSystemFeature(PackageManager.FEATURE_WATCH);
+    }
+
     protected void testSupportActionModeAppCompatCallbacks(final boolean fromWindow) {
         final A activity = getActivity();
 
diff --git a/v7/appcompat/tests/src/android/support/v7/app/BaseKeyEventsTestCase.java b/v7/appcompat/tests/src/android/support/v7/app/BaseKeyEventsTestCase.java
index fbfd963..88f60ef 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/BaseKeyEventsTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/BaseKeyEventsTestCase.java
@@ -21,6 +21,7 @@
 import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
 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.withClassName;
 import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 
@@ -39,6 +40,7 @@
 import android.view.Menu;
 import android.view.MenuItem;
 
+import org.hamcrest.Matchers;
 import org.junit.Test;
 
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -93,24 +95,25 @@
 
     @Test
     @LargeTest
-    public void testBackCollapsesSearchView() throws InterruptedException {
+    public void testBackCollapsesActionView() throws InterruptedException {
         // Click on the Search menu item
         onView(withId(R.id.action_search)).perform(click());
-        // Check that the SearchView is displayed
-        onView(withId(R.id.search_bar)).check(matches(isDisplayed()));
+        // Check that the action view is displayed (expanded)
+        onView(withClassName(Matchers.is(CustomCollapsibleView.class.getName())))
+                .check(matches(isDisplayed()));
 
-        // Wait for the IME to show
+        // Let things settle
         getInstrumentation().waitForIdleSync();
-        // Now send a back event to dismiss the IME
+        // Now send a back event to collapse the custom action view
         getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
-        // ...and another to collapse the SearchView
-        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
+        getInstrumentation().waitForIdleSync();
 
         // Check that the Activity is still running
         assertFalse(getActivity().isFinishing());
         assertFalse(getActivity().isDestroyed());
-        // ...and that the SearchView is not attached
-        onView(withId(R.id.search_bar)).check(doesNotExist());
+        // ... and that our action view is not attached
+        onView(withClassName(Matchers.is(CustomCollapsibleView.class.getName())))
+                .check(doesNotExist());
     }
 
     @Test
diff --git a/v7/appcompat/tests/src/android/support/v7/app/CustomCollapsibleView.java b/v7/appcompat/tests/src/android/support/v7/app/CustomCollapsibleView.java
new file mode 100644
index 0000000..27e6f8a
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/CustomCollapsibleView.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 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 android.content.Context;
+import android.support.annotation.Nullable;
+import android.support.v7.view.CollapsibleActionView;
+import android.util.AttributeSet;
+import android.view.View;
+
+public class CustomCollapsibleView extends View implements CollapsibleActionView {
+    public CustomCollapsibleView(Context context) {
+        this(context, null, 0);
+    }
+
+    public CustomCollapsibleView(Context context,
+            @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CustomCollapsibleView(Context context,
+            @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        setBackgroundColor(0xFFFF0000);
+    }
+
+    @Override
+    public void onActionViewExpanded() {
+    }
+
+    @Override
+    public void onActionViewCollapsed() {
+    }
+}
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 9b8051e..34c5d64 100755
--- a/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.support.v7.app;
 
 import static android.support.test.espresso.Espresso.onView;
@@ -44,8 +45,10 @@
 import android.support.test.espresso.action.GeneralSwipeAction;
 import android.support.test.espresso.action.Press;
 import android.support.test.espresso.action.Swipe;
+import android.support.test.filters.FlakyTest;
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.MediumTest;
+import android.support.test.filters.Suppress;
 import android.support.v4.view.GravityCompat;
 import android.support.v4.widget.DrawerLayout;
 import android.support.v7.appcompat.test.R;
@@ -425,6 +428,8 @@
         mDrawerLayout.removeDrawerListener(mockedListener);
     }
 
+    @Suppress
+    @FlakyTest(bugId = 33659300)
     @Test
     @MediumTest
     public void testDrawerListenerCallbacksOnOpeningViaSwipes() {
diff --git a/v7/appcompat/tests/src/android/support/v7/app/KeyEventsTestCaseWithToolbar.java b/v7/appcompat/tests/src/android/support/v7/app/KeyEventsTestCaseWithToolbar.java
index cdaecc0..08d66c4 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/KeyEventsTestCaseWithToolbar.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/KeyEventsTestCaseWithToolbar.java
@@ -16,8 +16,66 @@
 
 package android.support.v7.app;
 
+import android.support.test.filters.SmallTest;
+import android.support.v7.widget.Toolbar;
+import android.view.KeyEvent;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
 public class KeyEventsTestCaseWithToolbar extends BaseKeyEventsTestCase<ToolbarAppCompatActivity> {
     public KeyEventsTestCaseWithToolbar() {
         super(ToolbarAppCompatActivity.class);
     }
+
+    @Test
+    @SmallTest
+    @Override
+    public void testMenuKeyEventReachesActivity() throws InterruptedException {
+        // With Toolbar, MENU key gets sent-to (and consumed by) Toolbar rather than Activity
+    }
+
+    @Test
+    @SmallTest
+    public void testMenuKeyOpensToolbarMenu() {
+        // Base test only checks that *a* menu is opened, we check here that the toolbar's menu
+        // specifically is opened.
+        Toolbar toolbar = mActivityTestRule.getActivity().getToolbar();
+        assertFalse(toolbar.isOverflowMenuShowing());
+
+        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
+        getInstrumentation().waitForIdleSync();
+        assertTrue(toolbar.isOverflowMenuShowing());
+
+        getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
+        getInstrumentation().waitForIdleSync();
+        assertFalse(toolbar.isOverflowMenuShowing());
+    }
+
+    @Test
+    @SmallTest
+    public void testOpenMenuOpensToolbarMenu() throws Throwable {
+        Toolbar toolbar = mActivityTestRule.getActivity().getToolbar();
+        assertFalse(toolbar.isOverflowMenuShowing());
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivityTestRule.getActivity().openOptionsMenu();
+            }
+        });
+        getInstrumentation().waitForIdleSync();
+        assertTrue(toolbar.isOverflowMenuShowing());
+
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mActivityTestRule.getActivity().closeOptionsMenu();
+            }
+        });
+        getInstrumentation().waitForIdleSync();
+        assertFalse(toolbar.isOverflowMenuShowing());
+    }
 }
diff --git a/v7/appcompat/tests/src/android/support/v7/app/ToolbarAppCompatActivity.java b/v7/appcompat/tests/src/android/support/v7/app/ToolbarAppCompatActivity.java
index 9042363..3213f40 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/ToolbarAppCompatActivity.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/ToolbarAppCompatActivity.java
@@ -34,4 +34,8 @@
         mToolbar = (Toolbar) findViewById(R.id.toolbar);
         setSupportActionBar(mToolbar);
     }
+
+    public Toolbar getToolbar() {
+        return mToolbar;
+    }
 }
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
index d9c3c55..0a28102 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
@@ -22,6 +22,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.content.res.ColorStateList;
 import android.graphics.Color;
 import android.support.test.filters.SmallTest;
 import android.support.v4.content.ContextCompat;
@@ -118,4 +119,18 @@
         assertEquals(ContextCompat.getColor(textView.getContext(), R.color.sand_disabled),
                 textView.getCurrentTextColor());
     }
+
+    private void verifyTextLinkColor(TextView textView) {
+        ColorStateList linkColorStateList = textView.getLinkTextColors();
+        assertEquals(ContextCompat.getColor(textView.getContext(), R.color.lilac_default),
+                linkColorStateList.getColorForState(new int[] { android.R.attr.state_enabled}, 0));
+        assertEquals(ContextCompat.getColor(textView.getContext(), R.color.lilac_disabled),
+                linkColorStateList.getColorForState(new int[] { -android.R.attr.state_enabled}, 0));
+    }
+
+    @Test
+    public void testTextLinkColor() {
+        verifyTextLinkColor((TextView) mContainer.findViewById(R.id.view_text_link_enabled));
+        verifyTextLinkColor((TextView) mContainer.findViewById(R.id.view_text_link_disabled));
+    }
 }
diff --git a/v7/cardview/AndroidManifest.xml b/v7/cardview/AndroidManifest.xml
index b83c815..8fcf55a 100644
--- a/v7/cardview/AndroidManifest.xml
+++ b/v7/cardview/AndroidManifest.xml
@@ -16,6 +16,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.v7.cardview">
     <uses-sdk android:minSdkVersion="14"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/v7/cardview/src/.readme b/v7/cardview/src/.readme
deleted file mode 100644
index 4bcebad..0000000
--- a/v7/cardview/src/.readme
+++ /dev/null
@@ -1,2 +0,0 @@
-This hidden file is there to ensure there is an src folder.
-Once we support binary library this will go away.
\ No newline at end of file
diff --git a/v7/gridlayout/.classpath b/v7/gridlayout/.classpath
deleted file mode 100644
index a4763d1..0000000
--- a/v7/gridlayout/.classpath
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="src" path="gen"/>
-	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
-	<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
-	<classpathentry kind="output" path="bin/classes"/>
-</classpath>
diff --git a/v7/gridlayout/.gitignore b/v7/gridlayout/.gitignore
deleted file mode 100644
index c3c25e0..0000000
--- a/v7/gridlayout/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-.settings
-bin
-libs
-gen
diff --git a/v7/gridlayout/.project b/v7/gridlayout/.project
deleted file mode 100644
index 1e67516..0000000
--- a/v7/gridlayout/.project
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>android-support-v7-gridlayout</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>com.android.ide.eclipse.adt.ApkBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-	</natures>
-</projectDescription>
diff --git a/v7/gridlayout/AndroidManifest.xml b/v7/gridlayout/AndroidManifest.xml
index 6f12842..b6cb783 100644
--- a/v7/gridlayout/AndroidManifest.xml
+++ b/v7/gridlayout/AndroidManifest.xml
@@ -16,6 +16,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.v7.gridlayout">
     <uses-sdk android:minSdkVersion="14"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/v7/gridlayout/README.txt b/v7/gridlayout/README.txt
deleted file mode 100644
index e754187..0000000
--- a/v7/gridlayout/README.txt
+++ /dev/null
@@ -1,66 +0,0 @@
-Library Project including compatibility GridLayout.
-
-This can be used by an Android project to provide
-access to GridLayout on applications running on API 7+.
-
-There is technically no source, but the src folder is necessary
-to ensure that the build system works.  The content is actually
-located in libs/android-support-v7-gridlayout.jar.
-The accompanying resources must also be included in the application.
-
-
-USAGE:
-
-Make sure you use <android.support.v7.widget.GridLayout> in your
-layouts instead of <GridLayout>.
-Same for <android.support.v4.widget.Space> instead of <Space>.
-
-Additionally, all of GridLayout's attributes should be put in the
-namespace of the app, as those attributes have been redefined in
-the library so that it can run on older platforms that don't offer
-those attributes in their namespace.
-
-To know which attributes need the application namespace, look at
-the two declare-styleable declared in res/values/attrs.xml
-
-
-
-For instance:
-
-<?xml version="1.0" encoding="utf-8"?>
-<android.support.v7.widget.GridLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"  <==== the namespace used for the library project
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    app:columnCount="6" >                                <===== notice how we're using app:columnCount here, not android:columnCount!
-
-    <Button
-        android:id="@+id/button1"
-        app:layout_column="1"                            <=== again, note the app: namespace
-        app:layout_columnSpan="2"
-        app:layout_gravity="left"
-        app:layout_row="1"
-        android:text="Button" />
-
-    <CheckBox
-        android:id="@+id/checkBox1"
-        app:layout_column="4"
-        app:layout_gravity="left"
-        app:layout_row="2"
-        android:text="CheckBox" />
-
-    <Button
-        android:id="@+id/button2"
-        app:layout_column="5"
-        app:layout_gravity="left"
-        app:layout_row="3"
-        android:text="Button" />
-
-    <android.support.v4.widget.Space                    <=== space widgets also need the full support package path
-        android:layout_width="21dp"                     <=== use the android namespace for width, height etc -- only use app: for the grid layout library's new resources
-        android:layout_height="1dp"
-        app:layout_column="0"
-        app:layout_gravity="fill_horizontal"
-        app:layout_row="0" />
-
diff --git a/v7/gridlayout/project.properties b/v7/gridlayout/project.properties
deleted file mode 100644
index 1e106c3..0000000
--- a/v7/gridlayout/project.properties
+++ /dev/null
@@ -1,15 +0,0 @@
-# This file is automatically generated by Android Tools.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
-# This file must be checked in Version Control Systems.
-#
-# To customize properties used by the Ant build system edit
-# "ant.properties", and override values to adapt the script to your
-# project structure.
-#
-# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
-#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
-
-# Project target.
-target=android-7
-android.library=true
\ No newline at end of file
diff --git a/v7/gridlayout/src/.readme b/v7/gridlayout/src/.readme
deleted file mode 100644
index 4bcebad..0000000
--- a/v7/gridlayout/src/.readme
+++ /dev/null
@@ -1,2 +0,0 @@
-This hidden file is there to ensure there is an src folder.
-Once we support binary library this will go away.
\ No newline at end of file
diff --git a/v7/mediarouter/.classpath b/v7/mediarouter/.classpath
deleted file mode 100644
index a4763d1..0000000
--- a/v7/mediarouter/.classpath
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-	<classpathentry kind="src" path="src"/>
-	<classpathentry kind="src" path="gen"/>
-	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
-	<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
-	<classpathentry kind="output" path="bin/classes"/>
-</classpath>
diff --git a/v7/mediarouter/.gitignore b/v7/mediarouter/.gitignore
deleted file mode 100644
index c3c25e0..0000000
--- a/v7/mediarouter/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-.settings
-bin
-libs
-gen
diff --git a/v7/mediarouter/.project b/v7/mediarouter/.project
deleted file mode 100644
index 2eca48b..0000000
--- a/v7/mediarouter/.project
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-	<name>android-support-v7-mediarouter</name>
-	<comment></comment>
-	<projects>
-	</projects>
-	<buildSpec>
-		<buildCommand>
-			<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>org.eclipse.jdt.core.javabuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-		<buildCommand>
-			<name>com.android.ide.eclipse.adt.ApkBuilder</name>
-			<arguments>
-			</arguments>
-		</buildCommand>
-	</buildSpec>
-	<natures>
-		<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
-		<nature>org.eclipse.jdt.core.javanature</nature>
-	</natures>
-</projectDescription>
diff --git a/v7/mediarouter/AndroidManifest.xml b/v7/mediarouter/AndroidManifest.xml
index 5466168..c4577f9 100644
--- a/v7/mediarouter/AndroidManifest.xml
+++ b/v7/mediarouter/AndroidManifest.xml
@@ -16,6 +16,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.v7.mediarouter">
     <uses-sdk android:minSdkVersion="14"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/v7/mediarouter/README.txt b/v7/mediarouter/README.txt
deleted file mode 100644
index 5d99185..0000000
--- a/v7/mediarouter/README.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-Library Project including compatibility MediaRouter.
-
-This can be used by an Android project to provide
-access to MediaRouter on applications running on API 7+.
-
-There is technically no source, but the src folder is necessary
-to ensure that the build system works.  The content is actually
-located in libs/android-support-v7-mediarouter.jar.
-The accompanying resources must also be included in the application.
-
diff --git a/v7/mediarouter/project.properties b/v7/mediarouter/project.properties
deleted file mode 100644
index 484dab0..0000000
--- a/v7/mediarouter/project.properties
+++ /dev/null
@@ -1,15 +0,0 @@
-# This file is automatically generated by Android Tools.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
-# This file must be checked in Version Control Systems.
-#
-# To customize properties used by the Ant build system edit
-# "ant.properties", and override values to adapt the script to your
-# project structure.
-#
-# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
-#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
-
-# Project target.
-target=android-17
-android.library=true
diff --git a/v7/mediarouter/src/.readme b/v7/mediarouter/src/.readme
deleted file mode 100644
index 4bcebad..0000000
--- a/v7/mediarouter/src/.readme
+++ /dev/null
@@ -1,2 +0,0 @@
-This hidden file is there to ensure there is an src folder.
-Once we support binary library this will go away.
\ No newline at end of file
diff --git a/v7/palette/.classpath b/v7/palette/.classpath
deleted file mode 100644
index 43cb38c..0000000
--- a/v7/palette/.classpath
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-    <classpathentry kind="src" path="src"/>
-    <classpathentry kind="src" path="gen"/>
-    <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
-    <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
-    <classpathentry kind="output" path="bin/classes"/>
-</classpath>
diff --git a/v7/palette/.project b/v7/palette/.project
deleted file mode 100644
index 76e11a6..0000000
--- a/v7/palette/.project
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-    <name>android-support-v7-palette</name>
-    <comment></comment>
-    <projects>
-    </projects>
-    <buildSpec>
-        <buildCommand>
-            <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
-            <arguments>
-            </arguments>
-        </buildCommand>
-        <buildCommand>
-            <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
-            <arguments>
-            </arguments>
-        </buildCommand>
-        <buildCommand>
-            <name>org.eclipse.jdt.core.javabuilder</name>
-            <arguments>
-            </arguments>
-        </buildCommand>
-        <buildCommand>
-            <name>com.android.ide.eclipse.adt.ApkBuilder</name>
-            <arguments>
-            </arguments>
-        </buildCommand>
-    </buildSpec>
-    <natures>
-        <nature>com.android.ide.eclipse.adt.AndroidNature</nature>
-        <nature>org.eclipse.jdt.core.javanature</nature>
-    </natures>
-</projectDescription>
diff --git a/v7/palette/AndroidManifest.xml b/v7/palette/AndroidManifest.xml
index 78735ba..8e5ffaa 100644
--- a/v7/palette/AndroidManifest.xml
+++ b/v7/palette/AndroidManifest.xml
@@ -16,6 +16,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.v7.palette">
     <uses-sdk android:minSdkVersion="14"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/v7/palette/README.txt b/v7/palette/README.txt
deleted file mode 100644
index 4a8b5e4..0000000
--- a/v7/palette/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-Library Project including Palette for color extraction from images
diff --git a/v7/palette/project.properties b/v7/palette/project.properties
deleted file mode 100644
index 1e106c3..0000000
--- a/v7/palette/project.properties
+++ /dev/null
@@ -1,15 +0,0 @@
-# This file is automatically generated by Android Tools.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
-# This file must be checked in Version Control Systems.
-#
-# To customize properties used by the Ant build system edit
-# "ant.properties", and override values to adapt the script to your
-# project structure.
-#
-# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
-#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
-
-# Project target.
-target=android-7
-android.library=true
\ No newline at end of file
diff --git a/v7/preference/AndroidManifest.xml b/v7/preference/AndroidManifest.xml
index 6923656..772b410 100644
--- a/v7/preference/AndroidManifest.xml
+++ b/v7/preference/AndroidManifest.xml
@@ -16,6 +16,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.support.v7.preference">
     <uses-sdk android:minSdkVersion="14" />
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
-    <application />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/v7/recyclerview/AndroidManifest.xml b/v7/recyclerview/AndroidManifest.xml
index 5eef157..5b03882 100644
--- a/v7/recyclerview/AndroidManifest.xml
+++ b/v7/recyclerview/AndroidManifest.xml
@@ -16,5 +16,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.v7.recyclerview">
     <uses-sdk android:minSdkVersion="14"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>
diff --git a/v7/recyclerview/src/android/support/v7/util/DiffUtil.java b/v7/recyclerview/src/android/support/v7/util/DiffUtil.java
index 6f0a078..6302666 100644
--- a/v7/recyclerview/src/android/support/v7/util/DiffUtil.java
+++ b/v7/recyclerview/src/android/support/v7/util/DiffUtil.java
@@ -205,7 +205,7 @@
                 // we can reach k from k - 1 or k + 1. Check which one is further in the graph
                 int x;
                 final boolean removal;
-                if (k == -d || k != d && forward[kOffset + k - 1] < forward[kOffset + k + 1]) {
+                if (k == -d || (k != d && forward[kOffset + k - 1] < forward[kOffset + k + 1])) {
                     x = forward[kOffset + k + 1];
                     removal = false;
                 } else {
@@ -238,8 +238,8 @@
                 final int backwardK = k + delta;
                 int x;
                 final boolean removal;
-                if (backwardK == d + delta || backwardK != -d + delta
-                        && backward[kOffset + backwardK - 1] < backward[kOffset + backwardK + 1]) {
+                if (backwardK == d + delta || (backwardK != -d + delta
+                        && backward[kOffset + backwardK - 1] < backward[kOffset + backwardK + 1])) {
                     x = backward[kOffset + backwardK - 1];
                     removal = false;
                 } else {
diff --git a/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java b/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
index 33ad434..11600a8 100644
--- a/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
+++ b/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
@@ -255,8 +255,8 @@
     public boolean animateMove(final ViewHolder holder, int fromX, int fromY,
             int toX, int toY) {
         final View view = holder.itemView;
-        fromX += holder.itemView.getTranslationX();
-        fromY += holder.itemView.getTranslationY();
+        fromX += (int) holder.itemView.getTranslationX();
+        fromY += (int) holder.itemView.getTranslationY();
         resetAnimation(holder);
         int deltaX = toX - fromX;
         int deltaY = toY - fromY;
diff --git a/v7/recyclerview/src/android/support/v7/widget/FastScroller.java b/v7/recyclerview/src/android/support/v7/widget/FastScroller.java
index 9e45909..fbe234b 100644
--- a/v7/recyclerview/src/android/support/v7/widget/FastScroller.java
+++ b/v7/recyclerview/src/android/support/v7/widget/FastScroller.java
@@ -228,7 +228,7 @@
         switch (mAnimationState) {
             case ANIMATION_STATE_FADING_OUT:
                 mShowHideAnimator.cancel();
-                // no break
+                // fall through
             case ANIMATION_STATE_OUT:
                 mAnimationState = ANIMATION_STATE_FADING_IN;
                 mShowHideAnimator.setFloatValues((float) mShowHideAnimator.getAnimatedValue(), 1);
@@ -248,7 +248,7 @@
         switch (mAnimationState) {
             case ANIMATION_STATE_FADING_IN:
                 mShowHideAnimator.cancel();
-                // no break
+                // fall through
             case ANIMATION_STATE_IN:
                 mAnimationState = ANIMATION_STATE_FADING_OUT;
                 mShowHideAnimator.setFloatValues((float) mShowHideAnimator.getAnimatedValue(), 0);
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index 6e9ea69..342a740 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -4892,7 +4892,7 @@
 
         private float distanceInfluenceForSnapDuration(float f) {
             f -= 0.5f; // center the values about 0.
-            f *= 0.3f * Math.PI / 2.0f;
+            f *= 0.3f * (float) Math.PI / 2.0f;
             return (float) Math.sin(f);
         }
 
@@ -11162,8 +11162,8 @@
          * @param scrollVector The vector that points to the target scroll position
          */
         protected void normalize(PointF scrollVector) {
-            final double magnitude = Math.sqrt(scrollVector.x * scrollVector.x + scrollVector.y
-                    * scrollVector.y);
+            final float magnitude = (float) Math.sqrt(scrollVector.x * scrollVector.x
+                    + scrollVector.y * scrollVector.y);
             scrollVector.x /= magnitude;
             scrollVector.y /= magnitude;
         }
diff --git a/v7/recyclerview/src/android/support/v7/widget/helper/ItemTouchHelper.java b/v7/recyclerview/src/android/support/v7/widget/helper/ItemTouchHelper.java
index a0a86b0..b0a2cb3 100644
--- a/v7/recyclerview/src/android/support/v7/widget/helper/ItemTouchHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/helper/ItemTouchHelper.java
@@ -2283,7 +2283,7 @@
         }
     }
 
-    private class RecoverAnimation implements Animator.AnimatorListener {
+    private static class RecoverAnimation implements Animator.AnimatorListener {
 
         final float mStartDx;
 
diff --git a/v7/recyclerview/tests/Android.mk b/v7/recyclerview/tests/Android.mk
index 9c523b7..c6299d7 100644
--- a/v7/recyclerview/tests/Android.mk
+++ b/v7/recyclerview/tests/Android.mk
@@ -32,6 +32,7 @@
         android-support-annotations
 
 LOCAL_PACKAGE_NAME := RecyclerViewTests
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_AAPT_FLAGS := \
         --auto-add-overlay \
         --extra-packages android.support.v7.recyclerview
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
index 2142832..f2b56a1 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-
 package android.support.v7.widget;
 
 import static android.support.v7.widget.RecyclerView.NO_POSITION;
@@ -51,8 +50,10 @@
 import android.os.Build;
 import android.os.SystemClock;
 import android.support.annotation.Nullable;
+import android.support.test.filters.FlakyTest;
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.SdkSuppress;
+import android.support.test.filters.Suppress;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v4.view.ViewCompat;
 import android.support.v7.util.TouchUtils;
@@ -1486,7 +1487,10 @@
         }
     }
 
+    @Suppress
+    @FlakyTest(bugId = 33949798)
     @Test
+    @LargeTest
     public void hasPendingUpdatesBeforeFirstLayout() throws Throwable {
         RecyclerView recyclerView = new RecyclerView(getActivity());
         TestLayoutManager layoutManager = new DumbLayoutManager();
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java
index 984db5b..6a32572 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java
@@ -16,7 +16,6 @@
 
 package android.support.v7.widget;
 
-
 import static android.support.v7.widget.LayoutState.LAYOUT_START;
 import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
 import static android.support.v7.widget.StaggeredGridLayoutManager.HORIZONTAL;
@@ -34,7 +33,9 @@
 import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.test.filters.FlakyTest;
 import android.support.test.filters.LargeTest;
+import android.support.test.filters.Suppress;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewParent;
@@ -738,7 +739,10 @@
         consistentRelayoutTest(mConfig, true);
     }
 
+    @Suppress
+    @FlakyTest(bugId = 34158822)
     @Test
+    @LargeTest
     public void dontRecycleViewsTranslatedOutOfBoundsFromStart() throws Throwable {
         final Config config = ((Config) mConfig.clone()).itemCount(1000);
         setupByConfig(config);
diff --git a/wearable/AndroidManifest.xml b/wearable/AndroidManifest.xml
index 77ac1b6..e4b1897 100644
--- a/wearable/AndroidManifest.xml
+++ b/wearable/AndroidManifest.xml
@@ -16,5 +16,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.support.wearable">
     <uses-sdk android:minSdkVersion="22"/>
-    <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    <application>
+        <meta-data android:name="android.support.VERSION" android:value="${support-version}" />
+    </application>
 </manifest>