Merge "MediaBrowserCompat: Fix NPE when CustomActionCallback is null" into oc-support-26.0-dev
diff --git a/annotations/src/android/support/annotation/MainThread.java b/annotations/src/android/support/annotation/MainThread.java
index 5478751..2f50306 100644
--- a/annotations/src/android/support/annotation/MainThread.java
+++ b/annotations/src/android/support/annotation/MainThread.java
@@ -34,9 +34,17 @@
  *  @MainThread
  *  public void deliverResult(D data) { ... }
  * </code></pre>
+ *
+ * <p class="note"><b>Note:</b> Ordinarily, an app's main thread is also the UI
+ * thread. However, However, under special circumstances, an app's main thread
+ * might not be its UI thread; for more information, see
+ * <a href="/studio/write/annotations.html#thread-annotations">Thread
+ * annotations</a>.
+ *
+ * @see android.support.annotation.UiThread
  */
 @Documented
 @Retention(CLASS)
 @Target({METHOD,CONSTRUCTOR,TYPE})
 public @interface MainThread {
-}
\ No newline at end of file
+}
diff --git a/annotations/src/android/support/annotation/UiThread.java b/annotations/src/android/support/annotation/UiThread.java
index ef81986..0a9a0c1 100644
--- a/annotations/src/android/support/annotation/UiThread.java
+++ b/annotations/src/android/support/annotation/UiThread.java
@@ -35,9 +35,17 @@
  *
  *  public abstract void setText(@NonNull String text) { ... }
  * </code></pre>
+ *
+ * <p class="note"><b>Note:</b> Ordinarily, an app's UI thread is also the main
+ * thread. However, However, under special circumstances, an app's UI thread
+ * might not be its main thread; for more information, see
+ * <a href="/studio/write/annotations.html#thread-annotations">Thread
+ * annotations</a>.
+ *
+ * @see android.support.annotation.MainThread
  */
 @Documented
 @Retention(CLASS)
 @Target({METHOD,CONSTRUCTOR,TYPE})
 public @interface UiThread {
-}
\ No newline at end of file
+}
diff --git a/api/26.0.0-SNAPSHOT.txt b/api/26.0.0-SNAPSHOT.txt
index 4001952..aafd5f9 100644
--- a/api/26.0.0-SNAPSHOT.txt
+++ b/api/26.0.0-SNAPSHOT.txt
@@ -1040,7 +1040,9 @@
     method public boolean hasThumbnail();
     method public boolean isThumbnailCompressed();
     method public void saveAttributes() throws java.io.IOException;
+    method public void setAltitude(double);
     method public void setAttribute(java.lang.String, java.lang.String);
+    method public void setGpsInfo(android.location.Location);
     method public void setLatLong(double, double);
     field public static final int ORIENTATION_FLIP_HORIZONTAL = 2; // 0x2
     field public static final int ORIENTATION_FLIP_VERTICAL = 4; // 0x4
@@ -6233,7 +6235,6 @@
     method public static int getActionCount(android.app.Notification);
     method public static int getBadgeIconType(android.app.Notification);
     method public static java.lang.String getCategory(android.app.Notification);
-    method public static deprecated java.lang.String getChannel(android.app.Notification);
     method public static java.lang.String getChannelId(android.app.Notification);
     method public static android.os.Bundle getExtras(android.app.Notification);
     method public static java.lang.String getGroup(android.app.Notification);
@@ -6241,7 +6242,6 @@
     method public static boolean getLocalOnly(android.app.Notification);
     method public static java.lang.String getShortcutId(android.app.Notification);
     method public static java.lang.String getSortKey(android.app.Notification);
-    method public static deprecated long getTimeout(android.app.Notification);
     method public static long getTimeoutAfter(android.app.Notification);
     method public static boolean isGroupSummary(android.app.Notification);
     field public static final int BADGE_ICON_LARGE = 2; // 0x2
@@ -6398,7 +6398,6 @@
     method public android.support.v4.app.NotificationCompat.Builder setAutoCancel(boolean);
     method public android.support.v4.app.NotificationCompat.Builder setBadgeIconType(int);
     method public android.support.v4.app.NotificationCompat.Builder setCategory(java.lang.String);
-    method public deprecated android.support.v4.app.NotificationCompat.Builder setChannel(java.lang.String);
     method public android.support.v4.app.NotificationCompat.Builder setChannelId(java.lang.String);
     method public android.support.v4.app.NotificationCompat.Builder setColor(int);
     method public android.support.v4.app.NotificationCompat.Builder setColorized(boolean);
@@ -6438,7 +6437,6 @@
     method public android.support.v4.app.NotificationCompat.Builder setSubText(java.lang.CharSequence);
     method public android.support.v4.app.NotificationCompat.Builder setTicker(java.lang.CharSequence);
     method public android.support.v4.app.NotificationCompat.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
-    method public deprecated android.support.v4.app.NotificationCompat.Builder setTimeout(long);
     method public android.support.v4.app.NotificationCompat.Builder setTimeoutAfter(long);
     method public android.support.v4.app.NotificationCompat.Builder setUsesChronometer(boolean);
     method public android.support.v4.app.NotificationCompat.Builder setVibrate(long[]);
@@ -12656,7 +12654,8 @@
     method public void attachToRecyclerView(android.support.v7.widget.RecyclerView) throws java.lang.IllegalStateException;
     method public abstract int[] calculateDistanceToFinalSnap(android.support.v7.widget.RecyclerView.LayoutManager, android.view.View);
     method public int[] calculateScrollDistance(int, int);
-    method protected android.support.v7.widget.LinearSmoothScroller createSnapScroller(android.support.v7.widget.RecyclerView.LayoutManager);
+    method protected android.support.v7.widget.RecyclerView.SmoothScroller createScroller(android.support.v7.widget.RecyclerView.LayoutManager);
+    method protected deprecated android.support.v7.widget.LinearSmoothScroller createSnapScroller(android.support.v7.widget.RecyclerView.LayoutManager);
     method public abstract android.view.View findSnapView(android.support.v7.widget.RecyclerView.LayoutManager);
     method public abstract int findTargetSnapPosition(android.support.v7.widget.RecyclerView.LayoutManager, int, int);
     method public boolean onFling(int, int);
@@ -12958,6 +12957,34 @@
     field public int boxedEdges;
   }
 
+  public class CircularProgressLayout extends android.widget.FrameLayout {
+    ctor public CircularProgressLayout(android.content.Context);
+    ctor public CircularProgressLayout(android.content.Context, android.util.AttributeSet);
+    ctor public CircularProgressLayout(android.content.Context, android.util.AttributeSet, int);
+    ctor public CircularProgressLayout(android.content.Context, android.util.AttributeSet, int, int);
+    method public int getBackgroundColor();
+    method public int[] getColorSchemeColors();
+    method public android.support.wear.widget.CircularProgressLayout.OnTimerFinishedListener getOnTimerFinishedListener();
+    method public android.support.v4.widget.CircularProgressDrawable getProgressDrawable();
+    method public float getStartingRotation();
+    method public float getStrokeWidth();
+    method public long getTotalTime();
+    method public boolean isIndeterminate();
+    method public boolean isTimerRunning();
+    method public void setColorSchemeColors(int...);
+    method public void setIndeterminate(boolean);
+    method public void setOnTimerFinishedListener(android.support.wear.widget.CircularProgressLayout.OnTimerFinishedListener);
+    method public void setStartingRotation(float);
+    method public void setStrokeWidth(float);
+    method public void setTotalTime(long);
+    method public void startTimer();
+    method public void stopTimer();
+  }
+
+  public static abstract interface CircularProgressLayout.OnTimerFinishedListener {
+    method public abstract void onTimerFinished(android.support.wear.widget.CircularProgressLayout);
+  }
+
   public class CurvingLayoutCallback extends android.support.wear.widget.WearableLinearLayoutManager.LayoutCallback {
     ctor public CurvingLayoutCallback(android.content.Context);
     method public void adjustAnchorOffsetXY(android.view.View, float[]);
diff --git a/buildSrc/dependencies.gradle b/buildSrc/dependencies.gradle
index 5cf3ae6..c71f344 100644
--- a/buildSrc/dependencies.gradle
+++ b/buildSrc/dependencies.gradle
@@ -25,6 +25,7 @@
 libs.espresso_core = 'com.android.support.test.espresso:espresso-core:2.3-alpha'
 libs.espresso_contrib = 'com.android.support.test.espresso:espresso-contrib:2.3-alpha'
 libs.jacoco = 'org.jacoco:org.jacoco.core:0.7.8'
+libs.test_rules = 'com.android.support.test:rules:0.6-alpha'
 
 def androidPluginVersionOverride = System.getenv("GRADLE_PLUGIN_VERSION")
 
diff --git a/buildSrc/init.gradle b/buildSrc/init.gradle
index ee92488..4e66dc8 100644
--- a/buildSrc/init.gradle
+++ b/buildSrc/init.gradle
@@ -201,26 +201,26 @@
                             destinationDir file(project.distDir)
                             archiveName "${project.archivesBaseName}-${v.baseName}-allclasses.jar"
                         }
-                        def jacocoAntConfig =
-                                project.configurations[JacocoPlugin.ANT_CONFIGURATION_NAME]
-                        def jacocoAntArtifacts = jacocoAntConfig.resolvedConfiguration
-                                .resolvedArtifacts
-                        def version = jacocoAntArtifacts.find { "org.jacoco.ant".equals(it.name) }
-                                .moduleVersion.id.version
+
                         def collectJacocoAntPackages = project.tasks.create(
                                 name: "collectJacocoAntPackages",
                                 type: Jar) {
-                            from(jacocoAntArtifacts.collect { zipTree(it.getFile()) }) {
-                                // exclude all the signatures the jar might have
-                                exclude "META-INF/*.SF"
-                                exclude "META-INF/*.DSA"
-                                exclude "META-INF/*.RSA"
-                            }
+                            inputs.files project.configurations[JacocoPlugin.ANT_CONFIGURATION_NAME]
+                            from {
+                                project.configurations[JacocoPlugin.ANT_CONFIGURATION_NAME]
+                                        .resolvedConfiguration
+                                        .resolvedArtifacts.collect{ zipTree(it.getFile()) }} {
+                                    // exclude all the signatures the jar might have
+                                    exclude "META-INF/*.SF"
+                                    exclude "META-INF/*.DSA"
+                                    exclude "META-INF/*.RSA"
+                                }
                             destinationDir file(project.distDir)
-                            archiveName "jacocoant-" + version + ".jar"
+                            archiveName "jacocoant.jar"
                         }
+
                         jarifyTask.dependsOn v.getJavaCompiler()
-                        v.assemble.dependsOn jarifyTask, collectJacocoAntPackages
+                        v.assemble.dependsOn jarifyTask , collectJacocoAntPackages
                     }
                 }
 
diff --git a/compat/api24/android/support/v4/app/NotificationCompatApi24.java b/compat/api24/android/support/v4/app/NotificationCompatApi24.java
index 17e5cad..67ea5dc 100644
--- a/compat/api24/android/support/v4/app/NotificationCompatApi24.java
+++ b/compat/api24/android/support/v4/app/NotificationCompatApi24.java
@@ -30,6 +30,7 @@
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Parcelable;
 import android.support.annotation.RequiresApi;
 import android.widget.RemoteViews;
 
@@ -181,8 +182,89 @@
         }
         actionExtras.putBoolean(NotificationCompatJellybean.EXTRA_ALLOW_GENERATED_REPLIES,
                 action.getAllowGeneratedReplies());
-        actionBuilder.addExtras(actionExtras);
         actionBuilder.setAllowGeneratedReplies(action.getAllowGeneratedReplies());
+        actionBuilder.addExtras(actionExtras);
         b.addAction(actionBuilder.build());
     }
+
+    public static NotificationCompatBase.Action getAction(Notification notif,
+            int actionIndex, NotificationCompatBase.Action.Factory actionFactory,
+            RemoteInputCompatBase.RemoteInput.Factory remoteInputFactory) {
+        return getActionCompatFromAction(notif.actions[actionIndex], actionFactory,
+                remoteInputFactory);
+    }
+
+    private static NotificationCompatBase.Action getActionCompatFromAction(
+            Notification.Action action, NotificationCompatBase.Action.Factory actionFactory,
+            RemoteInputCompatBase.RemoteInput.Factory remoteInputFactory) {
+        RemoteInputCompatBase.RemoteInput[] remoteInputs = RemoteInputCompatApi20.toCompat(
+                action.getRemoteInputs(), remoteInputFactory);
+        boolean allowGeneratedReplies = action.getExtras().getBoolean(
+                NotificationCompatJellybean.EXTRA_ALLOW_GENERATED_REPLIES)
+                || action.getAllowGeneratedReplies();
+        return actionFactory.build(action.icon, action.title, action.actionIntent,
+                action.getExtras(), remoteInputs, null, allowGeneratedReplies);
+    }
+
+    private static Notification.Action getActionFromActionCompat(
+            NotificationCompatBase.Action actionCompat) {
+        Notification.Action.Builder actionBuilder = new Notification.Action.Builder(
+                actionCompat.getIcon(), actionCompat.getTitle(), actionCompat.getActionIntent());
+        Bundle actionExtras;
+        if (actionCompat.getExtras() != null) {
+            actionExtras = new Bundle(actionCompat.getExtras());
+        } else {
+            actionExtras = new Bundle();
+        }
+        actionExtras.putBoolean(NotificationCompatJellybean.EXTRA_ALLOW_GENERATED_REPLIES,
+                actionCompat.getAllowGeneratedReplies());
+        actionBuilder.setAllowGeneratedReplies(actionCompat.getAllowGeneratedReplies());
+        actionBuilder.addExtras(actionExtras);
+        RemoteInputCompatBase.RemoteInput[] remoteInputCompats = actionCompat.getRemoteInputs();
+        if (remoteInputCompats != null) {
+            RemoteInput[] remoteInputs = RemoteInputCompatApi20.fromCompat(remoteInputCompats);
+            for (RemoteInput remoteInput : remoteInputs) {
+                actionBuilder.addRemoteInput(remoteInput);
+            }
+        }
+        return actionBuilder.build();
+    }
+
+    /**
+     * Get a list of notification compat actions by parsing actions stored within a list of
+     * parcelables using the {@link Bundle#getParcelableArrayList} function in the same
+     * manner that framework code would do so. In API20, Using Action parcelable directly
+     * is correct.
+     */
+    public static NotificationCompatBase.Action[] getActionsFromParcelableArrayList(
+            ArrayList<Parcelable> parcelables,
+            NotificationCompatBase.Action.Factory actionFactory,
+            RemoteInputCompatBase.RemoteInput.Factory remoteInputFactory) {
+        if (parcelables == null) {
+            return null;
+        }
+        NotificationCompatBase.Action[] actions = actionFactory.newArray(parcelables.size());
+        for (int i = 0; i < actions.length; i++) {
+            Notification.Action action = (Notification.Action) parcelables.get(i);
+            actions[i] = getActionCompatFromAction(action, actionFactory, remoteInputFactory);
+        }
+        return actions;
+    }
+
+    /**
+     * Get an array list of parcelables, suitable for {@link Bundle#putParcelableArrayList},
+     * that matches what framework code would do to store an actions list in this way. In API20,
+     * action parcelables were directly placed as entries in the array list.
+     */
+    public static ArrayList<Parcelable> getParcelableArrayListForActions(
+            NotificationCompatBase.Action[] actions) {
+        if (actions == null) {
+            return null;
+        }
+        ArrayList<Parcelable> parcelables = new ArrayList<Parcelable>(actions.length);
+        for (NotificationCompatBase.Action action : actions) {
+            parcelables.add(getActionFromActionCompat(action));
+        }
+        return parcelables;
+    }
 }
diff --git a/compat/ics/android/support/v4/graphics/PaintCompatApi14.java b/compat/ics/android/support/v4/graphics/PaintCompatApi14.java
index 86e87d8..7a7de7c 100644
--- a/compat/ics/android/support/v4/graphics/PaintCompatApi14.java
+++ b/compat/ics/android/support/v4/graphics/PaintCompatApi14.java
@@ -24,6 +24,7 @@
 class PaintCompatApi14 {
     // U+DFFFD which is very end of unassigned plane.
     private static final String TOFU_STRING = "\uDB3F\uDFFD";
+    private static final String EM_STRING = "m";
 
     private static final ThreadLocal<Pair<Rect, Rect>> sRectThreadLocal = new ThreadLocal<>();
 
@@ -36,6 +37,8 @@
         }
 
         final float missingGlyphWidth = paint.measureText(TOFU_STRING);
+        final float emGlyphWidth = paint.measureText(EM_STRING);
+
         final float width = paint.measureText(string);
 
         if (width == 0f) {
@@ -46,7 +49,7 @@
         if (string.codePointCount(0, string.length()) > 1) {
             // Heuristic to detect fallback glyphs for ligatures like flags and ZWJ sequences
             // Return false if string is rendered too widely
-            if (width > 2 * missingGlyphWidth) {
+            if (width > 2 * emGlyphWidth) {
                 return false;
             }
 
diff --git a/compat/java/android/support/v4/app/NotificationCompat.java b/compat/java/android/support/v4/app/NotificationCompat.java
index 9129cc7..10da04a 100644
--- a/compat/java/android/support/v4/app/NotificationCompat.java
+++ b/compat/java/android/support/v4/app/NotificationCompat.java
@@ -24,6 +24,7 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.media.AudioManager;
@@ -34,9 +35,15 @@
 import android.support.annotation.ColorInt;
 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.text.BidiFormatter;
 import android.support.v4.view.GravityCompat;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.TextAppearanceSpan;
 import android.view.Gravity;
 import android.widget.RemoteViews;
 
@@ -597,10 +604,27 @@
     @RestrictTo(LIBRARY_GROUP)
     protected static class BuilderExtender {
         public Notification build(Builder b, NotificationBuilderWithBuilderAccessor builder) {
+            RemoteViews styleContentView = b.mStyle != null
+                    ? b.mStyle.makeContentView(builder)
+                    : null;
             Notification n = builder.build();
-            if (b.mContentView != null) {
+            if (styleContentView != null) {
+                n.contentView = styleContentView;
+            } else if (b.mContentView != null) {
                 n.contentView = b.mContentView;
             }
+            if (Build.VERSION.SDK_INT >= 16 && b.mStyle != null) {
+                RemoteViews styleBigContentView = b.mStyle.makeBigContentView(builder);
+                if (styleBigContentView != null) {
+                    n.bigContentView = styleBigContentView;
+                }
+            }
+            if (Build.VERSION.SDK_INT >= 21 && b.mStyle != null) {
+                RemoteViews styleHeadsUpContentView = b.mStyle.makeHeadsUpContentView(builder);
+                if (styleHeadsUpContentView != null) {
+                    n.headsUpContentView = styleHeadsUpContentView;
+                }
+            }
             return n;
         }
     }
@@ -655,7 +679,7 @@
         public Notification build(Builder b, BuilderExtender extender) {
             BuilderBase builder =
                     new BuilderBase(b.mContext, b.mNotification,
-                            b.resolveTitle(), b.resolveText(), b.mContentInfo, b.mTickerView,
+                            b.mContentTitle, b.mContentText, b.mContentInfo, b.mTickerView,
                             b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
                             b.mProgressMax, b.mProgress, b.mProgressIndeterminate);
             return extender.build(b, builder);
@@ -694,13 +718,15 @@
         @Override
         public Notification build(Builder b, BuilderExtender extender) {
             NotificationCompatJellybean.Builder builder = new NotificationCompatJellybean.Builder(
-                    b.mContext, b.mNotification, b.resolveTitle(), b.resolveText(), b.mContentInfo,
+                    b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo,
                     b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
                     b.mProgressMax, b.mProgress, b.mProgressIndeterminate,
                     b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly, b.mExtras,
                     b.mGroupKey, b.mGroupSummary, b.mSortKey, b.mContentView, b.mBigContentView);
             addActionsToBuilder(builder, b.mActions);
-            addStyleToBuilderJellybean(builder, b.mStyle);
+            if (b.mStyle != null) {
+                b.mStyle.apply(builder);
+            }
             Notification notification = extender.build(b, builder);
             if (b.mStyle != null) {
                 Bundle extras = getExtras(notification);
@@ -736,14 +762,16 @@
         @Override
         public Notification build(Builder b, BuilderExtender extender) {
             NotificationCompatKitKat.Builder builder = new NotificationCompatKitKat.Builder(
-                    b.mContext, b.mNotification, b.resolveTitle(), b.resolveText(), b.mContentInfo,
+                    b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo,
                     b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
                     b.mProgressMax, b.mProgress, b.mProgressIndeterminate, b.mShowWhen,
                     b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly,
                     b.mPeople, b.mExtras, b.mGroupKey, b.mGroupSummary, b.mSortKey,
                     b.mContentView, b.mBigContentView);
             addActionsToBuilder(builder, b.mActions);
-            addStyleToBuilderJellybean(builder, b.mStyle);
+            if (b.mStyle != null) {
+                b.mStyle.apply(builder);
+            }
             return extender.build(b, builder);
         }
 
@@ -759,14 +787,16 @@
         @Override
         public Notification build(Builder b, BuilderExtender extender) {
             NotificationCompatApi20.Builder builder = new NotificationCompatApi20.Builder(
-                    b.mContext, b.mNotification, b.resolveTitle(), b.resolveText(), b.mContentInfo,
+                    b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo,
                     b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
                     b.mProgressMax, b.mProgress, b.mProgressIndeterminate, b.mShowWhen,
                     b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly, b.mPeople, b.mExtras,
                     b.mGroupKey, b.mGroupSummary, b.mSortKey, b.mContentView, b.mBigContentView,
                     b.mGroupAlertBehavior);
             addActionsToBuilder(builder, b.mActions);
-            addStyleToBuilderJellybean(builder, b.mStyle);
+            if (b.mStyle != null) {
+                b.mStyle.apply(builder);
+            }
             Notification notification = extender.build(b, builder);
             if (b.mStyle != null) {
                 b.mStyle.addCompatExtras(getExtras(notification));
@@ -799,7 +829,7 @@
         @Override
         public Notification build(Builder b, BuilderExtender extender) {
             NotificationCompatApi21.Builder builder = new NotificationCompatApi21.Builder(
-                    b.mContext, b.mNotification, b.resolveTitle(), b.resolveText(), b.mContentInfo,
+                    b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo,
                     b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
                     b.mProgressMax, b.mProgress, b.mProgressIndeterminate, b.mShowWhen,
                     b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly, b.mCategory,
@@ -807,7 +837,9 @@
                     b.mGroupKey, b.mGroupSummary, b.mSortKey, b.mContentView, b.mBigContentView,
                     b.mHeadsUpContentView, b.mGroupAlertBehavior);
             addActionsToBuilder(builder, b.mActions);
-            addStyleToBuilderJellybean(builder, b.mStyle);
+            if (b.mStyle != null) {
+                b.mStyle.apply(builder);
+            }
             Notification notification = extender.build(b, builder);
             if (b.mStyle != null) {
                 b.mStyle.addCompatExtras(getExtras(notification));
@@ -843,13 +875,34 @@
                     b.mGroupKey, b.mGroupSummary, b.mSortKey, b.mRemoteInputHistory, b.mContentView,
                     b.mBigContentView, b.mHeadsUpContentView, b.mGroupAlertBehavior);
             addActionsToBuilder(builder, b.mActions);
-            addStyleToBuilderApi24(builder, b.mStyle);
+            if (b.mStyle != null) {
+                b.mStyle.apply(builder);
+            }
             Notification notification = extender.build(b, builder);
             if (b.mStyle != null) {
                 b.mStyle.addCompatExtras(getExtras(notification));
             }
             return notification;
         }
+
+        @Override
+        public Action getAction(Notification n, int actionIndex) {
+            return (Action) NotificationCompatApi24.getAction(n, actionIndex, Action.FACTORY,
+                    RemoteInput.FACTORY);
+        }
+
+        @Override
+        public Action[] getActionsFromParcelableArrayList(
+                ArrayList<Parcelable> parcelables) {
+            return (Action[]) NotificationCompatApi24.getActionsFromParcelableArrayList(
+                    parcelables, Action.FACTORY, RemoteInput.FACTORY);
+        }
+
+        @Override
+        public ArrayList<Parcelable> getParcelableArrayListForActions(
+                Action[] actions) {
+            return NotificationCompatApi24.getParcelableArrayListForActions(actions);
+        }
     }
 
     @RequiresApi(26)
@@ -868,7 +921,9 @@
                     b.mShortcutId, b.mTimeout, b.mColorized, b.mColorizedSet,
                     b.mGroupAlertBehavior);
             addActionsToBuilder(builder, b.mActions);
-            addStyleToBuilderApi24(builder, b.mStyle);
+            if (b.mStyle != null) {
+                b.mStyle.apply(builder);
+            }
             Notification notification = extender.build(b, builder);
             if (b.mStyle != null) {
                 b.mStyle.addCompatExtras(getExtras(notification));
@@ -884,65 +939,6 @@
         }
     }
 
-    @RequiresApi(16)
-    static void addStyleToBuilderJellybean(NotificationBuilderWithBuilderAccessor builder,
-            Style style) {
-        if (style != null) {
-            if (style instanceof BigTextStyle) {
-                BigTextStyle bigTextStyle = (BigTextStyle) style;
-                NotificationCompatJellybean.addBigTextStyle(builder,
-                        bigTextStyle.mBigContentTitle,
-                        bigTextStyle.mSummaryTextSet,
-                        bigTextStyle.mSummaryText,
-                        bigTextStyle.mBigText);
-            } else if (style instanceof InboxStyle) {
-                InboxStyle inboxStyle = (InboxStyle) style;
-                NotificationCompatJellybean.addInboxStyle(builder,
-                        inboxStyle.mBigContentTitle,
-                        inboxStyle.mSummaryTextSet,
-                        inboxStyle.mSummaryText,
-                        inboxStyle.mTexts);
-            } else if (style instanceof BigPictureStyle) {
-                BigPictureStyle bigPictureStyle = (BigPictureStyle) style;
-                NotificationCompatJellybean.addBigPictureStyle(builder,
-                        bigPictureStyle.mBigContentTitle,
-                        bigPictureStyle.mSummaryTextSet,
-                        bigPictureStyle.mSummaryText,
-                        bigPictureStyle.mPicture,
-                        bigPictureStyle.mBigLargeIcon,
-                        bigPictureStyle.mBigLargeIconSet);
-            }
-        }
-    }
-
-    @RequiresApi(24)
-    static void addStyleToBuilderApi24(NotificationBuilderWithBuilderAccessor builder,
-            Style style) {
-        if (style != null) {
-            if (style instanceof MessagingStyle) {
-                MessagingStyle messagingStyle = (MessagingStyle) style;
-                List<CharSequence> texts = new ArrayList<>();
-                List<Long> timestamps = new ArrayList<>();
-                List<CharSequence> senders = new ArrayList<>();
-                List<String> dataMimeTypes = new ArrayList<>();
-                List<Uri> dataUris = new ArrayList<>();
-
-                for (MessagingStyle.Message message : messagingStyle.mMessages) {
-                    texts.add(message.getText());
-                    timestamps.add(message.getTimestamp());
-                    senders.add(message.getSender());
-                    dataMimeTypes.add(message.getDataMimeType());
-                    dataUris.add(message.getDataUri());
-                }
-                NotificationCompatApi24.addMessagingStyle(builder, messagingStyle.mUserDisplayName,
-                        messagingStyle.mConversationTitle, texts, timestamps, senders,
-                        dataMimeTypes, dataUris);
-            } else {
-                addStyleToBuilderJellybean(builder, style);
-            }
-        }
-    }
-
     static {
         if (Build.VERSION.SDK_INT >= 26) {
             IMPL = new NotificationCompatApi26Impl();
@@ -1778,12 +1774,6 @@
             return this;
         }
 
-        /** @deprecated removed from API 26 */
-        @Deprecated
-        public Builder setChannel(@NonNull String channelId) {
-            return setChannelId(channelId);
-        }
-
         /**
          * Specifies the time at which this notification should be canceled, if it is not already
          * canceled.
@@ -1793,12 +1783,6 @@
             return this;
         }
 
-        /** @deprecated removed from API 26 */
-        @Deprecated
-        public Builder setTimeout(long durationMs) {
-            return setTimeoutAfter(durationMs);
-        }
-
         /**
          * If this notification is duplicative of a Launcher shortcut, sets the
          * {@link android.support.v4.content.pm.ShortcutInfoCompat#getId() id} of the shortcut, in
@@ -1936,27 +1920,6 @@
         public int getColor() {
             return mColor;
         }
-
-
-        /**
-         * @return the text of the notification
-         *
-         * @hide
-         */
-        @RestrictTo(LIBRARY_GROUP)
-        protected CharSequence resolveText() {
-            return mContentText;
-        }
-
-        /**
-         * @return the title of the notification
-         *
-         * @hide
-         */
-        @RestrictTo(LIBRARY_GROUP)
-        protected CharSequence resolveTitle() {
-            return mContentTitle;
-        }
     }
 
     /**
@@ -1967,7 +1930,11 @@
      * effect.
      */
     public static abstract class Style {
-        Builder mBuilder;
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        protected Builder mBuilder;
         CharSequence mBigContentTitle;
         CharSequence mSummaryText;
         boolean mSummaryTextSet = false;
@@ -1994,6 +1961,38 @@
          */
         @RestrictTo(LIBRARY_GROUP)
         // TODO: implement for all styles
+        public void apply(NotificationBuilderWithBuilderAccessor builder) {
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
+            return null;
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
+            return null;
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        public RemoteViews makeHeadsUpContentView(NotificationBuilderWithBuilderAccessor builder) {
+            return null;
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        // TODO: implement for all styles
         public void addCompatExtras(Bundle extras) {
         }
 
@@ -2027,9 +2026,9 @@
      * @see Notification#bigContentView
      */
     public static class BigPictureStyle extends Style {
-        Bitmap mPicture;
-        Bitmap mBigLargeIcon;
-        boolean mBigLargeIconSet;
+        private Bitmap mPicture;
+        private Bitmap mBigLargeIcon;
+        private boolean mBigLargeIconSet;
 
         public BigPictureStyle() {
         }
@@ -2072,6 +2071,23 @@
             mBigLargeIconSet = true;
             return this;
         }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public void apply(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 16) {
+                NotificationCompatJellybean.addBigPictureStyle(builder,
+                        mBigContentTitle,
+                        mSummaryTextSet,
+                        mSummaryText,
+                        mPicture,
+                        mBigLargeIcon,
+                        mBigLargeIconSet);
+            }
+        }
     }
 
     /**
@@ -2096,7 +2112,7 @@
      * @see Notification#bigContentView
      */
     public static class BigTextStyle extends Style {
-        CharSequence mBigText;
+        private CharSequence mBigText;
 
         public BigTextStyle() {
         }
@@ -2131,6 +2147,21 @@
             mBigText = Builder.limitCharSequenceLength(cs);
             return this;
         }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public void apply(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 16) {
+                NotificationCompatJellybean.addBigTextStyle(builder,
+                        mBigContentTitle,
+                        mSummaryTextSet,
+                        mSummaryText,
+                        mBigText);
+            }
+        }
     }
 
     /**
@@ -2283,6 +2314,121 @@
             return style;
         }
 
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public void apply(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 24) {
+                List<CharSequence> texts = new ArrayList<>();
+                List<Long> timestamps = new ArrayList<>();
+                List<CharSequence> senders = new ArrayList<>();
+                List<String> dataMimeTypes = new ArrayList<>();
+                List<Uri> dataUris = new ArrayList<>();
+
+                for (MessagingStyle.Message message : mMessages) {
+                    texts.add(message.getText());
+                    timestamps.add(message.getTimestamp());
+                    senders.add(message.getSender());
+                    dataMimeTypes.add(message.getDataMimeType());
+                    dataUris.add(message.getDataUri());
+                }
+                NotificationCompatApi24.addMessagingStyle(builder, mUserDisplayName,
+                        mConversationTitle, texts, timestamps, senders,
+                        dataMimeTypes, dataUris);
+            } else {
+                MessagingStyle.Message latestIncomingMessage = findLatestIncomingMessage();
+                // Set the title
+                if (mConversationTitle != null) {
+                    builder.getBuilder().setContentTitle(mConversationTitle);
+                } else if (latestIncomingMessage != null) {
+                    builder.getBuilder().setContentTitle(latestIncomingMessage.getSender());
+                }
+                // Set the text
+                if (latestIncomingMessage != null) {
+                    builder.getBuilder().setContentText(mConversationTitle != null
+                            ? makeMessageLine(latestIncomingMessage)
+                            : latestIncomingMessage.getText());
+                }
+                // Build a fallback BigTextStyle for API 16-23 devices
+                if (Build.VERSION.SDK_INT >= 16) {
+                    SpannableStringBuilder completeMessage = new SpannableStringBuilder();
+                    boolean showNames = mConversationTitle != null
+                            || hasMessagesWithoutSender();
+                    for (int i = mMessages.size() - 1; i >= 0; i--) {
+                        MessagingStyle.Message message = mMessages.get(i);
+                        CharSequence line;
+                        line = showNames ? makeMessageLine(message) : message.getText();
+                        if (i != mMessages.size() - 1) {
+                            completeMessage.insert(0, "\n");
+                        }
+                        completeMessage.insert(0, line);
+                    }
+                    NotificationCompatJellybean.addBigTextStyle(builder,
+                            null,
+                            false,
+                            null,
+                            completeMessage);
+                }
+            }
+        }
+
+        @Nullable
+        private MessagingStyle.Message findLatestIncomingMessage() {
+            for (int i = mMessages.size() - 1; i >= 0; i--) {
+                MessagingStyle.Message message = mMessages.get(i);
+                // Incoming messages have a non-empty sender.
+                if (!TextUtils.isEmpty(message.getSender())) {
+                    return message;
+                }
+            }
+            if (!mMessages.isEmpty()) {
+                // No incoming messages, fall back to outgoing message
+                return mMessages.get(mMessages.size() - 1);
+            }
+            return null;
+        }
+
+        private boolean hasMessagesWithoutSender() {
+            for (int i = mMessages.size() - 1; i >= 0; i--) {
+                MessagingStyle.Message message = mMessages.get(i);
+                if (message.getSender() == null) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private CharSequence makeMessageLine(MessagingStyle.Message message) {
+            BidiFormatter bidi = BidiFormatter.getInstance();
+            SpannableStringBuilder sb = new SpannableStringBuilder();
+            final boolean afterLollipop = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
+            int color = afterLollipop ? Color.BLACK : Color.WHITE;
+            CharSequence replyName = message.getSender();
+            if (TextUtils.isEmpty(message.getSender())) {
+                replyName = mUserDisplayName == null
+                        ? "" : mUserDisplayName;
+                color = afterLollipop && mBuilder.getColor() != NotificationCompat.COLOR_DEFAULT
+                        ? mBuilder.getColor()
+                        : color;
+            }
+            CharSequence senderText = bidi.unicodeWrap(replyName);
+            sb.append(senderText);
+            sb.setSpan(makeFontColorSpan(color),
+                    sb.length() - senderText.length(),
+                    sb.length(),
+                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE /* flags */);
+            CharSequence text = message.getText() == null ? "" : message.getText();
+            sb.append("  ").append(bidi.unicodeWrap(text));
+            return sb;
+        }
+
+        @NonNull
+        private TextAppearanceSpan makeFontColorSpan(int color) {
+            return new TextAppearanceSpan(null, 0, 0, ColorStateList.valueOf(color), null);
+        }
+
         @Override
         public void addCompatExtras(Bundle extras) {
             super.addCompatExtras(extras);
@@ -2514,7 +2660,7 @@
      * @see Notification#bigContentView
      */
     public static class InboxStyle extends Style {
-        ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>();
+        private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>();
 
         public InboxStyle() {
         }
@@ -2548,6 +2694,21 @@
             mTexts.add(Builder.limitCharSequenceLength(cs));
             return this;
         }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public void apply(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 16) {
+                NotificationCompatJellybean.addInboxStyle(builder,
+                        mBigContentTitle,
+                        mSummaryTextSet,
+                        mSummaryText,
+                        mTexts);
+            }
+        }
     }
 
     /**
@@ -4415,12 +4576,6 @@
         }
     }
 
-    /** @deprecated removed from API 26 */
-    @Deprecated
-    public static String getChannel(Notification notification) {
-        return getChannelId(notification);
-    }
-
     /**
      * Returns the time at which this notification should be canceled by the system, if it's not
      * canceled already.
@@ -4433,12 +4588,6 @@
         }
     }
 
-    /** @deprecated removed from API 26 */
-    @Deprecated
-    public static long getTimeout(Notification notification) {
-        return getTimeoutAfter(notification);
-    }
-
     /**
      * Returns what icon should be shown for this notification if it is being displayed in a
      * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE},
diff --git a/core-utils/java/android/support/v4/text/BidiFormatter.java b/compat/java/android/support/v4/text/BidiFormatter.java
similarity index 100%
rename from core-utils/java/android/support/v4/text/BidiFormatter.java
rename to compat/java/android/support/v4/text/BidiFormatter.java
diff --git a/compat/tests/java/android/support/v4/app/NotificationCompatTest.java b/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
index e7c6f13..dd870dd 100644
--- a/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
+++ b/compat/tests/java/android/support/v4/app/NotificationCompatTest.java
@@ -85,12 +85,12 @@
     public void testTimeout() throws Throwable {
         long timeout = 23552;
         Notification n = new NotificationCompat.Builder(mActivityTestRule.getActivity())
-                .setTimeout(timeout)
+                .setTimeoutAfter(timeout)
                 .build();
         if (Build.VERSION.SDK_INT >= 26) {
-            assertEquals(timeout, NotificationCompat.getTimeout(n));
+            assertEquals(timeout, NotificationCompat.getTimeoutAfter(n));
         } else {
-            assertEquals(0, NotificationCompat.getTimeout(n));
+            assertEquals(0, NotificationCompat.getTimeoutAfter(n));
         }
     }
 
@@ -111,12 +111,12 @@
     public void testNotificationChannel() throws Throwable {
         String channelId = "new ID";
         Notification n  = new NotificationCompat.Builder(mActivityTestRule.getActivity())
-                .setChannel(channelId)
+                .setChannelId(channelId)
                 .build();
         if (Build.VERSION.SDK_INT >= 26) {
-            assertEquals(channelId, NotificationCompat.getChannel(n));
+            assertEquals(channelId, NotificationCompat.getChannelId(n));
         } else {
-            assertNull(NotificationCompat.getChannel(n));
+            assertNull(NotificationCompat.getChannelId(n));
         }
     }
 
@@ -126,9 +126,9 @@
         Notification n  = new NotificationCompat.Builder(mActivityTestRule.getActivity(), channelId)
                 .build();
         if (Build.VERSION.SDK_INT >= 26) {
-            assertEquals(channelId, NotificationCompat.getChannel(n));
+            assertEquals(channelId, NotificationCompat.getChannelId(n));
         } else {
-            assertNull(NotificationCompat.getChannel(n));
+            assertNull(NotificationCompat.getChannelId(n));
         }
     }
 
@@ -179,6 +179,18 @@
         assertEquals(a.getAllowGeneratedReplies(), aCopy.getAllowGeneratedReplies());
     }
 
+    @SdkSuppress(minSdkVersion = 24)
+    @TargetApi(24)
+    @Test
+    public void testFrameworkNotificationActionBuilder_setAllowGeneratedRepliesTrue()
+            throws Throwable {
+        Notification notif = new Notification.Builder(mContext)
+                .addAction(new Notification.Action.Builder(0, "title", null)
+                        .setAllowGeneratedReplies(true).build()).build();
+        NotificationCompat.Action action = NotificationCompat.getAction(notif, 0);
+        assertTrue(action.getAllowGeneratedReplies());
+    }
+
     @Test
     public void testNotificationActionBuilder_defaultAllowGeneratedRepliesTrue() throws Throwable {
         NotificationCompat.Action a = newActionBuilder().build();
diff --git a/compat/tests/java/android/support/v4/graphics/PaintCompatHasGlyphTest.java b/compat/tests/java/android/support/v4/graphics/PaintCompatHasGlyphTest.java
index 26f0691..f17c881 100644
--- a/compat/tests/java/android/support/v4/graphics/PaintCompatHasGlyphTest.java
+++ b/compat/tests/java/android/support/v4/graphics/PaintCompatHasGlyphTest.java
@@ -47,6 +47,7 @@
                 {"\t\t\t", false},  // more white space
                 {"☺", SDK_INT >= 16}, // glyph added in API 16
                 {"\uD83D\uDC66\uD83C\uDFFF", SDK_INT >= 24}, // glyph added in API 24
+                {"\uD83C\uDDEF\uD83C\uDDF5", SDK_INT >= 20}, // Japan flag emoji, added in API 20
         });
     }
 
diff --git a/core-utils/tests/java/android/support/v4/text/BidiFormatterTest.java b/compat/tests/java/android/support/v4/text/BidiFormatterTest.java
similarity index 100%
rename from core-utils/tests/java/android/support/v4/text/BidiFormatterTest.java
rename to compat/tests/java/android/support/v4/text/BidiFormatterTest.java
diff --git a/core-ui/java/android/support/v4/widget/DrawerLayout.java b/core-ui/java/android/support/v4/widget/DrawerLayout.java
index 3b3a781..c7b40e9 100644
--- a/core-ui/java/android/support/v4/widget/DrawerLayout.java
+++ b/core-ui/java/android/support/v4/widget/DrawerLayout.java
@@ -148,7 +148,8 @@
      */
     public static final int LOCK_MODE_UNDEFINED = 3;
 
-    @IntDef({Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END})
+    @IntDef(value = {Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END},
+            flag = true)
     @Retention(RetentionPolicy.SOURCE)
     private @interface EdgeGravity {}
 
diff --git a/core-ui/java/android/support/v4/widget/SlidingPaneLayout.java b/core-ui/java/android/support/v4/widget/SlidingPaneLayout.java
index c949cbb..e3f2462 100644
--- a/core-ui/java/android/support/v4/widget/SlidingPaneLayout.java
+++ b/core-ui/java/android/support/v4/widget/SlidingPaneLayout.java
@@ -477,7 +477,7 @@
         }
 
         int layoutHeight = 0;
-        int maxLayoutHeight = -1;
+        int maxLayoutHeight = 0;
         switch (heightMode) {
             case MeasureSpec.EXACTLY:
                 layoutHeight = maxLayoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
diff --git a/core-ui/lint-baseline.xml b/core-ui/lint-baseline.xml
index 1c675b0..493c8c5 100644
--- a/core-ui/lint-baseline.xml
+++ b/core-ui/lint-baseline.xml
@@ -2,102 +2,14 @@
 <issues format="4" by="lint 2.4.0-alpha6">
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/core-utils/java/android/support/v4/content/WakefulBroadcastReceiver.java b/core-utils/java/android/support/v4/content/WakefulBroadcastReceiver.java
index b960939..b0cd653 100644
--- a/core-utils/java/android/support/v4/content/WakefulBroadcastReceiver.java
+++ b/core-utils/java/android/support/v4/content/WakefulBroadcastReceiver.java
@@ -70,8 +70,7 @@
 public abstract class WakefulBroadcastReceiver extends BroadcastReceiver {
     private static final String EXTRA_WAKE_LOCK_ID = "android.support.content.wakelockid";
 
-    private static final SparseArray<PowerManager.WakeLock> mActiveWakeLocks
-            = new SparseArray<PowerManager.WakeLock>();
+    private static final SparseArray<PowerManager.WakeLock> sActiveWakeLocks = new SparseArray<>();
     private static int mNextId = 1;
 
     /**
@@ -89,7 +88,7 @@
      * Context.startService}.
      */
     public static ComponentName startWakefulService(Context context, Intent intent) {
-        synchronized (mActiveWakeLocks) {
+        synchronized (sActiveWakeLocks) {
             int id = mNextId;
             mNextId++;
             if (mNextId <= 0) {
@@ -106,8 +105,8 @@
             PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                     "wake:" + comp.flattenToShortString());
             wl.setReferenceCounted(false);
-            wl.acquire(60*1000);
-            mActiveWakeLocks.put(id, wl);
+            wl.acquire(60 * 1000);
+            sActiveWakeLocks.put(id, wl);
             return comp;
         }
     }
@@ -125,11 +124,11 @@
         if (id == 0) {
             return false;
         }
-        synchronized (mActiveWakeLocks) {
-            PowerManager.WakeLock wl = mActiveWakeLocks.get(id);
+        synchronized (sActiveWakeLocks) {
+            PowerManager.WakeLock wl = sActiveWakeLocks.get(id);
             if (wl != null) {
                 wl.release();
-                mActiveWakeLocks.remove(id);
+                sActiveWakeLocks.remove(id);
                 return true;
             }
             // We return true whether or not we actually found the wake lock
@@ -138,7 +137,7 @@
             // We just log a warning here if there is no wake lock found, which could
             // happen for example if this function is called twice on the same
             // intent or the process is killed and restarted before processing the intent.
-            Log.w("WakefulBroadcastReceiver", "No active wake lock id #" + id);
+            Log.w("WakefulBroadcastReceiv.", "No active wake lock id #" + id);
             return true;
         }
     }
diff --git a/core-utils/java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java b/core-utils/java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
index 5efc74b..5e144c7 100644
--- a/core-utils/java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
+++ b/core-utils/java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
@@ -31,7 +31,7 @@
  * either from Bitmaps directly, or from streams and files.
  */
 public final class RoundedBitmapDrawableFactory {
-    private static final String TAG = "RoundedBitmapDrawableFactory";
+    private static final String TAG = "RoundedBitmapDrawableFa";
 
     private static class DefaultRoundedBitmapDrawable extends RoundedBitmapDrawable {
         DefaultRoundedBitmapDrawable(Resources res, Bitmap bitmap) {
diff --git a/core-utils/lint-baseline.xml b/core-utils/lint-baseline.xml
index d75d39e..172bbf6 100644
--- a/core-utils/lint-baseline.xml
+++ b/core-utils/lint-baseline.xml
@@ -1,37 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <issues format="4" by="lint 2.4.0-alpha6">
 
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
 </issues>
diff --git a/design/lint-baseline.xml b/design/lint-baseline.xml
index 7ece01b..6cd8289 100644
--- a/design/lint-baseline.xml
+++ b/design/lint-baseline.xml
@@ -94,17 +94,6 @@
     </issue>
 
     <issue
-        id="ResourceType"
-        message="Expected resource of type xml"
-        errorLine1="            final XmlPullParser parser = res.getXml(resId);"
-        errorLine2="                                                    ~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="557"
-            column="53"/>
-    </issue>
-
-    <issue
         id="Range"
         message="Value must be ≥ 0 (was -2147483648)"
         errorLine1="                                MeasureSpec.makeMeasureSpec(largestChildHeight,"
@@ -127,72 +116,6 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
         id="LongLogTag"
         message="The logging tag can be at most 23 characters, was 30 (ActionBarDrawerToggleHoneycomb)"
         errorLine1="                Log.w(TAG, &quot;Couldn&apos;t set home-as-up indicator via JB-MR2 API&quot;, e);"
@@ -292,171 +215,6 @@
     </issue>
 
     <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;found saved state: &quot; + mPendingSavedState);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="783"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;Deciding anchor info from fresh state&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="828"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;invalid saved state class&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1187"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;saved state:\n&quot; + state);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1236"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;FILLING targetLine: &quot; + targetLine + &quot;,&quot;"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1559"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;assigned &quot; + currentSpan.mIndex + &quot; for &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1580"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;using &quot; + spanIndex + &quot; for pos &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1584"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;asked &quot; + dt + &quot; scrolled&quot; + totalScroll);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2153"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;Unknown focus request:&quot; + focusDirection);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2385"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
         id="AppCompatCustomView"
         message="This custom view should extend `android.support.v7.widget.AppCompatAutoCompleteTextView` instead"
         errorLine1="public class AppCompatAutoCompleteTextView extends AutoCompleteTextView implements"
@@ -633,21 +391,6 @@
     </issue>
 
     <issue
-        id="UniqueConstants"
-        message="Constants `FLAG_CVE_EQ_PVE` and `FLAG_CVE_EQ_PVE` specify the same exact value (8192); this is usually a cut &amp; paste or merge error"
-        errorLine1="            FLAG_CVE_EQ_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE"
-        errorLine2="                             ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="30"/>
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="13"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
         message="Must be one of: PixelFormat.UNKNOWN, PixelFormat.TRANSLUCENT, PixelFormat.TRANSPARENT, PixelFormat.OPAQUE"
         errorLine1="        return 0;"
@@ -660,35 +403,13 @@
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
     <issue
@@ -713,15 +434,4 @@
             column="36"/>
     </issue>
 
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL"
-        errorLine1="            return isAutoMirrored() &amp;&amp; getLayoutDirection() == LayoutDirection.RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="821"
-            column="64"/>
-    </issue>
-
 </issues>
diff --git a/design/src/android/support/design/internal/BottomNavigationItemView.java b/design/src/android/support/design/internal/BottomNavigationItemView.java
index 885ea02..fe5e636 100644
--- a/design/src/android/support/design/internal/BottomNavigationItemView.java
+++ b/design/src/android/support/design/internal/BottomNavigationItemView.java
@@ -125,7 +125,6 @@
     public void setTitle(CharSequence title) {
         mSmallLabel.setText(title);
         mLargeLabel.setText(title);
-        setContentDescription(title);
     }
 
     @Override
diff --git a/design/src/android/support/design/widget/TextInputLayout.java b/design/src/android/support/design/widget/TextInputLayout.java
index 52efdde..c9e8010 100644
--- a/design/src/android/support/design/widget/TextInputLayout.java
+++ b/design/src/android/support/design/widget/TextInputLayout.java
@@ -961,6 +961,7 @@
 
     static class SavedState extends AbsSavedState {
         CharSequence error;
+        boolean isPasswordToggledVisible;
 
         SavedState(Parcelable superState) {
             super(superState);
@@ -969,6 +970,7 @@
         SavedState(Parcel source, ClassLoader loader) {
             super(source, loader);
             error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+            isPasswordToggledVisible = (source.readInt() == 1);
 
         }
 
@@ -976,6 +978,7 @@
         public void writeToParcel(Parcel dest, int flags) {
             super.writeToParcel(dest, flags);
             TextUtils.writeToParcel(error, dest, flags);
+            dest.writeInt(isPasswordToggledVisible ? 1 : 0);
         }
 
         @Override
@@ -1010,6 +1013,7 @@
         if (mErrorShown) {
             ss.error = getError();
         }
+        ss.isPasswordToggledVisible = mPasswordToggledVisible;
         return ss;
     }
 
@@ -1022,6 +1026,9 @@
         SavedState ss = (SavedState) state;
         super.onRestoreInstanceState(ss.getSuperState());
         setError(ss.error);
+        if (ss.isPasswordToggledVisible) {
+            passwordVisibilityToggleRequested(true);
+        }
         requestLayout();
     }
 
@@ -1100,7 +1107,7 @@
                 mPasswordToggleView.setOnClickListener(new View.OnClickListener() {
                     @Override
                     public void onClick(View view) {
-                        passwordVisibilityToggleRequested();
+                        passwordVisibilityToggleRequested(false);
                     }
                 });
             }
@@ -1313,7 +1320,7 @@
         applyPasswordToggleTint();
     }
 
-    void passwordVisibilityToggleRequested() {
+    private void passwordVisibilityToggleRequested(boolean shouldSkipAnimations) {
         if (mPasswordToggleEnabled) {
             // Store the current cursor position
             final int selection = mEditText.getSelectionEnd();
@@ -1327,6 +1334,9 @@
             }
 
             mPasswordToggleView.setChecked(mPasswordToggledVisible);
+            if (shouldSkipAnimations) {
+                mPasswordToggleView.jumpDrawablesToCurrentState();
+            }
 
             // And restore the cursor position
             mEditText.setSelection(selection);
diff --git a/design/tests/res/menu/bottom_navigation_view_content.xml b/design/tests/res/menu/bottom_navigation_view_content.xml
index 6d19060..d61d6d8 100644
--- a/design/tests/res/menu/bottom_navigation_view_content.xml
+++ b/design/tests/res/menu/bottom_navigation_view_content.xml
@@ -13,14 +13,21 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+      xmlns:app="http://schemas.android.com/apk/res-auto">
     <item android:id="@+id/destination_home"
+          android:icon="@drawable/test_drawable_red"
           android:title="@string/navigate_home"
-          android:icon="@drawable/test_drawable_red" />
+          app:contentDescription="@string/navigate_home"
+          app:tooltipText="@string/navigate_home"/>
     <item android:id="@+id/destination_profile"
+          android:icon="@drawable/test_drawable_green"
           android:title="@string/navigate_profile"
-          android:icon="@drawable/test_drawable_green" />
+          app:contentDescription="@string/navigate_profile"
+          app:tooltipText="@string/navigate_profile"/>
     <item android:id="@+id/destination_people"
+          android:icon="@drawable/test_drawable_blue"
           android:title="@string/navigate_people"
-          android:icon="@drawable/test_drawable_blue" />
+          app:contentDescription="@string/navigate_people"
+          app:tooltipText="@string/navigate_people"/>
 </menu>
diff --git a/design/tests/src/android/support/design/testutils/ActivityUtils.java b/design/tests/src/android/support/design/testutils/ActivityUtils.java
new file mode 100644
index 0000000..1ed6a3f
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/ActivityUtils.java
@@ -0,0 +1,90 @@
+/*
+ * 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.design.testutils;
+
+import static org.junit.Assert.assertTrue;
+
+import android.os.Looper;
+import android.support.test.rule.ActivityTestRule;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Utility methods for testing activities.
+ */
+public class ActivityUtils {
+    private static final Runnable DO_NOTHING = new Runnable() {
+        @Override
+        public void run() {
+        }
+    };
+
+    public static void waitForExecution(
+            final ActivityTestRule<? extends RecreatedAppCompatActivity> rule) {
+        // Wait for two cycles. When starting a postponed transition, it will post to
+        // the UI thread and then the execution will be added onto the queue after that.
+        // The two-cycle wait makes sure fragments have the opportunity to complete both
+        // before returning.
+        try {
+            rule.runOnUiThread(DO_NOTHING);
+            rule.runOnUiThread(DO_NOTHING);
+        } catch (Throwable throwable) {
+            throw new RuntimeException(throwable);
+        }
+    }
+
+    private static void runOnUiThreadRethrow(
+            ActivityTestRule<? extends RecreatedAppCompatActivity> rule, Runnable r) {
+        if (Looper.getMainLooper() == Looper.myLooper()) {
+            r.run();
+        } else {
+            try {
+                rule.runOnUiThread(r);
+            } catch (Throwable t) {
+                throw new RuntimeException(t);
+            }
+        }
+    }
+
+    /**
+     * Restarts the RecreatedAppCompatActivity and waits for the new activity to be resumed.
+     *
+     * @return The newly-restarted RecreatedAppCompatActivity
+     */
+    public static <T extends RecreatedAppCompatActivity> T recreateActivity(
+            ActivityTestRule<? extends RecreatedAppCompatActivity> rule, final T activity)
+            throws InterruptedException {
+        // Now switch the orientation
+        RecreatedAppCompatActivity.sResumed = new CountDownLatch(1);
+        RecreatedAppCompatActivity.sDestroyed = new CountDownLatch(1);
+
+        runOnUiThreadRethrow(rule, new Runnable() {
+            @Override
+            public void run() {
+                activity.recreate();
+            }
+        });
+        assertTrue(RecreatedAppCompatActivity.sResumed.await(1, TimeUnit.SECONDS));
+        assertTrue(RecreatedAppCompatActivity.sDestroyed.await(1, TimeUnit.SECONDS));
+        T newActivity = (T) RecreatedAppCompatActivity.sActivity;
+
+        waitForExecution(rule);
+
+        RecreatedAppCompatActivity.clearState();
+        return newActivity;
+    }
+}
diff --git a/design/tests/src/android/support/design/testutils/RecreatedAppCompatActivity.java b/design/tests/src/android/support/design/testutils/RecreatedAppCompatActivity.java
new file mode 100644
index 0000000..52ba059
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/RecreatedAppCompatActivity.java
@@ -0,0 +1,62 @@
+/*
+ * 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.design.testutils;
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Activity that keeps track of resume / destroy lifecycle events, as well as of the last
+ * instance of itself.
+ */
+public class RecreatedAppCompatActivity extends AppCompatActivity {
+    // These must be cleared after each test using clearState()
+    public static RecreatedAppCompatActivity sActivity;
+    public static CountDownLatch sResumed;
+    public static CountDownLatch sDestroyed;
+
+    public static void clearState() {
+        sActivity = null;
+        sResumed = null;
+        sDestroyed = null;
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        sActivity = this;
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (sResumed != null) {
+            sResumed.countDown();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (sDestroyed != null) {
+            sDestroyed.countDown();
+        }
+    }
+}
diff --git a/design/tests/src/android/support/design/testutils/TextInputLayoutActions.java b/design/tests/src/android/support/design/testutils/TextInputLayoutActions.java
index f49644f..c4d4520 100755
--- a/design/tests/src/android/support/design/testutils/TextInputLayoutActions.java
+++ b/design/tests/src/android/support/design/testutils/TextInputLayoutActions.java
@@ -19,9 +19,11 @@
 import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
 
 import android.graphics.Typeface;
+import android.support.design.R;
 import android.support.design.widget.TextInputLayout;
 import android.support.test.espresso.UiController;
 import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.matcher.ViewMatchers;
 import android.view.View;
 
 import org.hamcrest.Matcher;
@@ -196,4 +198,32 @@
         };
     }
 
+    /**
+     * Toggles password.
+     */
+    public static ViewAction clickPasswordToggle() {
+        return new ViewAction() {
+
+            @Override
+            public Matcher<View> getConstraints() {
+                return ViewMatchers.isAssignableFrom(TextInputLayout.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "Clicks the password toggle";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                TextInputLayout textInputLayout = (TextInputLayout) view;
+                // Reach in and find the password toggle since we don't have a public API
+                // to get a reference to it
+                View passwordToggle = textInputLayout.findViewById(R.id.text_input_password_toggle);
+                passwordToggle.performClick();
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
 }
diff --git a/design/tests/src/android/support/design/testutils/TextInputLayoutMatchers.java b/design/tests/src/android/support/design/testutils/TextInputLayoutMatchers.java
index d13a39d..d67e555 100755
--- a/design/tests/src/android/support/design/testutils/TextInputLayoutMatchers.java
+++ b/design/tests/src/android/support/design/testutils/TextInputLayoutMatchers.java
@@ -16,8 +16,11 @@
 
 package android.support.design.testutils;
 
+import android.support.design.R;
+import android.support.design.widget.CheckableImageButton;
 import android.support.design.widget.TextInputLayout;
 import android.text.TextUtils;
+import android.view.View;
 
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
@@ -29,7 +32,7 @@
      * Returns a matcher that matches TextInputLayouts with non-empty content descriptions for
      * the password toggle.
      */
-    public static Matcher hasPasswordToggleContentDescription() {
+    public static Matcher passwordToggleHasContentDescription() {
         return new TypeSafeMatcher<TextInputLayout>() {
             @Override
             public void describeTo(Description description) {
@@ -39,9 +42,53 @@
 
             @Override
             protected boolean matchesSafely(TextInputLayout item) {
-                return !TextUtils.isEmpty(item.getPasswordVisibilityToggleContentDescription());
+                // Reach in and find the password toggle since we don't have a public API
+                // to get a reference to it
+                View passwordToggle = item.findViewById(R.id.text_input_password_toggle);
+                return !TextUtils.isEmpty(item.getPasswordVisibilityToggleContentDescription())
+                    && !TextUtils.isEmpty(passwordToggle.getContentDescription());
             }
         };
     }
 
+    /**
+     * Returns a matcher that matches TextInputLayouts with non-displayed password toggles
+     */
+    public static Matcher doesNotShowPasswordToggle() {
+        return new TypeSafeMatcher<TextInputLayout>() {
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("TextInputLayout shows password toggle.");
+            }
+
+            @Override
+            protected boolean matchesSafely(TextInputLayout item) {
+                // Reach in and find the password toggle since we don't have a public API
+                // to get a reference to it
+                View passwordToggle = item.findViewById(R.id.text_input_password_toggle);
+                return passwordToggle.getVisibility() != View.VISIBLE;
+            }
+        };
+    }
+
+    /**
+     * Returns a matcher that matches TextInputLayouts with non-displayed password toggles
+     */
+    public static Matcher passwordToggleIsNotChecked() {
+        return new TypeSafeMatcher<TextInputLayout>() {
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("TextInputLayout has checked password toggle.");
+            }
+
+            @Override
+            protected boolean matchesSafely(TextInputLayout item) {
+                // Reach in and find the password toggle since we don't have a public API
+                // to get a reference to it
+                CheckableImageButton passwordToggle = (CheckableImageButton) item.findViewById(
+                        R.id.text_input_password_toggle);
+                return !passwordToggle.isChecked();
+            }
+        };
+    }
 }
diff --git a/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarStateRestoreTest.java b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarStateRestoreTest.java
index 82c9582..b9a6518 100644
--- a/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarStateRestoreTest.java
+++ b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarStateRestoreTest.java
@@ -23,14 +23,10 @@
 import static android.support.test.espresso.assertion.ViewAssertions.matches;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 
-import android.app.Activity;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
 import android.support.design.test.R;
+import android.support.design.testutils.ActivityUtils;
 import android.support.test.filters.LargeTest;
-import android.support.testutils.PollingCheck;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -38,8 +34,7 @@
 public class AppBarWithCollapsingToolbarStateRestoreTest
         extends BaseInstrumentationTestCase<AppBarLayoutCollapsePinTestActivity> {
 
-    private Activity mActivity;
-    private int mOldOrientation;
+    private AppBarLayoutCollapsePinTestActivity mActivity;
 
     public AppBarWithCollapsingToolbarStateRestoreTest() {
         super(AppBarLayoutCollapsePinTestActivity.class);
@@ -48,22 +43,10 @@
     @Before
     public void setup() {
         mActivity = mActivityTestRule.getActivity();
-        mOldOrientation = mActivity.getResources().getConfiguration().orientation;
-    }
-
-    @After
-    public void tearDown() {
-        mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return mActivity.getResources().getConfiguration().orientation == mOldOrientation;
-            }
-        });
     }
 
     @Test
-    public void testRotateAndRestore() {
+    public void testRecreateAndRestore() throws Throwable {
         final AppBarLayout appBar = (AppBarLayout) mActivity.findViewById(R.id.app_bar);
 
         // Swipe up and collapse the AppBarLayout
@@ -76,27 +59,12 @@
                 .check(matches(hasZ()))
                 .check(matches(isCollapsed()));
 
-        // Now rotate the Activity
-        final int newOrientation = mOldOrientation == Configuration.ORIENTATION_PORTRAIT
-                ? Configuration.ORIENTATION_LANDSCAPE
-                : Configuration.ORIENTATION_PORTRAIT;
-        mActivity.setRequestedOrientation(toActivityInfoConfiguration(newOrientation));
-        PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
-            @Override
-            public boolean canProceed() {
-                return mActivity.getResources().getConfiguration().orientation == newOrientation;
-            }
-        });
+        mActivity = ActivityUtils.recreateActivity(mActivityTestRule, mActivity);
+        ActivityUtils.waitForExecution(mActivityTestRule);
 
         // And check that the app bar still is restored correctly
         onView(withId(R.id.app_bar))
                 .check(matches(hasZ()))
                 .check(matches(isCollapsed()));
     }
-
-    private static int toActivityInfoConfiguration(int configuration) {
-        return configuration == Configuration.ORIENTATION_PORTRAIT
-                ? ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
-                : ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
-    }
 }
diff --git a/design/tests/src/android/support/design/widget/BaseTestActivity.java b/design/tests/src/android/support/design/widget/BaseTestActivity.java
index 0c801cb..bdeb231 100755
--- a/design/tests/src/android/support/design/widget/BaseTestActivity.java
+++ b/design/tests/src/android/support/design/widget/BaseTestActivity.java
@@ -17,10 +17,10 @@
 package android.support.design.widget;
 
 import android.os.Bundle;
-import android.support.v7.app.AppCompatActivity;
+import android.support.design.testutils.RecreatedAppCompatActivity;
 import android.view.WindowManager;
 
-abstract class BaseTestActivity extends AppCompatActivity {
+abstract class BaseTestActivity extends RecreatedAppCompatActivity {
 
     private boolean mDestroyed;
 
diff --git a/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java b/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java
index cbf2fc9..3365fac 100644
--- a/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java
+++ b/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java
@@ -56,6 +56,7 @@
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.view.View;
+import android.view.ViewGroup;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -434,6 +435,22 @@
         });
     }
 
+    @UiThreadTest
+    @Test
+    @SmallTest
+    public void testContentDescription() {
+        ViewGroup menuView = (ViewGroup) mBottomNavigation.getChildAt(0);
+        final int count = menuView.getChildCount();
+        for (int i = 0; i < count; i++) {
+            View child = menuView.getChildAt(i);
+            // We're using the same strings for content description
+            assertEquals(mMenuStringContent.get(child.getId()),
+                    child.getContentDescription().toString());
+        }
+
+        menuView.getChildAt(0).getContentDescription();
+    }
+
     private void checkAndVerifyExclusiveItem(final Menu menu, final int id) throws Throwable {
         menu.findItem(id).setChecked(true);
         for (int i = 0; i < menu.size(); i++) {
diff --git a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
index 226463c..52471a9 100755
--- a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
+++ b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
@@ -21,6 +21,7 @@
 import static android.support.design.testutils.TestUtilsMatchers.withCompoundDrawable;
 import static android.support.design.testutils.TestUtilsMatchers.withTextColor;
 import static android.support.design.testutils.TestUtilsMatchers.withTypeface;
+import static android.support.design.testutils.TextInputLayoutActions.clickPasswordToggle;
 import static android.support.design.testutils.TextInputLayoutActions.setCounterEnabled;
 import static android.support.design.testutils.TextInputLayoutActions.setCounterMaxLength;
 import static android.support.design.testutils.TextInputLayoutActions.setError;
@@ -29,8 +30,10 @@
 import static android.support.design.testutils.TextInputLayoutActions
         .setPasswordVisibilityToggleEnabled;
 import static android.support.design.testutils.TextInputLayoutActions.setTypeface;
+import static android.support.design.testutils.TextInputLayoutMatchers.doesNotShowPasswordToggle;
 import static android.support.design.testutils.TextInputLayoutMatchers
-        .hasPasswordToggleContentDescription;
+        .passwordToggleHasContentDescription;
+import static android.support.design.testutils.TextInputLayoutMatchers.passwordToggleIsNotChecked;
 import static android.support.test.InstrumentationRegistry.getInstrumentation;
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.action.ViewActions.click;
@@ -38,15 +41,15 @@
 import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
 import static android.support.test.espresso.assertion.ViewAssertions.matches;
 import static android.support.test.espresso.contrib.AccessibilityChecks.accessibilityAssertion;
-import static android.support.test.espresso.matcher.ViewMatchers.hasContentDescription;
 import static android.support.test.espresso.matcher.ViewMatchers.hasFocus;
-import static android.support.test.espresso.matcher.ViewMatchers.isChecked;
+import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
 import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static android.support.test.espresso.matcher.ViewMatchers.isEnabled;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 import static android.support.test.espresso.matcher.ViewMatchers.withText;
 
 import static org.hamcrest.Matchers.not;
+import static org.hamcrest.core.AllOf.allOf;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
@@ -62,14 +65,19 @@
 import android.os.Build;
 import android.os.Parcelable;
 import android.support.design.test.R;
+import android.support.design.testutils.ActivityUtils;
+import android.support.design.testutils.RecreatedAppCompatActivity;
 import android.support.design.testutils.TestUtils;
 import android.support.design.testutils.ViewStructureImpl;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.espresso.NoMatchingViewException;
 import android.support.test.espresso.ViewAssertion;
+import android.support.test.filters.LargeTest;
 import android.support.test.filters.MediumTest;
 import android.support.test.filters.SdkSuppress;
 import android.support.v4.widget.TextViewCompat;
+import android.text.method.PasswordTransformationMethod;
+import android.text.method.TransformationMethod;
 import android.util.AttributeSet;
 import android.util.SparseArray;
 import android.view.KeyEvent;
@@ -167,7 +175,7 @@
         assertNotEquals(INPUT_TEXT, textInput.getLayout().getText().toString());
 
         // Now click the toggle button
-        onView(withId(R.id.text_input_password_toggle)).perform(click());
+        onView(withId(R.id.textinput_password)).perform(clickPasswordToggle());
 
         // And assert that the password is not disguised
         assertEquals(INPUT_TEXT, textInput.getLayout().getText().toString());
@@ -189,7 +197,7 @@
                 .perform(setPasswordVisibilityToggleEnabled(false));
 
         // Check that the password toggle view is not visible
-        onView(withId(R.id.text_input_password_toggle)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.textinput_password)).check(matches(doesNotShowPasswordToggle()));
         // ...and that the password is disguised still
         assertNotEquals(INPUT_TEXT, textInput.getLayout().getText().toString());
     }
@@ -205,7 +213,7 @@
         assertNotEquals(INPUT_TEXT, textInput.getLayout().getText().toString());
 
         // Now click the toggle button
-        onView(withId(R.id.text_input_password_toggle)).perform(click());
+        onView(withId(R.id.textinput_password)).perform(clickPasswordToggle());
         // Disable the password toggle
         onView(withId(R.id.textinput_password))
                 .perform(setPasswordVisibilityToggleEnabled(false));
@@ -252,7 +260,7 @@
 
         // Type some text on the EditText and then click the toggle button
         onView(withId(R.id.textinput_edittext_pwd)).perform(typeText(INPUT_TEXT));
-        onView(withId(R.id.text_input_password_toggle)).perform(click());
+        onView(withId(R.id.textinput_password)).perform(clickPasswordToggle());
 
         // Disable the password toggle, and then re-enable it
         onView(withId(R.id.textinput_password))
@@ -261,7 +269,7 @@
 
         // Check that the password is disguised and the toggle button reflects the same state
         assertNotEquals(INPUT_TEXT, textInput.getLayout().getText().toString());
-        onView(withId(R.id.text_input_password_toggle)).check(matches(not(isChecked())));
+        onView(withId(R.id.textinput_password)).check(matches(passwordToggleIsNotChecked()));
     }
 
     @Test
@@ -430,13 +438,10 @@
 
     @Test
     public void testPasswordToggleHasDefaultContentDescription() {
-        // Check that the TextInputLayout says that it has a content description
+        // Check that the TextInputLayout says that it has a content description and that the
+        // underlying toggle has content description as well
         onView(withId(R.id.textinput_password))
-                .check(matches(hasPasswordToggleContentDescription()));
-
-        // Check that the underlying toggle view says that it also has a content description
-        onView(withId(R.id.text_input_password_toggle))
-                .check(matches(hasContentDescription()));
+                .check(matches(passwordToggleHasContentDescription()));
     }
 
     /**
@@ -445,8 +450,8 @@
      */
     @Test
     public void testPasswordToggleIsAccessible() {
-        onView(withId(R.id.text_input_password_toggle))
-                .check(accessibilityAssertion());
+        onView(allOf(withId(R.id.text_input_password_toggle),
+                isDescendantOfA(withId(R.id.textinput_password)))).check(accessibilityAssertion());
     }
 
     @Test
@@ -489,8 +494,7 @@
 
     @Test
     public void testTextSetViaAttributeCollapsedHint() {
-        onView(withId(R.id.textinput_with_text))
-                .check(isHintExpanded(false));
+        onView(withId(R.id.textinput_with_text)).check(isHintExpanded(false));
     }
 
     @Test
@@ -508,6 +512,25 @@
                 .check(matches(hasFocus()));
     }
 
+    @Test
+    @LargeTest
+    public void testSaveAndRestorePasswordVisibility() throws Throwable {
+        // Type some text on the EditText
+        onView(withId(R.id.textinput_edittext_pwd)).perform(typeText(INPUT_TEXT));
+        onView(withId(R.id.textinput_password)).check(isPasswordToggledVisible(false));
+
+        // Toggle password to be shown as plain text
+        onView(withId(R.id.textinput_password)).perform(clickPasswordToggle());
+        onView(withId(R.id.textinput_password)).check(isPasswordToggledVisible(true));
+
+        RecreatedAppCompatActivity activity = mActivityTestRule.getActivity();
+        activity = ActivityUtils.recreateActivity(mActivityTestRule, activity);
+        ActivityUtils.waitForExecution(mActivityTestRule);
+
+        // Check that the password is still toggled to be shown as plain text
+        onView(withId(R.id.textinput_password)).check(isPasswordToggledVisible(true));
+    }
+
     static ViewAssertion isHintExpanded(final boolean expanded) {
         return new ViewAssertion() {
             @Override
@@ -517,4 +540,20 @@
             }
         };
     }
+
+    static ViewAssertion isPasswordToggledVisible(final boolean isToggledVisible) {
+        return new ViewAssertion() {
+            @Override
+            public void check(View view, NoMatchingViewException noViewFoundException) {
+                assertTrue(view instanceof TextInputLayout);
+                EditText editText = ((TextInputLayout) view).getEditText();
+                TransformationMethod transformationMethod = editText.getTransformationMethod();
+                if (isToggledVisible) {
+                    assertNull(transformationMethod);
+                } else {
+                    assertEquals(PasswordTransformationMethod.getInstance(), transformationMethod);
+                }
+            }
+        };
+    }
 }
diff --git a/dynamic-animation/lint-baseline.xml b/dynamic-animation/lint-baseline.xml
index d75d39e..172bbf6 100644
--- a/dynamic-animation/lint-baseline.xml
+++ b/dynamic-animation/lint-baseline.xml
@@ -1,37 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <issues format="4" by="lint 2.4.0-alpha6">
 
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
 </issues>
diff --git a/dynamic-animation/src/android/support/animation/DynamicAnimation.java b/dynamic-animation/src/android/support/animation/DynamicAnimation.java
index 18b7d60..8ea48b9 100644
--- a/dynamic-animation/src/android/support/animation/DynamicAnimation.java
+++ b/dynamic-animation/src/android/support/animation/DynamicAnimation.java
@@ -380,7 +380,8 @@
     }
 
     /**
-     * Start velocity of the animation. Default velocity is 0. Unit: pixel/second
+     * Start velocity of the animation. Default velocity is 0. Unit: change in property per
+     * second (e.g. pixels per second, scale/alpha value change per second).
      *
      * <p>Note when using a fixed value as the start velocity (as opposed to getting the velocity
      * through touch events), it is recommended to define such a value in dp/second and convert it
@@ -393,7 +394,7 @@
      *         getResources().getDisplayMetrics());
      * </pre>
      *
-     * @param startVelocity start velocity of the animation in pixel/second
+     * @param startVelocity start velocity of the animation
      * @return the Animation whose start velocity is being set
      */
     public T setStartVelocity(float startVelocity) {
diff --git a/emoji/appcompat/lint-baseline.xml b/emoji/appcompat/lint-baseline.xml
index 91d58da..3b6c11d 100644
--- a/emoji/appcompat/lint-baseline.xml
+++ b/emoji/appcompat/lint-baseline.xml
@@ -94,17 +94,6 @@
     </issue>
 
     <issue
-        id="ResourceType"
-        message="Expected resource of type xml"
-        errorLine1="            final XmlPullParser parser = res.getXml(resId);"
-        errorLine2="                                                    ~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="557"
-            column="53"/>
-    </issue>
-
-    <issue
         id="Range"
         message="Value must be ≥ 0 (was -2147483648)"
         errorLine1="                                MeasureSpec.makeMeasureSpec(largestChildHeight,"
@@ -127,72 +116,6 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
         id="LongLogTag"
         message="The logging tag can be at most 23 characters, was 30 (ActionBarDrawerToggleHoneycomb)"
         errorLine1="                Log.w(TAG, &quot;Couldn&apos;t set home-as-up indicator via JB-MR2 API&quot;, e);"
@@ -292,72 +215,6 @@
     </issue>
 
     <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
         id="AppCompatCustomView"
         message="This custom view should extend `android.support.v7.widget.AppCompatAutoCompleteTextView` instead"
         errorLine1="public class AppCompatAutoCompleteTextView extends AutoCompleteTextView implements"
@@ -579,35 +436,13 @@
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
     <issue
@@ -632,15 +467,4 @@
             column="36"/>
     </issue>
 
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL"
-        errorLine1="            return isAutoMirrored() &amp;&amp; getLayoutDirection() == LayoutDirection.RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="821"
-            column="64"/>
-    </issue>
-
 </issues>
diff --git a/exifinterface/src/android/support/media/ExifInterface.java b/exifinterface/src/android/support/media/ExifInterface.java
index e7ebe3a..4a68dd7 100644
--- a/exifinterface/src/android/support/media/ExifInterface.java
+++ b/exifinterface/src/android/support/media/ExifInterface.java
@@ -19,6 +19,7 @@
 import android.content.res.AssetManager;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.location.Location;
 import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
 import android.util.Log;
@@ -51,6 +52,7 @@
 import java.util.HashSet;
 import java.util.Map;
 import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -517,6 +519,10 @@
         public final long numerator;
         public final long denominator;
 
+        private Rational(double value) {
+            this((long) (value * 10000), 10000);
+        }
+
         private Rational(long numerator, long denominator) {
             // Handle erroneous case
             if (denominator == 0) {
@@ -1446,7 +1452,7 @@
             } else {
                 try {
                     double doubleValue = Double.parseDouble(value);
-                    value = (long) (doubleValue * 10000L) + "/10000";
+                    value = new Rational(doubleValue).toString();
                 } catch (NumberFormatException e) {
                     Log.w(TAG, "Invalid value for " + tag + " : " + value);
                     return;
@@ -1899,6 +1905,28 @@
     }
 
     /**
+     * Sets the GPS-related information. It will set GPS processing method, latitude and longitude
+     * values, GPS timestamp, and speed information at the same time.
+     *
+     * @param location the {@link Location} object returned by GPS service.
+     */
+    public void setGpsInfo(Location location) {
+        if (location == null) {
+            return;
+        }
+        setAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD, location.getProvider());
+        setLatLong(location.getLatitude(), location.getLongitude());
+        setAltitude(location.getAltitude());
+        // Location objects store speeds in m/sec. Translates it to km/hr here.
+        setAttribute(TAG_GPS_SPEED_REF, "K");
+        setAttribute(TAG_GPS_SPEED, new Rational(location.getSpeed()
+                * TimeUnit.HOURS.toSeconds(1) / 1000).toString());
+        String[] dateTime = sFormatter.format(new Date(location.getTime())).split("\\s+");
+        setAttribute(ExifInterface.TAG_GPS_DATESTAMP, dateTime[0]);
+        setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, dateTime[1]);
+    }
+
+    /**
      * Sets the latitude and longitude values.
      *
      * @param latitude the decimal value of latitude. Must be a valid double value between -90.0 and
@@ -1939,6 +1967,15 @@
     }
 
     /**
+     * Sets the altitude in meters.
+     */
+    public void setAltitude(double altitude) {
+        String ref = altitude >= 0 ? "0" : "1";
+        setAttribute(TAG_GPS_ALTITUDE, new Rational(Math.abs(altitude)).toString());
+        setAttribute(TAG_GPS_ALTITUDE_REF, ref);
+    }
+
+    /**
      * Set the date time value.
      *
      * @param timeStamp number of milliseconds since Jan. 1, 1970, midnight local time.
diff --git a/exifinterface/tests/src/android/support/media/ExifInterfaceTest.java b/exifinterface/tests/src/android/support/media/ExifInterfaceTest.java
index 89ae0b6..2223029 100644
--- a/exifinterface/tests/src/android/support/media/ExifInterfaceTest.java
+++ b/exifinterface/tests/src/android/support/media/ExifInterfaceTest.java
@@ -25,6 +25,7 @@
 
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
+import android.location.Location;
 import android.os.Environment;
 import android.support.exifinterface.test.R;
 import android.support.test.filters.LargeTest;
@@ -46,6 +47,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Test {@link ExifInterface}.
@@ -66,6 +68,8 @@
 
     private static final String TEST_TEMP_FILE_NAME = "testImage";
     private static final double DELTA = 1e-8;
+    // We translate double to rational in a 1/10000 precision.
+    private static final double RATIONAL_DELTA = 0.0001;
     private static final int TEST_LAT_LONG_VALUES_ARRAY_LENGTH = 8;
     private static final double[] TEST_LATITUDE_VALID_VALUES = new double[]
             {0, 45, 90, -60, 0.00000001, -89.999999999, 14.2465923626, -68.3434534737};
@@ -77,6 +81,8 @@
     private static final double[] TEST_LONGITUDE_INVALID_VALUES = new double[]
             {Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, 180.0000000001,
                     263.34763236326, -1e10, 347.325252623, -4000.346323236};
+    private static final double[] TEST_ALTITUDE_VALUES = new double[]
+            {0, -2000, 10000, -355.99999999999, 18.02038};
 
     private static final String[] EXIF_TAGS = {
             ExifInterface.TAG_MAKE,
@@ -251,6 +257,38 @@
 
     @Test
     @SmallTest
+    public void testSetGpsInfo() throws IOException {
+        final String provider = "ExifInterfaceTest";
+        final long timestamp = System.currentTimeMillis();
+        final float speedInMeterPerSec = 36.627533f;
+        Location location = new Location(provider);
+        location.setLatitude(TEST_LATITUDE_VALID_VALUES[TEST_LATITUDE_VALID_VALUES.length - 1]);
+        location.setLongitude(TEST_LONGITUDE_VALID_VALUES[TEST_LONGITUDE_VALID_VALUES.length - 1]);
+        location.setAltitude(TEST_ALTITUDE_VALUES[TEST_ALTITUDE_VALUES.length - 1]);
+        location.setSpeed(speedInMeterPerSec);
+        location.setTime(timestamp);
+        ExifInterface exif = createTestExifInterface();
+        exif.setGpsInfo(location);
+
+        double[] latLong = exif.getLatLong();
+        assertNotNull(latLong);
+        assertEquals(TEST_LATITUDE_VALID_VALUES[TEST_LATITUDE_VALID_VALUES.length - 1],
+                latLong[0], DELTA);
+        assertEquals(TEST_LONGITUDE_VALID_VALUES[TEST_LONGITUDE_VALID_VALUES.length - 1],
+                latLong[1], DELTA);
+        assertEquals(TEST_ALTITUDE_VALUES[TEST_ALTITUDE_VALUES.length - 1], exif.getAltitude(0),
+                RATIONAL_DELTA);
+        assertEquals("K", exif.getAttribute(ExifInterface.TAG_GPS_SPEED_REF));
+        assertEquals(speedInMeterPerSec, exif.getAttributeDouble(ExifInterface.TAG_GPS_SPEED, 0.0)
+                * 1000 / TimeUnit.HOURS.toSeconds(1), RATIONAL_DELTA);
+        assertEquals(provider, exif.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD));
+        // GPS time's precision is secs.
+        assertEquals(TimeUnit.MILLISECONDS.toSeconds(timestamp),
+                TimeUnit.MILLISECONDS.toSeconds(exif.getGpsDateTime()));
+    }
+
+    @Test
+    @SmallTest
     public void testSetLatLong_withValidValues() throws IOException {
         for (int i = 0; i < TEST_LAT_LONG_VALUES_ARRAY_LENGTH; i++) {
             ExifInterface exif = createTestExifInterface();
@@ -297,6 +335,16 @@
 
     @Test
     @SmallTest
+    public void testSetAltitude() throws IOException {
+        for (int i = 0; i < TEST_ALTITUDE_VALUES.length; i++) {
+            ExifInterface exif = createTestExifInterface();
+            exif.setAltitude(TEST_ALTITUDE_VALUES[i]);
+            assertEquals(TEST_ALTITUDE_VALUES[i], exif.getAltitude(Double.NaN), RATIONAL_DELTA);
+        }
+    }
+
+    @Test
+    @SmallTest
     public void testSetDateTime() throws IOException {
         final String dateTimeValue = "2017:02:02 22:22:22";
         final String dateTimeOriginalValue = "2017:01:01 11:11:11";
diff --git a/fragment/java/android/support/v4/app/FragmentStatePagerAdapter.java b/fragment/java/android/support/v4/app/FragmentStatePagerAdapter.java
index 7aa3d20..fc27c4f 100644
--- a/fragment/java/android/support/v4/app/FragmentStatePagerAdapter.java
+++ b/fragment/java/android/support/v4/app/FragmentStatePagerAdapter.java
@@ -62,7 +62,7 @@
  *      complete}
  */
 public abstract class FragmentStatePagerAdapter extends PagerAdapter {
-    private static final String TAG = "FragmentStatePagerAdapter";
+    private static final String TAG = "FragmentStatePagerAdapt";
     private static final boolean DEBUG = false;
 
     private final FragmentManager mFragmentManager;
diff --git a/fragment/lint-baseline.xml b/fragment/lint-baseline.xml
index 2a79d28..4d3d334 100644
--- a/fragment/lint-baseline.xml
+++ b/fragment/lint-baseline.xml
@@ -24,168 +24,14 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/gradle.properties b/gradle.properties
index d1c2c14..b8e17ca 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,6 @@
 org.gradle.jvmargs=-Xmx4g
 org.gradle.daemon=true
 org.gradle.configureondemand=true
-org.gradle.parallel=true
\ No newline at end of file
+org.gradle.parallel=true
+org.gradle.caching=true
+android.useDexArchive=false
diff --git a/graphics/drawable/animated/lint-baseline.xml b/graphics/drawable/animated/lint-baseline.xml
index a3f6f77..42fb119 100644
--- a/graphics/drawable/animated/lint-baseline.xml
+++ b/graphics/drawable/animated/lint-baseline.xml
@@ -13,124 +13,14 @@
     </issue>
 
     <issue
-        id="ResourceType"
-        message="Expected resource of type xml"
-        errorLine1="            final XmlPullParser parser = res.getXml(resId);"
-        errorLine2="                                                    ~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="557"
-            column="53"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL"
-        errorLine1="            return isAutoMirrored() &amp;&amp; getLayoutDirection() == LayoutDirection.RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="821"
-            column="64"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/graphics/drawable/static/lint-baseline.xml b/graphics/drawable/static/lint-baseline.xml
index 2809a1e..172bbf6 100644
--- a/graphics/drawable/static/lint-baseline.xml
+++ b/graphics/drawable/static/lint-baseline.xml
@@ -1,26 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <issues format="4" by="lint 2.4.0-alpha6">
 
-    <issue
-        id="ResourceType"
-        message="Expected resource of type xml"
-        errorLine1="            final XmlPullParser parser = res.getXml(resId);"
-        errorLine2="                                                    ~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="557"
-            column="53"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL"
-        errorLine1="            return isAutoMirrored() &amp;&amp; getLayoutDirection() == LayoutDirection.RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="821"
-            column="64"/>
-    </issue>
-
 </issues>
diff --git a/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java b/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
index f9753eb..b68ef1b 100644
--- a/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
+++ b/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
@@ -16,6 +16,7 @@
 
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 
+import android.annotation.SuppressLint;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
@@ -554,7 +555,7 @@
         }
 
         try {
-            final XmlPullParser parser = res.getXml(resId);
+            @SuppressLint("ResourceType") final XmlPullParser parser = res.getXml(resId);
             final AttributeSet attrs = Xml.asAttributeSet(parser);
             int type;
             while ((type = parser.next()) != XmlPullParser.START_TAG &&
@@ -818,7 +819,8 @@
     // We don't support RTL auto mirroring since the getLayoutDirection() is for API 17+.
     private boolean needMirroring() {
         if (Build.VERSION.SDK_INT >= 17) {
-            return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
+            return isAutoMirrored()
+                    && DrawableCompat.getLayoutDirection(this) == LayoutDirection.RTL;
         } else {
             return false;
         }
diff --git a/media-compat/java/android/support/v4/media/session/MediaControllerCompat.java b/media-compat/java/android/support/v4/media/session/MediaControllerCompat.java
index 47bef3b..cfe13b3 100644
--- a/media-compat/java/android/support/v4/media/session/MediaControllerCompat.java
+++ b/media-compat/java/android/support/v4/media/session/MediaControllerCompat.java
@@ -241,7 +241,7 @@
     }
 
     /**
-     * Get a {@link TransportControls} instance for this session.
+     * Gets a {@link TransportControls} instance for this session.
      *
      * @return A controls instance
      */
@@ -250,7 +250,7 @@
     }
 
     /**
-     * Send the specified media button event to the session. Only media keys can
+     * Sends the specified media button event to the session. Only media keys can
      * be sent by this method, other keys will be ignored.
      *
      * @param keyEvent The media button event to dispatch.
@@ -264,7 +264,7 @@
     }
 
     /**
-     * Get the current playback state for this session.
+     * Gets the current playback state for this session.
      *
      * @return The current PlaybackState or null
      */
@@ -273,7 +273,7 @@
     }
 
     /**
-     * Get the current metadata for this session.
+     * Gets the current metadata for this session.
      *
      * @return The current MediaMetadata or null.
      */
@@ -282,7 +282,7 @@
     }
 
     /**
-     * Get the current play queue for this session if one is set. If you only
+     * Gets the current play queue for this session if one is set. If you only
      * care about the current item {@link #getMetadata()} should be used.
      *
      * @return The current play queue or null.
@@ -292,7 +292,7 @@
     }
 
     /**
-     * Add a queue item from the given {@code description} at the end of the play queue
+     * Adds a queue item from the given {@code description} at the end of the play queue
      * of this session. Not all sessions may support this. To know whether the session supports
      * this, get the session's flags with {@link #getFlags()} and check that the flag
      * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
@@ -308,7 +308,7 @@
     }
 
     /**
-     * Add a queue item from the given {@code description} at the specified position
+     * Adds a queue item from the given {@code description} at the specified position
      * in the play queue of this session. Shifts the queue item currently at that position
      * (if any) and any subsequent queue items to the right (adds one to their indices).
      * Not all sessions may support this. To know whether the session supports this,
@@ -328,7 +328,7 @@
     }
 
     /**
-     * Remove the first occurrence of the specified {@link MediaSessionCompat.QueueItem}
+     * Removes the first occurrence of the specified {@link MediaSessionCompat.QueueItem}
      * with the given {@link MediaDescriptionCompat description} in the play queue of the
      * associated session. Not all sessions may support this. To know whether the session supports
      * this, get the session's flags with {@link #getFlags()} and check that the flag
@@ -345,7 +345,7 @@
     }
 
     /**
-     * Remove an queue item at the specified position in the play queue
+     * Removes an queue item at the specified position in the play queue
      * of this session. Not all sessions may support this. To know whether the session supports
      * this, get the session's flags with {@link #getFlags()} and check that the flag
      * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
@@ -368,21 +368,21 @@
     }
 
     /**
-     * Get the queue title for this session.
+     * Gets the queue title for this session.
      */
     public CharSequence getQueueTitle() {
         return mImpl.getQueueTitle();
     }
 
     /**
-     * Get the extras for this session.
+     * Gets the extras for this session.
      */
     public Bundle getExtras() {
         return mImpl.getExtras();
     }
 
     /**
-     * Get the rating type supported by the session. One of:
+     * Gets the rating type supported by the session. One of:
      * <ul>
      * <li>{@link RatingCompat#RATING_NONE}</li>
      * <li>{@link RatingCompat#RATING_HEART}</li>
@@ -400,7 +400,7 @@
     }
 
     /**
-     * Return whether captioning is enabled for this session.
+     * Returns whether captioning is enabled for this session.
      *
      * @return {@code true} if captioning is enabled, {@code false} if disabled or not set.
      */
@@ -409,7 +409,7 @@
     }
 
     /**
-     * Get the repeat mode for this session.
+     * Gets the repeat mode for this session.
      *
      * @return The latest repeat mode set to the session, or
      *         {@link PlaybackStateCompat#REPEAT_MODE_NONE} if not set.
@@ -419,7 +419,7 @@
     }
 
     /**
-     * Return whether the shuffle mode is enabled for this session.
+     * Returns whether the shuffle mode is enabled for this session.
      *
      * @return {@code true} if the shuffle mode is enabled, {@code false} if disabled or not set.
      * @deprecated Use {@link #getShuffleMode} instead.
@@ -430,7 +430,7 @@
     }
 
     /**
-     * Get the shuffle mode for this session.
+     * Gets the shuffle mode for this session.
      *
      * @return The latest shuffle mode set to the session, or
      *         {@link PlaybackStateCompat#SHUFFLE_MODE_NONE} if not set.
@@ -440,7 +440,7 @@
     }
 
     /**
-     * Get the flags for this session. Flags are defined in
+     * Gets the flags for this session. Flags are defined in
      * {@link MediaSessionCompat}.
      *
      * @return The current set of flags for the session.
@@ -450,7 +450,7 @@
     }
 
     /**
-     * Get the current playback info for this session.
+     * Gets the current playback info for this session.
      *
      * @return The current playback info or null.
      */
@@ -459,7 +459,7 @@
     }
 
     /**
-     * Get an intent for launching UI associated with this session if one
+     * Gets an intent for launching UI associated with this session if one
      * exists.
      *
      * @return A {@link PendingIntent} to launch UI or null.
@@ -469,7 +469,7 @@
     }
 
     /**
-     * Get the token for the session this controller is connected to.
+     * Gets the token for the session this controller is connected to.
      *
      * @return The session's token.
      */
@@ -478,7 +478,7 @@
     }
 
     /**
-     * Set the volume of the output this session is playing on. The command will
+     * Sets the volume of the output this session is playing on. The command will
      * be ignored if it does not support
      * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in
      * {@link AudioManager} may be used to affect the handling.
@@ -493,7 +493,7 @@
     }
 
     /**
-     * Adjust the volume of the output this session is playing on. The direction
+     * Adjusts the volume of the output this session is playing on. The direction
      * must be one of {@link AudioManager#ADJUST_LOWER},
      * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
      * The command will be ignored if the session does not support
@@ -538,7 +538,7 @@
     }
 
     /**
-     * Stop receiving updates on the specified callback. If an update has
+     * Stops receiving updates on the specified callback. If an update has
      * already been posted you may still receive it after calling this method.
      *
      * @param callback The callback to remove
@@ -567,7 +567,7 @@
     }
 
     /**
-     * Get the session owner's package name.
+     * Gets the session owner's package name.
      *
      * @return The package name of of the session owner.
      */
@@ -665,7 +665,7 @@
         }
 
         /**
-         * Override to handle chagnes to the {@link MediaSessionCompat} extras.
+         * Override to handle changes to the {@link MediaSessionCompat} extras.
          *
          * @param extras The extras that can include other information
          *            associated with the {@link MediaSessionCompat}.
@@ -728,7 +728,7 @@
         }
 
         /**
-         * Set the handler to use for pre 21 callbacks.
+         * Sets the handler to use for pre 21 callbacks.
          */
         private void setHandler(Handler handler) {
             mHandler = new MessageHandler(handler.getLooper());
@@ -1040,7 +1040,7 @@
         public abstract void playFromUri(Uri uri, Bundle extras);
 
         /**
-         * Play an item with a specific id in the play queue. If you specify an
+         * Plays an item with a specific id in the play queue. If you specify an
          * id that is not in the play queue, the behavior is undefined.
          */
         public abstract void skipToQueueItem(long id);
@@ -1058,36 +1058,36 @@
         public abstract void stop();
 
         /**
-         * Move to a new location in the media stream.
+         * Moves to a new location in the media stream.
          *
          * @param pos Position to move to, in milliseconds.
          */
         public abstract void seekTo(long pos);
 
         /**
-         * Start fast forwarding. If playback is already fast forwarding this
+         * Starts fast forwarding. If playback is already fast forwarding this
          * may increase the rate.
          */
         public abstract void fastForward();
 
         /**
-         * Skip to the next item.
+         * Skips to the next item.
          */
         public abstract void skipToNext();
 
         /**
-         * Start rewinding. If playback is already rewinding this may increase
+         * Starts rewinding. If playback is already rewinding this may increase
          * the rate.
          */
         public abstract void rewind();
 
         /**
-         * Skip to the previous item.
+         * Skips to the previous item.
          */
         public abstract void skipToPrevious();
 
         /**
-         * Rate the current content. This will cause the rating to be set for
+         * Rates the current content. This will cause the rating to be set for
          * the current user. The Rating type must match the type returned by
          * {@link #getRatingType()}.
          *
@@ -1096,14 +1096,14 @@
         public abstract void setRating(RatingCompat rating);
 
         /**
-         * Enable/disable captioning for this session.
+         * Enables/disables captioning for this session.
          *
          * @param enabled {@code true} to enable captioning, {@code false} to disable.
          */
         public abstract void setCaptioningEnabled(boolean enabled);
 
         /**
-         * Set the repeat mode for this session.
+         * Sets the repeat mode for this session.
          *
          * @param repeatMode The repeat mode. Must be one of the followings:
          *                   {@link PlaybackStateCompat#REPEAT_MODE_NONE},
@@ -1114,7 +1114,7 @@
         public abstract void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode);
 
         /**
-         * Set the shuffle mode for this session.
+         * Sets the shuffle mode for this session.
          *
          * @param enabled {@code true} to enable the shuffle mode, {@code false} to disable.
          * @deprecated Use {@link #setShuffleMode} instead.
@@ -1123,7 +1123,7 @@
         public abstract void setShuffleModeEnabled(boolean enabled);
 
         /**
-         * Set the shuffle mode for this session.
+         * Sets the shuffle mode for this session.
          *
          * @param shuffleMode The shuffle mode. Must be one of the followings:
          *                    {@link PlaybackStateCompat#SHUFFLE_MODE_NONE},
@@ -1133,7 +1133,7 @@
         public abstract void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode);
 
         /**
-         * Send a custom action for the {@link MediaSessionCompat} to perform.
+         * Sends a custom action for the {@link MediaSessionCompat} to perform.
          *
          * @param customAction The action to perform.
          * @param args Optional arguments to supply to the
@@ -1143,7 +1143,7 @@
                 Bundle args);
 
         /**
-         * Send the id and args from a custom action for the
+         * Sends the id and args from a custom action for the
          * {@link MediaSessionCompat} to perform.
          *
          * @see #sendCustomAction(PlaybackStateCompat.CustomAction action,
@@ -1190,7 +1190,7 @@
         }
 
         /**
-         * Get the type of volume handling, either local or remote. One of:
+         * Gets the type of volume handling, either local or remote. One of:
          * <ul>
          * <li>{@link PlaybackInfo#PLAYBACK_TYPE_LOCAL}</li>
          * <li>{@link PlaybackInfo#PLAYBACK_TYPE_REMOTE}</li>
@@ -1203,7 +1203,7 @@
         }
 
         /**
-         * Get the stream this is currently controlling volume on. When the volume
+         * Gets the stream this is currently controlling volume on. When the volume
          * type is {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} this value does not
          * have meaning and should be ignored.
          *
@@ -1215,7 +1215,7 @@
         }
 
         /**
-         * Get the type of volume control that can be used. One of:
+         * Gets the type of volume control that can be used. One of:
          * <ul>
          * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li>
          * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li>
@@ -1230,7 +1230,7 @@
         }
 
         /**
-         * Get the maximum volume that may be set for this session.
+         * Gets the maximum volume that may be set for this session.
          *
          * @return The maximum allowed volume where this session is playing.
          */
@@ -1239,7 +1239,7 @@
         }
 
         /**
-         * Get the current volume for this session.
+         * Gets the current volume for this session.
          *
          * @return The current volume where this session is playing.
          */
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 ecc2dbc..5df4b72 100644
--- a/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -115,19 +115,19 @@
     public @interface SessionFlags {}
 
     /**
-     * Set this flag on the session to indicate that it can handle media button
+     * Sets this flag on the session to indicate that it can handle media button
      * events.
      */
     public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0;
 
     /**
-     * Set this flag on the session to indicate that it handles transport
+     * Sets this flag on the session to indicate that it handles transport
      * control commands through its {@link Callback}.
      */
     public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
 
     /**
-     * Set this flag on the session to indicate that it handles queue
+     * Sets this flag on the session to indicate that it handles queue
      * management commands through its {@link Callback}.
      */
     public static final int FLAG_HANDLES_QUEUE_COMMANDS = 1 << 2;
@@ -435,7 +435,7 @@
     }
 
     /**
-     * Add a callback to receive updates on for the MediaSession. This includes
+     * Adds a callback to receive updates on for the MediaSession. This includes
      * media button and volume events. The caller's thread will be used to post
      * events.
      *
@@ -446,7 +446,7 @@
     }
 
     /**
-     * Set the callback to receive updates for the MediaSession. This includes
+     * Sets the callback to receive updates for the MediaSession. This includes
      * media button and volume events. Set the callback to null to stop
      * receiving events.
      *
@@ -458,7 +458,7 @@
     }
 
     /**
-     * Set an intent for launching UI for this Session. This can be used as a
+     * Sets an intent for launching UI for this Session. This can be used as a
      * quick link to an ongoing media screen. The intent should be for an
      * activity that may be started using
      * {@link Activity#startActivity(Intent)}.
@@ -470,7 +470,7 @@
     }
 
     /**
-     * Set a pending intent for your media button receiver to allow restarting
+     * Sets a pending intent for your media button receiver to allow restarting
      * playback after the session has been stopped. If your app is started in
      * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
      * the pending intent.
@@ -487,7 +487,7 @@
     }
 
     /**
-     * Set any flags for the session.
+     * Sets any flags for the session.
      *
      * @param flags The flags to set for this session.
      */
@@ -496,7 +496,7 @@
     }
 
     /**
-     * Set the stream this session is playing on. This will affect the system's
+     * Sets the stream this session is playing on. This will affect the system's
      * volume handling for this session. If {@link #setPlaybackToRemote} was
      * previously called it will stop receiving volume commands and the system
      * will begin sending volume changes to the appropriate stream.
@@ -510,7 +510,7 @@
     }
 
     /**
-     * Configure this session to use remote volume handling. This must be called
+     * Configures this session to use remote volume handling. This must be called
      * to receive volume button events, otherwise the system will adjust the
      * current stream volume for this session. If {@link #setPlaybackToLocal}
      * was previously called that stream will stop receiving volume changes for
@@ -532,7 +532,7 @@
     }
 
     /**
-     * Set if this session is currently active and ready to receive commands. If
+     * Sets if this session is currently active and ready to receive commands. If
      * set to false your session's controller may not be discoverable. You must
      * set the session to active before it can start receiving media button
      * events or transport commands.
@@ -552,7 +552,7 @@
     }
 
     /**
-     * Get the current active state of this session.
+     * Gets the current active state of this session.
      *
      * @return True if the session is active, false otherwise.
      */
@@ -561,7 +561,7 @@
     }
 
     /**
-     * Send a proprietary event to all MediaControllers listening to this
+     * Sends a proprietary event to all MediaControllers listening to this
      * Session. It's up to the Controller/Session owner to determine the meaning
      * of any events.
      *
@@ -585,7 +585,7 @@
     }
 
     /**
-     * Retrieve a token object that can be used by apps to create a
+     * Retrieves a token object that can be used by apps to create a
      * {@link MediaControllerCompat} for interacting with this session. The
      * owner of the session is responsible for deciding how to distribute these
      * tokens.
@@ -603,7 +603,7 @@
     }
 
     /**
-     * Get a controller for this session. This is a convenience method to avoid
+     * Gets a controller for this session. This is a convenience method to avoid
      * having to cache your own controller in process.
      *
      * @return A controller for this session.
@@ -613,7 +613,7 @@
     }
 
     /**
-     * Update the current playback state.
+     * Updates the current playback state.
      *
      * @param state The current state of playback
      */
@@ -622,7 +622,7 @@
     }
 
     /**
-     * Update the current metadata. New metadata can be created using
+     * Updates the current metadata. New metadata can be created using
      * {@link android.support.v4.media.MediaMetadataCompat.Builder}. This operation may take time
      * proportional to the size of the bitmap to replace large bitmaps with a scaled down copy.
      *
@@ -634,7 +634,7 @@
     }
 
     /**
-     * Update the list of items in the play queue. It is an ordered list and
+     * Updates the list of items in the play queue. It is an ordered list and
      * should contain the current item, and previous or upcoming items if they
      * exist. Specify null if there is no current play queue.
      * <p>
@@ -649,7 +649,7 @@
     }
 
     /**
-     * Set the title of the play queue. The UI should display this title along
+     * Sets the title of the play queue. The UI should display this title along
      * with the play queue itself. e.g. "Play Queue", "Now Playing", or an album
      * name.
      *
@@ -660,7 +660,7 @@
     }
 
     /**
-     * Set the style of rating used by this session. Apps trying to set the
+     * Sets the style of rating used by this session. Apps trying to set the
      * rating should use this style. Must be one of the following:
      * <ul>
      * <li>{@link RatingCompat#RATING_NONE}</li>
@@ -677,7 +677,7 @@
     }
 
     /**
-     * Enable/disable captioning for this session.
+     * Enables/disables captioning for this session.
      *
      * @param enabled {@code true} to enable captioning, {@code false} to disable.
      */
@@ -686,7 +686,7 @@
     }
 
     /**
-     * Set the repeat mode for this session.
+     * Sets the repeat mode for this session.
      * <p>
      * Note that if this method is not called before, {@link MediaControllerCompat#getRepeatMode}
      * will return {@link PlaybackStateCompat#REPEAT_MODE_NONE}.
@@ -702,7 +702,7 @@
     }
 
     /**
-     * Set the shuffle mode for this session.
+     * Sets the shuffle mode for this session.
      * <p>
      * Note that if this method is not called before,
      * {@link MediaControllerCompat#isShuffleModeEnabled} will return {@code false}.
@@ -716,7 +716,7 @@
     }
 
     /**
-     * Set the shuffle mode for this session.
+     * Sets the shuffle mode for this session.
      * <p>
      * Note that if this method is not called before, {@link MediaControllerCompat#getShuffleMode}
      * will return {@link PlaybackStateCompat#SHUFFLE_MODE_NONE}.
@@ -731,7 +731,7 @@
     }
 
     /**
-     * Set some extras that can be associated with the
+     * Sets some extras that can be associated with the
      * {@link MediaSessionCompat}. No assumptions should be made as to how a
      * {@link MediaControllerCompat} will handle these extras. Keys should be
      * fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts.
@@ -867,7 +867,7 @@
         final Object mCallbackObj;
         private WeakReference<MediaSessionImpl> mSessionImpl;
         private CallbackHandler mCallbackHandler = null;
-        private boolean mMediaPlayPauseKeyHandled;
+        private boolean mMediaPlayPauseKeyPending;
 
         public Callback() {
             if (android.os.Build.VERSION.SDK_INT >= 24) {
@@ -924,42 +924,45 @@
                 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
                 case KeyEvent.KEYCODE_HEADSETHOOK:
                     if (keyEvent.getRepeatCount() > 0) {
+                        // Consider long-press as a single tap.
+                        handleMediaPlayPauseKeySingleTapIfPending();
+                    } else if (mMediaPlayPauseKeyPending) {
                         mCallbackHandler.removeMessages(
                                 CallbackHandler.MSG_MEDIA_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
-                        if (keyEvent.getRepeatCount() == 1) {
-                            handleMediaPlayPauseKeySingleTapIfUnhandled();
-                        }
-                    } else if (mCallbackHandler.hasMessages(
-                            CallbackHandler.MSG_MEDIA_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT)) {
-                        mCallbackHandler.removeMessages(
-                                CallbackHandler.MSG_MEDIA_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
+                        mMediaPlayPauseKeyPending = false;
                         PlaybackStateCompat state = impl.getPlaybackState();
                         long validActions = state == null ? 0 : state.getActions();
                         // Consider double tap as the next.
                         if ((validActions & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
                             onSkipToNext();
                         }
-                        mMediaPlayPauseKeyHandled = true;
                     } else {
-                        mMediaPlayPauseKeyHandled = false;
+                        mMediaPlayPauseKeyPending = true;
                         mCallbackHandler.sendEmptyMessageDelayed(
                                 CallbackHandler.MSG_MEDIA_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT,
                                 ViewConfiguration.getDoubleTapTimeout());
                     }
                     return true;
+                default:
+                    // If another key is pressed within double tap timeout, consider the pending
+                    // pending play/pause as a single tap to handle media keys in order.
+                    handleMediaPlayPauseKeySingleTapIfPending();
+                    break;
             }
             return false;
         }
 
-        private void handleMediaPlayPauseKeySingleTapIfUnhandled() {
-            if (mMediaPlayPauseKeyHandled) {
+        private void handleMediaPlayPauseKeySingleTapIfPending() {
+            if (!mMediaPlayPauseKeyPending) {
                 return;
             }
+            mMediaPlayPauseKeyPending = false;
+            mCallbackHandler.removeMessages(
+                    CallbackHandler.MSG_MEDIA_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
             MediaSessionImpl impl = mSessionImpl.get();
             if (impl == null) {
                 return;
             }
-            mMediaPlayPauseKeyHandled = true;
             PlaybackStateCompat state = impl.getPlaybackState();
             long validActions = state == null ? 0 : state.getActions();
             boolean isPlaying = state != null
@@ -1230,7 +1233,7 @@
             @Override
             public void handleMessage(Message msg) {
                 if (msg.what == MSG_MEDIA_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT) {
-                    handleMediaPlayPauseKeySingleTapIfUnhandled();
+                    handleMediaPlayPauseKeySingleTapIfPending();
                 }
             }
         }
@@ -1585,7 +1588,7 @@
         private Object mItem;
 
         /**
-         * Create a new {@link MediaSessionCompat.QueueItem}.
+         * Creates a new {@link MediaSessionCompat.QueueItem}.
          *
          * @param description The {@link MediaDescriptionCompat} for this item.
          * @param id An identifier for this item. It must be unique within the
@@ -1613,14 +1616,14 @@
         }
 
         /**
-         * Get the description for this item.
+         * Gets the description for this item.
          */
         public MediaDescriptionCompat getDescription() {
             return mDescription;
         }
 
         /**
-         * Get the queue id for this item.
+         * Gets the queue id for this item.
          */
         public long getQueueId() {
             return mId;
@@ -1638,7 +1641,7 @@
         }
 
         /**
-         * Get the underlying
+         * Gets the underlying
          * {@link android.media.session.MediaSession.QueueItem}.
          * <p>
          * On builds before {@link android.os.Build.VERSION_CODES#LOLLIPOP} null
diff --git a/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java b/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java
index a3744cc..b44a085 100644
--- a/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java
+++ b/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java
@@ -600,18 +600,8 @@
         PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, mediaButtonIntent, 0);
         mSession.setMediaButtonReceiver(pi);
 
-        long supportedActions = PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE
-                | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_STOP
-                | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
-                | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
-                | PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND;
-
         // Set state to STATE_PLAYING to get higher priority.
-        PlaybackStateCompat defaultState = new PlaybackStateCompat.Builder()
-                .setActions(supportedActions)
-                .setState(PlaybackStateCompat.STATE_PLAYING, 0L, 0.0f)
-                .build();
-        mSession.setPlaybackState(defaultState);
+        setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
 
         sessionCallback.reset(1);
         sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY);
@@ -651,24 +641,21 @@
         // Test PLAY_PAUSE button twice.
         // First, send PLAY_PAUSE button event while in STATE_PAUSED.
         sessionCallback.reset(1);
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder().setActions(supportedActions)
-                .setState(PlaybackStateCompat.STATE_PAUSED, 0L, 0.0f).build());
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
         sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
         assertTrue(sessionCallback.await(TIME_OUT_MS));
         assertEquals(1, sessionCallback.mOnPlayCalledCount);
 
         // Next, send PLAY_PAUSE button event while in STATE_PLAYING.
         sessionCallback.reset(1);
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder().setActions(supportedActions)
-                .setState(PlaybackStateCompat.STATE_PLAYING, 0L, 0.0f).build());
+        setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
         sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
         assertTrue(sessionCallback.await(TIME_OUT_MS));
         assertTrue(sessionCallback.mOnPauseCalled);
 
         // Double tap of PLAY_PAUSE is the next track.
         sessionCallback.reset(2);
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder().setActions(supportedActions)
-                .setState(PlaybackStateCompat.STATE_PAUSED, 0L, 0.0f).build());
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
         sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
         sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
         assertFalse(sessionCallback.await(WAIT_TIME_MS));
@@ -679,8 +666,7 @@
         // Test PLAY_PAUSE button long-press.
         // It should be the same as the single short-press.
         sessionCallback.reset(1);
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder().setActions(supportedActions)
-                .setState(PlaybackStateCompat.STATE_PAUSED, 0L, 0.0f).build());
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
         sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
         assertTrue(sessionCallback.await(TIME_OUT_MS));
         assertEquals(1, sessionCallback.mOnPlayCalledCount);
@@ -700,16 +686,53 @@
         // Initial long-press of the PLAY_PAUSE is considered as the single short-press already,
         // so it shouldn't be used as the first tap of the double tap.
         sessionCallback.reset(2);
-        mSession.setPlaybackState(new PlaybackStateCompat.Builder().setActions(supportedActions)
-                .setState(PlaybackStateCompat.STATE_PAUSED, 0L, 0.0f).build());
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
         sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true);
         sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
         assertTrue(sessionCallback.await(TIME_OUT_MS));
         // onMediaButtonEvent() calls either onPlay() or onPause() depending on the playback state,
         // so onPlay() should be called twice while onPause() isn't called.
-        assertEquals(2, sessionCallback.mOnPlayCalledCount);
-        assertFalse(sessionCallback.mOnPauseCalled);
+        assertEquals(1, sessionCallback.mOnPlayCalledCount);
+        assertTrue(sessionCallback.mOnPauseCalled);
         assertFalse(sessionCallback.mOnSkipToNextCalled);
+
+        // If another media key is pressed while the double tap of PLAY_PAUSE,
+        // PLAY_PAUSE should be handles as normal.
+        sessionCallback.reset(3);
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_STOP);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        assertTrue(sessionCallback.await(TIME_OUT_MS));
+        assertFalse(sessionCallback.mOnSkipToNextCalled);
+        assertTrue(sessionCallback.mOnStopCalled);
+        assertEquals(2, sessionCallback.mOnPlayCalledCount);
+
+        // Test if media keys are handled in order.
+        sessionCallback.reset(2);
+        setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_STOP);
+        assertTrue(sessionCallback.await(TIME_OUT_MS));
+        assertEquals(1, sessionCallback.mOnPlayCalledCount);
+        assertTrue(sessionCallback.mOnStopCalled);
+        synchronized (mWaitLock) {
+            assertEquals(PlaybackStateCompat.STATE_STOPPED,
+                    mSession.getController().getPlaybackState().getState());
+        }
+    }
+
+    private void setPlaybackState(int state) {
+        final long allActions = PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE
+                | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_STOP
+                | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
+                | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
+                | PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND;
+        PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder().setActions(allActions)
+                .setState(state, 0L, 0.0f).build();
+        synchronized (mWaitLock) {
+            mSession.setPlaybackState(playbackState);
+        }
     }
 
     @Test
@@ -995,18 +1018,21 @@
         @Override
         public void onPlay() {
             mOnPlayCalledCount++;
+            setPlaybackState(PlaybackStateCompat.STATE_PLAYING);
             mLatch.countDown();
         }
 
         @Override
         public void onPause() {
             mOnPauseCalled = true;
+            setPlaybackState(PlaybackStateCompat.STATE_PAUSED);
             mLatch.countDown();
         }
 
         @Override
         public void onStop() {
             mOnStopCalled = true;
+            setPlaybackState(PlaybackStateCompat.STATE_STOPPED);
             mLatch.countDown();
         }
 
diff --git a/recommendation/lint-baseline.xml b/recommendation/lint-baseline.xml
index 2a79d28..4d3d334 100644
--- a/recommendation/lint-baseline.xml
+++ b/recommendation/lint-baseline.xml
@@ -24,168 +24,14 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/samples/Support13Demos/AndroidManifest.xml b/samples/Support13Demos/AndroidManifest.xml
index cdc246f..af7fad2 100644
--- a/samples/Support13Demos/AndroidManifest.xml
+++ b/samples/Support13Demos/AndroidManifest.xml
@@ -24,8 +24,6 @@
 
     <uses-permission android:name="android.permission.READ_CONTACTS" />
 
-    <uses-sdk android:minSdkVersion="14" />
-
     <!-- The smallest screen this app works on is a phone.  The app will
          scale its UI to larger screens but doesn't make good use of them
          so allow the compatibility mode button to be shown (mostly because
diff --git a/samples/Support13Demos/build.gradle b/samples/Support13Demos/build.gradle
index d1d4471..c63312f 100644
--- a/samples/Support13Demos/build.gradle
+++ b/samples/Support13Demos/build.gradle
@@ -9,6 +9,7 @@
 
     defaultConfig {
         minSdkVersion 14
+        targetSdkVersion project.ext.currentSdk
     }
 
     sourceSets {
diff --git a/samples/Support4Demos/AndroidManifest.xml b/samples/Support4Demos/AndroidManifest.xml
index 893259c..812d4e8 100644
--- a/samples/Support4Demos/AndroidManifest.xml
+++ b/samples/Support4Demos/AndroidManifest.xml
@@ -26,8 +26,6 @@
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
 
-    <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="22" />
-
     <!-- The smallest screen this app works on is a phone.  The app will
          scale its UI to larger screens but doesn't make good use of them
          so allow the compatibility mode button to be shown (mostly because
@@ -51,7 +49,7 @@
         <activity android:name=".app.SendResult"
                 android:theme="@style/ThemeDialogWhenLarge">
         </activity>
-        
+
         <!-- Fragment Support Samples -->
 
         <activity android:name=".app.FragmentAlertDialogSupport"
@@ -144,7 +142,7 @@
                 <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" />
             </intent-filter>
         </activity>
-        
+
         <activity android:name=".app.FragmentRetainInstanceSupport"
                 android:label="@string/fragment_retain_instance_support">
             <intent-filter>
@@ -216,7 +214,7 @@
                 <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" />
             </intent-filter>
         </activity>
-        
+
         <activity android:name=".app.LoaderCustomSupport"
                 android:label="@string/loader_custom_support">
             <intent-filter>
diff --git a/samples/Support4Demos/build.gradle b/samples/Support4Demos/build.gradle
index 353b17f..836b212 100644
--- a/samples/Support4Demos/build.gradle
+++ b/samples/Support4Demos/build.gradle
@@ -9,6 +9,7 @@
 
     defaultConfig {
         minSdkVersion 14
+        targetSdkVersion project.ext.currentSdk
     }
 
     sourceSets {
diff --git a/samples/Support7Demos/AndroidManifest.xml b/samples/Support7Demos/AndroidManifest.xml
index 25b2427..1604267 100644
--- a/samples/Support7Demos/AndroidManifest.xml
+++ b/samples/Support7Demos/AndroidManifest.xml
@@ -34,8 +34,6 @@
     <!-- Permission for ACCESS_COARSE_LOCATION is required for DayNight themes. -->
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
 
-    <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" />
-
     <!-- The smallest screen this app works on is a phone.  The app will
          scale its UI to larger screens but doesn't make good use of them
          so allow the compatibility mode button to be shown (mostly because
diff --git a/samples/Support7Demos/build.gradle b/samples/Support7Demos/build.gradle
index 0da74be..618cfab 100644
--- a/samples/Support7Demos/build.gradle
+++ b/samples/Support7Demos/build.gradle
@@ -14,6 +14,7 @@
 
     defaultConfig {
         minSdkVersion 14
+        targetSdkVersion project.ext.currentSdk
         vectorDrawables.useSupportLibrary = true
     }
 
diff --git a/samples/SupportAnimationDemos/AndroidManifest.xml b/samples/SupportAnimationDemos/AndroidManifest.xml
index 25e5ec8..243e6e4 100644
--- a/samples/SupportAnimationDemos/AndroidManifest.xml
+++ b/samples/SupportAnimationDemos/AndroidManifest.xml
@@ -16,9 +16,6 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.example.android.support.animation">
-
-    <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="25" />
-
     <application android:label="@string/activity_sample_code"
             android:supportsRtl="true"
             android:icon="@drawable/app_sample_code"
diff --git a/samples/SupportAnimationDemos/build.gradle b/samples/SupportAnimationDemos/build.gradle
index 6f3daf9..c619f6b 100644
--- a/samples/SupportAnimationDemos/build.gradle
+++ b/samples/SupportAnimationDemos/build.gradle
@@ -9,6 +9,7 @@
 
     defaultConfig {
         minSdkVersion 16
+        targetSdkVersion project.ext.currentSdk
     }
 
     sourceSets {
diff --git a/samples/SupportAppNavigation/AndroidManifest.xml b/samples/SupportAppNavigation/AndroidManifest.xml
index c8e4fa8..4681b33 100644
--- a/samples/SupportAppNavigation/AndroidManifest.xml
+++ b/samples/SupportAppNavigation/AndroidManifest.xml
@@ -18,9 +18,6 @@
         android:versionName="1"
         xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.example.android.support.appnavigation">
-
-    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15" />
-
     <application android:label="@string/app_name">
         <activity android:name=".app.AppNavHomeActivity"
                 android:label="@string/app_nav_home_label">
diff --git a/samples/SupportAppNavigation/build.gradle b/samples/SupportAppNavigation/build.gradle
index 456d677..bb48f2f 100644
--- a/samples/SupportAppNavigation/build.gradle
+++ b/samples/SupportAppNavigation/build.gradle
@@ -25,6 +25,7 @@
 
     defaultConfig {
         minSdkVersion 14
+        targetSdkVersion project.ext.currentSdk
     }
 
     sourceSets {
diff --git a/samples/SupportDesignDemos/AndroidManifest.xml b/samples/SupportDesignDemos/AndroidManifest.xml
index 88c423a..bedbea9 100644
--- a/samples/SupportDesignDemos/AndroidManifest.xml
+++ b/samples/SupportDesignDemos/AndroidManifest.xml
@@ -22,8 +22,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.example.android.support.design">
 
-    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
-
     <application android:label="@string/activity_sample_code"
             android:supportsRtl="true"
             android:icon="@drawable/app_sample_code"
diff --git a/samples/SupportDesignDemos/build.gradle b/samples/SupportDesignDemos/build.gradle
index ac0b714..7987f52 100644
--- a/samples/SupportDesignDemos/build.gradle
+++ b/samples/SupportDesignDemos/build.gradle
@@ -9,6 +9,7 @@
 
     defaultConfig {
         minSdkVersion 14
+        targetSdkVersion project.ext.currentSdk
         vectorDrawables.useSupportLibrary = true
     }
 
diff --git a/samples/SupportDesignDemos/res/menu/sample_bottom_menu.xml b/samples/SupportDesignDemos/res/menu/sample_bottom_menu.xml
index d6d4761..f485789 100644
--- a/samples/SupportDesignDemos/res/menu/sample_bottom_menu.xml
+++ b/samples/SupportDesignDemos/res/menu/sample_bottom_menu.xml
@@ -13,14 +13,21 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+      xmlns:app="http://schemas.android.com/apk/res-auto">
     <item android:id="@+id/action_search"
           android:title="@string/menu_search"
+          app:contentDescription="@string/menu_search"
+          app:tooltipText="@string/menu_search"
           android:icon="@drawable/ic_search"/>
     <item android:id="@+id/action_settings"
           android:title="@string/menu_settings"
+          app:contentDescription="@string/menu_settings"
+          app:tooltipText="@string/menu_settings"
           android:icon="@drawable/ic_add"/>
     <item android:id="@+id/action_music"
           android:title="@string/tab_text"
+          app:contentDescription="@string/tab_text"
+          app:tooltipText="@string/tab_text"
           android:icon="@drawable/ic_action_navigation_menu"/>
-</menu>
\ No newline at end of file
+</menu>
diff --git a/samples/SupportEmojiDemos/build.gradle b/samples/SupportEmojiDemos/build.gradle
index 3c32d48..230e478 100644
--- a/samples/SupportEmojiDemos/build.gradle
+++ b/samples/SupportEmojiDemos/build.gradle
@@ -31,6 +31,7 @@
 
     defaultConfig {
         minSdkVersion 14
+        targetSdkVersion project.ext.currentSdk
     }
 
     sourceSets {
diff --git a/samples/SupportLeanbackDemos/AndroidManifest.xml b/samples/SupportLeanbackDemos/AndroidManifest.xml
index 067bb36..93bddce 100644
--- a/samples/SupportLeanbackDemos/AndroidManifest.xml
+++ b/samples/SupportLeanbackDemos/AndroidManifest.xml
@@ -4,7 +4,6 @@
     android:versionCode="1"
     android:versionName="1.0">
 
-    <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="23" />
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
diff --git a/samples/SupportLeanbackDemos/build.gradle b/samples/SupportLeanbackDemos/build.gradle
index 122d718..ea6ed9e 100644
--- a/samples/SupportLeanbackDemos/build.gradle
+++ b/samples/SupportLeanbackDemos/build.gradle
@@ -10,6 +10,7 @@
 
     defaultConfig {
         minSdkVersion 17
+        targetSdkVersion project.ext.currentSdk
     }
 
     sourceSets {
diff --git a/samples/SupportLeanbackJank/AndroidManifest.xml b/samples/SupportLeanbackJank/AndroidManifest.xml
index 87be772..348f2d6 100644
--- a/samples/SupportLeanbackJank/AndroidManifest.xml
+++ b/samples/SupportLeanbackJank/AndroidManifest.xml
@@ -17,11 +17,6 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.google.android.leanbackjank">
-
-    <uses-sdk
-        android:minSdkVersion="21"
-        android:targetSdkVersion="24"/>
-
     <uses-feature
         android:name="android.hardware.touchscreen"
         android:required="false"/>
diff --git a/samples/SupportLeanbackJank/build.gradle b/samples/SupportLeanbackJank/build.gradle
index 0b22bed..44346f7 100644
--- a/samples/SupportLeanbackJank/build.gradle
+++ b/samples/SupportLeanbackJank/build.gradle
@@ -11,6 +11,7 @@
 
     defaultConfig {
         minSdkVersion 17
+        targetSdkVersion project.ext.currentSdk
     }
 
     sourceSets {
diff --git a/samples/SupportPercentDemos/AndroidManifest.xml b/samples/SupportPercentDemos/AndroidManifest.xml
index 5c22277..c720dbd 100644
--- a/samples/SupportPercentDemos/AndroidManifest.xml
+++ b/samples/SupportPercentDemos/AndroidManifest.xml
@@ -21,9 +21,6 @@
      to come from a domain that you own or have control over. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.example.android.support.percent">
-
-    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
-
     <application android:label="@string/activity_sample_code"
             android:supportsRtl="true"
             android:icon="@drawable/app_sample_code">
diff --git a/samples/SupportPercentDemos/build.gradle b/samples/SupportPercentDemos/build.gradle
index 78eba62..7990a21 100644
--- a/samples/SupportPercentDemos/build.gradle
+++ b/samples/SupportPercentDemos/build.gradle
@@ -9,6 +9,7 @@
 
     defaultConfig {
         minSdkVersion 14
+        targetSdkVersion project.ext.currentSdk
     }
 
     sourceSets {
diff --git a/samples/SupportPreferenceDemos/AndroidManifest.xml b/samples/SupportPreferenceDemos/AndroidManifest.xml
index f6554b3..4fb2492 100644
--- a/samples/SupportPreferenceDemos/AndroidManifest.xml
+++ b/samples/SupportPreferenceDemos/AndroidManifest.xml
@@ -19,7 +19,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     package="com.example.android.supportpreference">
 
-    <uses-sdk android:targetSdkVersion="24"
+    <uses-sdk
         tools:overrideLibrary="android.support.v17.preference, android.support.v17.leanback" />
 
     <uses-feature android:name="android.software.Leanback" android:required="false" />
diff --git a/samples/SupportPreferenceDemos/build.gradle b/samples/SupportPreferenceDemos/build.gradle
index 572ae73..5bee5e9 100644
--- a/samples/SupportPreferenceDemos/build.gradle
+++ b/samples/SupportPreferenceDemos/build.gradle
@@ -14,6 +14,7 @@
 
     defaultConfig {
         minSdkVersion 14
+        targetSdkVersion project.ext.currentSdk
     }
 
     sourceSets {
diff --git a/samples/SupportTransitionDemos/AndroidManifest.xml b/samples/SupportTransitionDemos/AndroidManifest.xml
index a6f52db..a58c189 100644
--- a/samples/SupportTransitionDemos/AndroidManifest.xml
+++ b/samples/SupportTransitionDemos/AndroidManifest.xml
@@ -21,9 +21,6 @@
      to come from a domain that you own or have control over. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.example.android.support.transition">
-
-    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="23" />
-
     <application android:label="@string/activity_sample_code"
             android:supportsRtl="true"
             android:icon="@drawable/app_sample_code"
diff --git a/samples/SupportTransitionDemos/build.gradle b/samples/SupportTransitionDemos/build.gradle
index c5033f7..7eda8b3 100644
--- a/samples/SupportTransitionDemos/build.gradle
+++ b/samples/SupportTransitionDemos/build.gradle
@@ -10,6 +10,7 @@
 
     defaultConfig {
         minSdkVersion 14
+        targetSdkVersion project.ext.currentSdk
     }
 
     sourceSets {
diff --git a/samples/SupportVectorDrawableDemos/AndroidManifest.xml b/samples/SupportVectorDrawableDemos/AndroidManifest.xml
index 1de3a5f..b9ccf77 100644
--- a/samples/SupportVectorDrawableDemos/AndroidManifest.xml
+++ b/samples/SupportVectorDrawableDemos/AndroidManifest.xml
@@ -16,9 +16,6 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.example.android.support.vectordrawable" >
-
-    <uses-sdk android:minSdkVersion="11" android:targetSdkVersion="23"/>
-
     <application android:icon="@drawable/app_sample_code" android:label="SupportVectorDrawableDemos" >
         <activity android:name="com.example.android.support.vectordrawable.app.SupportVectorDrawableDemos">
             <intent-filter>
diff --git a/samples/SupportVectorDrawableDemos/build.gradle b/samples/SupportVectorDrawableDemos/build.gradle
index b7c2167..8520eb1 100644
--- a/samples/SupportVectorDrawableDemos/build.gradle
+++ b/samples/SupportVectorDrawableDemos/build.gradle
@@ -27,6 +27,7 @@
 
     defaultConfig {
         minSdkVersion 14
+        targetSdkVersion project.ext.currentSdk
         vectorDrawables.useSupportLibrary = true
     }
 
diff --git a/settings.gradle b/settings.gradle
index dd3db92..ffc34ee 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -94,9 +94,6 @@
 include ':support-tv-provider'
 project(':support-tv-provider').projectDir = new File(rootDir, 'tv-provider')
 
-include ':support-instantvideo'
-project(':support-instantvideo').projectDir = new File(rootDir, 'instantvideo')
-
 include ':support-emoji'
 project(':support-emoji').projectDir = new File(rootDir, 'emoji/core')
 
diff --git a/transition/lint-baseline.xml b/transition/lint-baseline.xml
index 2a79d28..4d3d334 100644
--- a/transition/lint-baseline.xml
+++ b/transition/lint-baseline.xml
@@ -24,168 +24,14 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/v13/lint-baseline.xml b/v13/lint-baseline.xml
index 2a79d28..4d3d334 100644
--- a/v13/lint-baseline.xml
+++ b/v13/lint-baseline.xml
@@ -24,168 +24,14 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/v14/preference/lint-baseline.xml b/v14/preference/lint-baseline.xml
index 65f542a..adeb0d6 100644
--- a/v14/preference/lint-baseline.xml
+++ b/v14/preference/lint-baseline.xml
@@ -94,17 +94,6 @@
     </issue>
 
     <issue
-        id="ResourceType"
-        message="Expected resource of type xml"
-        errorLine1="            final XmlPullParser parser = res.getXml(resId);"
-        errorLine2="                                                    ~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="557"
-            column="53"/>
-    </issue>
-
-    <issue
         id="Range"
         message="Value must be ≥ 0 (was -2147483648)"
         errorLine1="                                MeasureSpec.makeMeasureSpec(largestChildHeight,"
@@ -127,72 +116,6 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
         id="Suspicious0dp"
         message="Suspicious size: this will make the view invisible, should be used with `layout_weight`"
         errorLine1="        android:layout_width=&quot;0dp&quot;"
@@ -347,171 +270,6 @@
     </issue>
 
     <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;found saved state: &quot; + mPendingSavedState);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="783"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;Deciding anchor info from fresh state&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="828"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;invalid saved state class&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1187"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;saved state:\n&quot; + state);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1236"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;FILLING targetLine: &quot; + targetLine + &quot;,&quot;"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1559"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;assigned &quot; + currentSpan.mIndex + &quot; for &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1580"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;using &quot; + spanIndex + &quot; for pos &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1584"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;asked &quot; + dt + &quot; scrolled&quot; + totalScroll);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2153"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;Unknown focus request:&quot; + focusDirection);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2385"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
         id="AppCompatCustomView"
         message="This custom view should extend `android.support.v7.widget.AppCompatAutoCompleteTextView` instead"
         errorLine1="public class AppCompatAutoCompleteTextView extends AutoCompleteTextView implements"
@@ -688,21 +446,6 @@
     </issue>
 
     <issue
-        id="UniqueConstants"
-        message="Constants `FLAG_CVE_EQ_PVE` and `FLAG_CVE_EQ_PVE` specify the same exact value (8192); this is usually a cut &amp; paste or merge error"
-        errorLine1="            FLAG_CVE_EQ_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE"
-        errorLine2="                             ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="30"/>
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="13"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
         message="Must be one of: PixelFormat.UNKNOWN, PixelFormat.TRANSLUCENT, PixelFormat.TRANSPARENT, PixelFormat.OPAQUE"
         errorLine1="        return 0;"
@@ -715,35 +458,13 @@
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
     <issue
@@ -768,15 +489,4 @@
             column="36"/>
     </issue>
 
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL"
-        errorLine1="            return isAutoMirrored() &amp;&amp; getLayoutDirection() == LayoutDirection.RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="821"
-            column="64"/>
-    </issue>
-
 </issues>
diff --git a/v17/leanback/lint-baseline.xml b/v17/leanback/lint-baseline.xml
index d87e4b2..24cb762 100644
--- a/v17/leanback/lint-baseline.xml
+++ b/v17/leanback/lint-baseline.xml
@@ -57,72 +57,6 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
         id="LongLogTag"
         message="The logging tag can be at most 23 characters, was 27 (DetailsOverviewRowPresenter)"
         errorLine1="                if (DEBUG) Log.v(TAG, &quot;onLayoutChange &quot; + v);"
@@ -146,39 +80,6 @@
 
     <issue
         id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
         message="The logging tag can be at most 23 characters, was 36 (FullWidthDetailsOverviewRowPresenter)"
         errorLine1="                if (DEBUG) Log.v(TAG, &quot;onLayoutChange &quot; + v);"
         errorLine2="                                 ~~~">
@@ -206,7 +107,7 @@
         errorLine2="                  ~~~">
         <location
             file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="641"
+            line="642"
             column="19"/>
     </issue>
 
@@ -217,7 +118,7 @@
         errorLine2="                         ~~~">
         <location
             file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="999"
+            line="1000"
             column="26"/>
     </issue>
 
@@ -228,7 +129,7 @@
         errorLine2="                         ~~~">
         <location
             file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="1038"
+            line="1039"
             column="26"/>
     </issue>
 
@@ -239,7 +140,7 @@
         errorLine2="                         ~~~">
         <location
             file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="1237"
+            line="1239"
             column="26"/>
     </issue>
 
@@ -250,7 +151,7 @@
         errorLine2="                             ~~~">
         <location
             file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="1350"
+            line="1352"
             column="30"/>
     </issue>
 
@@ -261,7 +162,7 @@
         errorLine2="                      ~~~">
         <location
             file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="1362"
+            line="1364"
             column="23"/>
     </issue>
 
@@ -399,127 +300,6 @@
 
     <issue
         id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;found saved state: &quot; + mPendingSavedState);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="783"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;Deciding anchor info from fresh state&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="828"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;invalid saved state class&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1187"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;saved state:\n&quot; + state);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1236"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;FILLING targetLine: &quot; + targetLine + &quot;,&quot;"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1559"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;assigned &quot; + currentSpan.mIndex + &quot; for &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1580"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;using &quot; + spanIndex + &quot; for pos &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1584"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;asked &quot; + dt + &quot; scrolled&quot; + totalScroll);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2153"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;Unknown focus request:&quot; + focusDirection);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2385"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
         message="The logging tag can be at most 23 characters, was 27 (VerticalGridSupportFragment)"
         errorLine1="            if (DEBUG) Log.v(TAG, &quot;grid selected position &quot; + position);"
         errorLine2="                             ~~~">
@@ -530,32 +310,6 @@
     </issue>
 
     <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="UniqueConstants"
-        message="Constants `FLAG_CVE_EQ_PVE` and `FLAG_CVE_EQ_PVE` specify the same exact value (8192); this is usually a cut &amp; paste or merge error"
-        errorLine1="            FLAG_CVE_EQ_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE"
-        errorLine2="                             ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="30"/>
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="13"/>
-    </issue>
-
-    <issue
         id="Orientation"
         message="No orientation specified, and the default is horizontal. This is a common source of bugs when children are added dynamically."
         errorLine1="            &lt;LinearLayout"
@@ -590,35 +344,13 @@
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
     <issue
@@ -639,7 +371,7 @@
         errorLine2="                                            ~~~~~~~~~~~~~~~~">
         <location
             file="src/android/support/v17/leanback/widget/GridLayoutManager.java"
-            line="3468"
+            line="3476"
             column="45"/>
     </issue>
 
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
index 3eea164..33ec805 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
@@ -35,6 +35,7 @@
 import android.support.v17.leanback.widget.GuidedActionAdapter;
 import android.support.v17.leanback.widget.GuidedActionAdapterGroup;
 import android.support.v17.leanback.widget.GuidedActionsStylist;
+import android.support.v17.leanback.widget.NonOverlappingLinearLayout;
 import android.support.v4.app.ActivityCompat;
 import android.support.v7.widget.RecyclerView;
 import android.util.Log;
@@ -1045,6 +1046,7 @@
 
         ViewGroup guidanceContainer = (ViewGroup) root.findViewById(R.id.content_fragment);
         ViewGroup actionContainer = (ViewGroup) root.findViewById(R.id.action_fragment);
+        ((NonOverlappingLinearLayout) actionContainer).setFocusableViewAvailableFixEnabled(true);
 
         Guidance guidance = onCreateGuidance(savedInstanceState);
         View guidanceView = mGuidanceStylist.onCreateView(inflater, guidanceContainer, guidance);
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
index a1fd860..9bd9b08 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
@@ -38,6 +38,7 @@
 import android.support.v17.leanback.widget.GuidedActionAdapter;
 import android.support.v17.leanback.widget.GuidedActionAdapterGroup;
 import android.support.v17.leanback.widget.GuidedActionsStylist;
+import android.support.v17.leanback.widget.NonOverlappingLinearLayout;
 import android.support.v4.app.ActivityCompat;
 import android.support.v7.widget.RecyclerView;
 import android.util.Log;
@@ -1048,6 +1049,7 @@
 
         ViewGroup guidanceContainer = (ViewGroup) root.findViewById(R.id.content_fragment);
         ViewGroup actionContainer = (ViewGroup) root.findViewById(R.id.action_fragment);
+        ((NonOverlappingLinearLayout) actionContainer).setFocusableViewAvailableFixEnabled(true);
 
         Guidance guidance = onCreateGuidance(savedInstanceState);
         View guidanceView = mGuidanceStylist.onCreateView(inflater, guidanceContainer, guidance);
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 d4e694a..748c993 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -3315,6 +3315,15 @@
         return count == 0 || mBaseGridView.findViewHolderForAdapterPosition(0) != null;
     }
 
+    boolean isItemFullyVisible(int pos) {
+        RecyclerView.ViewHolder vh = mBaseGridView.findViewHolderForAdapterPosition(pos);
+        if (vh == null) {
+            return false;
+        }
+        return vh.itemView.getLeft() >= 0 && vh.itemView.getRight() < mBaseGridView.getWidth()
+                && vh.itemView.getTop() >= 0 && vh.itemView.getBottom() < mBaseGridView.getHeight();
+    }
+
     boolean canScrollTo(View view) {
         return view.getVisibility() == View.VISIBLE && (!hasFocus() || view.hasFocusable());
     }
@@ -3609,11 +3618,10 @@
         saveContext(recycler, state);
         switch (action) {
             case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD:
-                // try to focus all the way to the last visible item on the same row.
-                processSelectionMoves(false, -mState.getItemCount());
+                processSelectionMoves(false, -1);
                 break;
             case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD:
-                processSelectionMoves(false, mState.getItemCount());
+                processSelectionMoves(false, 1);
                 break;
         }
         leaveContext();
@@ -3678,11 +3686,12 @@
     public void onInitializeAccessibilityNodeInfo(Recycler recycler, State state,
             AccessibilityNodeInfoCompat info) {
         saveContext(recycler, state);
-        if (mScrollEnabled && !hasCreatedFirstItem()) {
+        int count = state.getItemCount();
+        if (mScrollEnabled && count > 1 && !isItemFullyVisible(0)) {
             info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
             info.setScrollable(true);
         }
-        if (mScrollEnabled && !hasCreatedLastItem()) {
+        if (mScrollEnabled && count > 1 && !isItemFullyVisible(count - 1)) {
             info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
             info.setScrollable(true);
         }
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingLinearLayout.java b/v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingLinearLayout.java
index 28a5c0c..8985f82 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingLinearLayout.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingLinearLayout.java
@@ -13,11 +13,26 @@
  */
 package android.support.v17.leanback.widget;
 
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
 import android.content.Context;
+import android.support.annotation.RestrictTo;
 import android.util.AttributeSet;
+import android.view.View;
 import android.widget.LinearLayout;
 
-class NonOverlappingLinearLayout extends LinearLayout {
+import java.util.ArrayList;
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public class NonOverlappingLinearLayout extends LinearLayout {
+
+    boolean mFocusableViewAvailableFixEnabled = false;
+    boolean mDeferFocusableViewAvailableInLayout;
+    final ArrayList<ArrayList<View>> mSortedAvailableViews = new ArrayList();
+
 
     public NonOverlappingLinearLayout(Context context) {
         this(context, null);
@@ -38,4 +53,60 @@
     public boolean hasOverlappingRendering() {
         return false;
     }
+
+    public void setFocusableViewAvailableFixEnabled(boolean enabled) {
+        mFocusableViewAvailableFixEnabled = enabled;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        try {
+            mDeferFocusableViewAvailableInLayout = mFocusableViewAvailableFixEnabled
+                    && getOrientation() == HORIZONTAL
+                    && getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+            if (mDeferFocusableViewAvailableInLayout) {
+                while (mSortedAvailableViews.size() > getChildCount()) {
+                    mSortedAvailableViews.remove(mSortedAvailableViews.size() - 1);
+                }
+                while (mSortedAvailableViews.size() < getChildCount()) {
+                    mSortedAvailableViews.add(new ArrayList());
+                }
+            }
+            super.onLayout(changed, l, t, r, b);
+            if (mDeferFocusableViewAvailableInLayout) {
+                for (int i = 0; i < mSortedAvailableViews.size(); i++) {
+                    for (int j = 0; j < mSortedAvailableViews.get(i).size(); j++) {
+                        super.focusableViewAvailable(mSortedAvailableViews.get(i).get(j));
+                    }
+                }
+            }
+        } finally {
+            if (mDeferFocusableViewAvailableInLayout) {
+                mDeferFocusableViewAvailableInLayout = false;
+                for (int i = 0; i < mSortedAvailableViews.size(); i++) {
+                    mSortedAvailableViews.get(i).clear();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void focusableViewAvailable(View v) {
+        if (mDeferFocusableViewAvailableInLayout) {
+            View i = v;
+            int index = -1;
+            while (i != this && i != null) {
+                if (i.getParent() == this) {
+                    index = indexOfChild(i);
+                    break;
+                }
+                i = (View) i.getParent();
+            }
+            if (index != -1) {
+                mSortedAvailableViews.get(index).add(v);
+            }
+        } else {
+            super.focusableViewAvailable(v);
+        }
+    }
 }
\ No newline at end of file
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java b/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
index a651b2e..3ddb6f0 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
@@ -254,27 +254,49 @@
                 }
             }
             if (!isMaxUnknown && !isMinUnknown) {
-                if (!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0
-                        : (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
-                    if (!mReversedFlow ? isPreferKeylineOverLowEdge()
-                            : isPreferKeylineOverHighEdge()) {
-                        // if we prefer key line, might align max child to key line for minScroll
-                        mMinScroll = Math.min(mMinScroll,
-                                calculateScrollToKeyLine(maxChildViewCenter, keyLine));
-                    } else {
-                        // don't over scroll max
-                        mMaxScroll = Math.max(mMinScroll, mMaxScroll);
+                if (!mReversedFlow) {
+                    if ((mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
+                        if (isPreferKeylineOverLowEdge()) {
+                            // if we prefer key line, might align max child to key line for
+                            // minScroll
+                            mMinScroll = Math.min(mMinScroll,
+                                    calculateScrollToKeyLine(maxChildViewCenter, keyLine));
+                        } else {
+                            // don't over scroll max
+                            mMaxScroll = Math.max(mMinScroll, mMaxScroll);
+                        }
+                    } else if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
+                        if (isPreferKeylineOverHighEdge()) {
+                            // if we prefer key line, might align min child to key line for
+                            // maxScroll
+                            mMaxScroll = Math.max(mMaxScroll,
+                                    calculateScrollToKeyLine(minChildViewCenter, keyLine));
+                        } else {
+                            // don't over scroll min
+                            mMinScroll = Math.min(mMinScroll, mMaxScroll);
+                        }
                     }
-                } else if (!mReversedFlow ? (mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0
-                        : (mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
-                    if (!mReversedFlow ? isPreferKeylineOverHighEdge()
-                            : isPreferKeylineOverLowEdge()) {
-                        // if we prefer key line, might align min child to key line for maxScroll
-                        mMaxScroll = Math.max(mMaxScroll,
-                                calculateScrollToKeyLine(minChildViewCenter, keyLine));
-                    } else {
-                        // don't over scroll min
-                        mMinScroll = Math.min(mMinScroll, mMaxScroll);
+                } else {
+                    if ((mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0) {
+                        if (isPreferKeylineOverLowEdge()) {
+                            // if we prefer key line, might align min child to key line for
+                            // maxScroll
+                            mMaxScroll = Math.max(mMaxScroll,
+                                    calculateScrollToKeyLine(minChildViewCenter, keyLine));
+                        } else {
+                            // don't over scroll min
+                            mMinScroll = Math.min(mMinScroll, mMaxScroll);
+                        }
+                    } else if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0) {
+                        if (isPreferKeylineOverHighEdge()) {
+                            // if we prefer key line, might align max child to key line for
+                            // minScroll
+                            mMinScroll = Math.min(mMinScroll,
+                                    calculateScrollToKeyLine(maxChildViewCenter, keyLine));
+                        } else {
+                            // don't over scroll max
+                            mMaxScroll = Math.max(mMinScroll, mMaxScroll);
+                        }
                     }
                 }
             }
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java
index bd245aa..fa324bf 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java
@@ -420,4 +420,32 @@
 
     }
 
+    @Test
+    public void buttonActionsRtl() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        GuidedStepTestFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                actions.add(new GuidedAction.Builder().id(1000).title("action").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                actions.add(new GuidedAction.Builder().id(1001).title("button action").build());
+                return null;
+            }
+        }).when(first).onCreateButtonActions(any(List.class), nullable(Bundle.class));
+
+        final GuidedStepFragmentTestActivity activity = launchTestActivity(firstFragmentName,
+                true, View.LAYOUT_DIRECTION_RTL);
+
+        assertEquals(View.LAYOUT_DIRECTION_RTL, first.getFragment().getView().getLayoutDirection());
+        View firstView = first.getFragment().getActionItemView(0);
+        assertTrue(firstView.hasFocus());
+    }
 }
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestActivity.java
index 06beab6..4dcf188 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestActivity.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestActivity.java
@@ -32,12 +32,21 @@
      */
     public static final String EXTRA_ADD_AS_ROOT = "addAsRoot";
 
+    /**
+     * Layout direction
+     */
+    public static final String EXTRA_LAYOUT_DIRECTION = "layoutDir";
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         Intent intent = getIntent();
 
+        int layoutDirection = intent.getIntExtra(EXTRA_LAYOUT_DIRECTION, -1);
+        if (layoutDirection != -1) {
+            findViewById(android.R.id.content).setLayoutDirection(layoutDirection);
+        }
         if (savedInstanceState == null) {
             String firstTestName = intent.getStringExtra(EXTRA_TEST_NAME);
             if (firstTestName != null) {
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestBase.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestBase.java
index 6ed254a..7059c9a 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestBase.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTestBase.java
@@ -125,6 +125,15 @@
         return activityTestRule.launchActivity(intent);
     }
 
+    public GuidedStepFragmentTestActivity launchTestActivity(String firstTestName,
+            boolean addAsRoot, int layoutDirection) {
+        Intent intent = new Intent();
+        intent.putExtra(GuidedStepFragmentTestActivity.EXTRA_TEST_NAME, firstTestName);
+        intent.putExtra(GuidedStepFragmentTestActivity.EXTRA_ADD_AS_ROOT, addAsRoot);
+        intent.putExtra(GuidedStepFragmentTestActivity.EXTRA_LAYOUT_DIRECTION, layoutDirection);
+        return activityTestRule.launchActivity(intent);
+    }
+
     public GuidedStepTestFragment.Provider mockProvider(String testName) {
         GuidedStepTestFragment.Provider test = mock(GuidedStepTestFragment.Provider.class);
         when(test.getActivity()).thenCallRealMethod();
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java
index 5741181..b4d9b59 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java
@@ -105,7 +105,7 @@
         verify(second, times(1)).onCreateGuidance(nullable(Bundle.class));
         verify(second, times(1)).onCreateActions(any(List.class), nullable(Bundle.class));
         verify(second, times(1)).onCreateButtonActions(any(List.class), nullable(Bundle.class));
-        verify(second, times(1)).onCreateView(any(LayoutInflater.class), any(ViewGroup.class),
+        verify(second, times(1)).onCreateView(any(LayoutInflater.class), nullable(ViewGroup.class),
                 nullable(Bundle.class), any(View.class));
         verify(second, times(1)).onViewStateRestored(nullable(Bundle.class));
         verify(second, times(1)).onStart();
@@ -423,4 +423,32 @@
 
     }
 
+    @Test
+    public void buttonActionsRtl() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        GuidedStepTestSupportFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                actions.add(new GuidedAction.Builder().id(1000).title("action").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), nullable(Bundle.class));
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                actions.add(new GuidedAction.Builder().id(1001).title("button action").build());
+                return null;
+            }
+        }).when(first).onCreateButtonActions(any(List.class), nullable(Bundle.class));
+
+        final GuidedStepSupportFragmentTestActivity activity = launchTestActivity(firstFragmentName,
+                true, View.LAYOUT_DIRECTION_RTL);
+
+        assertEquals(View.LAYOUT_DIRECTION_RTL, first.getFragment().getView().getLayoutDirection());
+        View firstView = first.getFragment().getActionItemView(0);
+        assertTrue(firstView.hasFocus());
+    }
 }
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestActivity.java
index 2fc8d1e..fb877ed 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestActivity.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestActivity.java
@@ -35,12 +35,21 @@
      */
     public static final String EXTRA_ADD_AS_ROOT = "addAsRoot";
 
+    /**
+     * Layout direction
+     */
+    public static final String EXTRA_LAYOUT_DIRECTION = "layoutDir";
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         Intent intent = getIntent();
 
+        int layoutDirection = intent.getIntExtra(EXTRA_LAYOUT_DIRECTION, -1);
+        if (layoutDirection != -1) {
+            findViewById(android.R.id.content).setLayoutDirection(layoutDirection);
+        }
         if (savedInstanceState == null) {
             String firstTestName = intent.getStringExtra(EXTRA_TEST_NAME);
             if (firstTestName != null) {
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestBase.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestBase.java
index 4fe4a24..17533fa 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestBase.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTestBase.java
@@ -128,6 +128,15 @@
         return activityTestRule.launchActivity(intent);
     }
 
+    public GuidedStepSupportFragmentTestActivity launchTestActivity(String firstTestName,
+            boolean addAsRoot, int layoutDirection) {
+        Intent intent = new Intent();
+        intent.putExtra(GuidedStepSupportFragmentTestActivity.EXTRA_TEST_NAME, firstTestName);
+        intent.putExtra(GuidedStepSupportFragmentTestActivity.EXTRA_ADD_AS_ROOT, addAsRoot);
+        intent.putExtra(GuidedStepSupportFragmentTestActivity.EXTRA_LAYOUT_DIRECTION, layoutDirection);
+        return activityTestRule.launchActivity(intent);
+    }
+
     public GuidedStepTestSupportFragment.Provider mockProvider(String testName) {
         GuidedStepTestSupportFragment.Provider test = mock(GuidedStepTestSupportFragment.Provider.class);
         when(test.getActivity()).thenCallRealMethod();
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java b/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
index 1a173b4..86fb4eb 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
@@ -1419,6 +1419,27 @@
     }
 
     @Test
+    public void testItemMovedHorizontalRtl() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear_rtl);
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        intent.putExtra(GridActivity.EXTRA_ITEMS, new int[] {40, 40, 40});
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        performAndWaitForAnimation(new Runnable() {
+            @Override
+            public void run() {
+                mActivity.moveItem(0, 1, true);
+            }
+        });
+        assertEquals(mGridView.getWidth() - mGridView.getPaddingRight(),
+                mGridView.findViewHolderForAdapterPosition(0).itemView.getRight());
+    }
+
+    @Test
     public void testScrollSecondaryCannotScroll() throws Throwable {
         Intent intent = new Intent();
         intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
@@ -3888,6 +3909,108 @@
         assertTrue(selectedPosition2 < selectedPosition1);
     }
 
+    @Test
+    public void testAccessibilityScrollForwardHalfVisible() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.item_button_at_bottom);
+        intent.putExtra(GridActivity.EXTRA_ITEMS,  new int[]{});
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        int height = mGridView.getHeight() - mGridView.getPaddingTop()
+                - mGridView.getPaddingBottom();
+        final int childHeight = height - mGridView.getVerticalSpacing() - 100;
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
+                mGridView.setWindowAlignmentOffset(100);
+                mGridView.setWindowAlignmentOffsetPercent(BaseGridView
+                        .WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+                mGridView.setItemAlignmentOffset(0);
+                mGridView.setItemAlignmentOffsetPercent(BaseGridView
+                        .ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+            }
+        });
+        mActivity.addItems(0, new int[]{childHeight, childHeight});
+        waitForItemAnimation();
+        setSelectedPosition(0);
+
+        final RecyclerViewAccessibilityDelegate delegateCompat = mGridView
+                .getCompatAccessibilityDelegate();
+        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
+            }
+        });
+        assertTrue("test sanity", info.isScrollable());
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                delegateCompat.performAccessibilityAction(mGridView,
+                        AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD, null);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(1, mGridView.getSelectedPosition());
+    }
+
+    @Test
+    public void testAccessibilityScrollBackwardHalfVisible() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.item_button_at_top);
+        intent.putExtra(GridActivity.EXTRA_ITEMS,  new int[]{});
+        intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+        initActivity(intent);
+        mOrientation = BaseGridView.VERTICAL;
+        mNumRows = 1;
+
+        int height = mGridView.getHeight() - mGridView.getPaddingTop()
+                - mGridView.getPaddingBottom();
+        final int childHeight = height - mGridView.getVerticalSpacing() - 100;
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setWindowAlignment(BaseGridView.WINDOW_ALIGN_NO_EDGE);
+                mGridView.setWindowAlignmentOffset(100);
+                mGridView.setWindowAlignmentOffsetPercent(BaseGridView
+                        .WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+                mGridView.setItemAlignmentOffset(0);
+                mGridView.setItemAlignmentOffsetPercent(BaseGridView
+                        .ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+            }
+        });
+        mActivity.addItems(0, new int[]{childHeight, childHeight});
+        waitForItemAnimation();
+        setSelectedPosition(1);
+
+        final RecyclerViewAccessibilityDelegate delegateCompat = mGridView
+                .getCompatAccessibilityDelegate();
+        final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                delegateCompat.onInitializeAccessibilityNodeInfo(mGridView, info);
+            }
+        });
+        assertTrue("test sanity", info.isScrollable());
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                delegateCompat.performAccessibilityAction(mGridView,
+                        AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD, null);
+            }
+        });
+        waitForScrollIdle(mVerifyLayout);
+        assertEquals(0, mGridView.getSelectedPosition());
+    }
+
     void slideInAndWaitIdle() throws Throwable {
         slideInAndWaitIdle(5000);
     }
@@ -4884,14 +5007,14 @@
 
     void prepareKeyLineTest(int numItems) throws Throwable {
         Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
         int[] items = new int[numItems];
         for (int i = 0; i < items.length; i++) {
             items[i] = 32;
         }
         intent.putExtra(GridActivity.EXTRA_ITEMS, items);
         intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
-        mOrientation = BaseGridView.VERTICAL;
+        mOrientation = BaseGridView.HORIZONTAL;
         mNumRows = 1;
 
         initActivity(intent);
@@ -4932,51 +5055,76 @@
             final boolean preferKeyLineOverHigh,
             ItemAt assertFirstItemLocation,
             ItemAt assertLastItemLocation) throws Throwable {
+        TestPreferKeyLineOptions options = new TestPreferKeyLineOptions();
+        options.mAssertItemLocations = new ItemAt[] {assertFirstItemLocation,
+                assertLastItemLocation};
+        options.mPreferKeyLineOverLow = preferKeyLineOverLow;
+        options.mPreferKeyLineOverHigh = preferKeyLineOverHigh;
+        options.mWindowAlignment = windowAlignment;
+
+        options.mRtl = false;
+        testPreferKeyLine(options);
+
+        options.mRtl = true;
+        testPreferKeyLine(options);
+    }
+
+    static class TestPreferKeyLineOptions {
+        int mWindowAlignment;
+        boolean mPreferKeyLineOverLow;
+        boolean mPreferKeyLineOverHigh;
+        ItemAt[] mAssertItemLocations;
+        boolean mRtl;
+    }
+
+    public void testPreferKeyLine(final TestPreferKeyLineOptions options) throws Throwable {
         startWaitLayout();
         mActivityTestRule.runOnUiThread(new Runnable() {
             @Override
             public void run() {
-                mGridView.setWindowAlignment(windowAlignment);
+                if (options.mRtl) {
+                    mGridView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
+                } else {
+                    mGridView.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
+                }
+                mGridView.setWindowAlignment(options.mWindowAlignment);
                 mGridView.setWindowAlignmentOffsetPercent(50);
                 mGridView.setWindowAlignmentOffset(0);
-                mGridView.setWindowAlignmentPreferKeyLineOverLowEdge(preferKeyLineOverLow);
-                mGridView.setWindowAlignmentPreferKeyLineOverHighEdge(preferKeyLineOverHigh);
+                mGridView.setWindowAlignmentPreferKeyLineOverLowEdge(options.mPreferKeyLineOverLow);
+                mGridView.setWindowAlignmentPreferKeyLineOverHighEdge(
+                        options.mPreferKeyLineOverHigh);
             }
         });
         waitForLayout();
 
-        final int lowPadding = mGridView.getPaddingTop();
-        final int highPadding = mGridView.getHeight() - mGridView.getPaddingBottom();
-        final int windowAlignCenter = mGridView.getHeight() / 2;
+        final int paddingStart = mGridView.getPaddingStart();
+        final int paddingEnd = mGridView.getPaddingEnd();
+        final int windowAlignCenter = mGridView.getWidth() / 2;
 
-        setSelectedPosition(assertFirstItemLocation.mScrollPosition);
-        View view = mGridView.findViewHolderForAdapterPosition(assertFirstItemLocation.mPosition)
-                .itemView;
-        switch (assertFirstItemLocation.mLocation) {
-            case ITEM_AT_LOW:
-                assertEquals(lowPadding, view.getTop());
-                break;
-            case ITEM_AT_HIGH:
-                assertEquals(highPadding, view.getBottom());
-                break;
-            case ITEM_AT_KEY_LINE:
-                assertEquals(windowAlignCenter, view.getTop() + view.getHeight() / 2, DELTA);
-                break;
-        }
-
-        setSelectedPosition(assertLastItemLocation.mScrollPosition);
-        view = mGridView.findViewHolderForAdapterPosition(assertLastItemLocation.mPosition)
-                .itemView;
-        switch (assertLastItemLocation.mLocation) {
-            case ITEM_AT_LOW:
-                assertEquals(lowPadding, view.getTop());
-                break;
-            case ITEM_AT_HIGH:
-                assertEquals(highPadding, view.getBottom());
-                break;
-            case ITEM_AT_KEY_LINE:
-                assertEquals(windowAlignCenter, view.getTop() + view.getHeight() / 2, DELTA);
-                break;
+        for (int i = 0; i < options.mAssertItemLocations.length; i++) {
+            ItemAt assertItemLocation = options.mAssertItemLocations[i];
+            setSelectedPosition(assertItemLocation.mScrollPosition);
+            View view = mGridView.findViewHolderForAdapterPosition(assertItemLocation.mPosition)
+                    .itemView;
+            switch (assertItemLocation.mLocation) {
+                case ITEM_AT_LOW:
+                    if (options.mRtl) {
+                        assertEquals(mGridView.getWidth() - paddingStart, view.getRight());
+                    } else {
+                        assertEquals(paddingStart, view.getLeft());
+                    }
+                    break;
+                case ITEM_AT_HIGH:
+                    if (options.mRtl) {
+                        assertEquals(paddingEnd, view.getLeft());
+                    } else {
+                        assertEquals(mGridView.getWidth() - paddingEnd, view.getRight());
+                    }
+                    break;
+                case ITEM_AT_KEY_LINE:
+                    assertEquals(windowAlignCenter, (view.getLeft() + view.getRight()) / 2, DELTA);
+                    break;
+            }
         }
     }
 
diff --git a/v17/leanback/tests/res/layout/item_button_at_bottom.xml b/v17/leanback/tests/res/layout/item_button_at_bottom.xml
new file mode 100644
index 0000000..8afc622
--- /dev/null
+++ b/v17/leanback/tests/res/layout/item_button_at_bottom.xml
@@ -0,0 +1,32 @@
+<!--
+  ~ 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="400dp"
+    android:layout_height="400dp"
+    >
+    <TextView
+        android:layout_alignParentTop="true"
+        android:text="unfocusable text"
+        android:layout_width="match_parent"
+        android:layout_height="50dp"/>
+    <Button
+        android:layout_alignParentBottom="true"
+        android:text="button"
+        android:focusable="true"
+        android:layout_width="match_parent"
+        android:layout_height="50dp"/>
+</RelativeLayout>
diff --git a/v17/leanback/tests/res/layout/item_button_at_top.xml b/v17/leanback/tests/res/layout/item_button_at_top.xml
new file mode 100644
index 0000000..5199193
--- /dev/null
+++ b/v17/leanback/tests/res/layout/item_button_at_top.xml
@@ -0,0 +1,33 @@
+<!--
+  ~ 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="400dp"
+    android:layout_height="400dp"
+    >
+    <TextView
+        android:layout_alignParentBottom="true"
+        android:text="unfocusable text"
+        android:layout_width="match_parent"
+        android:layout_height="50dp"/>
+    <Button
+        android:layout_alignParentTop="true"
+        android:text="button"
+        android:focusable="true"
+        android:layout_width="match_parent"
+        android:layout_height="50dp"/>
+</RelativeLayout>
diff --git a/v17/preference-leanback/lint-baseline.xml b/v17/preference-leanback/lint-baseline.xml
index 724d38a..923181b 100644
--- a/v17/preference-leanback/lint-baseline.xml
+++ b/v17/preference-leanback/lint-baseline.xml
@@ -127,17 +127,6 @@
     </issue>
 
     <issue
-        id="ResourceType"
-        message="Expected resource of type xml"
-        errorLine1="            final XmlPullParser parser = res.getXml(resId);"
-        errorLine2="                                                    ~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="557"
-            column="53"/>
-    </issue>
-
-    <issue
         id="Range"
         message="Value must be ≥ 0 (was -2147483648)"
         errorLine1="                                MeasureSpec.makeMeasureSpec(largestChildHeight,"
@@ -160,72 +149,6 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
         id="Suspicious0dp"
         message="Suspicious size: this will make the view invisible, should be used with `layout_weight`"
         errorLine1="        &lt;Space android:layout_width=&quot;0dp&quot; android:layout_height=&quot;@dimen/lb_preference_item_text_space_top&quot; />"
@@ -447,39 +370,6 @@
 
     <issue
         id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
         message="The logging tag can be at most 23 characters, was 36 (FullWidthDetailsOverviewRowPresenter)"
         errorLine1="                if (DEBUG) Log.v(TAG, &quot;onLayoutChange &quot; + v);"
         errorLine2="                                 ~~~">
@@ -507,7 +397,7 @@
         errorLine2="                  ~~~">
         <location
             file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="641"
+            line="642"
             column="19"/>
     </issue>
 
@@ -518,7 +408,7 @@
         errorLine2="                         ~~~">
         <location
             file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="999"
+            line="1000"
             column="26"/>
     </issue>
 
@@ -529,7 +419,7 @@
         errorLine2="                         ~~~">
         <location
             file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="1038"
+            line="1039"
             column="26"/>
     </issue>
 
@@ -540,7 +430,7 @@
         errorLine2="                         ~~~">
         <location
             file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="1237"
+            line="1239"
             column="26"/>
     </issue>
 
@@ -551,7 +441,7 @@
         errorLine2="                             ~~~">
         <location
             file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="1350"
+            line="1352"
             column="30"/>
     </issue>
 
@@ -562,7 +452,7 @@
         errorLine2="                      ~~~">
         <location
             file="src/android/support/v17/leanback/app/GuidedStepSupportFragment.java"
-            line="1362"
+            line="1364"
             column="23"/>
     </issue>
 
@@ -700,127 +590,6 @@
 
     <issue
         id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;found saved state: &quot; + mPendingSavedState);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="783"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;Deciding anchor info from fresh state&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="828"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;invalid saved state class&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1187"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;saved state:\n&quot; + state);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1236"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;FILLING targetLine: &quot; + targetLine + &quot;,&quot;"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1559"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;assigned &quot; + currentSpan.mIndex + &quot; for &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1580"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;using &quot; + spanIndex + &quot; for pos &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1584"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;asked &quot; + dt + &quot; scrolled&quot; + totalScroll);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2153"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;Unknown focus request:&quot; + focusDirection);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2385"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
         message="The logging tag can be at most 23 characters, was 27 (VerticalGridSupportFragment)"
         errorLine1="            if (DEBUG) Log.v(TAG, &quot;grid selected position &quot; + position);"
         errorLine2="                             ~~~">
@@ -831,17 +600,6 @@
     </issue>
 
     <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
         id="AppCompatCustomView"
         message="This custom view should extend `android.support.v7.widget.AppCompatAutoCompleteTextView` instead"
         errorLine1="public class AppCompatAutoCompleteTextView extends AutoCompleteTextView implements"
@@ -1073,21 +831,6 @@
     </issue>
 
     <issue
-        id="UniqueConstants"
-        message="Constants `FLAG_CVE_EQ_PVE` and `FLAG_CVE_EQ_PVE` specify the same exact value (8192); this is usually a cut &amp; paste or merge error"
-        errorLine1="            FLAG_CVE_EQ_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE"
-        errorLine2="                             ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="30"/>
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="13"/>
-    </issue>
-
-    <issue
         id="Orientation"
         message="No orientation specified, and the default is horizontal. This is a common source of bugs when children are added dynamically."
         errorLine1="            &lt;LinearLayout"
@@ -1133,35 +876,13 @@
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
     <issue
@@ -1182,7 +903,7 @@
         errorLine2="                                            ~~~~~~~~~~~~~~~~">
         <location
             file="src/android/support/v17/leanback/widget/GridLayoutManager.java"
-            line="3468"
+            line="3476"
             column="45"/>
     </issue>
 
@@ -1231,17 +952,6 @@
     </issue>
 
     <issue
-        id="WrongConstant"
-        message="Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL"
-        errorLine1="            return isAutoMirrored() &amp;&amp; getLayoutDirection() == LayoutDirection.RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="821"
-            column="64"/>
-    </issue>
-
-    <issue
         id="RtlCompat"
         message="Inconsistent alignment specification between `textAlignment` and `gravity` attributes: was `center_vertical`, expected `start`"
         errorLine1="            android:textAlignment=&quot;viewStart&quot; />"
diff --git a/v4/lint-baseline.xml b/v4/lint-baseline.xml
index 2a79d28..4d3d334 100644
--- a/v4/lint-baseline.xml
+++ b/v4/lint-baseline.xml
@@ -24,168 +24,14 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/v7/appcompat/lint-baseline.xml b/v7/appcompat/lint-baseline.xml
index 9436020..f0d1d20 100644
--- a/v7/appcompat/lint-baseline.xml
+++ b/v7/appcompat/lint-baseline.xml
@@ -94,17 +94,6 @@
     </issue>
 
     <issue
-        id="ResourceType"
-        message="Expected resource of type xml"
-        errorLine1="            final XmlPullParser parser = res.getXml(resId);"
-        errorLine2="                                                    ~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="557"
-            column="53"/>
-    </issue>
-
-    <issue
         id="Range"
         message="Value must be ≥ 0 (was -2147483648)"
         errorLine1="                                MeasureSpec.makeMeasureSpec(largestChildHeight,"
@@ -127,72 +116,6 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
         id="LongLogTag"
         message="The logging tag can be at most 23 characters, was 30 (ActionBarDrawerToggleHoneycomb)"
         errorLine1="                Log.w(TAG, &quot;Couldn&apos;t set home-as-up indicator via JB-MR2 API&quot;, e);"
@@ -292,72 +215,6 @@
     </issue>
 
     <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
         message="Must be one of: PixelFormat.UNKNOWN, PixelFormat.TRANSLUCENT, PixelFormat.TRANSPARENT, PixelFormat.OPAQUE"
         errorLine1="        return 0;"
@@ -370,35 +227,13 @@
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
     <issue
@@ -423,15 +258,4 @@
             column="36"/>
     </issue>
 
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL"
-        errorLine1="            return isAutoMirrored() &amp;&amp; getLayoutDirection() == LayoutDirection.RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="821"
-            column="64"/>
-    </issue>
-
 </issues>
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompat.java b/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
index 16840d2..0ec4c77 100644
--- a/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
+++ b/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
@@ -21,8 +21,7 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Color;
+import android.media.session.MediaSession;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -32,22 +31,12 @@
 import android.support.v4.app.BundleCompat;
 import android.support.v4.app.NotificationBuilderWithBuilderAccessor;
 import android.support.v4.media.session.MediaSessionCompat;
-import android.support.v4.text.BidiFormatter;
 import android.support.v7.appcompat.R;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.style.TextAppearanceSpan;
+import android.view.View;
 import android.widget.RemoteViews;
 
-import java.util.List;
-
 /**
- * An extension of {@link android.support.v4.app.NotificationCompat} which supports
- * {@link android.support.v7.app.NotificationCompat.MediaStyle},
- * {@link android.support.v7.app.NotificationCompat.DecoratedCustomViewStyle},
- * and {@link android.support.v7.app.NotificationCompat.DecoratedMediaCustomViewStyle}.
- * You should start using this variant if you need support any of these styles.
+ * An extension of {@link android.support.v4.app.NotificationCompat} which adds additional styles.
  */
 public class NotificationCompat extends android.support.v4.app.NotificationCompat {
 
@@ -83,299 +72,8 @@
         return null;
     }
 
-    @RequiresApi(24)
-    private static void addStyleToBuilderApi24(NotificationBuilderWithBuilderAccessor builder,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        if (b.mStyle instanceof DecoratedCustomViewStyle) {
-            NotificationCompatImpl24.addDecoratedCustomViewStyle(builder);
-        } else if (b.mStyle instanceof DecoratedMediaCustomViewStyle) {
-            DecoratedMediaCustomViewStyle mediaStyle = (DecoratedMediaCustomViewStyle) b.mStyle;
-            NotificationCompatImpl24.addDecoratedMediaCustomViewStyle(builder,
-                    mediaStyle.mActionsToShowInCompact,
-                    mediaStyle.mToken != null ? mediaStyle.mToken.getToken() : null);
-        } else if (!(b.mStyle instanceof MessagingStyle)) {
-            addStyleGetContentViewLollipop(builder, b);
-        }
-    }
-
-    @RequiresApi(21)
-    private static RemoteViews addStyleGetContentViewLollipop(
-            NotificationBuilderWithBuilderAccessor builder,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        if (b.mStyle instanceof MediaStyle) {
-            MediaStyle mediaStyle = (MediaStyle) b.mStyle;
-            NotificationCompatImpl21.addMediaStyle(builder,
-                    mediaStyle.mActionsToShowInCompact,
-                    mediaStyle.mToken != null ? mediaStyle.mToken.getToken() : null);
-
-            boolean hasContentView = b.getContentView() != null;
-            // If we are on L/M the media notification will only be colored if the expanded version
-            // is of media style, so we have to create a custom view for the collapsed version as
-            // well in that case.
-            boolean isMorL = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
-                    && Build.VERSION.SDK_INT <= Build.VERSION_CODES.M;
-            boolean createCustomContent = hasContentView
-                    || (isMorL && b.getBigContentView() != null);
-            if (b.mStyle instanceof DecoratedMediaCustomViewStyle && createCustomContent) {
-                RemoteViews contentViewMedia = NotificationCompatImplBase.overrideContentViewMedia(
-                        builder, b.mContext, b.mContentTitle, b.mContentText, b.mContentInfo,
-                        b.mNumber, b.mLargeIcon, b.mSubText, b.mUseChronometer,
-                        b.getWhenIfShowing(), b.getPriority(), b.mActions,
-                        mediaStyle.mActionsToShowInCompact, false /* no cancel button on L */,
-                        null /* cancelButtonIntent */, hasContentView /* isDecoratedCustomView */);
-                if (hasContentView) {
-                    NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, contentViewMedia,
-                            b.getContentView());
-                }
-                setBackgroundColor(b.mContext, contentViewMedia, b.getColor());
-                return contentViewMedia;
-            }
-            return null;
-        } else if (b.mStyle instanceof DecoratedCustomViewStyle) {
-            return getDecoratedContentView(b);
-        }
-        return addStyleGetContentViewJellybean(builder, b);
-    }
-
-    @RequiresApi(16)
-    private static RemoteViews addStyleGetContentViewJellybean(
-            NotificationBuilderWithBuilderAccessor builder,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        if (b.mStyle instanceof MessagingStyle) {
-            addMessagingFallBackStyle((MessagingStyle) b.mStyle, builder, b);
-        }
-        return addStyleGetContentViewIcs(builder, b);
-    }
-
-    private static MessagingStyle.Message findLatestIncomingMessage(MessagingStyle style) {
-        List<MessagingStyle.Message> messages = style.getMessages();
-        for (int i = messages.size() - 1; i >= 0; i--) {
-            MessagingStyle.Message m = messages.get(i);
-            // Incoming messages have a non-empty sender.
-            if (!TextUtils.isEmpty(m.getSender())) {
-                return m;
-            }
-        }
-        if (!messages.isEmpty()) {
-            // No incoming messages, fall back to outgoing message
-            return messages.get(messages.size() - 1);
-        }
-        return null;
-    }
-
-    private static CharSequence makeMessageLine(android.support.v4.app.NotificationCompat.Builder b,
-            MessagingStyle style,
-            MessagingStyle.Message m) {
-        BidiFormatter bidi = BidiFormatter.getInstance();
-        SpannableStringBuilder sb = new SpannableStringBuilder();
-        boolean afterLollipop = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
-        int color = afterLollipop || Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1
-                ? Color.BLACK : Color.WHITE;
-        CharSequence replyName = m.getSender();
-        if (TextUtils.isEmpty(m.getSender())) {
-            replyName = style.getUserDisplayName() == null
-                    ? "" : style.getUserDisplayName();
-            color = afterLollipop && b.getColor() != NotificationCompat.COLOR_DEFAULT
-                    ? b.getColor()
-                    : color;
-        }
-        CharSequence senderText = bidi.unicodeWrap(replyName);
-        sb.append(senderText);
-        sb.setSpan(makeFontColorSpan(color),
-                sb.length() - senderText.length(),
-                sb.length(),
-                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE /* flags */);
-        CharSequence text = m.getText() == null ? "" : m.getText();
-        sb.append("  ").append(bidi.unicodeWrap(text));
-        return sb;
-    }
-
-    private static TextAppearanceSpan makeFontColorSpan(int color) {
-        return new TextAppearanceSpan(null, 0, 0, ColorStateList.valueOf(color), null);
-    }
-
-    @RequiresApi(16)
-    private static void addMessagingFallBackStyle(MessagingStyle style,
-            NotificationBuilderWithBuilderAccessor builder,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        SpannableStringBuilder completeMessage = new SpannableStringBuilder();
-        List<MessagingStyle.Message> messages = style.getMessages();
-        boolean showNames = style.getConversationTitle() != null
-                || hasMessagesWithoutSender(style.getMessages());
-        for (int i = messages.size() - 1; i >= 0; i--) {
-            MessagingStyle.Message m = messages.get(i);
-            CharSequence line;
-            line = showNames ? makeMessageLine(b, style, m) : m.getText();
-            if (i != messages.size() - 1) {
-                completeMessage.insert(0, "\n");
-            }
-            completeMessage.insert(0, line);
-        }
-        NotificationCompatImplJellybean.addBigTextStyle(builder, completeMessage);
-    }
-
-    private static boolean hasMessagesWithoutSender(
-            List<MessagingStyle.Message> messages) {
-        for (int i = messages.size() - 1; i >= 0; i--) {
-            MessagingStyle.Message m = messages.get(i);
-            if (m.getSender() == null) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @RequiresApi(14)
-    private static RemoteViews addStyleGetContentViewIcs(
-            NotificationBuilderWithBuilderAccessor builder,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        if (b.mStyle instanceof MediaStyle) {
-            MediaStyle mediaStyle = (MediaStyle) b.mStyle;
-            boolean isDecorated = b.mStyle instanceof DecoratedMediaCustomViewStyle
-                    && b.getContentView() != null;
-            RemoteViews contentViewMedia = NotificationCompatImplBase.overrideContentViewMedia(
-                    builder, b.mContext, b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber,
-                    b.mLargeIcon, b.mSubText, b.mUseChronometer, b.getWhenIfShowing(),
-                    b.getPriority(), b.mActions, mediaStyle.mActionsToShowInCompact,
-                    mediaStyle.mShowCancelButton, mediaStyle.mCancelButtonIntent, isDecorated);
-            if (isDecorated) {
-                NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, contentViewMedia,
-                        b.getContentView());
-                return contentViewMedia;
-            }
-        } else if (b.mStyle instanceof DecoratedCustomViewStyle) {
-            return getDecoratedContentView(b);
-        }
-        return null;
-    }
-
-    @RequiresApi(16)
-    private static void addBigStyleToBuilderJellybean(Notification n,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        if (b.mStyle instanceof MediaStyle) {
-            MediaStyle mediaStyle = (MediaStyle) b.mStyle;
-            RemoteViews innerView = b.getBigContentView() != null
-                    ? b.getBigContentView()
-                    : b.getContentView();
-            boolean isDecorated = b.mStyle instanceof DecoratedMediaCustomViewStyle
-                    && innerView != null;
-            NotificationCompatImplBase.overrideMediaBigContentView(n, b.mContext,
-                    b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber, b.mLargeIcon,
-                    b.mSubText, b.mUseChronometer, b.getWhenIfShowing(), b.getPriority(), 0,
-                    b.mActions, mediaStyle.mShowCancelButton, mediaStyle.mCancelButtonIntent,
-                    isDecorated);
-            if (isDecorated) {
-                NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, n.bigContentView,
-                        innerView);
-            }
-        } else if (b.mStyle instanceof DecoratedCustomViewStyle) {
-            addDecoratedBigStyleToBuilderJellybean(n, b);
-        }
-    }
-
-    private static RemoteViews getDecoratedContentView(
-            android.support.v4.app.NotificationCompat.Builder b) {
-        if (b.getContentView() == null) {
-            // No special content view
-            return null;
-        }
-        RemoteViews remoteViews = NotificationCompatImplBase.applyStandardTemplateWithActions(
-                b.mContext, b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber,
-                b.mNotification.icon, b.mLargeIcon, b.mSubText, b.mUseChronometer,
-                b.getWhenIfShowing(), b.getPriority(), b.getColor(),
-                R.layout.notification_template_custom_big, false /* fitIn1U */, null /* actions */);
-        NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, remoteViews,
-                b.getContentView());
-        return remoteViews;
-    }
-
-    @RequiresApi(16)
-    private static void addDecoratedBigStyleToBuilderJellybean(Notification n,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        RemoteViews bigContentView = b.getBigContentView();
-        RemoteViews innerView = bigContentView != null ? bigContentView : b.getContentView();
-        if (innerView == null) {
-            // No expandable notification
-            return;
-        }
-        RemoteViews remoteViews = NotificationCompatImplBase.applyStandardTemplateWithActions(
-                b.mContext, b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber,
-                n.icon ,b.mLargeIcon, b.mSubText, b.mUseChronometer, b.getWhenIfShowing(),
-                b.getPriority(), b.getColor(), R.layout.notification_template_custom_big,
-                false /* fitIn1U */, b.mActions);
-        NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, remoteViews, innerView);
-        n.bigContentView = remoteViews;
-    }
-
-    @RequiresApi(21)
-    private static void addDecoratedHeadsUpToBuilderLollipop(Notification n,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        RemoteViews headsUp = b.getHeadsUpContentView();
-        RemoteViews innerView = headsUp != null ? headsUp : b.getContentView();
-        if (headsUp == null) {
-            // No expandable notification
-            return;
-        }
-        RemoteViews remoteViews = NotificationCompatImplBase.applyStandardTemplateWithActions(
-                b.mContext, b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber, n.icon,
-                b.mLargeIcon, b.mSubText, b.mUseChronometer, b.getWhenIfShowing(), b.getPriority(),
-                b.getColor(), R.layout.notification_template_custom_big, false /* fitIn1U */,
-                b.mActions);
-        NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, remoteViews, innerView);
-        n.headsUpContentView = remoteViews;
-    }
-
-    @RequiresApi(21)
-    private static void addBigStyleToBuilderLollipop(Notification n,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        RemoteViews innerView = b.getBigContentView() != null
-                ? b.getBigContentView()
-                : b.getContentView();
-        if (b.mStyle instanceof DecoratedMediaCustomViewStyle && innerView != null) {
-            NotificationCompatImplBase.overrideMediaBigContentView(n, b.mContext,
-                    b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber, b.mLargeIcon,
-                    b.mSubText, b.mUseChronometer, b.getWhenIfShowing(), b.getPriority(), 0,
-                    b.mActions, false /* showCancelButton */, null /* cancelButtonIntent */,
-                    true /* decoratedCustomView */);
-                    NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, n.bigContentView,
-                            innerView);
-            setBackgroundColor(b.mContext, n.bigContentView, b.getColor());
-        } else if (b.mStyle instanceof DecoratedCustomViewStyle) {
-            addDecoratedBigStyleToBuilderJellybean(n, b);
-        }
-    }
-
-    private static void setBackgroundColor(Context context, RemoteViews views, int color) {
-        if (color == COLOR_DEFAULT) {
-            color = context.getResources().getColor(
-                    R.color.notification_material_background_media_default_color);
-        }
-        views.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor", color);
-    }
-
-    @RequiresApi(21)
-    private static void addHeadsUpToBuilderLollipop(Notification n,
-            android.support.v4.app.NotificationCompat.Builder b) {
-        RemoteViews innerView = b.getHeadsUpContentView() != null
-                ? b.getHeadsUpContentView()
-                : b.getContentView();
-        if (b.mStyle instanceof DecoratedMediaCustomViewStyle && innerView != null) {
-            n.headsUpContentView = NotificationCompatImplBase.generateMediaBigView(b.mContext,
-                    b.mContentTitle, b.mContentText, b.mContentInfo, b.mNumber,
-                    b.mLargeIcon, b.mSubText, b.mUseChronometer, b.getWhenIfShowing(),
-                    b.getPriority(), 0, b.mActions, false /* showCancelButton */,
-                    null /* cancelButtonIntent */, true /* decoratedCustomView */);
-            NotificationCompatImplBase.buildIntoRemoteViews(b.mContext, n.headsUpContentView,
-                    innerView);
-            setBackgroundColor(b.mContext, n.headsUpContentView, b.getColor());
-        } else if (b.mStyle instanceof DecoratedCustomViewStyle) {
-            addDecoratedHeadsUpToBuilderLollipop(n, b);
-        }
-    }
-
     /**
-     * See {@link android.support.v4.app.NotificationCompat}. In addition to the builder in v4, this
-     * builder also supports {@link MediaStyle}.
+     * See {@link android.support.v4.app.NotificationCompat}.
      */
     public static class Builder extends android.support.v4.app.NotificationCompat.Builder {
 
@@ -385,140 +83,6 @@
         public Builder(Context context) {
             super(context);
         }
-
-        /**
-         * @return the text of the notification
-         *
-         * @hide
-         */
-        @RestrictTo(LIBRARY_GROUP)
-        @Override
-        protected CharSequence resolveText() {
-            if (mStyle instanceof MessagingStyle) {
-                MessagingStyle style = (MessagingStyle) mStyle;
-                MessagingStyle.Message m = findLatestIncomingMessage(style);
-                CharSequence conversationTitle = style.getConversationTitle();
-                if (m != null) {
-                    return conversationTitle != null ? makeMessageLine(this, style, m)
-                            : m.getText();
-                }
-            }
-            return super.resolveText();
-        }
-
-        /**
-         * @return the title of the notification
-         *
-         * @hide
-         */
-        @RestrictTo(LIBRARY_GROUP)
-        @Override
-        protected CharSequence resolveTitle() {
-            if (mStyle instanceof MessagingStyle) {
-                MessagingStyle style = (MessagingStyle) mStyle;
-                MessagingStyle.Message m = findLatestIncomingMessage(style);
-                CharSequence conversationTitle = style.getConversationTitle();
-                if (conversationTitle != null || m != null) {
-                    return conversationTitle != null ? conversationTitle : m.getSender();
-                }
-            }
-            return super.resolveTitle();
-        }
-
-        /**
-         * @hide
-         */
-        @RestrictTo(LIBRARY_GROUP)
-        @Override
-        protected BuilderExtender getExtender() {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-                return new Api24Extender();
-            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-                return new LollipopExtender();
-            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
-                return new JellybeanExtender();
-            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
-                return new IceCreamSandwichExtender();
-            } else {
-                return super.getExtender();
-            }
-        }
-    }
-
-    @RequiresApi(14)
-    private static class IceCreamSandwichExtender extends BuilderExtender {
-
-        IceCreamSandwichExtender() {
-        }
-
-        @Override
-        public Notification build(android.support.v4.app.NotificationCompat.Builder b,
-                NotificationBuilderWithBuilderAccessor builder) {
-            RemoteViews contentView = addStyleGetContentViewIcs(builder, b);
-            Notification n = builder.build();
-            // The above call might override decorated content views again, let's make sure it
-            // sticks.
-            if (contentView != null) {
-                n.contentView = contentView;
-            } else if (b.getContentView() != null) {
-                n.contentView = b.getContentView();
-            }
-            return n;
-        }
-    }
-
-    @RequiresApi(16)
-    private static class JellybeanExtender extends BuilderExtender {
-
-        JellybeanExtender() {
-        }
-
-        @Override
-        public Notification build(android.support.v4.app.NotificationCompat.Builder b,
-                NotificationBuilderWithBuilderAccessor builder) {
-            RemoteViews contentView = addStyleGetContentViewJellybean(builder, b);
-            Notification n = builder.build();
-            // The above call might override decorated content views again, let's make sure it
-            // sticks.
-            if (contentView != null) {
-                n.contentView = contentView;
-            }
-            addBigStyleToBuilderJellybean(n, b);
-            return n;
-        }
-    }
-
-    @RequiresApi(21)
-    private static class LollipopExtender extends BuilderExtender {
-
-        LollipopExtender() {
-        }
-
-        @Override
-        public Notification build(android.support.v4.app.NotificationCompat.Builder b,
-                NotificationBuilderWithBuilderAccessor builder) {
-            RemoteViews contentView = addStyleGetContentViewLollipop(builder, b);
-            Notification n = builder.build();
-            // The above call might override decorated content views again, let's make sure it
-            // sticks.
-            if (contentView != null) {
-                n.contentView = contentView;
-            }
-            addBigStyleToBuilderLollipop(n, b);
-            addHeadsUpToBuilderLollipop(n, b);
-            return n;
-        }
-    }
-
-    @RequiresApi(24)
-    private static class Api24Extender extends BuilderExtender {
-
-        @Override
-        public Notification build(android.support.v4.app.NotificationCompat.Builder b,
-                NotificationBuilderWithBuilderAccessor builder) {
-            addStyleToBuilderApi24(builder, b);
-            return builder.build();
-        }
     }
 
     /**
@@ -562,6 +126,9 @@
      */
     public static class MediaStyle extends android.support.v4.app.NotificationCompat.Style {
 
+        private static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3;
+        private static final int MAX_MEDIA_BUTTONS = 5;
+
         int[] mActionsToShowInCompact = null;
         MediaSessionCompat.Token mToken;
         boolean mShowCancelButton;
@@ -619,7 +186,9 @@
          * @param show whether to show a cancel button
          */
         public MediaStyle setShowCancelButton(boolean show) {
-            mShowCancelButton = show;
+            if (Build.VERSION.SDK_INT < 21) {
+                mShowCancelButton = show;
+            }
             return this;
         }
 
@@ -633,6 +202,142 @@
             mCancelButtonIntent = pendingIntent;
             return this;
         }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public void apply(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 21) {
+                builder.getBuilder().setStyle(
+                        fillInMediaStyle(new Notification.MediaStyle()));
+            } else if (mShowCancelButton) {
+                builder.getBuilder().setOngoing(true);
+            }
+        }
+
+        @RequiresApi(21)
+        Notification.MediaStyle fillInMediaStyle(Notification.MediaStyle style) {
+            if (mActionsToShowInCompact != null) {
+                style.setShowActionsInCompactView(mActionsToShowInCompact);
+            }
+            if (mToken != null) {
+                style.setMediaSession((MediaSession.Token) mToken.getToken());
+            }
+            return style;
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 21) {
+                // No custom content view required
+                return null;
+            }
+            return generateContentView();
+        }
+
+        RemoteViews generateContentView() {
+            RemoteViews view = NotificationCompatImplBase.applyStandardTemplate(
+                    mBuilder, false /* showSmallIcon */,
+                    getContentViewLayoutResource(), true /* fitIn1U */);
+
+            final int numActions = mBuilder.mActions.size();
+            final int numActionsInCompact = mActionsToShowInCompact == null
+                    ? 0
+                    : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
+            view.removeAllViews(R.id.media_actions);
+            if (numActionsInCompact > 0) {
+                for (int i = 0; i < numActionsInCompact; i++) {
+                    if (i >= numActions) {
+                        throw new IllegalArgumentException(String.format(
+                                "setShowActionsInCompactView: action %d out of bounds (max %d)",
+                                i, numActions - 1));
+                    }
+
+                    final NotificationCompat.Action action =
+                            mBuilder.mActions.get(mActionsToShowInCompact[i]);
+                    final RemoteViews button = generateMediaActionButton(action);
+                    view.addView(R.id.media_actions, button);
+                }
+            }
+            if (mShowCancelButton) {
+                view.setViewVisibility(R.id.end_padder, View.GONE);
+                view.setViewVisibility(R.id.cancel_action, View.VISIBLE);
+                view.setOnClickPendingIntent(R.id.cancel_action, mCancelButtonIntent);
+                view.setInt(R.id.cancel_action, "setAlpha", mBuilder.mContext
+                        .getResources().getInteger(R.integer.cancel_button_image_alpha));
+            } else {
+                view.setViewVisibility(R.id.end_padder, View.VISIBLE);
+                view.setViewVisibility(R.id.cancel_action, View.GONE);
+            }
+            return view;
+        }
+
+        private RemoteViews generateMediaActionButton(NotificationCompat.Action action) {
+            final boolean tombstone = (action.getActionIntent() == null);
+            RemoteViews button = new RemoteViews(mBuilder.mContext.getPackageName(),
+                    R.layout.notification_media_action);
+            button.setImageViewResource(R.id.action0, action.getIcon());
+            if (!tombstone) {
+                button.setOnClickPendingIntent(R.id.action0, action.getActionIntent());
+            }
+            if (Build.VERSION.SDK_INT >= 15) {
+                button.setContentDescription(R.id.action0, action.getTitle());
+            }
+            return button;
+        }
+
+        int getContentViewLayoutResource() {
+            return R.layout.notification_template_media;
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 21) {
+                // No custom content view required
+                return null;
+            }
+            return generateBigContentView();
+        }
+
+        RemoteViews generateBigContentView() {
+            final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
+            RemoteViews big = NotificationCompatImplBase.applyStandardTemplate(
+                    mBuilder, false /* showSmallIcon */,
+                    getBigContentViewLayoutResource(actionCount), false /* fitIn1U */);
+
+            big.removeAllViews(R.id.media_actions);
+            if (actionCount > 0) {
+                for (int i = 0; i < actionCount; i++) {
+                    final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i));
+                    big.addView(R.id.media_actions, button);
+                }
+            }
+            if (mShowCancelButton) {
+                big.setViewVisibility(R.id.cancel_action, View.VISIBLE);
+                big.setInt(R.id.cancel_action, "setAlpha", mBuilder.mContext
+                        .getResources().getInteger(R.integer.cancel_button_image_alpha));
+                big.setOnClickPendingIntent(R.id.cancel_action, mCancelButtonIntent);
+            } else {
+                big.setViewVisibility(R.id.cancel_action, View.GONE);
+            }
+            return big;
+        }
+
+        int getBigContentViewLayoutResource(int actionCount) {
+            return actionCount <= 3
+                    ? R.layout.notification_template_big_media_narrow
+                    : R.layout.notification_template_big_media;
+        }
     }
 
 
@@ -666,8 +371,121 @@
      */
     public static class DecoratedCustomViewStyle extends Style {
 
+        private static final int MAX_ACTION_BUTTONS = 3;
+
         public DecoratedCustomViewStyle() {
         }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public void apply(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 24) {
+                builder.getBuilder().setStyle(new Notification.DecoratedCustomViewStyle());
+            }
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 24) {
+                // No custom content view required
+                return null;
+            }
+            if (mBuilder.getContentView() == null) {
+                // No special content view
+                return null;
+            }
+            return createRemoteViews(mBuilder.getContentView(), false);
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 24) {
+                // No custom big content view required
+                return null;
+            }
+            RemoteViews bigContentView = mBuilder.getBigContentView();
+            RemoteViews innerView = bigContentView != null
+                    ? bigContentView
+                    : mBuilder.getContentView();
+            if (innerView == null) {
+                // No expandable notification
+                return null;
+            }
+            return createRemoteViews(innerView, true);
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public RemoteViews makeHeadsUpContentView(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 24) {
+                // No custom heads up content view required
+                return null;
+            }
+            RemoteViews headsUp = mBuilder.getHeadsUpContentView();
+            RemoteViews innerView = headsUp != null ? headsUp : mBuilder.getContentView();
+            if (headsUp == null) {
+                // No expandable notification
+                return null;
+            }
+            return createRemoteViews(innerView, true);
+        }
+
+        private RemoteViews createRemoteViews(RemoteViews innerView, boolean showActions) {
+            RemoteViews remoteViews = NotificationCompatImplBase.applyStandardTemplate(
+                    mBuilder, true /* showSmallIcon */,
+                    R.layout.notification_template_custom_big, false /* fitIn1U */);
+            remoteViews.removeAllViews(R.id.actions);
+            boolean actionsVisible = false;
+            if (showActions && mBuilder.mActions != null) {
+                int numActions = Math.max(mBuilder.mActions.size(), MAX_ACTION_BUTTONS);
+                if (numActions > 0) {
+                    actionsVisible = true;
+                    for (int i = 0; i < numActions; i++) {
+                        final RemoteViews button = generateActionButton(mBuilder.mActions.get(i));
+                        remoteViews.addView(R.id.actions, button);
+                    }
+                }
+            }
+            int actionVisibility = actionsVisible ? View.VISIBLE : View.GONE;
+            remoteViews.setViewVisibility(R.id.actions, actionVisibility);
+            remoteViews.setViewVisibility(R.id.action_divider, actionVisibility);
+            NotificationCompatImplBase.buildIntoRemoteViews(mBuilder.mContext,
+                    remoteViews, innerView);
+            return remoteViews;
+        }
+
+        private RemoteViews generateActionButton(NotificationCompat.Action action) {
+            final boolean tombstone = (action.actionIntent == null);
+            RemoteViews button = new RemoteViews(mBuilder.mContext.getPackageName(),
+                    tombstone ? R.layout.notification_action_tombstone
+                            : R.layout.notification_action);
+            button.setImageViewBitmap(R.id.action_image,
+                    NotificationCompatImplBase.createColoredBitmap(mBuilder.mContext,
+                            action.getIcon(), mBuilder.mContext.getResources()
+                                    .getColor(R.color.notification_action_color_filter)));
+            button.setTextViewText(R.id.action_text, action.title);
+            if (!tombstone) {
+                button.setOnClickPendingIntent(R.id.action_container, action.actionIntent);
+            }
+            if (Build.VERSION.SDK_INT >= 15) {
+                button.setContentDescription(R.id.action_container, action.title);
+            }
+            return button;
+        }
     }
 
     /**
@@ -707,5 +525,134 @@
 
         public DecoratedMediaCustomViewStyle() {
         }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public void apply(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 24) {
+                builder.getBuilder().setStyle(
+                        fillInMediaStyle(new Notification.DecoratedMediaCustomViewStyle()));
+            } else {
+                super.apply(builder);
+            }
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public RemoteViews makeContentView(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 24) {
+                // No custom content view required
+                return null;
+            }
+            boolean hasContentView = mBuilder.getContentView() != null;
+            if (Build.VERSION.SDK_INT >= 21) {
+                // If we are on L/M the media notification will only be colored if the expanded
+                // version is of media style, so we have to create a custom view for the collapsed
+                // version as well in that case.
+                boolean createCustomContent = hasContentView
+                        || mBuilder.getBigContentView() != null;
+                if (createCustomContent) {
+                    RemoteViews contentView = generateContentView();
+                    if (hasContentView) {
+                        NotificationCompatImplBase.buildIntoRemoteViews(mBuilder.mContext,
+                                contentView,
+                                mBuilder.getContentView());
+                    }
+                    setBackgroundColor(contentView);
+                    return contentView;
+                }
+            } else {
+                RemoteViews contentView = generateContentView();
+                if (hasContentView) {
+                    NotificationCompatImplBase.buildIntoRemoteViews(mBuilder.mContext,
+                            contentView,
+                            mBuilder.getContentView());
+                    return contentView;
+                }
+            }
+            return null;
+        }
+
+        @Override
+        int getContentViewLayoutResource() {
+            return mBuilder.getContentView() != null
+                    ? R.layout.notification_template_media_custom
+                    : super.getContentViewLayoutResource();
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public RemoteViews makeBigContentView(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 24) {
+                // No custom big content view required
+                return null;
+            }
+            RemoteViews innerView = mBuilder.getBigContentView() != null
+                    ? mBuilder.getBigContentView()
+                    : mBuilder.getContentView();
+            if (innerView == null) {
+                // No expandable notification
+                return null;
+            }
+            RemoteViews bigContentView = generateBigContentView();
+            NotificationCompatImplBase.buildIntoRemoteViews(mBuilder.mContext,
+                    bigContentView,
+                    innerView);
+            if (Build.VERSION.SDK_INT >= 21) {
+                setBackgroundColor(bigContentView);
+            }
+            return bigContentView;
+        }
+
+        @Override
+        int getBigContentViewLayoutResource(int actionCount) {
+            return actionCount <= 3
+                    ? R.layout.notification_template_big_media_narrow_custom
+                    : R.layout.notification_template_big_media_custom;
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        @Override
+        public RemoteViews makeHeadsUpContentView(NotificationBuilderWithBuilderAccessor builder) {
+            if (Build.VERSION.SDK_INT >= 24) {
+                // No custom heads up content view required
+                return null;
+            }
+            RemoteViews innerView = mBuilder.getHeadsUpContentView() != null
+                    ? mBuilder.getHeadsUpContentView()
+                    : mBuilder.getContentView();
+            if (innerView == null) {
+                // No expandable notification
+                return null;
+            }
+            RemoteViews headsUpContentView = generateBigContentView();
+            NotificationCompatImplBase.buildIntoRemoteViews(mBuilder.mContext,
+                    headsUpContentView,
+                    innerView);
+            if (Build.VERSION.SDK_INT >= 21) {
+                setBackgroundColor(headsUpContentView);
+            }
+            return headsUpContentView;
+        }
+
+        private void setBackgroundColor(RemoteViews views) {
+            int color = mBuilder.getColor() != COLOR_DEFAULT
+                    ? mBuilder.getColor()
+                    : mBuilder.mContext.getResources().getColor(
+                        R.color.notification_material_background_media_default_color);
+            views.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor", color);
+        }
     }
 }
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompatImpl21.java b/v7/appcompat/src/android/support/v7/app/NotificationCompatImpl21.java
deleted file mode 100644
index 2a4bf7b..0000000
--- a/v7/appcompat/src/android/support/v7/app/NotificationCompatImpl21.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v7.app;
-
-import android.app.Notification;
-import android.media.session.MediaSession;
-import android.support.annotation.RequiresApi;
-import android.support.v4.app.NotificationBuilderWithBuilderAccessor;
-
-@RequiresApi(21)
-class NotificationCompatImpl21 {
-
-    public static void addMediaStyle(NotificationBuilderWithBuilderAccessor b,
-            int[] actionsToShowInCompact,
-            Object token) {
-        Notification.MediaStyle style = new Notification.MediaStyle(b.getBuilder());
-        if (actionsToShowInCompact != null) {
-            style.setShowActionsInCompactView(actionsToShowInCompact);
-        }
-        if (token != null) {
-            style.setMediaSession((MediaSession.Token) token);
-        }
-    }
-}
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompatImpl24.java b/v7/appcompat/src/android/support/v7/app/NotificationCompatImpl24.java
deleted file mode 100644
index cc09bb5..0000000
--- a/v7/appcompat/src/android/support/v7/app/NotificationCompatImpl24.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.support.v7.app;
-
-import android.app.Notification;
-import android.media.session.MediaSession;
-import android.support.annotation.RequiresApi;
-import android.support.v4.app.NotificationBuilderWithBuilderAccessor;
-
-@RequiresApi(24)
-class NotificationCompatImpl24 {
-
-    public static void addDecoratedCustomViewStyle(NotificationBuilderWithBuilderAccessor b) {
-        Notification.Builder builder = b.getBuilder();
-        builder.setStyle(new Notification.DecoratedCustomViewStyle());
-    }
-
-    public static void addDecoratedMediaCustomViewStyle(NotificationBuilderWithBuilderAccessor b,
-            int[] actionsToShowInCompact,
-            Object token) {
-        Notification.Builder builder = b.getBuilder();
-        Notification.DecoratedMediaCustomViewStyle style =
-                new Notification.DecoratedMediaCustomViewStyle();
-        if (actionsToShowInCompact != null) {
-            style.setShowActionsInCompactView(actionsToShowInCompact);
-        }
-        if (token != null) {
-            style.setMediaSession((MediaSession.Token) token);
-        }
-        builder.setStyle(style);
-    }
-}
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompatImplBase.java b/v7/appcompat/src/android/support/v7/app/NotificationCompatImplBase.java
index c1432c9..b2d5eaa 100644
--- a/v7/appcompat/src/android/support/v7/app/NotificationCompatImplBase.java
+++ b/v7/appcompat/src/android/support/v7/app/NotificationCompatImplBase.java
@@ -16,8 +16,6 @@
 
 package android.support.v7.app;
 
-import android.app.Notification;
-import android.app.PendingIntent;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -29,17 +27,13 @@
 import android.os.Build;
 import android.os.SystemClock;
 import android.support.annotation.RequiresApi;
-import android.support.v4.app.NotificationBuilderWithBuilderAccessor;
 import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationCompatBase;
 import android.support.v7.appcompat.R;
 import android.util.TypedValue;
 import android.view.View;
 import android.widget.RemoteViews;
 
 import java.text.NumberFormat;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Helper class to generate MediaStyle notifications for pre-Lollipop platforms. Overrides
@@ -48,196 +42,7 @@
 @RequiresApi(9)
 class NotificationCompatImplBase {
 
-    static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3;
-    static final int MAX_MEDIA_BUTTONS = 5;
-    private static final int MAX_ACTION_BUTTONS = 3;
-
-    @RequiresApi(11)
-    public static <T extends NotificationCompatBase.Action> RemoteViews overrideContentViewMedia(
-            NotificationBuilderWithBuilderAccessor builder,
-            Context context, CharSequence contentTitle, CharSequence contentText,
-            CharSequence contentInfo, int number, Bitmap largeIcon, CharSequence subText,
-            boolean useChronometer, long when, int priority, List<T> actions,
-            int[] actionsToShowInCompact, boolean showCancelButton,
-            PendingIntent cancelButtonIntent, boolean isDecoratedCustomView) {
-        RemoteViews views = generateContentViewMedia(context, contentTitle, contentText, contentInfo,
-                number, largeIcon, subText, useChronometer, when, priority, actions,
-                actionsToShowInCompact, showCancelButton, cancelButtonIntent,
-                isDecoratedCustomView);
-        builder.getBuilder().setContent(views);
-        if (showCancelButton) {
-            builder.getBuilder().setOngoing(true);
-        }
-        return views;
-    }
-
-    @RequiresApi(11)
-    private static <T extends NotificationCompatBase.Action> RemoteViews generateContentViewMedia(
-            Context context, CharSequence contentTitle, CharSequence contentText,
-            CharSequence contentInfo, int number, Bitmap largeIcon, CharSequence subText,
-            boolean useChronometer, long when, int priority, List<T> actions,
-            int[] actionsToShowInCompact, boolean showCancelButton,
-            PendingIntent cancelButtonIntent, boolean isDecoratedCustomView) {
-        RemoteViews view = applyStandardTemplate(context, contentTitle, contentText, contentInfo,
-                number, 0 /* smallIcon */, largeIcon, subText, useChronometer, when, priority,
-                0 /* color is unused on media */,
-                isDecoratedCustomView ? R.layout.notification_template_media_custom
-                        : R.layout.notification_template_media,
-                true /* fitIn1U */);
-
-        final int numActions = actions.size();
-        final int N = actionsToShowInCompact == null
-                ? 0
-                : Math.min(actionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT);
-        view.removeAllViews(R.id.media_actions);
-        if (N > 0) {
-            for (int i = 0; i < N; i++) {
-                if (i >= numActions) {
-                    throw new IllegalArgumentException(String.format(
-                            "setShowActionsInCompactView: action %d out of bounds (max %d)",
-                            i, numActions - 1));
-                }
-
-                final NotificationCompatBase.Action action = actions.get(actionsToShowInCompact[i]);
-                final RemoteViews button = generateMediaActionButton(context, action);
-                view.addView(R.id.media_actions, button);
-            }
-        }
-        if (showCancelButton) {
-            view.setViewVisibility(R.id.end_padder, View.GONE);
-            view.setViewVisibility(R.id.cancel_action, View.VISIBLE);
-            view.setOnClickPendingIntent(R.id.cancel_action, cancelButtonIntent);
-            view.setInt(R.id.cancel_action, "setAlpha",
-                    context.getResources().getInteger(R.integer.cancel_button_image_alpha));
-        } else {
-            view.setViewVisibility(R.id.end_padder, View.VISIBLE);
-            view.setViewVisibility(R.id.cancel_action, View.GONE);
-        }
-        return view;
-    }
-
-    @RequiresApi(16)
-    public static <T extends NotificationCompatBase.Action> void overrideMediaBigContentView(
-            Notification n, Context context, CharSequence contentTitle, CharSequence contentText,
-            CharSequence contentInfo, int number, Bitmap largeIcon, CharSequence subText,
-            boolean useChronometer, long when, int priority, int color, List<T> actions,
-            boolean showCancelButton, PendingIntent cancelButtonIntent,
-            boolean decoratedCustomView) {
-        n.bigContentView = generateMediaBigView(context, contentTitle, contentText, contentInfo,
-                number, largeIcon, subText, useChronometer, when, priority, color, actions,
-                showCancelButton, cancelButtonIntent, decoratedCustomView);
-        if (showCancelButton) {
-            n.flags |= Notification.FLAG_ONGOING_EVENT;
-        }
-    }
-
-    @RequiresApi(11)
-    public static <T extends NotificationCompatBase.Action> RemoteViews generateMediaBigView(
-            Context context, CharSequence contentTitle, CharSequence contentText,
-            CharSequence contentInfo, int number, Bitmap largeIcon, CharSequence subText,
-            boolean useChronometer, long when, int priority, int color, List<T> actions,
-            boolean showCancelButton, PendingIntent cancelButtonIntent,
-            boolean decoratedCustomView) {
-        final int actionCount = Math.min(actions.size(), MAX_MEDIA_BUTTONS);
-        RemoteViews big = applyStandardTemplate(context, contentTitle, contentText, contentInfo,
-                number, 0 /* smallIcon */, largeIcon, subText, useChronometer, when, priority,
-                color,  /* fitIn1U */getBigMediaLayoutResource(decoratedCustomView, actionCount),
-                false);
-
-        big.removeAllViews(R.id.media_actions);
-        if (actionCount > 0) {
-            for (int i = 0; i < actionCount; i++) {
-                final RemoteViews button = generateMediaActionButton(context, actions.get(i));
-                big.addView(R.id.media_actions, button);
-            }
-        }
-        if (showCancelButton) {
-            big.setViewVisibility(R.id.cancel_action, View.VISIBLE);
-            big.setInt(R.id.cancel_action, "setAlpha",
-                    context.getResources().getInteger(R.integer.cancel_button_image_alpha));
-            big.setOnClickPendingIntent(R.id.cancel_action, cancelButtonIntent);
-        } else {
-            big.setViewVisibility(R.id.cancel_action, View.GONE);
-        }
-        return big;
-    }
-
-    @RequiresApi(11)
-    private static RemoteViews generateMediaActionButton(Context context,
-            NotificationCompatBase.Action action) {
-        final boolean tombstone = (action.getActionIntent() == null);
-        RemoteViews button = new RemoteViews(context.getPackageName(),
-                R.layout.notification_media_action);
-        button.setImageViewResource(R.id.action0, action.getIcon());
-        if (!tombstone) {
-            button.setOnClickPendingIntent(R.id.action0, action.getActionIntent());
-        }
-        if (Build.VERSION.SDK_INT >= 15) {
-            button.setContentDescription(R.id.action0, action.getTitle());
-        }
-        return button;
-    }
-
-    @RequiresApi(11)
-    private static int getBigMediaLayoutResource(boolean decoratedCustomView, int actionCount) {
-        if (actionCount <= 3) {
-            return decoratedCustomView
-                    ? R.layout.notification_template_big_media_narrow_custom
-                    : R.layout.notification_template_big_media_narrow;
-        } else {
-            return decoratedCustomView
-                    ? R.layout.notification_template_big_media_custom
-                    : R.layout.notification_template_big_media;
-        }
-    }
-
-    public static RemoteViews applyStandardTemplateWithActions(Context context,
-            CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
-            int number, int smallIcon, Bitmap largeIcon, CharSequence subText,
-            boolean useChronometer, long when, int priority, int color, int resId, boolean fitIn1U,
-            ArrayList<NotificationCompat.Action> actions) {
-        RemoteViews remoteViews = applyStandardTemplate(context, contentTitle, contentText,
-                contentInfo, number, smallIcon, largeIcon, subText, useChronometer, when, priority,
-                color, resId, fitIn1U);
-        remoteViews.removeAllViews(R.id.actions);
-        boolean actionsVisible = false;
-        if (actions != null) {
-            int N = actions.size();
-            if (N > 0) {
-                actionsVisible = true;
-                if (N > MAX_ACTION_BUTTONS) N = MAX_ACTION_BUTTONS;
-                for (int i = 0; i < N; i++) {
-                    final RemoteViews button = generateActionButton(context, actions.get(i));
-                    remoteViews.addView(R.id.actions, button);
-                }
-            }
-        }
-        int actionVisibility = actionsVisible ? View.VISIBLE : View.GONE;
-        remoteViews.setViewVisibility(R.id.actions, actionVisibility);
-        remoteViews.setViewVisibility(R.id.action_divider, actionVisibility);
-        return remoteViews;
-    }
-
-    private static RemoteViews generateActionButton(Context context,
-            NotificationCompat.Action action) {
-        final boolean tombstone = (action.actionIntent == null);
-        RemoteViews button =  new RemoteViews(context.getPackageName(),
-                tombstone ? getActionTombstoneLayoutResource()
-                        : getActionLayoutResource());
-        button.setImageViewBitmap(R.id.action_image,
-                createColoredBitmap(context, action.getIcon(),
-                        context.getResources().getColor(R.color.notification_action_color_filter)));
-        button.setTextViewText(R.id.action_text, action.title);
-        if (!tombstone) {
-            button.setOnClickPendingIntent(R.id.action_container, action.actionIntent);
-        }
-        if (Build.VERSION.SDK_INT >= 15) {
-            button.setContentDescription(R.id.action_container, action.title);
-        }
-        return button;
-    }
-
-    private static Bitmap createColoredBitmap(Context context, int iconId, int color) {
+    static Bitmap createColoredBitmap(Context context, int iconId, int color) {
         return createColoredBitmap(context, iconId, color, 0);
     }
 
@@ -256,25 +61,14 @@
         return resultBitmap;
     }
 
-    private static int getActionLayoutResource() {
-        return R.layout.notification_action;
-    }
-
-    private static int getActionTombstoneLayoutResource() {
-        return R.layout.notification_action_tombstone;
-    }
-
-    public static RemoteViews applyStandardTemplate(Context context,
-            CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
-            int number, int smallIcon, Bitmap largeIcon, CharSequence subText,
-            boolean useChronometer, long when, int priority, int color, int resId,
-            boolean fitIn1U) {
-        Resources res = context.getResources();
-        RemoteViews contentView = new RemoteViews(context.getPackageName(), resId);
+    public static RemoteViews applyStandardTemplate(NotificationCompat.Builder builder,
+            boolean showSmallIcon, int resId, boolean fitIn1U) {
+        Resources res = builder.mContext.getResources();
+        RemoteViews contentView = new RemoteViews(builder.mContext.getPackageName(), resId);
         boolean showLine3 = false;
         boolean showLine2 = false;
 
-        boolean minPriority = priority < NotificationCompat.PRIORITY_LOW;
+        boolean minPriority = builder.getPriority() < NotificationCompat.PRIORITY_LOW;
         if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 21) {
             // lets color the backgrounds
             if (minPriority) {
@@ -290,34 +84,34 @@
             }
         }
 
-        if (largeIcon != null) {
+        if (builder.mLargeIcon != null) {
             // On versions before Jellybean, the large icon was shown by SystemUI, so we need to hide
             // it here.
             if (Build.VERSION.SDK_INT >= 16) {
                 contentView.setViewVisibility(R.id.icon, View.VISIBLE);
-                contentView.setImageViewBitmap(R.id.icon, largeIcon);
+                contentView.setImageViewBitmap(R.id.icon, builder.mLargeIcon);
             } else {
                 contentView.setViewVisibility(R.id.icon, View.GONE);
             }
-            if (smallIcon != 0) {
+            if (showSmallIcon && builder.mNotification.icon != 0) {
                 int backgroundSize = res.getDimensionPixelSize(
                         R.dimen.notification_right_icon_size);
                 int iconSize = backgroundSize - res.getDimensionPixelSize(
                         R.dimen.notification_small_icon_background_padding) * 2;
                 if (Build.VERSION.SDK_INT >= 21) {
-                    Bitmap smallBit = createIconWithBackground(context,
-                            smallIcon,
+                    Bitmap smallBit = createIconWithBackground(builder.mContext,
+                            builder.mNotification.icon,
                             backgroundSize,
                             iconSize,
-                            color);
+                            builder.getColor());
                     contentView.setImageViewBitmap(R.id.right_icon, smallBit);
                 } else {
-                    contentView.setImageViewBitmap(R.id.right_icon,
-                            createColoredBitmap(context, smallIcon, Color.WHITE));
+                    contentView.setImageViewBitmap(R.id.right_icon, createColoredBitmap(
+                            builder.mContext, builder.mNotification.icon, Color.WHITE));
                 }
                 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
             }
-        } else if (smallIcon != 0) { // small icon at left
+        } else if (showSmallIcon && builder.mNotification.icon != 0) { // small icon at left
             contentView.setViewVisibility(R.id.icon, View.VISIBLE);
             if (Build.VERSION.SDK_INT >= 21) {
                 int backgroundSize = res.getDimensionPixelSize(
@@ -325,40 +119,40 @@
                         - res.getDimensionPixelSize(R.dimen.notification_big_circle_margin);
                 int iconSize = res.getDimensionPixelSize(
                         R.dimen.notification_small_icon_size_as_large);
-                Bitmap smallBit = createIconWithBackground(context,
-                        smallIcon,
+                Bitmap smallBit = createIconWithBackground(builder.mContext,
+                        builder.mNotification.icon,
                         backgroundSize,
                         iconSize,
-                        color);
+                        builder.getColor());
                 contentView.setImageViewBitmap(R.id.icon, smallBit);
             } else {
-                contentView.setImageViewBitmap(R.id.icon,
-                        createColoredBitmap(context, smallIcon, Color.WHITE));
+                contentView.setImageViewBitmap(R.id.icon, createColoredBitmap(
+                        builder.mContext, builder.mNotification.icon, Color.WHITE));
             }
         }
-        if (contentTitle != null) {
-            contentView.setTextViewText(R.id.title, contentTitle);
+        if (builder.mContentTitle != null) {
+            contentView.setTextViewText(R.id.title, builder.mContentTitle);
         }
-        if (contentText != null) {
-            contentView.setTextViewText(R.id.text, contentText);
+        if (builder.mContentText != null) {
+            contentView.setTextViewText(R.id.text, builder.mContentText);
             showLine3 = true;
         }
         // If there is a large icon we have a right side
-        boolean hasRightSide = !(Build.VERSION.SDK_INT >= 21) && largeIcon != null;
-        if (contentInfo != null) {
-            contentView.setTextViewText(R.id.info, contentInfo);
+        boolean hasRightSide = !(Build.VERSION.SDK_INT >= 21) && builder.mLargeIcon != null;
+        if (builder.mContentInfo != null) {
+            contentView.setTextViewText(R.id.info, builder.mContentInfo);
             contentView.setViewVisibility(R.id.info, View.VISIBLE);
             showLine3 = true;
             hasRightSide = true;
-        } else if (number > 0) {
+        } else if (builder.mNumber > 0) {
             final int tooBig = res.getInteger(
                     R.integer.status_bar_notification_info_maxnum);
-            if (number > tooBig) {
+            if (builder.mNumber > tooBig) {
                 contentView.setTextViewText(R.id.info, ((Resources) res).getString(
                         R.string.status_bar_notification_info_overflow));
             } else {
                 NumberFormat f = NumberFormat.getIntegerInstance();
-                contentView.setTextViewText(R.id.info, f.format(number));
+                contentView.setTextViewText(R.id.info, f.format(builder.mNumber));
             }
             contentView.setViewVisibility(R.id.info, View.VISIBLE);
             showLine3 = true;
@@ -368,10 +162,10 @@
         }
 
         // Need to show three lines? Only allow on Jellybean+
-        if (subText != null && Build.VERSION.SDK_INT >= 16) {
-            contentView.setTextViewText(R.id.text, subText);
-            if (contentText != null) {
-                contentView.setTextViewText(R.id.text2, contentText);
+        if (builder.mSubText != null && Build.VERSION.SDK_INT >= 16) {
+            contentView.setTextViewText(R.id.text, builder.mSubText);
+            if (builder.mContentText != null) {
+                contentView.setTextViewText(R.id.text2, builder.mContentText);
                 contentView.setViewVisibility(R.id.text2, View.VISIBLE);
                 showLine2 = true;
             } else {
@@ -391,15 +185,16 @@
             contentView.setViewPadding(R.id.line1, 0, 0, 0, 0);
         }
 
-        if (when != 0) {
-            if (useChronometer && Build.VERSION.SDK_INT >= 16) {
+        if (builder.getWhenIfShowing() != 0) {
+            if (builder.mUseChronometer && Build.VERSION.SDK_INT >= 16) {
                 contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
                 contentView.setLong(R.id.chronometer, "setBase",
-                        when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
+                        builder.getWhenIfShowing()
+                                + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
                 contentView.setBoolean(R.id.chronometer, "setStarted", true);
             } else {
                 contentView.setViewVisibility(R.id.time, View.VISIBLE);
-                contentView.setLong(R.id.time, "setTime", when);
+                contentView.setLong(R.id.time, "setTime", builder.getWhenIfShowing());
             }
             hasRightSide = true;
         }
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompatImplJellybean.java b/v7/appcompat/src/android/support/v7/app/NotificationCompatImplJellybean.java
deleted file mode 100644
index 2fca0f0..0000000
--- a/v7/appcompat/src/android/support/v7/app/NotificationCompatImplJellybean.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.support.v7.app;
-
-import android.app.Notification;
-import android.support.annotation.RequiresApi;
-import android.support.v4.app.NotificationBuilderWithBuilderAccessor;
-
-@RequiresApi(16)
-class NotificationCompatImplJellybean {
-
-    public static void addBigTextStyle(NotificationBuilderWithBuilderAccessor b,
-            CharSequence bigText) {
-        Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle(b.getBuilder());
-        bigTextStyle.bigText(bigText);
-    }
-}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java b/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java
index 7ef6a85..2681d0f 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java
@@ -182,9 +182,14 @@
         Builder popupBuilder = new Builder().setModal(setupAsModal).withDismissListener();
         popupBuilder.wireToActionButton();
 
+        final View.OnClickListener mockContainerClickListener = mock(View.OnClickListener.class);
         // Also register a click listener on the top-level container
-        View.OnClickListener mockContainerClickListener = mock(View.OnClickListener.class);
-        mContainer.setOnClickListener(mockContainerClickListener);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mContainer.setOnClickListener(mockContainerClickListener);
+            }
+        });
 
         onView(withId(R.id.test_button)).perform(click());
         assertTrue("Popup window showing", mListPopupWindow.isShowing());
diff --git a/v7/gridlayout/lint-baseline.xml b/v7/gridlayout/lint-baseline.xml
index 1c675b0..493c8c5 100644
--- a/v7/gridlayout/lint-baseline.xml
+++ b/v7/gridlayout/lint-baseline.xml
@@ -2,102 +2,14 @@
 <issues format="4" by="lint 2.4.0-alpha6">
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/v7/mediarouter/build.gradle b/v7/mediarouter/build.gradle
index d0448dd..f971a0b 100644
--- a/v7/mediarouter/build.gradle
+++ b/v7/mediarouter/build.gradle
@@ -5,7 +5,13 @@
     compile project(":support-appcompat-v7")
     compile project(":support-palette-v7")
 
-    androidTestCompile libs.test_runner
+    androidTestCompile (libs.test_runner) {
+        exclude module: 'support-annotations'
+    }
+    androidTestCompile (libs.espresso_core) {
+        exclude module: 'support-annotations'
+    }
+    androidTestCompile libs.test_rules
 }
 
 android {
diff --git a/v7/mediarouter/lint-baseline.xml b/v7/mediarouter/lint-baseline.xml
index edf2faf..fe44849 100644
--- a/v7/mediarouter/lint-baseline.xml
+++ b/v7/mediarouter/lint-baseline.xml
@@ -105,17 +105,6 @@
     </issue>
 
     <issue
-        id="ResourceType"
-        message="Expected resource of type xml"
-        errorLine1="            final XmlPullParser parser = res.getXml(resId);"
-        errorLine2="                                                    ~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="557"
-            column="53"/>
-    </issue>
-
-    <issue
         id="Range"
         message="Value must be ≥ 0 (was -2147483648)"
         errorLine1="                                MeasureSpec.makeMeasureSpec(largestChildHeight,"
@@ -138,72 +127,6 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
         id="LongLogTag"
         message="The logging tag can be at most 23 characters, was 30 (ActionBarDrawerToggleHoneycomb)"
         errorLine1="                Log.w(TAG, &quot;Couldn&apos;t set home-as-up indicator via JB-MR2 API&quot;, e);"
@@ -304,39 +227,6 @@
 
     <issue
         id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
         message="The logging tag can be at most 23 characters, was 24 (MediaRouteActionProvider)"
         errorLine1="            Log.e(TAG, &quot;onCreateActionView: this ActionProvider is already associated &quot; +"
         errorLine2="                  ~~~">
@@ -347,39 +237,6 @@
     </issue>
 
     <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
         id="AppCompatCustomView"
         message="This custom view should extend `android.support.v7.widget.AppCompatAutoCompleteTextView` instead"
         errorLine1="public class AppCompatAutoCompleteTextView extends AutoCompleteTextView implements"
@@ -568,35 +425,13 @@
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
     <issue
@@ -621,15 +456,4 @@
             column="36"/>
     </issue>
 
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL"
-        errorLine1="            return isAutoMirrored() &amp;&amp; getLayoutDirection() == LayoutDirection.RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="821"
-            column="64"/>
-    </issue>
-
 </issues>
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
index 9f2d396..0ab2eb1 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
@@ -92,7 +92,10 @@
     }
 
     public MediaRouteChooserDialog(Context context, int theme) {
-        super(MediaRouterThemeHelper.createThemedContext(context, theme), theme);
+        // If we pass theme ID of 0 to AppCompatDialog, it will apply dialogTheme on the context,
+        // which may override our style settings. Passes our uppermost theme ID to prevent this.
+        super(MediaRouterThemeHelper.createThemedContext(context, theme),
+                theme == 0 ? MediaRouterThemeHelper.createThemeForDialog(context, theme) : theme);
         context = getContext();
 
         mRouter = MediaRouter.getInstance(context);
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
index a7bceec..86f4753 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
@@ -201,8 +201,12 @@
     }
 
     public MediaRouteControllerDialog(Context context, int theme) {
+        // If we pass theme ID of 0 to AppCompatDialog, it will apply dialogTheme on the context,
+        // which may override our style settings. Passes our uppermost theme ID to prevent this.
         super(MediaRouterThemeHelper.createThemedContext(context,
-                MediaRouterThemeHelper.getAlertDialogResolvedTheme(context, theme)), theme);
+                MediaRouterThemeHelper.getAlertDialogResolvedTheme(context, theme)), theme == 0
+                ? MediaRouterThemeHelper.createThemeForDialog(context, MediaRouterThemeHelper
+                        .getAlertDialogResolvedTheme(context, theme)) : theme);
         mContext = getContext();
 
         mControllerCallback = new MediaControllerCallback();
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java b/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java
index 23f3bad..9ef218e 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java
@@ -54,29 +54,21 @@
      *              {@code 0} to use the parent {@code context}'s default theme.
      * @return The themed context.
      */
-    public static Context createThemedContext(Context context, int style) {
+    static Context createThemedContext(Context context, int style) {
         // First, apply dialog property overlay.
+        Context themedContext =
+                new ContextThemeWrapper(context, getStyledRouterThemeId(context, style));
+        int customizedThemeId = getThemeResource(context, R.attr.mediaRouteTheme);
+        return customizedThemeId == 0 ? themedContext
+                : new ContextThemeWrapper(themedContext, customizedThemeId);
+    }
 
-        int theme;
-        if (isLightTheme(context)) {
-            if (getControllerColor(context, style) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
-                theme = R.style.Theme_MediaRouter_Light;
-            } else {
-                theme = R.style.Theme_MediaRouter_Light_DarkControlPanel;
-            }
-        } else {
-            if (getControllerColor(context, style) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
-                theme = R.style.Theme_MediaRouter_LightControlPanel;
-            } else {
-                theme = R.style.Theme_MediaRouter;
-            }
-        }
-        int mediaRouteThemeResId = getThemeResource(context, R.attr.mediaRouteTheme);
-        Context themedContext = new ContextThemeWrapper(context, theme);
-        if (mediaRouteThemeResId != 0) {
-            themedContext = new ContextThemeWrapper(themedContext, mediaRouteThemeResId);
-        }
-        return themedContext;
+    /**
+     * Creates the theme resource ID intended to be used by dialogs.
+     */
+    static int createThemeForDialog(Context context, int style) {
+        int customizedThemeId = getThemeResource(context, R.attr.mediaRouteTheme);
+        return customizedThemeId != 0 ? customizedThemeId : getStyledRouterThemeId(context, style);
     }
 
     public static int getThemeResource(Context context, int attr) {
@@ -180,4 +172,22 @@
         }
         return value.data;
     }
+
+    private static int getStyledRouterThemeId(Context context, int style) {
+        int themeId;
+        if (isLightTheme(context)) {
+            if (getControllerColor(context, style) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
+                themeId = R.style.Theme_MediaRouter_Light;
+            } else {
+                themeId = R.style.Theme_MediaRouter_Light_DarkControlPanel;
+            }
+        } else {
+            if (getControllerColor(context, style) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
+                themeId = R.style.Theme_MediaRouter_LightControlPanel;
+            } else {
+                themeId = R.style.Theme_MediaRouter;
+            }
+        }
+        return themeId;
+    }
 }
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderDescriptor.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderDescriptor.java
index f6daeff..ac3ae5a 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderDescriptor.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderDescriptor.java
@@ -30,13 +30,12 @@
  * </p>
  */
 public final class MediaRouteProviderDescriptor {
-    static final String KEY_ROUTES = "routes";
+    private static final String KEY_ROUTES = "routes";
 
-    final Bundle mBundle;
-    List<MediaRouteDescriptor> mRoutes;
+    private final Bundle mBundle;
+    private List<MediaRouteDescriptor> mRoutes;
 
-    MediaRouteProviderDescriptor(Bundle bundle,
-            List<MediaRouteDescriptor> routes) {
+    private MediaRouteProviderDescriptor(Bundle bundle, List<MediaRouteDescriptor> routes) {
         mBundle = bundle;
         mRoutes = routes;
     }
@@ -49,7 +48,7 @@
         return mRoutes;
     }
 
-    void ensureRoutes() {
+    private void ensureRoutes() {
         if (mRoutes == null) {
             ArrayList<Bundle> routeBundles = mBundle.<Bundle>getParcelableArrayList(KEY_ROUTES);
             if (routeBundles == null || routeBundles.isEmpty()) {
@@ -179,6 +178,19 @@
         }
 
         /**
+         * Sets the list of routes.
+         */
+        Builder setRoutes(Collection<MediaRouteDescriptor> routes) {
+            if (routes == null || routes.isEmpty()) {
+                mRoutes = null;
+                mBundle.remove(KEY_ROUTES);
+            } else {
+                mRoutes = new ArrayList<>(routes);
+            }
+            return this;
+        }
+
+        /**
          * Builds the {@link MediaRouteProviderDescriptor media route provider descriptor}.
          */
         public MediaRouteProviderDescriptor build() {
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
index 0d9c396..d788a3c 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
@@ -56,12 +56,12 @@
 import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
+import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 import android.util.SparseArray;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Base class for media route provider services.
@@ -192,7 +192,8 @@
                         MediaRouteProviderDescriptor descriptor = mProvider.getDescriptor();
                         sendReply(messenger, SERVICE_MSG_REGISTERED,
                                 requestId, SERVICE_VERSION_CURRENT,
-                                createDescriptorBundleForClient(descriptor, client), null);
+                                createDescriptorBundleForClientVersion(descriptor,
+                                        client.mVersion), null);
                     }
                     return true;
                 }
@@ -413,32 +414,29 @@
         for (int i = 0; i < count; i++) {
             ClientRecord client = mClients.get(i);
             sendReply(client.mMessenger, SERVICE_MSG_DESCRIPTOR_CHANGED, 0, 0,
-                    createDescriptorBundleForClient(descriptor, client), null);
+                    createDescriptorBundleForClientVersion(descriptor, client.mVersion), null);
             if (DEBUG) {
                 Log.d(TAG, client + ": Sent descriptor change event, descriptor=" + descriptor);
             }
         }
     }
 
-    private Bundle createDescriptorBundleForClient(MediaRouteProviderDescriptor descriptor,
-            ClientRecord client) {
+    @VisibleForTesting
+    static Bundle createDescriptorBundleForClientVersion(MediaRouteProviderDescriptor descriptor,
+            int clientVersion) {
         if (descriptor == null) {
             return null;
         }
-        List<MediaRouteDescriptor> routes = descriptor.getRoutes();
-        for (int i = routes.size() - 1; i >= 0; i--) {
-            if (client.mVersion < routes.get(i).getMinClientVersion()
-                    || client.mVersion > routes.get(i).getMaxClientVersion()) {
-                routes.remove(i);
+        MediaRouteProviderDescriptor.Builder builder =
+                new MediaRouteProviderDescriptor.Builder(descriptor);
+        builder.setRoutes(null);
+        for (MediaRouteDescriptor route : descriptor.getRoutes()) {
+            if (clientVersion >= route.getMinClientVersion()
+                    && clientVersion <= route.getMaxClientVersion()) {
+                builder.addRoute(route);
             }
         }
-
-        // Keep the values of the bundle from descriptor excepts routes values.
-        Bundle bundle = descriptor.asBundle();
-        bundle.remove(MediaRouteProviderDescriptor.KEY_ROUTES);
-        return new MediaRouteProviderDescriptor.Builder(
-                MediaRouteProviderDescriptor.fromBundle(bundle))
-                .addRoutes(routes).build().asBundle();
+        return builder.build().asBundle();
     }
 
     boolean updateCompositeDiscoveryRequest() {
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
index e052035..8b64688 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
@@ -229,7 +229,7 @@
      */
     public static final int AVAILABILITY_FLAG_REQUIRE_MATCH = 1 << 1;
 
-    MediaRouter(Context context) {
+    private MediaRouter(Context context) {
         mContext = context;
     }
 
@@ -1973,10 +1973,11 @@
             // the framework media router.  This one is special and receives
             // synchronization messages from the media router.
             mSystemProvider = SystemMediaRouteProvider.obtain(applicationContext, this);
-            addProvider(mSystemProvider);
         }
 
         public void start() {
+            addProvider(mSystemProvider);
+
             // Start watching for routes published by registered media route
             // provider services.
             mRegisteredProviderWatcher = new RegisteredMediaRouteProviderWatcher(
@@ -2561,7 +2562,7 @@
 
         private void setSelectedRouteInternal(RouteInfo route, int unselectReason) {
             // TODO: Remove the following logging when no longer needed.
-            if (mBluetoothRoute != null && route.isDefault()) {
+            if (mBluetoothRoute != null && route != null && route.isDefault()) {
                 final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
                 StringBuffer sb = new StringBuffer();
                 // callStack[3] is the caller of this method.
diff --git a/v7/mediarouter/tests/AndroidManifest.xml b/v7/mediarouter/tests/AndroidManifest.xml
index 5187e65..e1c6443 100644
--- a/v7/mediarouter/tests/AndroidManifest.xml
+++ b/v7/mediarouter/tests/AndroidManifest.xml
@@ -19,6 +19,11 @@
     <uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
 
     <application android:supportsRtl="true">
+        <activity
+            android:name="android.support.v7.app.MediaRouteChooserDialogTestActivity"
+            android:label="MediaRouteChooserDialogTestActivity"
+            android:theme="@style/Theme.AppCompat" />
+
         <receiver android:name="android.support.v4.media.session.MediaButtonReceiver">
             <intent-filter>
                 <action android:name="android.intent.action.MEDIA_BUTTON"/>
diff --git a/v7/mediarouter/tests/res/layout/mr_chooser_dialog_activity.xml b/v7/mediarouter/tests/res/layout/mr_chooser_dialog_activity.xml
new file mode 100644
index 0000000..484bd23
--- /dev/null
+++ b/v7/mediarouter/tests/res/layout/mr_chooser_dialog_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.
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+</FrameLayout>
+
diff --git a/v7/mediarouter/tests/res/values/themes.xml b/v7/mediarouter/tests/res/values/themes.xml
new file mode 100644
index 0000000..31ae4ce
--- /dev/null
+++ b/v7/mediarouter/tests/res/values/themes.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.
+-->
+
+<resources>
+
+    <style name="HasWindowTitle">
+        <item name="windowNoTitle">false</item>
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTest.java b/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTest.java
index 7a21cdb..415ec1f 100644
--- a/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTest.java
+++ b/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTest.java
@@ -16,28 +16,76 @@
 
 package android.support.v7.app;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v7.media.MediaRouter.RouteInfo;
 import android.support.v7.media.TestUtils;
+import android.support.v7.mediarouter.test.R;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
-@SmallTest
 public class MediaRouteChooserDialogTest {
+
+    @Rule
+    public final ActivityTestRule<MediaRouteChooserDialogTestActivity> mActivityTestRule;
     private MediaRouteChooserDialog.RouteComparator mComparator;
 
+    public MediaRouteChooserDialogTest() {
+        mActivityTestRule = new ActivityTestRule<>(MediaRouteChooserDialogTestActivity.class);
+    }
+
     @Before
     public void setup() {
         mComparator = new MediaRouteChooserDialog.RouteComparator();
     }
 
     @Test
+    @SmallTest
+    @UiThreadTest
+    public void testWindowNoTitle() {
+        final Context context = mActivityTestRule.getActivity();
+        TypedArray typedArray;
+
+        // Without any base theme or customized theme
+        MediaRouteChooserDialog dialog = new MediaRouteChooserDialog(context);
+        typedArray = dialog.getContext().obtainStyledAttributes(R.styleable.AppCompatTheme);
+        assertTrue(typedArray.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false));
+        typedArray.recycle();
+
+        // No base theme, with a customized theme (has window title)
+        dialog = new MediaRouteChooserDialog(context, R.style.HasWindowTitle);
+        typedArray = dialog.getContext().obtainStyledAttributes(R.styleable.AppCompatTheme);
+        assertFalse(typedArray.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false));
+        typedArray.recycle();
+
+        // With base theme (has window title), no customized theme
+        context.setTheme(R.style.HasWindowTitle);
+        dialog = new MediaRouteChooserDialog(context);
+        typedArray = dialog.getContext().obtainStyledAttributes(R.styleable.AppCompatTheme);
+        assertTrue(typedArray.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false));
+        typedArray.recycle();
+
+        // With base theme and a customized theme (both has window title)
+        dialog = new MediaRouteChooserDialog(context, R.style.HasWindowTitle);
+        typedArray = dialog.getContext().obtainStyledAttributes(R.styleable.AppCompatTheme);
+        assertFalse(typedArray.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false));
+        typedArray.recycle();
+
+        context.setTheme(0);
+    }
+
+    @Test
     public void testRouteComparatorWithSameRouteName() {
         RouteInfo routeInfo1 = TestUtils.createRouteInfo("ROUTE_ID_1", "ROUTE_NAME_1");
         RouteInfo routeInfo2 = TestUtils.createRouteInfo("ROUTE_ID_2", "ROUTE_NAME_1");
diff --git a/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTestActivity.java b/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTestActivity.java
new file mode 100644
index 0000000..39e4d7c
--- /dev/null
+++ b/v7/mediarouter/tests/src/android/support/v7/app/MediaRouteChooserDialogTestActivity.java
@@ -0,0 +1,28 @@
+/*
+ * 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.os.Bundle;
+import android.support.v7.mediarouter.test.R;
+
+public class MediaRouteChooserDialogTestActivity extends AppCompatActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.mr_chooser_dialog_activity);
+    }
+}
diff --git a/v7/mediarouter/tests/src/android/support/v7/media/MediaRouteProviderTest.java b/v7/mediarouter/tests/src/android/support/v7/media/MediaRouteProviderTest.java
new file mode 100644
index 0000000..ff4409a
--- /dev/null
+++ b/v7/mediarouter/tests/src/android/support/v7/media/MediaRouteProviderTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.media;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Bundle;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test {@link MediaRouteProvider} and its related classes.
+ */
+@RunWith(AndroidJUnit4.class)
+public class MediaRouteProviderTest {
+    private static final String FAKE_MEDIA_ROUTE_ID_1 = "fakeMediaRouteId1";
+    private static final String FAKE_MEDIA_ROUTE_ID_2 = "fakeMediaRouteId2";
+    private static final String FAKE_MEDIA_ROUTE_ID_3 = "fakeMediaRouteId3";
+    private static final String FAKE_MEDIA_ROUTE_ID_4 = "fakeMediaRouteId4";
+    private static final String FAKE_MEDIA_ROUTE_NAME_1 = "fakeMediaRouteName1";
+    private static final String FAKE_MEDIA_ROUTE_NAME_2 = "fakeMediaRouteName2";
+    private static final String FAKE_MEDIA_ROUTE_NAME_3 = "fakeMediaRouteName3";
+    private static final String FAKE_MEDIA_ROUTE_NAME_4 = "fakeMediaRouteName4";
+
+    @Test
+    @SmallTest
+    public void testDescriptorBuilder() {
+        // Tests for empty descriptor
+        MediaRouteProviderDescriptor.Builder builder = new MediaRouteProviderDescriptor.Builder();
+        MediaRouteProviderDescriptor descriptor = builder.build();
+        assertTrue(descriptor.getRoutes().isEmpty());
+
+        // Tests for addRoute()
+        builder.addRoute(new MediaRouteDescriptor.Builder(FAKE_MEDIA_ROUTE_ID_1,
+                FAKE_MEDIA_ROUTE_NAME_1).build());
+        builder.addRoute(new MediaRouteDescriptor.Builder(FAKE_MEDIA_ROUTE_ID_2,
+                FAKE_MEDIA_ROUTE_NAME_2).build());
+        descriptor = builder.build();
+        List<MediaRouteDescriptor> routes = descriptor.getRoutes();
+        assertEquals(2, routes.size());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_1, routes.get(0).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_1, routes.get(0).getName());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_2, routes.get(1).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_2, routes.get(1).getName());
+
+        // Tests for addRoutes()
+        List<MediaRouteDescriptor> otherRoutes = new ArrayList<>();
+        otherRoutes.add(new MediaRouteDescriptor.Builder(FAKE_MEDIA_ROUTE_ID_3,
+                FAKE_MEDIA_ROUTE_NAME_3).build());
+        otherRoutes.add(new MediaRouteDescriptor.Builder(FAKE_MEDIA_ROUTE_ID_4,
+                FAKE_MEDIA_ROUTE_NAME_4).build());
+        builder.addRoutes(otherRoutes);
+        descriptor = builder.build();
+        routes = descriptor.getRoutes();
+        assertEquals(4, routes.size());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_1, routes.get(0).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_1, routes.get(0).getName());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_2, routes.get(1).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_2, routes.get(1).getName());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_3, routes.get(2).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_3, routes.get(2).getName());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_4, routes.get(3).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_4, routes.get(3).getName());
+
+        // Tests for setRoutes()
+        builder.setRoutes(otherRoutes);
+        descriptor = builder.build();
+        routes = descriptor.getRoutes();
+        assertEquals(2, routes.size());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_3, routes.get(0).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_3, routes.get(0).getName());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_4, routes.get(1).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_4, routes.get(1).getName());
+
+        // Tests setRoutes() for side effects
+        otherRoutes.add(new MediaRouteDescriptor.Builder(FAKE_MEDIA_ROUTE_ID_1,
+                FAKE_MEDIA_ROUTE_NAME_1).build());
+        descriptor = builder.build();
+        routes = descriptor.getRoutes();
+        assertEquals(2, routes.size());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_3, routes.get(0).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_3, routes.get(0).getName());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_4, routes.get(1).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_4, routes.get(1).getName());
+
+        // Tests setRoutes against null
+        builder.setRoutes(null);
+        descriptor = builder.build();
+        assertTrue(descriptor.getRoutes().isEmpty());
+    }
+
+    @Test
+    @SmallTest
+    public void testCreateDescriptorBundleForClient() {
+        MediaRouteProviderDescriptor.Builder builder = new MediaRouteProviderDescriptor.Builder();
+        builder.addRoute(new MediaRouteDescriptor.Builder(FAKE_MEDIA_ROUTE_ID_1,
+                FAKE_MEDIA_ROUTE_NAME_1).setMaxClientVersion(15).setMinClientVersion(10).build());
+        builder.addRoute(new MediaRouteDescriptor.Builder(FAKE_MEDIA_ROUTE_ID_2,
+                FAKE_MEDIA_ROUTE_NAME_2).setMaxClientVersion(18).setMinClientVersion(11).build());
+        builder.addRoute(new MediaRouteDescriptor.Builder(FAKE_MEDIA_ROUTE_ID_3,
+                FAKE_MEDIA_ROUTE_NAME_3).setMaxClientVersion(25).setMinClientVersion(16).build());
+        builder.addRoute(new MediaRouteDescriptor.Builder(FAKE_MEDIA_ROUTE_ID_4,
+                FAKE_MEDIA_ROUTE_NAME_4).setMaxClientVersion(12).setMinClientVersion(4).build());
+        MediaRouteProviderDescriptor descriptor = builder.build();
+
+        Bundle bundle = MediaRouteProviderService
+                .createDescriptorBundleForClientVersion(descriptor, 3);
+        MediaRouteProviderDescriptor resultDescriptor =
+                MediaRouteProviderDescriptor.fromBundle(bundle);
+        assertTrue(resultDescriptor.getRoutes().isEmpty());
+
+        bundle = MediaRouteProviderService.createDescriptorBundleForClientVersion(descriptor, 4);
+        resultDescriptor = MediaRouteProviderDescriptor.fromBundle(bundle);
+        List<MediaRouteDescriptor> routes = resultDescriptor.getRoutes();
+        assertEquals(1, routes.size());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_4, routes.get(0).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_4, routes.get(0).getName());
+
+        bundle = MediaRouteProviderService.createDescriptorBundleForClientVersion(descriptor, 10);
+        resultDescriptor = MediaRouteProviderDescriptor.fromBundle(bundle);
+        routes = resultDescriptor.getRoutes();
+        assertEquals(2, routes.size());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_1, routes.get(0).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_1, routes.get(0).getName());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_4, routes.get(1).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_4, routes.get(1).getName());
+
+        bundle = MediaRouteProviderService.createDescriptorBundleForClientVersion(descriptor, 12);
+        resultDescriptor = MediaRouteProviderDescriptor.fromBundle(bundle);
+        routes = resultDescriptor.getRoutes();
+        assertEquals(3, routes.size());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_1, routes.get(0).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_1, routes.get(0).getName());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_2, routes.get(1).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_2, routes.get(1).getName());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_4, routes.get(2).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_4, routes.get(2).getName());
+
+        bundle = MediaRouteProviderService.createDescriptorBundleForClientVersion(descriptor, 15);
+        resultDescriptor = MediaRouteProviderDescriptor.fromBundle(bundle);
+        routes = resultDescriptor.getRoutes();
+        assertEquals(2, routes.size());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_1, routes.get(0).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_1, routes.get(0).getName());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_2, routes.get(1).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_2, routes.get(1).getName());
+
+        bundle = MediaRouteProviderService.createDescriptorBundleForClientVersion(descriptor, 16);
+        resultDescriptor = MediaRouteProviderDescriptor.fromBundle(bundle);
+        routes = resultDescriptor.getRoutes();
+        assertEquals(2, routes.size());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_2, routes.get(0).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_2, routes.get(0).getName());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_3, routes.get(1).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_3, routes.get(1).getName());
+
+        bundle = MediaRouteProviderService.createDescriptorBundleForClientVersion(descriptor, 19);
+        resultDescriptor = MediaRouteProviderDescriptor.fromBundle(bundle);
+        routes = resultDescriptor.getRoutes();
+        assertEquals(1, routes.size());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_3, routes.get(0).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_3, routes.get(0).getName());
+
+        bundle = MediaRouteProviderService.createDescriptorBundleForClientVersion(descriptor, 26);
+        resultDescriptor = MediaRouteProviderDescriptor.fromBundle(bundle);
+        assertTrue(resultDescriptor.getRoutes().isEmpty());
+    }
+}
diff --git a/v7/palette/lint-baseline.xml b/v7/palette/lint-baseline.xml
index d75d39e..172bbf6 100644
--- a/v7/palette/lint-baseline.xml
+++ b/v7/palette/lint-baseline.xml
@@ -1,37 +1,4 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <issues format="4" by="lint 2.4.0-alpha6">
 
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
 </issues>
diff --git a/v7/preference/lint-baseline.xml b/v7/preference/lint-baseline.xml
index c9f4dab..eee7674 100644
--- a/v7/preference/lint-baseline.xml
+++ b/v7/preference/lint-baseline.xml
@@ -94,17 +94,6 @@
     </issue>
 
     <issue
-        id="ResourceType"
-        message="Expected resource of type xml"
-        errorLine1="            final XmlPullParser parser = res.getXml(resId);"
-        errorLine2="                                                    ~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="557"
-            column="53"/>
-    </issue>
-
-    <issue
         id="Range"
         message="Value must be ≥ 0 (was -2147483648)"
         errorLine1="                                MeasureSpec.makeMeasureSpec(largestChildHeight,"
@@ -127,72 +116,6 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
         id="Suspicious0dp"
         message="Suspicious size: this will make the view invisible, should be used with `layout_weight`"
         errorLine1="        android:layout_width=&quot;0dp&quot;"
@@ -314,171 +237,6 @@
     </issue>
 
     <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Adding item #&quot; + position + &quot;: f=&quot; + fragment);"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="110"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="        if (DEBUG) Log.v(TAG, &quot;Removing item #&quot; + position + &quot;: f=&quot; + object"
-        errorLine2="                         ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="135"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 25 (FragmentStatePagerAdapter)"
-        errorLine1="                        Log.w(TAG, &quot;Bad fragment at key &quot; + key);"
-        errorLine2="                              ~~~">
-        <location
-            file="java/android/support/v4/app/FragmentStatePagerAdapter.java"
-            line="224"
-            column="31"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + filepath);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="80"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 28 (RoundedBitmapDrawableFactory)"
-        errorLine1="            Log.w(TAG, &quot;RoundedBitmapDrawable cannot decode &quot; + is);"
-        errorLine2="                  ~~~">
-        <location
-            file="java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java"
-            line="93"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;found saved state: &quot; + mPendingSavedState);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="783"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;Deciding anchor info from fresh state&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="828"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;invalid saved state class&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1187"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;saved state:\n&quot; + state);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1236"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;FILLING targetLine: &quot; + targetLine + &quot;,&quot;"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1559"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;assigned &quot; + currentSpan.mIndex + &quot; for &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1580"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;using &quot; + spanIndex + &quot; for pos &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1584"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;asked &quot; + dt + &quot; scrolled&quot; + totalScroll);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2153"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;Unknown focus request:&quot; + focusDirection);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2385"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 24 (WakefulBroadcastReceiver)"
-        errorLine1="            Log.w(&quot;WakefulBroadcastReceiver&quot;, &quot;No active wake lock id #&quot; + id);"
-        errorLine2="                  ~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/content/WakefulBroadcastReceiver.java"
-            line="141"
-            column="19"/>
-    </issue>
-
-    <issue
         id="AppCompatCustomView"
         message="This custom view should extend `android.support.v7.widget.AppCompatAutoCompleteTextView` instead"
         errorLine1="public class AppCompatAutoCompleteTextView extends AutoCompleteTextView implements"
@@ -655,21 +413,6 @@
     </issue>
 
     <issue
-        id="UniqueConstants"
-        message="Constants `FLAG_CVE_EQ_PVE` and `FLAG_CVE_EQ_PVE` specify the same exact value (8192); this is usually a cut &amp; paste or merge error"
-        errorLine1="            FLAG_CVE_EQ_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE"
-        errorLine2="                             ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="30"/>
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="13"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
         message="Must be one of: PixelFormat.UNKNOWN, PixelFormat.TRANSLUCENT, PixelFormat.TRANSPARENT, PixelFormat.OPAQUE"
         errorLine1="        return 0;"
@@ -682,35 +425,13 @@
 
     <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
     <issue
@@ -735,15 +456,4 @@
             column="36"/>
     </issue>
 
-    <issue
-        id="WrongConstant"
-        message="Must be one of: View.LAYOUT_DIRECTION_LTR, View.LAYOUT_DIRECTION_RTL"
-        errorLine1="            return isAutoMirrored() &amp;&amp; getLayoutDirection() == LayoutDirection.RTL;"
-        errorLine2="                                                               ~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/graphics/drawable/VectorDrawableCompat.java"
-            line="821"
-            column="64"/>
-    </issue>
-
 </issues>
diff --git a/v7/recyclerview/lint-baseline.xml b/v7/recyclerview/lint-baseline.xml
index 443b10a..493c8c5 100644
--- a/v7/recyclerview/lint-baseline.xml
+++ b/v7/recyclerview/lint-baseline.xml
@@ -2,216 +2,14 @@
 <issues format="4" by="lint 2.4.0-alpha6">
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;found saved state: &quot; + mPendingSavedState);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="783"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;Deciding anchor info from fresh state&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="828"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;invalid saved state class&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1187"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;saved state:\n&quot; + state);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1236"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;FILLING targetLine: &quot; + targetLine + &quot;,&quot;"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1559"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;assigned &quot; + currentSpan.mIndex + &quot; for &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1580"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;using &quot; + spanIndex + &quot; for pos &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1584"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;asked &quot; + dt + &quot; scrolled&quot; + totalScroll);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2153"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;Unknown focus request:&quot; + focusDirection);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2385"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="UniqueConstants"
-        message="Constants `FLAG_CVE_EQ_PVE` and `FLAG_CVE_EQ_PVE` specify the same exact value (8192); this is usually a cut &amp; paste or merge error"
-        errorLine1="            FLAG_CVE_EQ_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE"
-        errorLine2="                             ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="30"/>
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="13"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index e452009..8033400 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -636,20 +636,15 @@
         setNestedScrollingEnabled(nestedScrollingEnabled);
     }
 
-    @Override
-    public String toString() {
-        return super.toString()
-                + ", adapter:" + mAdapter
-                + ", layout:" + mLayout
-                + ", context:" + getContext();
-    }
-
     /**
      * Label appended to all public exception strings, used to help find which RV in an app is
      * hitting an exception.
      */
     String exceptionLabel() {
-        return " " + this;
+        return " " + super.toString()
+                + ", adapter:" + mAdapter
+                + ", layout:" + mLayout
+                + ", context:" + getContext();
     }
 
     /**
diff --git a/v7/recyclerview/src/android/support/v7/widget/SnapHelper.java b/v7/recyclerview/src/android/support/v7/widget/SnapHelper.java
index d63045b..d85a27a 100644
--- a/v7/recyclerview/src/android/support/v7/widget/SnapHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/SnapHelper.java
@@ -19,6 +19,7 @@
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v7.widget.RecyclerView.LayoutManager;
+import android.support.v7.widget.RecyclerView.SmoothScroller;
 import android.support.v7.widget.RecyclerView.SmoothScroller.ScrollVectorProvider;
 import android.util.DisplayMetrics;
 import android.view.View;
@@ -159,7 +160,7 @@
             return false;
         }
 
-        RecyclerView.SmoothScroller smoothScroller = createSnapScroller(layoutManager);
+        SmoothScroller smoothScroller = createScroller(layoutManager);
         if (smoothScroller == null) {
             return false;
         }
@@ -203,9 +204,24 @@
      * @param layoutManager     The {@link RecyclerView.LayoutManager} associated with the attached
      *                          {@link RecyclerView}.
      *
-     * @return a {@link LinearSmoothScroller} which will handle the scrolling.
+     * @return a {@link SmoothScroller} which will handle the scrolling.
      */
     @Nullable
+    protected SmoothScroller createScroller(LayoutManager layoutManager) {
+        return createSnapScroller(layoutManager);
+    }
+
+    /**
+     * Creates a scroller to be used in the snapping implementation.
+     *
+     * @param layoutManager     The {@link RecyclerView.LayoutManager} associated with the attached
+     *                          {@link RecyclerView}.
+     *
+     * @return a {@link LinearSmoothScroller} which will handle the scrolling.
+     * @deprecated use {@link #createScroller(LayoutManager)} instead.
+     */
+    @Nullable
+    @Deprecated
     protected LinearSmoothScroller createSnapScroller(LayoutManager layoutManager) {
         if (!(layoutManager instanceof ScrollVectorProvider)) {
             return null;
@@ -281,4 +297,4 @@
      */
     public abstract int findTargetSnapPosition(LayoutManager layoutManager, int velocityX,
             int velocityY);
-}
\ No newline at end of file
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
index 679f483..f3ea045 100644
--- a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -55,7 +55,7 @@
 public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager implements
         RecyclerView.SmoothScroller.ScrollVectorProvider {
 
-    private static final String TAG = "StaggeredGridLayoutManager";
+    private static final String TAG = "StaggeredGridLManager";
 
     static final boolean DEBUG = false;
 
diff --git a/v7/recyclerview/src/android/support/v7/widget/ViewBoundsCheck.java b/v7/recyclerview/src/android/support/v7/widget/ViewBoundsCheck.java
index 8a4f89c..191a069 100644
--- a/v7/recyclerview/src/android/support/v7/widget/ViewBoundsCheck.java
+++ b/v7/recyclerview/src/android/support/v7/widget/ViewBoundsCheck.java
@@ -122,7 +122,7 @@
             FLAG_CVS_GT_PVS, FLAG_CVS_EQ_PVS, FLAG_CVS_LT_PVS,
             FLAG_CVS_GT_PVE, FLAG_CVS_EQ_PVE, FLAG_CVS_LT_PVE,
             FLAG_CVE_GT_PVS, FLAG_CVE_EQ_PVS, FLAG_CVE_LT_PVS,
-            FLAG_CVE_EQ_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE
+            FLAG_CVE_GT_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ViewBounds {}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
index a7530d1..5a13f52 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
@@ -460,32 +460,23 @@
     }
 
     @Test
-    public void toStringContainsClasses() {
-        RecyclerView recyclerView = new RecyclerView(getContext());
-        recyclerView.setAdapter(new MockAdapter(10));
-        recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
-
-        String string = recyclerView.toString();
-        assertTrue("must contain RV class", string.contains(RecyclerView.class.getName()));
-        assertTrue("must contain Adapter class", string.contains(MockAdapter.class.getName()));
-        assertTrue("must contain LM class", string.contains(LinearLayoutManager.class.getName()));
-        assertTrue("must contain ctx class", string.contains(getContext().getClass().getName()));
-    }
-
-    @Test
     public void exceptionContainsClasses() {
-        RecyclerView recyclerView = new RecyclerView(getContext());
-        recyclerView.setAdapter(new MockAdapter(10));
+        RecyclerView first = new RecyclerView(getContext());
+        first.setLayoutManager(new LinearLayoutManager(getContext()));
+        first.setAdapter(new MockAdapter(10));
 
+        RecyclerView second = new RecyclerView(getContext());
         try {
-            recyclerView.generateDefaultLayoutParams();
+            second.setLayoutManager(first.getLayoutManager());
             fail("exception expected");
-        } catch (IllegalStateException e) {
-            String message = e.getMessage();
-            assertTrue("must contain RV class",
-                    message.contains(RecyclerView.class.getName()));
-            assertTrue("must contain Adapter class",
-                    message.contains(MockAdapter.class.getName()));
+        } catch (IllegalArgumentException e) {
+            // Note: exception contains first RV
+            String m = e.getMessage();
+            assertTrue("must contain RV class", m.contains(RecyclerView.class.getName()));
+            assertTrue("must contain Adapter class", m.contains(MockAdapter.class.getName()));
+            assertTrue("must contain LM class", m.contains(LinearLayoutManager.class.getName()));
+            assertTrue("must contain ctx class", m.contains(getContext().getClass().getName()));
+
         }
     }
 
diff --git a/wear/build.gradle b/wear/build.gradle
index 6ceff5b..d81f84e 100644
--- a/wear/build.gradle
+++ b/wear/build.gradle
@@ -18,7 +18,7 @@
 
 android {
     defaultConfig {
-        minSdkVersion 24
+        minSdkVersion 23
     }
 
     sourceSets {
diff --git a/wear/lint-baseline.xml b/wear/lint-baseline.xml
index ba39be4..962399c 100644
--- a/wear/lint-baseline.xml
+++ b/wear/lint-baseline.xml
@@ -28,216 +28,14 @@
     </issue>
 
     <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="536"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);"
-        errorLine2="                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="538"
-            column="63"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="586"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                              ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="589"
-            column="79"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="608"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="Range"
-        message="Value must be ≥ 0 (was -1)"
-        errorLine1="                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,"
-        errorLine2="                                                                          ~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/SlidingPaneLayout.java"
-            line="611"
-            column="75"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;found saved state: &quot; + mPendingSavedState);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="783"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;Deciding anchor info from fresh state&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="828"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;invalid saved state class&quot;);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1187"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;saved state:\n&quot; + state);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1236"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;FILLING targetLine: &quot; + targetLine + &quot;,&quot;"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1559"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;assigned &quot; + currentSpan.mIndex + &quot; for &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1580"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;using &quot; + spanIndex + &quot; for pos &quot; + position);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="1584"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="            Log.d(TAG, &quot;asked &quot; + dt + &quot; scrolled&quot; + totalScroll);"
-        errorLine2="                  ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2153"
-            column="19"/>
-    </issue>
-
-    <issue
-        id="LongLogTag"
-        message="The logging tag can be at most 23 characters, was 26 (StaggeredGridLayoutManager)"
-        errorLine1="                    Log.d(TAG, &quot;Unknown focus request:&quot; + focusDirection);"
-        errorLine2="                          ~~~">
-        <location
-            file="src/android/support/v7/widget/StaggeredGridLayoutManager.java"
-            line="2385"
-            column="27"/>
-    </issue>
-
-    <issue
-        id="UniqueConstants"
-        message="Constants `FLAG_CVE_EQ_PVE` and `FLAG_CVE_EQ_PVE` specify the same exact value (8192); this is usually a cut &amp; paste or merge error"
-        errorLine1="            FLAG_CVE_EQ_PVE, FLAG_CVE_EQ_PVE, FLAG_CVE_LT_PVE"
-        errorLine2="                             ~~~~~~~~~~~~~~~">
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="30"/>
-        <location
-            file="src/android/support/v7/widget/ViewBoundsCheck.java"
-            line="125"
-            column="13"/>
-    </issue>
-
-    <issue
         id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        setDrawerLockMode(lockMode, gravity);"
-        errorLine2="                                    ~~~~~~~">
+        message="Must be one or more of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
+        errorLine1="                            + gravityToString(childGravity) + &quot; but this &quot; + TAG + &quot; already has a &quot;"
+        errorLine2="                                              ~~~~~~~~~~~~">
         <location
             file="java/android/support/v4/widget/DrawerLayout.java"
-            line="680"
-            column="37"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Must be one of: Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END"
-        errorLine1="        return getDrawerLockMode(drawerGravity);"
-        errorLine2="                                 ~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="753"
-            column="34"/>
-    </issue>
-
-    <issue
-        id="WrongConstant"
-        message="Flag not allowed here"
-        errorLine1="                        getDrawerViewAbsoluteGravity(child) &amp; Gravity.HORIZONTAL_GRAVITY_MASK;"
-        errorLine2="                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="java/android/support/v4/widget/DrawerLayout.java"
-            line="1086"
-            column="25"/>
+            line="1075"
+            column="47"/>
     </issue>
 
 </issues>
diff --git a/wear/res-public/values/public_styles.xml b/wear/res-public/values-v24/public_styles.xml
similarity index 100%
rename from wear/res-public/values/public_styles.xml
rename to wear/res-public/values-v24/public_styles.xml
diff --git a/wear/res-public/values/public_attrs.xml b/wear/res-public/values/public_attrs.xml
index 285c3dc..b4b35db 100644
--- a/wear/res-public/values/public_attrs.xml
+++ b/wear/res-public/values/public_attrs.xml
@@ -36,4 +36,10 @@
 
     <!-- WearableNavigationDrawerView -->
     <public type="attr" name="navigationStyle" />
+
+    <!-- CircularProgressLayout -->
+    <public type="attr" name="backgroundColor" />
+    <public type="attr" name="colorSchemeColors" />
+    <public type="attr" name="strokeWidth" />
+    <public type="attr" name="indeterminate" />
 </resources>
diff --git a/wear/res/values-v24/styles.xml b/wear/res/values-v24/styles.xml
new file mode 100644
index 0000000..a7f50fa
--- /dev/null
+++ b/wear/res/values-v24/styles.xml
@@ -0,0 +1,31 @@
+<?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>
+    <style name="Widget.Wear.RoundSwitch"
+            parent="@android:style/Widget.Material.CompoundButton.Switch">
+        <item name="android:layout_width">@dimen/ws_switch_size</item>
+        <item name="android:layout_height">@dimen/ws_switch_size</item>
+        <item name="android:switchMinWidth">@dimen/ws_switch_size</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:thumb">@drawable/ws_switch_thumb_material_anim</item>
+        <item name="android:thumbTint">@color/ws_switch_thumb_color_material</item>
+        <item name="android:thumbTintMode">multiply</item>
+        <item name="android:track">@drawable/ws_switch_track_mtrl</item>
+        <item name="android:trackTint">@color/ws_switch_track_color_material</item>
+        <item name="android:background">@empty</item>
+        <item name="android:showText">false</item>
+    </style>
+</resources>
diff --git a/wear/res/values/arrays.xml b/wear/res/values/arrays.xml
new file mode 100644
index 0000000..3d7740d
--- /dev/null
+++ b/wear/res/values/arrays.xml
@@ -0,0 +1,26 @@
+<?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>
+    <!-- Default color scheme colors for CircularProgressLayout -->
+    <array name="circular_progress_layout_color_scheme_colors">
+        <item>@color/circular_progress_layout_red</item>
+        <item>@color/circular_progress_layout_yellow</item>
+        <item>@color/circular_progress_layout_green</item>
+        <item>@color/circular_progress_layout_blue</item>
+    </array>
+</resources>
\ No newline at end of file
diff --git a/wear/res/values/attrs.xml b/wear/res/values/attrs.xml
index d98e1b6..c8b47c7 100644
--- a/wear/res/values/attrs.xml
+++ b/wear/res/values/attrs.xml
@@ -171,4 +171,16 @@
         </attr>
     </declare-styleable>
 
+    <declare-styleable name="CircularProgressLayout">
+        <!-- Sets the color of the background circle. -->
+        <attr name="backgroundColor" format="color" />
+        <!-- Sets the stroke width of the progress indicator. -->
+        <attr name="strokeWidth" format="dimension" />
+        <!-- Sets the color scheme used by the progress indicator. This may be an array of colors or
+        a single color. If an array of colors is used, first color will be used for determinate
+        progress indicator, while the rest will be shown in order during indeterminate spinner. -->
+        <attr name="colorSchemeColors" format="reference|color" />
+        <!-- Sets if the progress should be shown as an indeterminate spinner. -->
+        <attr name="indeterminate" format="boolean" />
+    </declare-styleable>
 </resources>
diff --git a/wear/res/values/colors.xml b/wear/res/values/colors.xml
new file mode 100644
index 0000000..a44c7a0
--- /dev/null
+++ b/wear/res/values/colors.xml
@@ -0,0 +1,27 @@
+<?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>
+    <!-- From material color palette -->
+    <color name="circular_progress_layout_red">#FFF44336</color>
+    <color name="circular_progress_layout_yellow">#FFFFEB3B</color>
+    <color name="circular_progress_layout_green">#FF4CAF50</color>
+    <color name="circular_progress_layout_blue">#FF2196F3</color>
+
+    <!-- Default background color for CircularProgressLayout -->
+    <color name="circular_progress_layout_background_color">#00000000</color>
+</resources>
\ No newline at end of file
diff --git a/wear/res/values/dimens.xml b/wear/res/values/dimens.xml
index 4e5496d..26d6c0a 100644
--- a/wear/res/values/dimens.xml
+++ b/wear/res/values/dimens.xml
@@ -63,4 +63,7 @@
     <dimen name="ws_drawer_view_edge_size">38dp</dimen>
     <!-- Dimensions for the wearable switch. -->
     <dimen name="ws_switch_size">40dp</dimen>
+
+    <!-- Default stroke width for CircularProgressLayout -->
+    <dimen name="circular_progress_layout_stroke_width">4dp</dimen>
 </resources>
diff --git a/wear/res/values/styles.xml b/wear/res/values/styles.xml
index c0036f1..44ab0b0 100644
--- a/wear/res/values/styles.xml
+++ b/wear/res/values/styles.xml
@@ -37,18 +37,4 @@
         <item name="android:elevation">@dimen/ws_wearable_drawer_view_elevation</item>
         <item name="android:background">?android:attr/colorBackgroundFloating</item>
     </style>
-    <style name="Widget.Wear.RoundSwitch"
-            parent="@android:style/Widget.Material.CompoundButton.Switch">
-        <item name="android:layout_width">@dimen/ws_switch_size</item>
-        <item name="android:layout_height">@dimen/ws_switch_size</item>
-        <item name="android:switchMinWidth">@dimen/ws_switch_size</item>
-        <item name="android:layout_gravity">center</item>
-        <item name="android:thumb">@drawable/ws_switch_thumb_material_anim</item>
-        <item name="android:thumbTint">@color/ws_switch_thumb_color_material</item>
-        <item name="android:thumbTintMode">multiply</item>
-        <item name="android:track">@drawable/ws_switch_track_mtrl</item>
-        <item name="android:trackTint">@color/ws_switch_track_color_material</item>
-        <item name="android:background">@empty</item>
-        <item name="android:showText">false</item>
-    </style>
 </resources>
diff --git a/wear/src/android/support/wear/widget/CircularProgressLayout.java b/wear/src/android/support/wear/widget/CircularProgressLayout.java
new file mode 100644
index 0000000..1bfcc39
--- /dev/null
+++ b/wear/src/android/support/wear/widget/CircularProgressLayout.java
@@ -0,0 +1,365 @@
+/*
+ * 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.wear.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.widget.CircularProgressDrawable;
+import android.support.wear.R;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.FrameLayout;
+
+/**
+ * {@link CircularProgressLayout} adds a circular countdown timer behind the view it contains,
+ * typically used to automatically confirm an operation after a short delay has elapsed.
+ *
+ * <p>The developer can specify a countdown interval via {@link #setTotalTime(long)} and a listener
+ * via {@link #setOnTimerFinishedListener(OnTimerFinishedListener)} to be called when the time has
+ * elapsed after {@link #startTimer()} has been called. Tap action can be received via {@link
+ * #setOnClickListener(OnClickListener)} and can be used to cancel the timer via {@link
+ * #stopTimer()} method.
+ *
+ * <p>Alternatively, this layout can be used to show indeterminate progress by calling {@link
+ * #setIndeterminate(boolean)} method.
+ */
+public class CircularProgressLayout extends FrameLayout {
+
+    /**
+     * Update interval for 30 fps.
+     */
+    private static final long DEFAULT_UPDATE_INTERVAL = 1000 / 30;
+
+    /**
+     * Starting rotation for the progress indicator. Geometric clockwise [0..360] degree range
+     * correspond to [0..1] range. 0.75 corresponds to 12 o'clock direction on a watch.
+     */
+    private static final float DEFAULT_ROTATION = 0.75f;
+
+    /**
+     * Used as background of this layout.
+     */
+    private CircularProgressDrawable mProgressDrawable;
+
+    /**
+     * Used to control this layout.
+     */
+    private CircularProgressLayoutController mController;
+
+    /**
+     * Angle for the progress to start from.
+     */
+    private float mStartingRotation = DEFAULT_ROTATION;
+
+    /**
+     * Duration of the timer in milliseconds.
+     */
+    private long mTotalTime;
+
+
+    /**
+     * Interface to implement for listening to {@link
+     * OnTimerFinishedListener#onTimerFinished(CircularProgressLayout)} event.
+     */
+    public interface OnTimerFinishedListener {
+
+        /**
+         * Called when the timer started by {@link #startTimer()} method finishes.
+         *
+         * @param layout {@link CircularProgressLayout} that calls this method.
+         */
+        void onTimerFinished(CircularProgressLayout layout);
+    }
+
+    public CircularProgressLayout(Context context) {
+        this(context, null);
+    }
+
+    public CircularProgressLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CircularProgressLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public CircularProgressLayout(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+
+        mProgressDrawable = new CircularProgressDrawable(context);
+        mProgressDrawable.setProgressRotation(DEFAULT_ROTATION);
+        setBackground(mProgressDrawable);
+
+        // If a child view is added, make it center aligned so it fits in the progress drawable.
+        setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
+            @Override
+            public void onChildViewAdded(View parent, View child) {
+                // Ensure that child view is aligned in center
+                LayoutParams params = (LayoutParams) child.getLayoutParams();
+                params.gravity = Gravity.CENTER;
+                child.setLayoutParams(params);
+            }
+
+            @Override
+            public void onChildViewRemoved(View parent, View child) {
+
+            }
+        });
+
+        mController = new CircularProgressLayoutController(this);
+
+        Resources r = context.getResources();
+        TypedArray a = r.obtainAttributes(attrs, R.styleable.CircularProgressLayout);
+
+        if (a.getType(R.styleable.CircularProgressLayout_colorSchemeColors) == TypedValue
+                .TYPE_REFERENCE || !a.hasValue(
+                R.styleable.CircularProgressLayout_colorSchemeColors)) {
+            int arrayResId = a.getResourceId(R.styleable.CircularProgressLayout_colorSchemeColors,
+                    R.array.circular_progress_layout_color_scheme_colors);
+            setColorSchemeColors(getColorListFromResources(r, arrayResId));
+        } else {
+            setColorSchemeColors(a.getColor(R.styleable.CircularProgressLayout_colorSchemeColors,
+                    Color.BLACK));
+        }
+
+        setStrokeWidth(a.getDimensionPixelSize(R.styleable.CircularProgressLayout_strokeWidth,
+                r.getDimensionPixelSize(
+                        R.dimen.circular_progress_layout_stroke_width)));
+
+        setBackgroundColor(a.getColor(R.styleable.CircularProgressLayout_backgroundColor,
+                r.getColor(R.color.circular_progress_layout_background_color, null)));
+
+        setIndeterminate(a.getBoolean(R.styleable.CircularProgressLayout_indeterminate, false));
+
+        a.recycle();
+    }
+
+    private int[] getColorListFromResources(Resources resources, int arrayResId) {
+        TypedArray colorArray = resources.obtainTypedArray(arrayResId);
+        int[] colors = new int[colorArray.length()];
+        for (int i = 0; i < colorArray.length(); i++) {
+            colors[i] = colorArray.getColor(i, 0);
+        }
+        colorArray.recycle();
+        return colors;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (getChildCount() != 0) {
+            View childView = getChildAt(0);
+            // Wrap the drawable around the child view
+            mProgressDrawable.setCenterRadius(
+                    Math.min(childView.getWidth(), childView.getHeight()) / 2f);
+        } else {
+            // Fill the bounds if no child view is present
+            mProgressDrawable.setCenterRadius(0f);
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mController.reset();
+    }
+
+    /**
+     * Sets the background color of the {@link CircularProgressDrawable}, which is drawn as a circle
+     * inside the progress drawable. Colors are in ARGB format defined in {@link Color}.
+     *
+     * @param color an ARGB color
+     */
+    @Override
+    public void setBackgroundColor(@ColorInt int color) {
+        mProgressDrawable.setBackgroundColor(color);
+    }
+
+    /**
+     * Returns the background color of the {@link CircularProgressDrawable}.
+     *
+     * @return an ARGB color
+     */
+    @ColorInt
+    public int getBackgroundColor() {
+        return mProgressDrawable.getBackgroundColor();
+    }
+
+    /**
+     * Returns the {@link CircularProgressDrawable} used as background of this layout.
+     *
+     * @return {@link CircularProgressDrawable}
+     */
+    @NonNull
+    public CircularProgressDrawable getProgressDrawable() {
+        return mProgressDrawable;
+    }
+
+    /**
+     * Sets if progress should be shown as an indeterminate spinner.
+     *
+     * @param indeterminate {@code true} if indeterminate spinner should be shown, {@code false}
+     *                      otherwise.
+     */
+    public void setIndeterminate(boolean indeterminate) {
+        mController.setIndeterminate(indeterminate);
+    }
+
+    /**
+     * Returns if progress is showing as an indeterminate spinner.
+     *
+     * @return {@code true} if indeterminate spinner is shown, {@code false} otherwise.
+     */
+    public boolean isIndeterminate() {
+        return mController.isIndeterminate();
+    }
+
+    /**
+     * Sets the total time in milliseconds for the timer to countdown to. Calling this method while
+     * the timer is already running will not change the duration of the current timer.
+     *
+     * @param totalTime total time in milliseconds
+     */
+    public void setTotalTime(long totalTime) {
+        if (totalTime <= 0) {
+            throw new IllegalArgumentException("Total time should be greater than zero.");
+        }
+        mTotalTime = totalTime;
+    }
+
+    /**
+     * Returns the total time in milliseconds for the timer to countdown to.
+     *
+     * @return total time in milliseconds
+     */
+    public long getTotalTime() {
+        return mTotalTime;
+    }
+
+    /**
+     * Starts the timer countdown. Once the countdown is finished, if there is an {@link
+     * OnTimerFinishedListener} registered by {@link
+     * #setOnTimerFinishedListener(OnTimerFinishedListener)} method, its
+     * {@link OnTimerFinishedListener#onTimerFinished(CircularProgressLayout)} method is called. If
+     * this method is called while there is already a running timer, it will restart the timer.
+     */
+    public void startTimer() {
+        mController.startTimer(mTotalTime, DEFAULT_UPDATE_INTERVAL);
+        mProgressDrawable.setProgressRotation(mStartingRotation);
+    }
+
+    /**
+     * Stops the timer countdown. If there is no timer running, calling this method will not do
+     * anything.
+     */
+    public void stopTimer() {
+        mController.stopTimer();
+    }
+
+    /**
+     * Returns if the timer is running.
+     *
+     * @return {@code true} if the timer is running, {@code false} otherwise
+     */
+    public boolean isTimerRunning() {
+        return mController.isTimerRunning();
+    }
+
+    /**
+     * Sets the starting rotation for the progress drawable to start from. Default starting rotation
+     * is {@code 0.75} and it corresponds clockwise geometric 270 degrees (12 o'clock on a watch)
+     *
+     * @param rotation starting rotation from [0..1]
+     */
+    public void setStartingRotation(float rotation) {
+        mStartingRotation = rotation;
+    }
+
+    /**
+     * Returns the starting rotation of the progress drawable.
+     *
+     * @return starting rotation from [0..1]
+     */
+    public float getStartingRotation() {
+        return mStartingRotation;
+    }
+
+    /**
+     * Sets the stroke width of the progress drawable in pixels.
+     *
+     * @param strokeWidth stroke width in pixels
+     */
+    public void setStrokeWidth(float strokeWidth) {
+        mProgressDrawable.setStrokeWidth(strokeWidth);
+    }
+
+    /**
+     * Returns the stroke width of the progress drawable in pixels.
+     *
+     * @return stroke width in pixels
+     */
+    public float getStrokeWidth() {
+        return mProgressDrawable.getStrokeWidth();
+    }
+
+    /**
+     * Sets the color scheme colors of the progress drawable, which is equivalent to calling {@link
+     * CircularProgressDrawable#setColorSchemeColors(int...)} method on background drawable of this
+     * layout.
+     *
+     * @param colors list of ARGB colors
+     */
+    public void setColorSchemeColors(int... colors) {
+        mProgressDrawable.setColorSchemeColors(colors);
+    }
+
+    /**
+     * Returns the color scheme colors of the progress drawable
+     *
+     * @return list of ARGB colors
+     */
+    public int[] getColorSchemeColors() {
+        return mProgressDrawable.getColorSchemeColors();
+    }
+
+    /**
+     * Returns the {@link OnTimerFinishedListener} that is registered to this layout.
+     *
+     * @return registered {@link OnTimerFinishedListener}
+     */
+    @Nullable
+    public OnTimerFinishedListener getOnTimerFinishedListener() {
+        return mController.getOnTimerFinishedListener();
+    }
+
+    /**
+     * Sets the {@link OnTimerFinishedListener} to be notified when timer countdown is finished.
+     *
+     * @param listener {@link OnTimerFinishedListener} to be notified, or {@code null} to clear
+     */
+    public void setOnTimerFinishedListener(@Nullable OnTimerFinishedListener listener) {
+        mController.setOnTimerFinishedListener(listener);
+    }
+}
diff --git a/wear/src/android/support/wear/widget/CircularProgressLayoutController.java b/wear/src/android/support/wear/widget/CircularProgressLayoutController.java
new file mode 100644
index 0000000..4fd7156
--- /dev/null
+++ b/wear/src/android/support/wear/widget/CircularProgressLayoutController.java
@@ -0,0 +1,138 @@
+/*
+ * 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.wear.widget;
+
+import android.os.CountDownTimer;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+
+/**
+ * Controller for {@link CircularProgressLayout}.
+ */
+class CircularProgressLayoutController {
+
+    private final CircularProgressLayout mLayout;
+    @VisibleForTesting CountDownTimer mTimer;
+    private boolean mIsIndeterminate;
+    private boolean mIsTimerRunning;
+
+    /**
+     * Called when the timer is finished.
+     */
+    @Nullable
+    private CircularProgressLayout.OnTimerFinishedListener mOnTimerFinishedListener;
+
+    CircularProgressLayoutController(CircularProgressLayout layout) {
+        mLayout = layout;
+    }
+
+    /**
+     * Returns the registered {@link CircularProgressLayout.OnTimerFinishedListener}.
+     */
+    @Nullable
+    public CircularProgressLayout.OnTimerFinishedListener getOnTimerFinishedListener() {
+        return mOnTimerFinishedListener;
+    }
+
+    /**
+     * Sets the {@link CircularProgressLayout.OnTimerFinishedListener} to be notified when timer
+     * countdown is finished.
+     */
+    public void setOnTimerFinishedListener(
+            @Nullable CircularProgressLayout.OnTimerFinishedListener listener) {
+        mOnTimerFinishedListener = listener;
+    }
+
+    /** Returns true if the progress is shown as an indeterminate spinner. */
+    boolean isIndeterminate() {
+        return mIsIndeterminate;
+    }
+
+    /** Returns true if timer is running. */
+    boolean isTimerRunning() {
+        return mIsTimerRunning;
+    }
+
+    /** Sets if the progress should be shown as an indeterminate spinner. */
+    void setIndeterminate(boolean indeterminate) {
+        if (mIsIndeterminate == indeterminate) {
+            return;
+        }
+        mIsIndeterminate = indeterminate;
+        if (mIsIndeterminate) {
+            if (mIsTimerRunning) {
+                stopTimer();
+            }
+            mLayout.getProgressDrawable().start();
+        } else {
+            mLayout.getProgressDrawable().stop();
+        }
+    }
+
+    void startTimer(long totalTime, long updateInterval) {
+        reset();
+        mIsTimerRunning = true;
+        mTimer = new CircularProgressTimer(totalTime, updateInterval);
+        mTimer.start();
+    }
+
+    void stopTimer() {
+        if (mIsTimerRunning) {
+            mTimer.cancel();
+            mIsTimerRunning = false;
+            mLayout.getProgressDrawable().setStartEndTrim(0f, 0f); // Reset the progress
+        }
+    }
+
+    /**
+     * Resets everything.
+     */
+    void reset() {
+        setIndeterminate(false); // If showing indeterminate progress, stop it
+        stopTimer(); // Stop the previous timer if there is one
+        mLayout.getProgressDrawable().setStartEndTrim(0f, 0f); // Reset the progress
+    }
+
+    /**
+     * Class to handle timing for {@link CircularProgressLayout}.
+     */
+    private class CircularProgressTimer extends CountDownTimer {
+
+        private final long mTotalTime;
+
+        CircularProgressTimer(long totalTime, long updateInterval) {
+            super(totalTime, updateInterval);
+            mTotalTime = totalTime;
+        }
+
+        @Override
+        public void onTick(long millisUntilFinished) {
+            mLayout.getProgressDrawable()
+                    .setStartEndTrim(0f, 1f - (float) millisUntilFinished / (float) mTotalTime);
+            mLayout.invalidate();
+        }
+
+        @Override
+        public void onFinish() {
+            mLayout.getProgressDrawable().setStartEndTrim(0f, 1f);
+            if (mOnTimerFinishedListener != null) {
+                mOnTimerFinishedListener.onTimerFinished(mLayout);
+            }
+            mIsTimerRunning = false;
+        }
+    }
+}
diff --git a/wear/tests/res/layout/circular_progress_layout.xml b/wear/tests/res/layout/circular_progress_layout.xml
new file mode 100644
index 0000000..cce69e6
--- /dev/null
+++ b/wear/tests/res/layout/circular_progress_layout.xml
@@ -0,0 +1,35 @@
+<?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"
+             xmlns:app="http://schemas.android.com/apk/res-auto"
+             android:orientation="vertical">
+    <android.support.wear.widget.CircularProgressLayout
+        android:id="@+id/circular_progress_layout"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:padding="10dp"
+        app:strokeWidth="10dp">
+        <View
+            android:id="@+id/child_view"
+            android:layout_width="40dp"
+            android:layout_height="40dp"/>
+    </android.support.wear.widget.CircularProgressLayout>
+</FrameLayout >
\ No newline at end of file
diff --git a/wear/tests/src/android/support/wear/widget/CircularProgressLayoutControllerTest.java b/wear/tests/src/android/support/wear/widget/CircularProgressLayoutControllerTest.java
new file mode 100644
index 0000000..8d5406d
--- /dev/null
+++ b/wear/tests/src/android/support/wear/widget/CircularProgressLayoutControllerTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.wear.widget;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.widget.CircularProgressDrawable;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class CircularProgressLayoutControllerTest {
+
+    private static final long TOTAL_TIME = TimeUnit.SECONDS.toMillis(1);
+    private static final long UPDATE_INTERVAL = TimeUnit.MILLISECONDS.toMillis(30);
+
+    private CircularProgressLayoutController mControllerUnderTest;
+
+    @Mock
+    CircularProgressDrawable mMockDrawable;
+    @Mock
+    CircularProgressLayout mMockLayout;
+    @Mock
+    CircularProgressLayout.OnTimerFinishedListener mMockListener;
+
+    @Before
+    public void setUp() {
+        mMockDrawable = mock(CircularProgressDrawable.class);
+        mMockLayout = mock(CircularProgressLayout.class);
+        mMockListener = mock(CircularProgressLayout.OnTimerFinishedListener.class);
+        when(mMockLayout.getProgressDrawable()).thenReturn(mMockDrawable);
+        when(mMockLayout.getOnTimerFinishedListener()).thenReturn(mMockListener);
+        mControllerUnderTest = new CircularProgressLayoutController(mMockLayout);
+    }
+
+    @Test
+    public void testSetIndeterminate() {
+        mControllerUnderTest.setIndeterminate(true);
+
+        assertEquals(true, mControllerUnderTest.isIndeterminate());
+        verify(mMockDrawable).start();
+    }
+
+    @Test
+    public void testIsIndeterminateAfterSetToFalse() {
+        mControllerUnderTest.setIndeterminate(true);
+        mControllerUnderTest.setIndeterminate(false);
+
+        assertEquals(false, mControllerUnderTest.isIndeterminate());
+        verify(mMockDrawable).stop();
+    }
+
+    @Test
+    @UiThreadTest
+    public void testIsTimerRunningAfterStart() {
+        mControllerUnderTest.startTimer(TOTAL_TIME, UPDATE_INTERVAL);
+
+        assertEquals(true, mControllerUnderTest.isTimerRunning());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testIsTimerRunningAfterStop() {
+        mControllerUnderTest.startTimer(TOTAL_TIME, UPDATE_INTERVAL);
+        mControllerUnderTest.stopTimer();
+
+        assertEquals(false, mControllerUnderTest.isTimerRunning());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testSwitchFromIndeterminateToDeterminate() {
+        mControllerUnderTest.setIndeterminate(true);
+        mControllerUnderTest.startTimer(TOTAL_TIME, UPDATE_INTERVAL);
+
+        assertEquals(false, mControllerUnderTest.isIndeterminate());
+        assertEquals(true, mControllerUnderTest.isTimerRunning());
+        verify(mMockDrawable).stop();
+    }
+
+    @Test
+    @UiThreadTest
+    public void testSwitchFromDeterminateToIndeterminate() {
+        mControllerUnderTest.startTimer(TOTAL_TIME, UPDATE_INTERVAL);
+        mControllerUnderTest.setIndeterminate(true);
+
+        assertEquals(true, mControllerUnderTest.isIndeterminate());
+        assertEquals(false, mControllerUnderTest.isTimerRunning());
+        verify(mMockDrawable).start();
+    }
+}
diff --git a/wear/tests/src/android/support/wear/widget/CircularProgressLayoutTest.java b/wear/tests/src/android/support/wear/widget/CircularProgressLayoutTest.java
new file mode 100644
index 0000000..ff98c30
--- /dev/null
+++ b/wear/tests/src/android/support/wear/widget/CircularProgressLayoutTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.wear.widget;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Intent;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.wear.test.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class CircularProgressLayoutTest {
+
+    private static final long TOTAL_TIME = TimeUnit.SECONDS.toMillis(1);
+
+    @Rule
+    public final ActivityTestRule<LayoutTestActivity> mActivityRule = new ActivityTestRule<>(
+            LayoutTestActivity.class, true, false);
+    private CircularProgressLayout mLayoutUnderTest;
+
+    @Before
+    public void setUp() {
+        mActivityRule.launchActivity(new Intent().putExtra(LayoutTestActivity
+                .EXTRA_LAYOUT_RESOURCE_ID, R.layout.circular_progress_layout));
+        mLayoutUnderTest = mActivityRule.getActivity().findViewById(R.id.circular_progress_layout);
+        mLayoutUnderTest.setOnTimerFinishedListener(new FakeListener());
+    }
+
+    @Test
+    public void testListenerIsNotified() {
+        mLayoutUnderTest.setTotalTime(TOTAL_TIME);
+        startTimerOnUiThread();
+        waitForTimer(TOTAL_TIME + 100);
+        assertNotNull(mLayoutUnderTest.getOnTimerFinishedListener());
+        assertTrue(((FakeListener) mLayoutUnderTest.getOnTimerFinishedListener()).mFinished);
+    }
+
+    @Test
+    public void testListenerIsNotNotifiedWhenStopped() {
+        mLayoutUnderTest.setTotalTime(TOTAL_TIME);
+        startTimerOnUiThread();
+        stopTimerOnUiThread();
+        waitForTimer(TOTAL_TIME + 100);
+        assertNotNull(mLayoutUnderTest.getOnTimerFinishedListener());
+        assertFalse(((FakeListener) mLayoutUnderTest.getOnTimerFinishedListener()).mFinished);
+    }
+
+    private class FakeListener implements CircularProgressLayout.OnTimerFinishedListener {
+
+        boolean mFinished;
+
+        @Override
+        public void onTimerFinished(CircularProgressLayout layout) {
+            mFinished = true;
+        }
+    }
+
+    private void startTimerOnUiThread() {
+        mActivityRule.getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mLayoutUnderTest.startTimer();
+            }
+        });
+    }
+
+    private void stopTimerOnUiThread() {
+        mActivityRule.getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mLayoutUnderTest.stopTimer();
+            }
+        });
+    }
+
+    private void waitForTimer(long time) {
+        try {
+            Thread.sleep(time);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+}