am 7bd54691: am bfec5af0: Merge "Added C++11 flag to RS support."

* commit '7bd54691f8151be83364b544fcaa2808751da57b':
  Added C++11 flag to RS support.
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..7af3b53
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,19 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+# Don't include in unbundled build.
+ifeq ($(TARGET_BUILD_APPS),)
+include $(call all-makefiles-under,$(LOCAL_PATH))
+endif
diff --git a/CleanSpec.mk b/CleanSpec.mk
index ea4358f..317bb03 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -48,6 +48,8 @@
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
 
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/annotations/src/android/support/annotation/IntDef.java b/annotations/src/android/support/annotation/IntDef.java
index 3232ff2..ce49b6e 100644
--- a/annotations/src/android/support/annotation/IntDef.java
+++ b/annotations/src/android/support/annotation/IntDef.java
@@ -29,7 +29,7 @@
 /**
  * Denotes that the annotated element of integer type, represents
  * a logical type and that its value should be one of the explicitly
- * named constants. If the {@link #flag()} attribute is set to true,
+ * named constants. If the IntDef#flag() attribute is set to true,
  * multiple constants can be combined.
  * <p>
  * Example:
diff --git a/build.gradle b/build.gradle
index a95df22..8012388 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,13 +5,14 @@
         maven { url '../../prebuilts/tools/common/m2/internal' }
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:0.9.+'
+        classpath 'com.android.tools.build:gradle:0.10.0'
     }
 }
 
-ext.supportVersion = '19.1.0'
-ext.extraVersion = 5
+ext.supportVersion = '21.0.0-rc1'
+ext.extraVersion = 6
 ext.supportRepoOut = ''
+ext.buildToolsVersion = '19.0.3'
 
 /*
  * With the build server you are given two env variables.
@@ -50,16 +51,20 @@
 createArchive.dependsOn createRepository
 
 // prepare repository with older versions
-task prepareRepo(type: Copy) {
+task unzipRepo(type: Copy) {
     from "$rootDir/../../prebuilts/maven_repo/android"
     into project.ext.supportRepoOut
 }
 
-prepareRepo.doFirst {
+unzipRepo.doFirst {
     project.ext.supportRepoOut.deleteDir()
     project.ext.supportRepoOut.mkdirs()
 }
 
+// anchor for prepare repo. This is post unzip + sourceProp.
+task(prepareRepo) << {
+}
+
 import com.google.common.io.Files
 import com.google.common.base.Charsets
 
@@ -93,6 +98,21 @@
 }
 createArchive.dependsOn createXml
 
+task(createSourceProp) << {
+    def sourceProp =
+"Extra.VendorDisplay=Android\n\
+Extra.Path=m2repository\n\
+Archive.Arch=ANY\n\
+Extra.NameDisplay=Android Support Repository\n\
+Archive.Os=ANY\n\
+Pkg.Revision=${project.ext.extraVersion}.0.0\n\
+Extra.VendorId=android"
+
+    Files.write(sourceProp, new File(project.ext.supportRepoOut, 'source.properties'), Charsets.UTF_8)
+}
+createSourceProp.dependsOn unzipRepo
+prepareRepo.dependsOn createSourceProp
+
 
 import com.google.common.hash.HashCode
 import com.google.common.hash.HashFunction
@@ -126,6 +146,12 @@
     release.dependsOn rootProject.tasks.prepareRepo
     // make the mainupload depend on this one.
     mainUpload.dependsOn release
+
+    project.plugins.whenPluginAdded { plugin ->
+        if ("com.android.build.gradle.LibraryPlugin".equals(plugin.class.name)) {
+            project.android.buildToolsVersion = rootProject.buildToolsVersion
+        }
+    }
 }
 
 FileCollection getAndroidPrebuilt(String apiLevel) {
diff --git a/media/Android.mk b/media/Android.mk
new file mode 100644
index 0000000..14ff0aa
--- /dev/null
+++ b/media/Android.mk
@@ -0,0 +1,16 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/media/protocols/Android.mk b/media/protocols/Android.mk
new file mode 100644
index 0000000..f4f0202
--- /dev/null
+++ b/media/protocols/Android.mk
@@ -0,0 +1,22 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-media-protocols
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_JAVA_LIBRARIES := android-support-annotations
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/media/protocols/README.txt b/media/protocols/README.txt
new file mode 100644
index 0000000..8a22f6d
--- /dev/null
+++ b/media/protocols/README.txt
@@ -0,0 +1,12 @@
+Library Project including media protocol declarations.
+
+This library contains declarations for protocols used by
+applications to communicate with services provided by media
+routes.  For example, the MediaPlayerProtocol defines a protocol
+by which an application may enqueue content to play on a
+remote media device.
+
+There is technically no source, but the src folder is necessary
+to ensure that the build system works.  The content is actually
+located in libs/android-support-media-protocols.jar.
+
diff --git a/media/protocols/build.gradle b/media/protocols/build.gradle
new file mode 100644
index 0000000..254770c
--- /dev/null
+++ b/media/protocols/build.gradle
@@ -0,0 +1,34 @@
+apply plugin: 'java'
+
+archivesBaseName = 'support-media-protocols'
+
+sourceSets {
+    main.java.srcDir 'src'
+}
+
+jar {
+    from sourceSets.main.output
+}
+
+// configuration for the javadoc to include all source sets.
+javadoc {
+    source    sourceSets.main.allJava
+}
+
+// custom tasks for creating source/javadoc jars
+task sourcesJar(type: Jar, dependsOn:classes) {
+    classifier = 'sources'
+    from sourceSets.main.allSource
+}
+
+task javadocJar(type: Jar, dependsOn:javadoc) {
+    classifier         'javadoc'
+    from               javadoc.destinationDir
+}
+
+// add javadoc/source jar tasks as artifacts
+artifacts {
+    archives jar
+    archives sourcesJar
+    archives javadocJar
+}
diff --git a/media/protocols/src/android/support/media/protocols/MediaPlayerProtocol.java b/media/protocols/src/android/support/media/protocols/MediaPlayerProtocol.java
new file mode 100644
index 0000000..3dc72dc
--- /dev/null
+++ b/media/protocols/src/android/support/media/protocols/MediaPlayerProtocol.java
@@ -0,0 +1,2179 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.media.protocols;
+
+import android.content.Context;
+import android.graphics.Typeface;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Parcelable;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.view.accessibility.CaptioningManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Media route protocol for managing a queue of media to be played remotely
+ * by a media device.
+ */
+public class MediaPlayerProtocol extends MediaRouteProtocol {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = { RESUME_STATE_UNCHANGED, RESUME_STATE_PLAY,
+            RESUME_STATE_PAUSE })
+    public @interface ResumeState { }
+
+    /**
+     * A resume state indicating that the player state should be left unchanged.
+     */
+    public static final int RESUME_STATE_UNCHANGED = 0;
+
+    /**
+     * A resume state indicating that the player should be playing,
+     * regardless of its current state.
+     */
+    public static final int RESUME_STATE_PLAY = 1;
+
+    /**
+     * A resume state indicating that the player should be paused,
+     * regardless of its current state.
+     */
+    public static final int RESUME_STATE_PAUSE = 2;
+
+    /**
+     * Creates the protocol client object for an application to use to send
+     * messages to a media route.
+     * <p>
+     * This constructor is called automatically if you use
+     * {@link android.media.routing.MediaRouter.ConnectionInfo#getProtocolObject getProtocolObject}
+     * to obtain a protocol object from a media route connection.
+     * </p>
+     *
+     * @param binder The remote binder supplied by the media route service.  May be
+     * obtained using {@link android.media.routing.MediaRouter.ConnectionInfo#getProtocolBinder}
+     * on a route connection.
+     */
+    public MediaPlayerProtocol(@NonNull IBinder binder) {
+        super(binder);
+    }
+
+    /**
+     * Loads and optionally starts playback of a new media item.
+     * The media item starts playback at playPosition.
+     *
+     * @param mediaInfo An object describing the media item to load.
+     * @param autoplay Whether playback should start immediately.
+     * @param playPosition The initial playback position, in milliseconds from the
+     * beginning of the stream.
+     * @param extras Custom application-specific data to pass along with the request.
+     */
+    public void load(@NonNull MediaInfo mediaInfo, boolean autoplay,
+            long playPosition, @Nullable Bundle extras) {
+        if (mediaInfo == null) {
+            throw new IllegalArgumentException("mediaInfo must not be null");
+        }
+        Bundle args = new Bundle();
+        args.putBundle("mediaInfo", mediaInfo.toBundle());
+        args.putBoolean("autoplay", autoplay);
+        args.putLong("playPosition", playPosition);
+        args.putBundle("extras", extras);
+        sendRequest("load", args);
+    }
+
+    /**
+     * Begins or resumes playback of the current media item.
+     *
+     * @param extras Custom application-specific data to pass along with the request.
+     */
+    public void play(@Nullable Bundle extras) {
+        Bundle args = new Bundle();
+        args.putBundle("extras", extras);
+        sendRequest("play", args);
+    }
+
+    /**
+     * Pauses playback of the current media item.
+     *
+     * @param extras Custom application-specific data to pass along with the request.
+     */
+    public void pause(@Nullable Bundle extras) {
+        Bundle args = new Bundle();
+        args.putBundle("extras", extras);
+        sendRequest("pause", args);
+    }
+
+    /**
+     * Requests updated media status information from the receiver.
+     *
+     * @param extras Custom application-specific data to pass along with the request.
+     */
+    public void requestStatus(@Nullable Bundle extras) {
+        Bundle args = new Bundle();
+        args.putBundle("extras", extras);
+        sendRequest("requestStatus", args);
+    }
+
+    /**
+     * Seeks to a new position within the current media item.
+     *
+     * @param position The new position, in milliseconds from the beginning of the stream.
+     * @param resumeState The action to take after the seek operation has finished.
+     * @param extras Custom application-specific data to pass along with the request.
+     */
+    public void seek(long position, @ResumeState int resumeState, @Nullable Bundle extras) {
+        Bundle args = new Bundle();
+        args.putLong("position", position);
+        args.putInt("resumeState", resumeState);
+        args.putBundle("extras", extras);
+        sendRequest("seek", args);
+    }
+
+    /**
+     * Sets the active media tracks.
+     *
+     * @param trackIds The media track IDs.
+     * @param extras Custom application-specific data to pass along with the request.
+     */
+    public void setActiveMediaTracks(@NonNull long[] trackIds, @Nullable Bundle extras) {
+        if (trackIds == null) {
+            throw new IllegalArgumentException("trackIds must not be null");
+        }
+        Bundle args = new Bundle();
+        args.putLongArray("trackIds", trackIds);
+        args.putBundle("extras", extras);
+        sendRequest("setActiveMediaTracks", args);
+    }
+
+    /**
+     * Toggles the stream muting.
+     *
+     * @param muteState Whether the stream should be muted or unmuted.
+     * @param extras Custom application-specific data to pass along with the request.
+     */
+    public void setStreamMute(boolean muteState, @Nullable Bundle extras) {
+        Bundle args = new Bundle();
+        args.putBoolean("muteState", muteState);
+        args.putBundle("extras", extras);
+        sendRequest("setStreamMute", args);
+    }
+
+    /**
+     * Sets the stream volume.
+     * If volume is outside of the range [0.0, 1.0], then the value will be clipped.
+     *
+     * @param volume The new volume, in the range [0.0 - 1.0].
+     * @param extras Custom application-specific data to pass along with the request.
+     */
+    public void setStreamVolume(int volume, @Nullable Bundle extras) {
+        Bundle args = new Bundle();
+        args.putInt("volume", volume);
+        args.putBundle("extras", extras);
+        sendRequest("setStreamVolume", args);
+    }
+
+    /**
+     * Sets the text track style.
+     *
+     * @param trackStyle The track style.
+     * @param extras Custom application-specific data to pass along with the request.
+     */
+    public void setTextTrackStyle(@NonNull TextTrackStyle trackStyle, @Nullable Bundle extras) {
+        if (trackStyle == null) {
+            throw new IllegalArgumentException("trackStyle must not be null");
+        }
+        Bundle args = new Bundle();
+        args.putBundle("trackStyle", trackStyle.toBundle());
+        args.putBundle("extras", extras);
+        sendRequest("setTextTrackStyle", args);
+    }
+
+    /**
+     * Stops playback of the current media item.
+     *
+     * @param extras Custom application-specific data to pass along with the request.
+     */
+    public void stop(@Nullable Bundle extras) {
+        Bundle args = new Bundle();
+        args.putBundle("extras", extras);
+        sendRequest("stop", args);
+    }
+
+    /**
+     * Media player callbacks.
+     */
+    public static abstract class Callback extends MediaRouteProtocol.Callback {
+        /**
+         * Called when updated player status information is received.
+         *
+         * @param status The updated status, or null if none.
+         * @param extras Custom application-specific data to pass along with the request.
+         */
+        public void onStatusUpdated(@Nullable MediaStatus status,
+                @Nullable Bundle extras) { }
+
+        @Override
+        public void onEvent(String event, Bundle args) {
+            switch (event) {
+                case "statusUpdated":
+                    onStatusUpdated(MediaStatus.fromBundle(args.getBundle("status")),
+                            args.getBundle("extras"));
+                    return;
+            }
+            super.onEvent(event, args);
+        }
+    }
+
+    /**
+     * Media player stubs.
+     */
+    public static abstract class Stub extends MediaRouteProtocol.Stub {
+        /**
+         * Creates an implementation of a media route protocol.
+         *
+         * @param handler The handler on which to receive requests, or null to use
+         * the current looper thread.
+         */
+        public Stub(@Nullable Handler handler) {
+            super(handler);
+        }
+
+        /**
+         * Loads and optionally starts playback of a new media item.
+         * The media item starts playback at playPosition.
+         *
+         * @param mediaInfo An object describing the media item to load.
+         * @param autoplay Whether playback should start immediately.
+         * @param playPosition The initial playback position, in milliseconds from the
+         * beginning of the stream.
+         * @param extras Custom application-specific data to pass along with the request.
+         */
+        public void onLoad(@NonNull MediaInfo mediaInfo, boolean autoplay,
+                long playPosition, @Nullable Bundle extras) {
+            throw new UnsupportedOperationException();
+        }
+
+        /**
+         * Begins or resumes playback of the current media item.
+         *
+         * @param extras Custom application-specific data to pass along with the request.
+         */
+        public void onPlay(@Nullable Bundle extras) {
+            throw new UnsupportedOperationException();
+        }
+
+        /**
+         * Pauses playback of the current media item.
+         *
+         * @param extras Custom application-specific data to pass along with the request.
+         */
+        public void onPause(@Nullable Bundle extras) {
+            throw new UnsupportedOperationException();
+        }
+
+        /**
+         * Requests updated media status information from the receiver.
+         *
+         * @param extras Custom application-specific data to pass along with the request.
+         */
+        public void onRequestStatus(@Nullable Bundle extras) {
+            throw new UnsupportedOperationException();
+        }
+
+        /**
+         * Seeks to a new position within the current media item.
+         *
+         * @param position The new position, in milliseconds from the beginning of the stream.
+         * @param resumeState The action to take after the seek operation has finished.
+         * @param extras Custom application-specific data to pass along with the request.
+         */
+        public void onSeek(long position, @ResumeState int resumeState, @Nullable Bundle extras) {
+            throw new UnsupportedOperationException();
+        }
+
+        /**
+         * Sets the active media tracks.
+         *
+         * @param trackIds The media track IDs.
+         * @param extras Custom application-specific data to pass along with the request.
+         */
+        public void onSetActiveMediaTracks(long[] trackIds, @Nullable Bundle extras) {
+            throw new UnsupportedOperationException();
+        }
+
+        /**
+         * Toggles the stream muting.
+         *
+         * @param muteState Whether the stream should be muted or unmuted.
+         * @param extras Custom application-specific data to pass along with the request.
+         */
+        public void onSetStreamMute(boolean muteState, @Nullable Bundle extras) {
+            throw new UnsupportedOperationException();
+        }
+
+        /**
+         * Sets the stream volume.
+         * If volume is outside of the range [0.0, 1.0], then the value will be clipped.
+         *
+         * @param volume The new volume, in the range [0.0 - 1.0].
+         * @param extras Custom application-specific data to pass along with the request.
+         */
+        public void onSetStreamVolume(int volume, @Nullable Bundle extras) {
+            throw new UnsupportedOperationException();
+        }
+
+        /**
+         * Sets the text track style.
+         *
+         * @param trackStyle The track style.
+         * @param extras Custom application-specific data to pass along with the request.
+         */
+        public void onSetTextTrackStyle(@NonNull TextTrackStyle trackStyle,
+                @Nullable Bundle extras) {
+            throw new UnsupportedOperationException();
+        }
+
+        /**
+         * Stops playback of the current media item.
+         *
+         * @param extras Custom application-specific data to pass along with the request.
+         */
+        public void onStop(@Nullable Bundle extras) {
+            throw new UnsupportedOperationException();
+        }
+
+        /**
+         * Sends a status updated event.
+         *
+         * @param status The updated media status, or null if none.
+         * @param extras Custom application-specific data to pass along with the request.
+         */
+        public void sendStatusUpdatedEvent(@Nullable MediaStatus status,
+                @Nullable Bundle extras) {
+            Bundle args = new Bundle();
+            args.putBundle("status", status.toBundle());
+            args.putBundle("extras", extras);
+            sendEvent("statusUpdated", args);
+        }
+
+        @Override
+        public void onRequest(String request, Bundle args)
+                throws UnsupportedOperationException {
+            switch (request) {
+                case "load":
+                    onLoad(MediaInfo.fromBundle(args.getBundle("mediaInfo")),
+                            args.getBoolean("autoplay"), args.getLong("playPosition"),
+                            args.getBundle("extras"));
+                    return;
+                case "play":
+                    onPlay(args.getBundle("extras"));
+                    return;
+                case "pause":
+                    onPause(args.getBundle("extras"));
+                    return;
+                case "requestStatus":
+                    onRequestStatus(args.getBundle("extras"));
+                    return;
+                case "seek":
+                    onSeek(args.getLong("position"), args.getInt("resumeState"),
+                            args.getBundle("extras"));
+                    return;
+                case "setActiveMediaTracks":
+                    onSetActiveMediaTracks(args.getLongArray("trackIds"), args.getBundle("extras"));
+                    return;
+                case "setStreamMute":
+                    onSetStreamMute(args.getBoolean("muteState"), args.getBundle("extras"));
+                    return;
+                case "setStreamVolume":
+                    onSetStreamVolume(args.getInt("volume"), args.getBundle("extras"));
+                    return;
+                case "setTextTrackStyle":
+                    onSetTextTrackStyle(TextTrackStyle.fromBundle(args.getBundle("trackStyle")),
+                            args.getBundle("extras"));
+                    return;
+                case "stop":
+                    onStop(args.getBundle("extras"));
+                    return;
+            }
+            super.onRequest(request, args);
+        }
+    }
+
+    /**
+     * A class that aggregates information about a media item.
+     */
+    public static final class MediaInfo {
+        /** A stream type of "none". */
+        public static final int STREAM_TYPE_NONE = 0;
+
+        /** A buffered stream type. */
+        public static final int STREAM_TYPE_BUFFERED = 1;
+
+        /** A live stream type. */
+        public static final int STREAM_TYPE_LIVE = 2;
+
+        /** An invalid (unknown) stream type. */
+        public static final int STREAM_TYPE_INVALID = -1;
+
+        private static final int STREAM_TYPE_MAX = STREAM_TYPE_LIVE;
+
+        private static final String KEY_CONTENT_ID = "contentId";
+        private static final String KEY_CONTENT_TYPE = "contentType";
+        private static final String KEY_EXTRAS = "extras";
+        private static final String KEY_DURATION = "duration";
+        private static final String KEY_METADATA = "metadata";
+        private static final String KEY_STREAM_TYPE = "streamType";
+        private static final String KEY_TEXT_TRACK_STYLE = "textTrackStyle";
+        private static final String KEY_TRACKS = "tracks";
+
+        private final String mContentId;
+        private final int mStreamType;
+        private final String mContentType;
+        private MediaMetadata mMediaMetadata;
+        private long mStreamDuration;
+        private final ArrayList<MediaTrack> mMediaTracks = new ArrayList<MediaTrack>();
+        private TextTrackStyle mTextTrackStyle;
+        private Bundle mExtras;
+
+        /**
+         * Constructs a new MediaInfo with the given content ID.
+         *
+         * @throws IllegalArgumentException If the content ID or content type
+         * is {@code null} or empty, or if the stream type is invalid.
+         */
+        public MediaInfo(@NonNull String contentId, int streamType,
+                @NonNull String contentType) {
+            if (TextUtils.isEmpty(contentId)) {
+                throw new IllegalArgumentException("content ID cannot be null or empty");
+            }
+            if ((streamType < STREAM_TYPE_INVALID) || (streamType > STREAM_TYPE_MAX)) {
+                throw new IllegalArgumentException("invalid stream type");
+            }
+            if (TextUtils.isEmpty(contentType)) {
+                throw new IllegalArgumentException("content type cannot be null or empty");
+            }
+            mContentId = contentId;
+            mStreamType = streamType;
+            mContentType = contentType;
+        }
+
+        /**
+         * Returns the content ID.
+         */
+        public @NonNull String getContentId() {
+            return mContentId;
+        }
+
+        /**
+         * Returns the stream type.
+         */
+        public int getStreamType() {
+            return mStreamType;
+        }
+
+        /**
+         * Returns the content (MIME) type.
+         */
+        public @NonNull String getContentType() {
+            return mContentType;
+        }
+
+        /**
+         * Sets the media item metadata.
+         */
+        public void setMetadata(@Nullable MediaMetadata metadata) {
+            mMediaMetadata = metadata;
+        }
+
+        /**
+         * Returns the media item metadata.
+         */
+        public @Nullable MediaMetadata getMetadata() {
+            return mMediaMetadata;
+        }
+
+        /**
+         * Sets the stream duration, in milliseconds.
+         *
+         * @throws IllegalArgumentException If the duration is negative.
+         */
+        public void setStreamDuration(long streamDuration) {
+            if (streamDuration < 0) {
+                throw new IllegalArgumentException("Stream duration cannot be negative");
+            }
+            mStreamDuration = streamDuration;
+        }
+
+        /**
+         * Returns the stream duration, in milliseconds.
+         */
+        public long getStreamDuration() {
+            return mStreamDuration;
+        }
+
+        /**
+         * Sets the media tracks.
+         */
+        public void setMediaTracks(@NonNull List<MediaTrack> mediaTracks) {
+            mMediaTracks.clear();
+            mMediaTracks.addAll(mediaTracks);
+        }
+
+        /**
+         * Returns the list of media tracks, or {@code null} if none have been specified.
+         */
+        public @NonNull List<MediaTrack> getMediaTracks() {
+            return mMediaTracks;
+        }
+
+        /**
+         * Sets the text track style.
+         */
+        public void setTextTrackStyle(@Nullable TextTrackStyle textTrackStyle) {
+            mTextTrackStyle = textTrackStyle;
+        }
+
+        /**
+         * Returns the text track style, or {@code null} if none has been specified.
+         */
+        public @Nullable TextTrackStyle getTextTrackStyle() {
+            return mTextTrackStyle;
+        }
+
+        /**
+         * Sets the custom application-specific data.
+         */
+        public void setExtras(@Nullable Bundle extras) {
+            mExtras = extras;
+        }
+
+        /**
+         * Returns the extras, if any.
+         */
+        public @Nullable Bundle getExtras() {
+            return mExtras;
+        }
+
+        /**
+         * Creates a bundle representation of the object.
+         */
+        public @NonNull Bundle toBundle() {
+            Bundle bundle = new Bundle();
+            bundle.putString(KEY_CONTENT_ID, mContentId);
+            bundle.putInt(KEY_STREAM_TYPE, mStreamType);
+            bundle.putString(KEY_CONTENT_TYPE, mContentType);
+            if (mMediaMetadata != null) {
+                bundle.putBundle(KEY_METADATA, mMediaMetadata.toBundle());
+            }
+            bundle.putLong(KEY_DURATION, mStreamDuration);
+            if (mTextTrackStyle != null) {
+                bundle.putBundle(KEY_TEXT_TRACK_STYLE, mTextTrackStyle.toBundle());
+            }
+            if (mExtras != null) {
+                bundle.putBundle(KEY_EXTRAS, mExtras);
+            }
+            if (!mMediaTracks.isEmpty()) {
+                Parcelable[] trackBundles = new Parcelable[mMediaTracks.size()];
+                for (int i = 0; i < trackBundles.length; i++) {
+                    trackBundles[i] = mMediaTracks.get(i).toBundle();
+                }
+                bundle.putParcelableArray(KEY_TRACKS, trackBundles);
+            }
+            return bundle;
+        }
+
+        /**
+         * Constructs a new {@link MediaInfo} object from a bundle.
+         */
+        public static @Nullable MediaInfo fromBundle(@Nullable Bundle bundle) {
+            if (bundle == null) {
+                return null;
+            }
+
+            String contentId = bundle.getString(KEY_CONTENT_ID);
+            int streamType = bundle.getInt(KEY_STREAM_TYPE, STREAM_TYPE_INVALID);
+            String contentType = bundle.getString(KEY_CONTENT_TYPE);
+
+            MediaInfo info = new MediaInfo(contentId, streamType, contentType);
+            info.setMetadata(MediaMetadata.fromBundle(bundle.getBundle(KEY_METADATA)));
+            info.setStreamDuration(bundle.getLong(KEY_DURATION));
+            info.setTextTrackStyle(TextTrackStyle.fromBundle(
+                    bundle.getBundle(KEY_TEXT_TRACK_STYLE)));
+            info.setExtras(bundle.getBundle("extras"));
+
+            Parcelable[] trackBundles = bundle.getParcelableArray(KEY_TRACKS);
+            if (trackBundles != null) {
+                for (int i = 0; i < trackBundles.length; ++i) {
+                    info.mMediaTracks.add(MediaTrack.fromBundle((Bundle)trackBundles[i]));
+                }
+            }
+
+            return info;
+        }
+    }
+
+    /**
+     * Container class for media metadata. Metadata has a media type, an optional
+     * list of images, and a collection of metadata fields. Keys for common
+     * metadata fields are predefined as constants, but the application is free to
+     * define and use additional fields of its own.
+     * <p>
+     * The values of the predefined fields have predefined types. For example, a track number is
+     * an <code>int</code> and a creation date is a <code>Calendar</code>. Attempting to
+     * store a value of an incorrect type in a field will result in a
+     * {@link IllegalArgumentException}.
+     */
+    public static final class MediaMetadata {
+        /** A media type representing generic media content. */
+        public static final int MEDIA_TYPE_GENERIC = 0;
+        /** A media type representing a movie. */
+        public static final int MEDIA_TYPE_MOVIE = 1;
+        /** A media type representing an TV show. */
+        public static final int MEDIA_TYPE_TV_SHOW = 2;
+        /** A media type representing a music track. */
+        public static final int MEDIA_TYPE_MUSIC_TRACK = 3;
+        /** A media type representing a photo. */
+        public static final int MEDIA_TYPE_PHOTO = 4;
+        /** The smallest media type value that can be assigned for application-defined media types. */
+        public static final int MEDIA_TYPE_USER = 100;
+
+        // Field types.
+        private static final int TYPE_NONE = 0;
+        private static final int TYPE_STRING = 1;
+        private static final int TYPE_INT = 2;
+        private static final int TYPE_DOUBLE = 3;
+        private static final int TYPE_DATE = 4;
+
+        // Field type names. Used when constructing exceptions.
+        private static final String[] sTypeNames = { null, "String", "int", "double", "Calendar" };
+
+        private final int mMediaType;
+        private final Bundle mFields;
+        private final ArrayList<WebImage> mImages;
+
+        private static final String BUNDLE_KEY_MEDIA_TYPE = "mediaType";
+        private static final String BUNDLE_KEY_FIELDS = "fields";
+        private static final String BUNDLE_KEY_IMAGES = "images";
+
+        /**
+         * String key: Creation date.
+         * <p>
+         * The value is the date and/or time at which the media was created.
+         * For example, this could be the date and time at which a photograph was taken or a piece of
+         * music was recorded.
+         */
+        public static final String KEY_CREATION_DATE =
+                "android.support.media.protocols.metadata.CREATION_DATE";
+
+        /**
+         * String key: Release date.
+         * <p>
+         * The value is the date and/or time at which the media was released.
+         * For example, this could be the date that a movie or music album was released.
+         */
+        public static final String KEY_RELEASE_DATE =
+                "android.support.media.protocols.metadata.RELEASE_DATE";
+
+        /**
+         * String key: Broadcast date.
+         * <p>
+         * The value is the date and/or time at which the media was first broadcast.
+         * For example, this could be the date that a TV show episode was first aired.
+         */
+        public static final String KEY_BROADCAST_DATE =
+                "android.support.media.protocols.metadata.BROADCAST_DATE";
+
+        /**
+         * String key: Title.
+         * <p>
+         * The title of the media. For example, this could be the title of a song, movie, or TV show
+         * episode. This value is suitable for display purposes.
+         */
+        public static final String KEY_TITLE =
+                "android.support.media.protocols.metadata.TITLE";
+
+        /**
+         * String key: Subtitle.
+         * <p>
+         * The subtitle of the media. This value is suitable for display purposes.
+         */
+        public static final String KEY_SUBTITLE =
+                "android.support.media.protocols.metadata.SUBTITLE";
+
+        /**
+         * String key: Artist.
+         * <p>
+         * The name of the artist who created the media. For example, this could be the name of a
+         * musician, performer, or photographer. This value is suitable for display purposes.
+         */
+        public static final String KEY_ARTIST =
+                "android.support.media.protocols.metadata.ARTIST";
+
+        /**
+         * String key: Album artist.
+         * <p>
+         * The name of the artist who produced an album. For example, in compilation albums such as DJ
+         * mixes, the album artist is not necessarily the same as the artist(s) of the individual songs
+         * on the album. This value is suitable for display purposes.
+         */
+        public static final String KEY_ALBUM_ARTIST =
+                "android.support.media.protocols.metadata.ALBUM_ARTIST";
+
+        /**
+         * String key: Album title.
+         * <p>
+         * The title of the album that a music track belongs to. This value is suitable for display
+         * purposes.
+         */
+        public static final String KEY_ALBUM_TITLE =
+                "android.support.media.protocols.metadata.ALBUM_TITLE";
+
+        /**
+         * String key: Composer.
+         * <p>
+         * The name of the composer of a music track. This value is suitable for display purposes.
+         */
+        public static final String KEY_COMPOSER =
+                "android.support.media.protocols.metadata.COMPOSER";
+
+        /**
+         * Integer key: Disc number.
+         * <p>
+         * The disc number (counting from 1) that a music track belongs to in a multi-disc album.
+         */
+        public static final String KEY_DISC_NUMBER =
+                "android.support.media.protocols.metadata.DISC_NUMBER";
+
+        /**
+         * Integer key: Track number.
+         * <p>
+         * The track number of a music track on an album disc. Typically track numbers are counted
+         * starting from 1, however this value may be 0 if it is a "hidden track" at the beginning of
+         * an album.
+         */
+        public static final String KEY_TRACK_NUMBER =
+                "android.support.media.protocols.metadata.TRACK_NUMBER";
+
+        /**
+         * Integer key: Season number.
+         * <p>
+         * The season number that a TV show episode belongs to. Typically season numbers are counted
+         * starting from 1, however this value may be 0 if it is a "pilot" episode that predates the
+         * official start of a TV series.
+         */
+        public static final String KEY_SEASON_NUMBER =
+                "android.support.media.protocols.metadata.SEASON_NUMBER";
+
+        /**
+         * Integer key: Episode number.
+         * <p>
+         * The number of an episode in a given season of a TV show. Typically episode numbers are
+         * counted starting from 1, however this value may be 0 if it is a "pilot" episode that is not
+         * considered to be an official episode of the first season.
+         */
+        public static final String KEY_EPISODE_NUMBER =
+                "android.support.media.protocols.metadata.EPISODE_NUMBER";
+
+        /**
+         * String key: Series title.
+         * <p>
+         * The name of a series. For example, this could be the name of a TV show or series of related
+         * music albums. This value is suitable for display purposes.
+         */
+        public static final String KEY_SERIES_TITLE =
+                "android.support.media.protocols.metadata.SERIES_TITLE";
+
+        /**
+         * String key: Studio.
+         * <p>
+         * The name of a recording studio that produced a piece of media. For example, this could be
+         * the name of a movie studio or music label. This value is suitable for display purposes.
+         */
+        public static final String KEY_STUDIO =
+                "android.support.media.protocols.metadata.STUDIO";
+
+        /**
+         * Integer key: Width.
+         *
+         * The width of a piece of media, in pixels. This would typically be used for providing the
+         * dimensions of a photograph.
+         */
+        public static final String KEY_WIDTH =
+                "android.support.media.protocols.metadata.WIDTH";
+
+        /**
+         * Integer key: Height.
+         *
+         * The height of a piece of media, in pixels. This would typically be used for providing the
+         * dimensions of a photograph.
+         */
+        public static final String KEY_HEIGHT =
+                "android.support.media.protocols.metadata.HEIGHT";
+
+        /**
+         * String key: Location name.
+         * <p>
+         * The name of a location where a piece of media was created. For example, this could be the
+         * location of a photograph or the principal filming location of a movie. This value is
+         * suitable for display purposes.
+         */
+        public static final String KEY_LOCATION_NAME =
+                "android.support.media.protocols.metadata.LOCATION_NAME";
+
+        /**
+         * Double key: Location latitude.
+         * <p>
+         * The latitude component of the geographical location where a piece of media was created.
+         * For example, this could be the location of a photograph or the principal filming location of
+         * a movie.
+         */
+        public static final String KEY_LOCATION_LATITUDE =
+                "android.support.media.protocols.metadata.LOCATION_LATITUDE";
+
+        /**
+         * Double key: Location longitude.
+         * <p>
+         * The longitude component of the geographical location where a piece of media was created.
+         * For example, this could be the location of a photograph or the principal filming location of
+         * a movie.
+         */
+        public static final String KEY_LOCATION_LONGITUDE =
+                "android.support.media.protocols.metadata.LOCATION_LONGITUDE";
+
+        /**
+         * Constructs a new, empty, MediaMetadata with a media type of {@link #MEDIA_TYPE_GENERIC}.
+         */
+        public MediaMetadata() {
+            this(MEDIA_TYPE_GENERIC);
+        }
+
+        /**
+         * Constructs a new, empty, MediaMetadata with the given media type.
+         *
+         * @param mediaType The media type; one of the {@code MEDIA_TYPE_*} constants, or a value
+         * greater than or equal to {@link #MEDIA_TYPE_USER} for custom media types.
+         */
+        public MediaMetadata(int mediaType) {
+            this(mediaType, null);
+        }
+
+        private MediaMetadata(int mediaType, Bundle fields) {
+            mMediaType = mediaType;
+            mFields = fields != null ? fields : new Bundle();
+            mImages = new ArrayList<WebImage>();
+        }
+
+        /**
+         * Gets the media type.
+         */
+        public int getMediaType() {
+            return mMediaType;
+        }
+
+        /**
+         * Clears this object. The media type is left unchanged.
+         */
+        public void clear() {
+            mFields.clear();
+            mImages.clear();
+        }
+
+        /**
+         * Tests if the object contains a field with the given key.
+         */
+        public boolean containsKey(@NonNull String key) {
+            return mFields.containsKey(key);
+        }
+
+        /**
+         * Returns a set of keys for all fields that are present in the object.
+         */
+        public @NonNull Set<String> keySet() {
+            return mFields.keySet();
+        }
+
+        /**
+         * Stores a value in a String field.
+         *
+         * @param key The key for the field.
+         * @param value The new value for the field.
+         * @throws IllegalArgumentException If the key is {@code null} or empty or refers to a
+         * predefined field which is not a {@code String} field.
+         */
+        public void putString(@NonNull String key, String value) {
+            throwIfWrongType(key, TYPE_STRING);
+            mFields.putString(key, value);
+        }
+
+        /**
+         * Reads the value of a String field.
+         *
+         * @return The value of the field, or {@code null} if the field has not been set.
+         * @throws IllegalArgumentException If the key is {@code null} or empty or refers to a
+         * predefined field which is not a {@code String} field.
+         */
+        public @Nullable String getString(@NonNull String key) {
+            throwIfWrongType(key, TYPE_STRING);
+            return mFields.getString(key);
+        }
+
+        /**
+         * Stores a value in an int field.
+         *
+         * @param key The key for the field.
+         * @param value The new value for the field.
+         * @throws IllegalArgumentException If the key is {@code null} or empty or refers to a
+         * predefined field which is not an {@code int} field.
+         */
+        public void putInt(@NonNull String key, int value) {
+            throwIfWrongType(key, TYPE_INT);
+            mFields.putInt(key, value);
+        }
+
+        /**
+         * Reads the value of an {@code int} field.
+         *
+         * @return The value of the field, or {@code null} if the field has not been set.
+         * @throws IllegalArgumentException If the key is {@code null} or empty or refers to a
+         * predefined field which is not an {@code int} field.
+         */
+        public int getInt(@NonNull String key) {
+            throwIfWrongType(key, TYPE_INT);
+            return mFields.getInt(key);
+        }
+
+        /**
+         * Stores a value in a {@code double} field.
+         *
+         * @param key The key for the field.
+         * @param value The new value for the field.
+         * @throws IllegalArgumentException If the key is {@code null} or empty or refers to a
+         * predefined field which is not a {@code double} field.
+         */
+        public void putDouble(@NonNull String key, double value) {
+            throwIfWrongType(key, TYPE_DOUBLE);
+            mFields.putDouble(key, value);
+        }
+
+        /**
+         * Reads the value of a {@code double} field.
+         *
+         * @return The value of the field, or {@code null} if the field has not been set.
+         * @throws IllegalArgumentException If the key is {@code null} or empty or refers to a
+         * predefined field which is not a {@code double} field.
+         */
+        public double getDouble(@NonNull String key) {
+            throwIfWrongType(key, TYPE_DOUBLE);
+            return mFields.getDouble(key);
+        }
+
+        /**
+         * Stores a value in a date field.
+         *
+         * @param key The key for the field.
+         * @param value The new value for the field.
+         * @throws IllegalArgumentException If the key is {@code null} or empty or refers to a
+         * predefined field which is not a date field.
+         */
+        public void putDate(@NonNull String key, @Nullable Calendar value) {
+            throwIfWrongType(key, TYPE_DATE);
+            if (value != null) {
+                mFields.putLong(key, value.getTimeInMillis());
+            } else {
+                mFields.remove(key);
+            }
+        }
+
+        /**
+         * Reads the value of a date field.
+         *
+         * @param key The field name.
+         * @return The date, as a {@link Calendar}, or {@code null} if this field has not been set.
+         * @throws IllegalArgumentException If the key is {@code null} or empty or the specified field's
+         * predefined type is not a date.
+         */
+        public @Nullable Calendar getDate(String key) {
+            throwIfWrongType(key, TYPE_DATE);
+            if (mFields.containsKey(key)) {
+                Calendar date = Calendar.getInstance();
+                date.setTimeInMillis(mFields.getLong(key));
+                return date;
+            }
+            return null;
+        }
+
+        /**
+         * Returns the list of images. If there are no images, returns an empty list.
+         */
+        public List<WebImage> getImages() {
+            return mImages;
+        }
+
+        /**
+         * Checks if the metadata includes any images.
+         */
+        public boolean hasImages() {
+            return (mImages != null) && !mImages.isEmpty();
+        }
+
+        /**
+         * Clears the list of images.
+         */
+        public void clearImages() {
+            mImages.clear();
+        }
+
+        /**
+         * Adds an image to the list of images.
+         */
+        public void addImage(WebImage image) {
+            mImages.add(image);
+        }
+
+        /*
+         * Verifies that a key is not empty, and if the key is a predefined key, verifies that it has
+         * the specified type.
+         */
+        private void throwIfWrongType(String key, int type) {
+            if (TextUtils.isEmpty(key)) {
+                throw new IllegalArgumentException("null and empty keys are not allowed");
+            }
+            int actualType = getFieldType(key);
+            if ((actualType != type) && (actualType != TYPE_NONE))
+                throw new IllegalArgumentException("Value for " + key + " must be a "
+                        + sTypeNames[type]);
+        }
+
+        /**
+         * Creates a bundle representation of the object.
+         */
+        public @NonNull Bundle toBundle() {
+            Bundle bundle = new Bundle();
+            bundle.putInt(BUNDLE_KEY_MEDIA_TYPE, mMediaType);
+            bundle.putBundle(BUNDLE_KEY_FIELDS, mFields);
+
+            if (mImages.isEmpty()) {
+                Parcelable[] imageBundles = new Parcelable[mImages.size()];
+                for (int i = 0; i < imageBundles.length; i++) {
+                    imageBundles[i] = mImages.get(i).toBundle();
+                }
+                bundle.putParcelableArray(BUNDLE_KEY_IMAGES, imageBundles);
+            }
+
+            return bundle;
+        }
+
+        /**
+         * Constructs a new {@link MediaMetadata} object from a bundle.
+         */
+        public static MediaMetadata fromBundle(Bundle bundle) {
+            if (bundle == null) {
+                return null;
+            }
+
+            int mediaType = bundle.getInt(BUNDLE_KEY_MEDIA_TYPE);
+            Bundle fields = bundle.getBundle(BUNDLE_KEY_FIELDS);
+            MediaMetadata metadata = new MediaMetadata(mediaType, fields);
+
+            Parcelable[] imageBundles = bundle.getParcelableArray(BUNDLE_KEY_IMAGES);
+            if (imageBundles != null) {
+                for (Parcelable imageBundle : imageBundles) {
+                    metadata.addImage(WebImage.fromBundle((Bundle)imageBundle));
+                }
+            }
+
+            return metadata;
+        }
+
+        private static int getFieldType(String key) {
+            switch (key) {
+                case KEY_CREATION_DATE: return TYPE_DATE;
+                case KEY_RELEASE_DATE: return TYPE_DATE;
+                case KEY_BROADCAST_DATE: return TYPE_DATE;
+                case KEY_TITLE: return TYPE_STRING;
+                case KEY_SUBTITLE: return TYPE_STRING;
+                case KEY_ARTIST: return TYPE_STRING;
+                case KEY_ALBUM_ARTIST: return TYPE_STRING;
+                case KEY_ALBUM_TITLE: return TYPE_STRING;
+                case KEY_COMPOSER: return TYPE_STRING;
+                case KEY_DISC_NUMBER: return TYPE_INT;
+                case KEY_TRACK_NUMBER: return TYPE_INT;
+                case KEY_SEASON_NUMBER: return TYPE_INT;
+                case KEY_EPISODE_NUMBER: return TYPE_INT;
+                case KEY_SERIES_TITLE: return TYPE_STRING;
+                case KEY_STUDIO: return TYPE_STRING;
+                case KEY_WIDTH: return TYPE_INT;
+                case KEY_HEIGHT: return TYPE_INT;
+                case KEY_LOCATION_NAME: return TYPE_STRING;
+                case KEY_LOCATION_LATITUDE: return TYPE_DOUBLE;
+                case KEY_LOCATION_LONGITUDE: return TYPE_DOUBLE;
+                default: return TYPE_NONE;
+            }
+        }
+    }
+
+    /**
+     * A class that holds status information about some media.
+     */
+    public static final class MediaStatus {
+        private static final String KEY_ACTIVE_TRACK_IDS = "activeTrackIds";
+        private static final String KEY_CURRENT_TIME = "currentTime";
+        private static final String KEY_EXTRAS = "extras";
+        private static final String KEY_IDLE_REASON = "idleReason";
+        private static final String KEY_MEDIA = "media";
+        private static final String KEY_MEDIA_SESSION_ID = "mediaSessionId";
+        private static final String KEY_MUTED = "muted";
+        private static final String KEY_PLAYBACK_RATE = "playbackRate";
+        private static final String KEY_PLAYER_STATE = "playerState";
+        private static final String KEY_SUPPORTED_MEDIA_COMMANDS = "supportedMediaCommands";
+        private static final String KEY_VOLUME = "volume";
+
+        /** A flag (bitmask) indicating that a media item can be paused. */
+        public static final long COMMAND_PAUSE = 1 << 0;
+
+        /** A flag (bitmask) indicating that a media item supports seeking. */
+        public static final long COMMAND_SEEK = 1 << 1;
+
+        /** A flag (bitmask) indicating that a media item's audio volume can be changed. */
+        public static final long COMMAND_SET_VOLUME = 1 << 2;
+
+        /** A flag (bitmask) indicating that a media item's audio can be muted. */
+        public static final long COMMAND_TOGGLE_MUTE = 1 << 3;
+
+        /** A flag (bitmask) indicating that a media item supports skipping forward. */
+        public static final long COMMAND_SKIP_FORWARD = 1 << 4;
+
+        /** A flag (bitmask) indicating that a media item supports skipping backward. */
+        public static final long COMMAND_SKIP_BACKWARD = 1 << 5;
+
+        /** Constant indicating unknown player state. */
+        public static final int PLAYER_STATE_UNKNOWN = 0;
+
+        /** Constant indicating that the media player is idle. */
+        public static final int PLAYER_STATE_IDLE = 1;
+
+        /** Constant indicating that the media player is playing. */
+        public static final int PLAYER_STATE_PLAYING = 2;
+
+        /** Constant indicating that the media player is paused. */
+        public static final int PLAYER_STATE_PAUSED = 3;
+
+        /** Constant indicating that the media player is buffering. */
+        public static final int PLAYER_STATE_BUFFERING = 4;
+
+        /** Constant indicating that the player currently has no idle reason. */
+        public static final int IDLE_REASON_NONE = 0;
+
+        /** Constant indicating that the player is idle because playback has finished. */
+        public static final int IDLE_REASON_FINISHED = 1;
+
+        /**
+         * Constant indicating that the player is idle because playback has been canceled in
+         * response to a STOP command.
+         */
+        public static final int IDLE_REASON_CANCELED = 2;
+
+        /**
+         * Constant indicating that the player is idle because playback has been interrupted by
+         * a LOAD command.
+         */
+        public static final int IDLE_REASON_INTERRUPTED = 3;
+
+        /** Constant indicating that the player is idle because a playback error has occurred. */
+        public static final int IDLE_REASON_ERROR = 4;
+
+        private final long mMediaSessionId;
+        private final MediaInfo mMediaInfo;
+        private double mPlaybackRate;
+        private int mPlayerState;
+        private int mIdleReason;
+        private long mStreamPosition;
+        private long mSupportedMediaCommands;
+        private double mVolume;
+        private boolean mMuteState;
+        private long mActiveTrackIds[];
+        private Bundle mExtras;
+
+        /**
+         * Constructs a new {@link MediaStatus} object with the given properties.
+         */
+        public MediaStatus(long mediaSessionId, @NonNull MediaInfo mediaInfo) {
+            if (mediaInfo == null) {
+                throw new IllegalArgumentException("mediaInfo must not be null");
+            }
+
+            mMediaSessionId = mediaSessionId;
+            mMediaInfo = mediaInfo;
+            mPlayerState = PLAYER_STATE_UNKNOWN;
+            mIdleReason = IDLE_REASON_NONE;
+        }
+
+        /**
+         * Returns the media session ID for this item.
+         */
+        public long getMediaSessionId() {
+            return mMediaSessionId;
+        }
+
+        /**
+         * Returns the {@link MediaInfo} for this item.
+         */
+        public @NonNull MediaInfo getMediaInfo() {
+            return mMediaInfo;
+        }
+
+        /**
+         * Gets the current media player state.
+         */
+        public int getPlayerState() {
+            return mPlayerState;
+        }
+
+        /**
+         * Sets the current media player state.
+         */
+        public void setPlayerState(int playerState) {
+            mPlayerState = playerState;
+        }
+
+        /**
+         * Gets the player state idle reason. This value is only meaningful if the player state is
+         * in fact {@link #PLAYER_STATE_IDLE}.
+         */
+        public int getIdleReason() {
+            return mIdleReason;
+        }
+
+        /**
+         * Sets the player state idle reason. This value is only meaningful if the player state is
+         * in fact {@link #PLAYER_STATE_IDLE}.
+         */
+        public void setIdleReason(int idleReason) {
+            mIdleReason = idleReason;
+        }
+
+        /**
+         * Gets the current stream playback rate. This will be negative if the stream is seeking
+         * backwards, 0 if the stream is paused, 1 if the stream is playing normally, and some other
+         * positive value if the stream is seeking forwards.
+         */
+        public double getPlaybackRate() {
+            return mPlaybackRate;
+        }
+
+        /**
+         * Sets the current stream playback rate. This will be negative if the stream is seeking
+         * backwards, 0 if the stream is paused, 1 if the stream is playing normally, and some other
+         * positive value if the stream is seeking forwards.
+         */
+        public void setPlaybackRate(double playbackRate) {
+            mPlaybackRate = playbackRate;
+        }
+
+        /**
+         * Returns the current stream position, in milliseconds.
+         */
+        public long getStreamPosition() {
+            return mStreamPosition;
+        }
+
+        /**
+         * Sets the current stream position, in milliseconds.
+         */
+        public void setStreamPosition(long streamPosition) {
+            mStreamPosition = streamPosition;
+        }
+
+        /**
+         * Tests if the stream supports a given control command.
+         *
+         * @param mediaCommand The media command.
+         * @return {@code true} if the command is supported, {@code false} otherwise.
+         */
+        public boolean isMediaCommandSupported(long mediaCommand) {
+            return (mSupportedMediaCommands & mediaCommand) != 0;
+        }
+
+        /**
+         * Sets whether the stream supports a given control command.
+         */
+        public void setSupportedMediaCommands(long supportedMediaCommands) {
+            mSupportedMediaCommands = supportedMediaCommands;
+        }
+
+        /**
+         * Returns the stream's volume.
+         */
+        public double getStreamVolume() {
+            return mVolume;
+        }
+
+        /**
+         * Sets the stream's volume.
+         */
+        public void setStreamVolume(double volume) {
+            mVolume = volume;
+        }
+
+        /**
+         * Returns the stream's mute state.
+         */
+        public boolean isMute() {
+            return mMuteState;
+        }
+
+        /**
+         * Sets the stream's mute state.
+         */
+        public void setMute(boolean muteState) {
+            mMuteState = muteState;
+        }
+
+        /**
+         * Returns the list of active track IDs, if any, otherwise {@code null}.
+         */
+        public @Nullable long[] getActiveTrackIds() {
+            return mActiveTrackIds;
+        }
+
+        /**
+         * Sets the list of active track IDs, if any, otherwise {@code null}.
+         */
+        public void setActiveTrackIds(@Nullable long[] trackIds) {
+            mActiveTrackIds = trackIds;
+        }
+
+        /**
+         * Returns any extras that are is associated with the media item.
+         */
+        public @Nullable Bundle getExtras() {
+            return mExtras;
+        }
+
+        /**
+         * Sets any extras that are associated with the media item.
+         */
+        public void setExtras(@Nullable Bundle extras) {
+            mExtras = extras;
+        }
+
+        /**
+         * Creates a bundle representation of the object.
+         */
+        public @NonNull Bundle toBundle() {
+            Bundle bundle = new Bundle();
+            bundle.putLong(KEY_MEDIA_SESSION_ID, mMediaSessionId);
+            bundle.putBundle(KEY_MEDIA, mMediaInfo.toBundle());
+            bundle.putLongArray(KEY_ACTIVE_TRACK_IDS, mActiveTrackIds);
+            bundle.putLong(KEY_CURRENT_TIME, mStreamPosition);
+            bundle.putInt(KEY_IDLE_REASON, mIdleReason);
+            bundle.putBoolean(KEY_MUTED, mMuteState);
+            bundle.putDouble(KEY_PLAYBACK_RATE, mPlaybackRate);
+            bundle.putInt(KEY_PLAYER_STATE, mPlayerState);
+            bundle.putLong(KEY_SUPPORTED_MEDIA_COMMANDS, mSupportedMediaCommands);
+            bundle.putDouble(KEY_VOLUME, mVolume);
+            return bundle;
+        }
+
+        /**
+         * Constructs a new {@link MediaStatus} object from a bundle.
+         */
+        public static MediaStatus fromBundle(Bundle bundle) {
+            if (bundle == null) {
+                return null;
+            }
+
+            long mediaSessionId = bundle.getLong(KEY_MEDIA_SESSION_ID);
+            MediaInfo mediaInfo = MediaInfo.fromBundle(bundle.getBundle(KEY_MEDIA));
+            MediaStatus status = new MediaStatus(mediaSessionId, mediaInfo);
+
+            status.setActiveTrackIds(bundle.getLongArray(KEY_ACTIVE_TRACK_IDS));
+            status.setStreamPosition(bundle.getLong(KEY_CURRENT_TIME));
+            status.setIdleReason(bundle.getInt(KEY_IDLE_REASON));
+            status.setMute(bundle.getBoolean(KEY_MUTED));
+            status.setPlaybackRate(bundle.getDouble(KEY_PLAYBACK_RATE));
+            status.setPlayerState(bundle.getInt(KEY_PLAYER_STATE));
+            status.setSupportedMediaCommands(bundle.getLong(KEY_SUPPORTED_MEDIA_COMMANDS));
+            status.setStreamVolume(bundle.getDouble(KEY_VOLUME));
+            status.setExtras(bundle.getBundle(KEY_EXTRAS));
+            return status;
+        }
+    }
+
+    /**
+     * A class that represents a media track, such as a language track or closed caption text track
+     * in a video.
+     */
+    public static final class MediaTrack {
+        private static final String KEY_TRACK_ID = "trackId";
+        private static final String KEY_TYPE = "type";
+        private static final String KEY_TRACK_CONTENT_ID = "trackContentId";
+        private static final String KEY_TRACK_CONTENT_TYPE = "trackContentType";
+        private static final String KEY_NAME = "name";
+        private static final String KEY_LANGUAGE = "language";
+        private static final String KEY_SUBTYPE = "subtype";
+        private static final String KEY_EXTRAS = "extras";
+
+        /** A media track type indicating an unknown track type. */
+        public static final int TYPE_UNKNOWN = 0;
+        /** A media track type indicating a text track. */
+        public static final int TYPE_TEXT = 1;
+        /** A media track type indicating an audio track. */
+        public static final int TYPE_AUDIO = 2;
+        /** A media track type indicating a video track. */
+        public static final int TYPE_VIDEO = 3;
+
+        /** A media track subtype indicating an unknown subtype. */
+        public static final int SUBTYPE_UNKNOWN = -1;
+        /** A media track subtype indicating no subtype. */
+        public static final int SUBTYPE_NONE = 0;
+        /** A media track subtype indicating subtitles. */
+        public static final int SUBTYPE_SUBTITLES = 1;
+        /** A media track subtype indicating closed captions. */
+        public static final int SUBTYPE_CAPTIONS = 2;
+        /** A media track subtype indicating descriptions. */
+        public static final int SUBTYPE_DESCRIPTIONS = 3;
+        /** A media track subtype indicating chapters. */
+        public static final int SUBTYPE_CHAPTERS = 4;
+        /** A media track subtype indicating metadata. */
+        public static final int SUBTYPE_METADATA = 5;
+
+        private long mId;
+        private int mType;
+        private String mContentId;
+        private String mContentType;
+        private String mName;
+        private String mLanguage;
+        private int mSubtype;
+        private Bundle mExtras;
+
+        /**
+         * Constructs a new track with the given track ID and type.
+         *
+         * @throws IllegalArgumentException If the track type is invalid.
+         */
+        public MediaTrack(long id, int type) {
+            clear();
+            mId = id;
+            if ((type <= TYPE_UNKNOWN) || (type > TYPE_VIDEO)) {
+                throw new IllegalArgumentException("invalid type " + type);
+            }
+            mType = type;
+        }
+
+        /**
+         * Returns the unique ID of the media track.
+         */
+        public long getId() {
+            return mId;
+        }
+
+        /**
+         * Returns the type of the track; one of the {@code TYPE_} constants defined above.
+         */
+        public int getType() {
+            return mType;
+        }
+
+        /**
+         * Returns the content ID of the media track.
+         */
+        public String getContentId() {
+            return mContentId;
+        }
+
+        /**
+         * Sets the content ID for the media track.
+         */
+        public void setContentId(String contentId) {
+            mContentId = contentId;
+        }
+
+        /**
+         * Returns the content type (MIME type) of the media track, or {@code null} if none was
+         * specified.
+         */
+        public String getContentType() {
+            return mContentType;
+        }
+
+        /**
+         * Sets the content type (MIME type) of the media track.
+         */
+        public void setContentType(String contentType) {
+            mContentType = contentType;
+        }
+
+        /**
+         * Returns the name of the media track, or {@code null} if none was specified.
+         */
+        public String getName() {
+            return mName;
+        }
+
+        /**
+         * Sets the track name.
+         */
+        public void setName(String name) {
+            mName = name;
+        }
+
+        /**
+         * Returns the language of this media track, or {@code null} if none was specified.
+         */
+        public String getLanguage() {
+            return mLanguage;
+        }
+
+        /**
+         * Sets the track language.
+         */
+        public void setLanguage(String language) {
+            mLanguage = language;
+        }
+
+        /**
+         * Returns the subtype of this media track; one of the {@code SUBTYPE_}
+         * constants defined above.
+         */
+        public int getSubtype() {
+            return mSubtype;
+        }
+
+        /**
+         * Sets the track subtype.
+         *
+         * @throws IllegalArgumentException If the subtype is invalid.
+         */
+        public void setSubtype(int subtype) {
+            if ((subtype <= SUBTYPE_UNKNOWN) || (subtype > SUBTYPE_METADATA)) {
+                throw new IllegalArgumentException("invalid subtype " + subtype);
+            }
+            if ((subtype != SUBTYPE_NONE) && (mType != TYPE_TEXT)) {
+                throw new IllegalArgumentException("subtypes are only valid for text tracks");
+            }
+
+            mSubtype = subtype;
+        }
+
+        /**
+         * Returns the extras object for this media track, or {@code null} if none was
+         * specified.
+         */
+        public Bundle getExtras() {
+            return mExtras;
+        }
+
+        /**
+         * Sets the track's extras object.
+         */
+        public void setExtras(Bundle extras) {
+            mExtras = extras;
+        }
+
+        private void clear() {
+            mId = 0;
+            mType = TYPE_UNKNOWN;
+            mContentId = null;
+            mName = null;
+            mLanguage = null;
+            mSubtype = SUBTYPE_UNKNOWN;
+            mExtras = null;
+        }
+
+        /**
+         * Creates a bundle representation of the object.
+         */
+        public @NonNull Bundle toBundle() {
+            Bundle bundle = new Bundle();
+            bundle.putLong(KEY_TRACK_ID, mId);
+            bundle.putInt(KEY_TYPE, mType);
+            bundle.putString(KEY_TRACK_CONTENT_ID, mContentId);
+            bundle.putString(KEY_TRACK_CONTENT_TYPE, mContentType);
+            bundle.putString(KEY_NAME, mName);
+            bundle.putString(KEY_LANGUAGE, mLanguage);
+            bundle.putInt(KEY_SUBTYPE, mSubtype);
+            bundle.putBundle(KEY_EXTRAS, mExtras);
+            return bundle;
+        }
+
+        /**
+         * Constructs a new {@link MediaTrack} object from a bundle.
+         */
+        public static MediaTrack fromBundle(Bundle bundle) {
+            if (bundle == null) {
+                return null;
+            }
+
+            long trackId = bundle.getLong(KEY_TRACK_ID);
+            int type = bundle.getInt(KEY_TYPE);
+            MediaTrack track = new MediaTrack(trackId, type);
+
+            track.setContentId(bundle.getString(KEY_TRACK_CONTENT_ID));
+            track.setContentType(bundle.getString(KEY_TRACK_CONTENT_TYPE));
+            track.setName(bundle.getString(KEY_NAME));
+            track.setLanguage(bundle.getString(KEY_LANGUAGE));
+            track.setSubtype(bundle.getInt(KEY_SUBTYPE));
+            track.setExtras(bundle.getBundle(KEY_EXTRAS));
+            return track;
+        }
+    }
+
+    /**
+     * A class that specifies how a text track's text will be displayed on-screen. The text is
+     * displayed inside a rectangular "window". The appearance of both the text and the window are
+     * configurable.
+     * <p>
+     * With the exception of the font scale, which has a predefined default value, any attribute that
+     * is not explicitly set will remain "unspecified", and the player will select an appropriate
+     * value.
+     */
+    public static final class TextTrackStyle {
+        /** The default font scale. */
+        public static final float DEFAULT_FONT_SCALE = 1.0f;
+
+        /** A color value that indicates an unspecified (unset) color. */
+        public static final int COLOR_UNSPECIFIED = 0;
+
+        /** An edge type indicating an unspecified edge type. */
+        public static final int EDGE_TYPE_UNSPECIFIED = -1;
+        /** An edge type indicating no edge. */
+        public static final int EDGE_TYPE_NONE = 0;
+        /** An edge type indicating an outline edge. */
+        public static final int EDGE_TYPE_OUTLINE = 1;
+        /** An edge type indicating a drop shadow edge. */
+        public static final int EDGE_TYPE_DROP_SHADOW = 2;
+        /** An edge type indicating a raised edge. */
+        public static final int EDGE_TYPE_RAISED = 3;
+        /** An edge type indicating a depressed edge. */
+        public static final int EDGE_TYPE_DEPRESSED = 4;
+
+        /** A window type indicating an unspecified window type. */
+        public static final int WINDOW_TYPE_UNSPECIFIED = -1;
+        /** A window type indicating no window type. */
+        public static final int WINDOW_TYPE_NONE = 0;
+        /** A window type indicating a normal window. */
+        public static final int WINDOW_TYPE_NORMAL = 1;
+        /** A window type indicating a window with rounded corners. */
+        public static final int WINDOW_TYPE_ROUNDED = 2;
+
+        /** A font family indicating an unspecified font family. */
+        public static final int FONT_FAMILY_UNSPECIFIED = -1;
+        /** A font family indicating Sans Serif. */
+        public static final int FONT_FAMILY_SANS_SERIF = 0;
+        /** A font family indicating Monospaced Sans Serif. */
+        public static final int FONT_FAMILY_MONOSPACED_SANS_SERIF = 1;
+        /** A font family indicating Serif. */
+        public static final int FONT_FAMILY_SERIF = 2;
+        /** A font family indicating Monospaced Serif. */
+        public static final int FONT_FAMILY_MONOSPACED_SERIF = 3;
+        /** A font family indicating Casual. */
+        public static final int FONT_FAMILY_CASUAL = 4;
+        /** A font family indicating Cursive. */
+        public static final int FONT_FAMILY_CURSIVE = 5;
+        /** A font family indicating Small Capitals. */
+        public static final int FONT_FAMILY_SMALL_CAPITALS = 6;
+
+        /** A font style indicating an unspecified style. */
+        public static final int FONT_STYLE_UNSPECIFIED = -1;
+        /** A font style indicating a normal style. */
+        public static final int FONT_STYLE_NORMAL = 0;
+        /** A font style indicating a bold style. */
+        public static final int FONT_STYLE_BOLD = 1;
+        /** A font style indicating an italic style. */
+        public static final int FONT_STYLE_ITALIC = 2;
+        /** A font style indicating a bold and italic style. */
+        public static final int FONT_STYLE_BOLD_ITALIC = 3;
+
+        private static final String KEY_FONT_SCALE = "fontScale";
+        private static final String KEY_FOREGROUND_COLOR = "foregroundColor";
+        private static final String KEY_BACKGROUND_COLOR = "backgroundColor";
+        private static final String KEY_EDGE_TYPE = "edgeType";
+        private static final String KEY_EDGE_COLOR = "edgeColor";
+        private static final String KEY_WINDOW_TYPE = "windowType";
+        private static final String KEY_WINDOW_COLOR = "windowColor";
+        private static final String KEY_WINDOW_CORNER_RADIUS = "windowRoundedCornerRadius";
+        private static final String KEY_FONT_FAMILY = "fontFamily";
+        private static final String KEY_FONT_GENERIC_FAMILY = "fontGenericFamily";
+        private static final String KEY_FONT_STYLE = "fontStyle";
+        private static final String KEY_EXTRAS = "extras";
+
+        private float mFontScale;
+        private int mForegroundColor;
+        private int mBackgroundColor;
+        private int mEdgeType;
+        private int mEdgeColor;
+        private int mWindowType;
+        private int mWindowColor;
+        private int mWindowCornerRadius;
+        private String mFontFamily;
+        private int mFontGenericFamily;
+        private int mFontStyle;
+        private Bundle mExtras;
+
+        /**
+         * Constructs a new TextTrackStyle.
+         */
+        public TextTrackStyle() {
+            clear();
+        }
+
+        /**
+         * Sets the font scale factor. The default is {@link #DEFAULT_FONT_SCALE}.
+         */
+        public void setFontScale(float fontScale) {
+            mFontScale = fontScale;
+        }
+
+        /**
+         * Gets the font scale factor.
+         */
+        public float getFontScale() {
+            return mFontScale;
+        }
+
+        /**
+         * Sets the text's foreground color.
+         *
+         * @param foregroundColor The color, as an ARGB value.
+         */
+        public void setForegroundColor(int foregroundColor) {
+            mForegroundColor = foregroundColor;
+        }
+
+        /**
+         * Gets the text's foreground color.
+         */
+        public int getForegroundColor() {
+            return mForegroundColor;
+        }
+
+        /**
+         * Sets the text's background color.
+         *
+         * @param backgroundColor The color, as an ARGB value.
+         */
+        public void setBackgroundColor(int backgroundColor) {
+            mBackgroundColor = backgroundColor;
+        }
+
+        /**
+         * Gets the text's background color.
+         */
+        public int getBackgroundColor() {
+            return mBackgroundColor;
+        }
+
+        /**
+         * Sets the caption window's edge type.
+         *
+         * @param edgeType The edge type; one of the {@code EDGE_TYPE_} constants defined above.
+         */
+        public void setEdgeType(int edgeType) {
+            if ((edgeType < EDGE_TYPE_NONE) || (edgeType > EDGE_TYPE_DEPRESSED)) {
+                throw new IllegalArgumentException("invalid edgeType");
+            }
+            mEdgeType = edgeType;
+        }
+
+        /**
+         * Gets the caption window's edge type.
+         */
+        public int getEdgeType() {
+            return mEdgeType;
+        }
+
+        /**
+         * Sets the window's edge color.
+         *
+         * @param edgeColor The color, as an ARGB value.
+         */
+        public void setEdgeColor(int edgeColor) {
+            mEdgeColor = edgeColor;
+        }
+
+        /**
+         * Gets the window's edge color.
+         */
+        public int getEdgeColor() {
+            return mEdgeColor;
+        }
+
+        /**
+         * Sets the window type.
+         *
+         * @param windowType The window type; one of the {@code WINDOW_TYPE_} constants defined above.
+         */
+        public void setWindowType(int windowType) {
+            if ((windowType < WINDOW_TYPE_NONE) || (windowType > WINDOW_TYPE_ROUNDED)) {
+                throw new IllegalArgumentException("invalid windowType");
+            }
+            mWindowType = windowType;
+        }
+
+        /**
+         * Gets the caption window type.
+         */
+        public int getWindowType() {
+            return mWindowType;
+        }
+
+        /**
+         * Sets the window's color.
+         *
+         * @param windowColor The color, as an ARGB value.
+         */
+        public void setWindowColor(int windowColor) {
+            mWindowColor = windowColor;
+        }
+
+        /**
+         * Gets the window's color.
+         */
+        public int getWindowColor() {
+            return mWindowColor;
+        }
+
+        /**
+         * If the window type is {@link #WINDOW_TYPE_ROUNDED}, sets the radius for the window's
+         * corners.
+         *
+         * @param windowCornerRadius The radius, in pixels. Must be a positive value.
+         */
+        public void setWindowCornerRadius(int windowCornerRadius) {
+            if (windowCornerRadius < 0) {
+                throw new IllegalArgumentException("invalid windowCornerRadius");
+            }
+            mWindowCornerRadius = windowCornerRadius;
+        }
+
+        /**
+         * Gets the window corner radius.
+         */
+        public int getWindowCornerRadius() {
+            return mWindowCornerRadius;
+        }
+
+        /**
+         * Sets the text's font family.
+         *
+         * @param fontFamily The text font family.
+         */
+        public void setFontFamily(String fontFamily) {
+            mFontFamily = fontFamily;
+        }
+
+        /**
+         * Gets the text's font family.
+         */
+        public String getFontFamily() {
+            return mFontFamily;
+        }
+
+        /**
+         * Sets the text's generic font family. This will be used if the font family specified with
+         * {@link #setFontFamily} (if any) is unavailable.
+         *
+         * @param fontGenericFamily The generic family; one of the {@code FONT_FAMILY_} constants
+         * defined above.
+         */
+        public void setFontGenericFamily(int fontGenericFamily) {
+            if ((fontGenericFamily < FONT_FAMILY_SANS_SERIF)
+                    || (fontGenericFamily > FONT_FAMILY_SMALL_CAPITALS)) {
+                throw new IllegalArgumentException("invalid fontGenericFamily");
+            }
+            mFontGenericFamily = fontGenericFamily;
+        }
+
+        /**
+         * Gets the text's generic font family.
+         */
+        public int getFontGenericFamily() {
+            return mFontGenericFamily;
+        }
+
+        /**
+         * Sets the text font style.
+         *
+         * @param fontStyle The font style; one of the {@code FONT_STYLE_} constants defined above.
+         */
+        public void setFontStyle(int fontStyle) {
+            if ((fontStyle < FONT_STYLE_NORMAL) || (fontStyle > FONT_STYLE_BOLD_ITALIC)) {
+                throw new IllegalArgumentException("invalid fontStyle");
+            }
+            mFontStyle = fontStyle;
+        }
+
+        /**
+         * Gets the text font style.
+         */
+        public int getFontStyle() {
+            return mFontStyle;
+        }
+
+        /**
+         * Sets the extras object.
+         */
+        public void setExtras(Bundle extras) {
+            mExtras = extras;
+        }
+
+        /**
+         * Gets the extras object.
+         */
+        public Bundle getExtras() {
+            return mExtras;
+        }
+
+        private void clear() {
+            mFontScale = DEFAULT_FONT_SCALE;
+            mForegroundColor = COLOR_UNSPECIFIED;
+            mBackgroundColor = COLOR_UNSPECIFIED;
+            mEdgeType = EDGE_TYPE_UNSPECIFIED;
+            mEdgeColor = COLOR_UNSPECIFIED;
+            mWindowType = WINDOW_TYPE_UNSPECIFIED;
+            mWindowColor = COLOR_UNSPECIFIED;
+            mWindowCornerRadius = 0;
+            mFontFamily = null;
+            mFontGenericFamily = FONT_FAMILY_UNSPECIFIED;
+            mFontStyle = FONT_STYLE_UNSPECIFIED;
+            mExtras = null;
+        }
+
+        /**
+         * Constructs a new TextTrackStyle based on the systems' current closed caption style settings.
+         * On platform levels below 19, this returns an object with "unspecified" values for all
+         * fields.
+         *
+         * @param context The calling context.
+         * @return The new TextTrackStyle.
+         */
+        public static TextTrackStyle fromSystemSettings(Context context) {
+            TextTrackStyle style = new TextTrackStyle();
+            if (Build.VERSION.SDK_INT >= 19) {
+                Impl19.loadSystemSettings(style, context);
+            }
+            return style;
+        }
+
+        /**
+         * Creates a bundle representation of the object.
+         */
+        public @NonNull Bundle toBundle() {
+            Bundle bundle = new Bundle();
+            bundle.putFloat(KEY_FONT_SCALE, mFontScale);
+            bundle.putInt(KEY_FOREGROUND_COLOR, mForegroundColor);
+            bundle.putInt(KEY_BACKGROUND_COLOR, mBackgroundColor);
+            bundle.putInt(KEY_EDGE_TYPE, mEdgeType);
+            bundle.putInt(KEY_EDGE_COLOR, mEdgeColor);
+            bundle.putInt(KEY_WINDOW_TYPE, mWindowType);
+            bundle.putInt(KEY_WINDOW_COLOR, mWindowColor);
+            bundle.putInt(KEY_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
+            bundle.putString(KEY_FONT_FAMILY, mFontFamily);
+            bundle.putInt(KEY_FONT_GENERIC_FAMILY, mFontGenericFamily);
+            bundle.putInt(KEY_FONT_STYLE, mFontStyle);
+            bundle.putBundle(KEY_EXTRAS, mExtras);
+            return bundle;
+        }
+
+        /**
+         * Constructs a new {@link MediaTrack} object from a bundle.
+         */
+        public static TextTrackStyle fromBundle(Bundle bundle) {
+            if (bundle == null) {
+                return null;
+            }
+
+            TextTrackStyle style = new TextTrackStyle();
+            style.setFontScale(bundle.getFloat(KEY_FONT_SCALE));
+            style.setForegroundColor(bundle.getInt(KEY_FOREGROUND_COLOR));
+            style.setBackgroundColor(bundle.getInt(KEY_BACKGROUND_COLOR));
+            style.setEdgeType(bundle.getInt(KEY_EDGE_TYPE));
+            style.setEdgeColor(bundle.getInt(KEY_EDGE_COLOR));
+            style.setWindowType(bundle.getInt(KEY_WINDOW_TYPE));
+            style.setWindowColor(bundle.getInt(KEY_WINDOW_COLOR));
+            style.setWindowCornerRadius(bundle.getInt(KEY_WINDOW_CORNER_RADIUS));
+            style.setFontFamily(bundle.getString(KEY_FONT_FAMILY));
+            style.setFontGenericFamily(bundle.getInt(KEY_FONT_GENERIC_FAMILY));
+            style.setFontStyle(bundle.getInt(KEY_FONT_STYLE));
+            style.setExtras(bundle.getBundle(KEY_EXTRAS));
+            return style;
+        }
+
+        // Compatibility for new platform features introduced in KitKat.
+        private static final class Impl19 {
+            public static void loadSystemSettings(TextTrackStyle style, Context context) {
+                CaptioningManager captioningManager =
+                        (CaptioningManager)context.getSystemService(Context.CAPTIONING_SERVICE);
+                style.setFontScale(captioningManager.getFontScale());
+
+                CaptioningManager.CaptionStyle userStyle = captioningManager.getUserStyle();
+                style.setBackgroundColor(userStyle.backgroundColor);
+                style.setForegroundColor(userStyle.foregroundColor);
+
+                switch (userStyle.edgeType) {
+                    case CaptioningManager.CaptionStyle.EDGE_TYPE_OUTLINE:
+                        style.setEdgeType(EDGE_TYPE_OUTLINE);
+                        break;
+
+                    case CaptioningManager.CaptionStyle.EDGE_TYPE_DROP_SHADOW:
+                        style.setEdgeType(EDGE_TYPE_DROP_SHADOW);
+                        break;
+
+                    case CaptioningManager.CaptionStyle.EDGE_TYPE_NONE:  // Fall through
+                    default:
+                        style.setEdgeType(EDGE_TYPE_NONE);
+                }
+
+                style.setEdgeColor(userStyle.edgeColor);
+
+                Typeface typeface = userStyle.getTypeface();
+                if (typeface != null) {
+                    if (Typeface.MONOSPACE.equals(typeface)) {
+                        style.setFontGenericFamily(FONT_FAMILY_MONOSPACED_SANS_SERIF);
+                    } else if (Typeface.SANS_SERIF.equals(typeface)) {
+                        style.setFontGenericFamily(FONT_FAMILY_SANS_SERIF);
+                    } else if (Typeface.SERIF.equals(typeface)) {
+                        style.setFontGenericFamily(FONT_FAMILY_SERIF);
+                    } else {
+                        // Otherwise, assume sans-serif.
+                        style.setFontGenericFamily(FONT_FAMILY_SANS_SERIF);
+                    }
+
+                    boolean bold = typeface.isBold();
+                    boolean italic = typeface.isItalic();
+
+                    if (bold && italic) {
+                        style.setFontStyle(FONT_STYLE_BOLD_ITALIC);
+                    } else if (bold) {
+                        style.setFontStyle(FONT_STYLE_BOLD);
+                    } else if (italic) {
+                        style.setFontStyle(FONT_STYLE_ITALIC);
+                    } else {
+                        style.setFontStyle(FONT_STYLE_NORMAL);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * A class that represents an image that is located on a web server.
+     */
+    public static final class WebImage {
+        private final Uri mUrl;
+        private final int mWidth;
+        private final int mHeight;
+
+        private static final String KEY_URL = "url";
+        private static final String KEY_HEIGHT = "height";
+        private static final String KEY_WIDTH = "width";
+
+        /**
+         * Constructs a new {@link WebImage} with the given URL.
+         *
+         * @param url The URL of the image.
+         * @throws IllegalArgumentException If the URL is null or empty.
+         */
+        public WebImage(@NonNull Uri url) throws IllegalArgumentException {
+            this(url, 0, 0);
+        }
+
+        /**
+         * Constructs a new {@link WebImage} with the given URL and dimensions.
+         *
+         * @param url The URL of the image.
+         * @param width The width of the image, in pixels.
+         * @param height The height of the image, in pixels.
+         * @throws IllegalArgumentException If the URL is null or empty,
+         * or the dimensions are invalid.
+         */
+        public WebImage(@NonNull Uri url, int width, int height) throws IllegalArgumentException {
+            if (url == null) {
+                throw new IllegalArgumentException("url cannot be null");
+            }
+
+            if ((width < 0) || (height < 0)) {
+                throw new IllegalArgumentException("width and height must not be negative");
+            }
+
+            mUrl = url;
+            mWidth = width;
+            mHeight = height;
+        }
+
+        /**
+         * Gets the image URL.
+         */
+        public @NonNull Uri getUrl() {
+            return mUrl;
+        }
+
+        /**
+         * Gets the image width, in pixels.
+         */
+        public int getWidth() {
+            return mWidth;
+        }
+
+        /**
+         * Gets the image height, in pixels.
+         */
+        public int getHeight() {
+            return mHeight;
+        }
+
+        /**
+         * Returns a string representation of this object.
+         */
+        @Override
+        public @NonNull String toString() {
+            return String.format("Image %dx%d %s", mWidth, mHeight, mUrl.toString());
+        }
+
+        /**
+         * Creates a bundle representation of this object.
+         */
+        public @NonNull Bundle toBundle() {
+            Bundle bundle = new Bundle();
+            bundle.putString(KEY_URL, mUrl.toString());
+            bundle.putInt(KEY_WIDTH, mWidth);
+            bundle.putInt(KEY_HEIGHT, mHeight);
+            return bundle;
+        }
+
+        /**
+         * Creates a {@link WebImage} from a bundle.
+         */
+        public static @Nullable WebImage fromBundle(@Nullable Bundle bundle) {
+            if (bundle == null) {
+                return null;
+            }
+            return new WebImage(Uri.parse(bundle.getString(KEY_URL)),
+                    bundle.getInt(KEY_WIDTH), bundle.getInt(KEY_HEIGHT));
+        }
+    }
+}
diff --git a/media/protocols/src/android/support/media/protocols/MediaRouteProtocol.java b/media/protocols/src/android/support/media/protocols/MediaRouteProtocol.java
new file mode 100644
index 0000000..cb49732
--- /dev/null
+++ b/media/protocols/src/android/support/media/protocols/MediaRouteProtocol.java
@@ -0,0 +1,574 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.media.protocols;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.lang.ref.WeakReference;
+
+/**
+ * Base class for media route protocols.
+ * <p>
+ * A media route protocol expresses an interface contract between an application and
+ * a media route that it would like to communicate with and control.  By using
+ * a protocol to send messages to a media route, an application can
+ * ask the media route to perform functions such as creating a playlist of music
+ * to be played on behalf of the application.
+ * </p><p>
+ * Subclasses should extend this class to offer specialized protocols.
+ * </p><p>
+ * Instances of this class are thread-safe but event will only be received
+ * on the handler that was specified when the callback was registered.
+ * </p>
+ *
+ * <h3>Overview</h3>
+ * <p>
+ * A media route protocol is essentially just a binder-based messaging interface.
+ * Messages sent from the application to the media route service are called "requests"
+ * whereas messages sent from the media route service back to the application are
+ * called "events" or "errors" depending on their purpose.
+ * </p><p>
+ * All communication through a protocol is asynchronous and is dispatched to a
+ * a {@link android.os.Looper} of the application or the media route service's choice
+ * (separate for each end).  Arguments are transferred through key/value pairs in
+ * {@link Bundle bundles}.
+ * </p><p>
+ * The overall interface is somewhat simpler than directly using AIDL and Binder which
+ * requires additional care to extend and maintain binary compatibility and to
+ * perform thread synchronization on either end of the communication channel.
+ * Media route protocols also support bidirectional asynchronous communication
+ * requests, events, and errors between the application and the media route service.
+ * </p>
+ *
+ * <h3>Using Protocols</h3>
+ * <p>
+ * To use a protocol, an application must do the following.
+ * </p><ul>
+ * <li>Create a {@link android.media.routing.MediaRouter media router}.
+ * <li>Add a {@link android.media.routing.MediaRouteSelector media route selector}
+ * that specifies the protocol as required or optional.
+ * <li>Show a media route button in the application's action bar to enable the
+ * user to choose a destination to connect to.
+ * <li>Once the connection has been established, obtain the protocol's
+ * binder from the {@link android.media.routing.MediaRouter.ConnectionInfo route connection}
+ * information and {@link MediaRouteProtocol#MediaRouteProtocol(IBinder) create} the protocol
+ * object.  There is also a convenience method called
+ * {@link android.media.routing.MediaRouter.ConnectionInfo#getProtocolObject getProtocolObject}
+ * to do this all in one step.
+ * <li>Set a {@link Callback} on the protocol object to receive events.
+ * <li>At this point, the application can begin sending requests to the media route
+ * and receiving events in response via the protocol object.
+ * </ul>
+ *
+ * <h3>Providing Protocols</h3>
+ * <p>
+ * The provide a protocol, a media route service must do the following.
+ * </p><ul>
+ * <li>Upon receiving a
+ * {@link android.media.routing.MediaRouter.DiscoveryRequest discovery request}
+ * from an application that contains a
+ * {@link android.media.routing.MediaRouteSelector media route selector}
+ * which asks to find routes that support known protocols during discovery, the media
+ * route service should indicate that it supports this protocol by adding it to the list
+ * of supported protocols in the
+ * {@link android.media.routing.MediaRouter.RouteInfo route information} for those
+ * routes that support them.
+ * <li>Upon receiving a
+ * {@link android.media.routing.MediaRouter.ConnectionRequest connection request}
+ * from an application that requests to connect to a route for which the application
+ * previously requested support of known protocols, the media route service should
+ * {@link MediaRouteProtocol.Stub#MediaRouteProtocol.Stub(Handler) create} a subclass of the stub
+ * object that implements the protocol then add it to the list of protocol binders
+ * in the {@link android.media.routing.MediaRouter.ConnectionInfo route connection}
+ * object it returns to the application.
+ * <li>Once the route is connected, the media route service should handle incoming
+ * protocol requests from the client and respond accordingly.
+ * <li>Once the route is disconnected, the media route service should cease to
+ * handle incoming protocol requests from the client and should clean up its state
+ * accordingly.
+ * </ul>
+ *
+ * <h3>Creating Custom Protocols</h3>
+ * <p>
+ * Although the framework provides standard media route protocols to encourage
+ * interoperability, it may be useful to create and publish custom protocols to
+ * access extended functionality only supported by certain routes.
+ * </p><p>
+ * To create a custom protocol, create a subclass of the {@link MediaRouteProtocol}
+ * class to declare the new request methods and marshal their arguments.  Also create
+ * a subclass of the {@link MediaRouteProtocol.Callback} class to decode any new kinds
+ * of events and subclass the {@link MediaRouteProtocol.Stub} class to decode
+ * incoming requests.
+ * </p><p>
+ * It may help to refer to the source code of the <code>android.support.media.protocol.jar</code>
+ * library for details.
+ * </p><p>
+ * Here is a simple example:
+ * </p><pre>
+ * public abstract class CustomProtocol extends MediaRouteProtocol {
+ *     public CustomProtocol(IBinder binder) {
+ *         super(binder);
+ *     }
+ *
+ *     // declare custom request
+ *     public void tuneRadio(int station) {
+ *         Bundle args = new Bundle();
+ *         args.putInt("station", station);
+ *         sendRequest("tuneRadio", args);
+ *     }
+ *
+ *     public static abstract class Callback extends MediaRouteProtocol.Callback {
+ *         // declare custom event
+ *         public void onStationTuned(int station, boolean hifi) { }
+ *
+ *         &#064;Override
+ *         public void onEvent(String event, Bundle args) {
+ *             switch (event) {
+ *                 case "radioTuned":
+ *                     onRadioTuned(args.getInt("station"), args.getBoolean("hifi"));
+ *                     return;
+ *             }
+ *             super.onEvent(event, args);
+ *         }
+ *     }
+ *
+ *     public static abstract class Stub extends MediaRouteProtocol.Stub {
+ *         // declare custom request stub
+ *         public abstract void onTuneRadio(int station);
+ *
+ *         &#064;Override
+ *         public void onRequest(String request, Bundle args) {
+ *             switch (request) {
+ *                 case "tuneRadio":
+ *                     onTuneRadio(args.getInt("station"));
+ *                     return;
+ *             }
+ *             super.onRequest(request, args);
+ *         }
+ *     }
+ * }
+ * </pre>
+ */
+public abstract class MediaRouteProtocol {
+    private static final String TAG = "MediaRouteProtocol";
+
+    private static final int REQUEST_MSG_SUBSCRIBE = 1;
+    private static final int REQUEST_MSG_COMMAND = 2;
+
+    private static final int REPLY_MSG_ERROR = 1;
+    private static final int REPLY_MSG_EVENT = 2;
+
+    private final Object mLock = new Object();
+    private final Messenger mRequestMessenger;
+    private Messenger mReplyMessenger;
+    private volatile Callback mCallback;
+    private Looper mCallbackLooper;
+    private Handler mCallbackHandler;
+
+    /**
+     * Error code: Some other unknown error occurred.
+     */
+    public static final String ERROR_UNKNOWN =
+            "android.support.errors.UNKNOWN";
+
+    /**
+     * Error code: The media route has been disconnected.
+     */
+    public static final String ERROR_DISCONNECTED =
+            "android.support.errors.DISCONNECTED";
+
+    /**
+     * Error code: The application issued an unsupported request.
+     */
+    public static final String ERROR_UNSUPPORTED_OPERATION =
+            "android.support.errors.UNSUPPORTED_OPERATION";
+
+    /**
+     * Creates the protocol client object for an application to use to send
+     * messages to a media route.
+     * <p>
+     * This constructor is called automatically if you use
+     * {@link android.media.routing.MediaRouter.ConnectionInfo#getProtocolObject getProtocolObject}
+     * to obtain a protocol object from a media route connection.
+     * </p>
+     *
+     * @param binder The remote binder supplied by the media route service.  May be
+     * obtained using {@link android.media.routing.MediaRouter.ConnectionInfo#getProtocolBinder}
+     * on a route connection.
+     */
+    public MediaRouteProtocol(@NonNull IBinder binder) {
+        if (binder == null) {
+            throw new IllegalArgumentException("binder must not be null");
+        }
+
+        mRequestMessenger = new Messenger(binder);
+    }
+
+    /**
+     * Sets the callback interface and handler on which to receive events and errors.
+     *
+     * @param callback The callback interface, or null if none.
+     * @param handler The handler on which to receive events and errors, or null to use
+     * the current looper thread.
+     */
+    public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
+        synchronized (mLock) {
+            Looper looper = callback != null ?
+                    (handler != null ? handler.getLooper() : Looper.myLooper()) : null;
+            if (mCallback != callback || mCallbackLooper != looper) {
+                mCallback = callback;
+                if (mCallback != null) {
+                    mCallbackLooper = looper;
+                    mCallbackHandler = handler != null ? handler : new Handler();
+                    mReplyMessenger = new Messenger(new ReplyHandler(this, looper));
+                } else {
+                    mCallbackLooper = null;
+                    mCallbackHandler = null;
+                    mReplyMessenger = null;
+                }
+
+                Message msg = Message.obtain();
+                msg.what = REQUEST_MSG_SUBSCRIBE;
+                msg.replyTo = mReplyMessenger;
+                sendSafelyLocked(msg);
+            }
+        }
+    }
+
+    /**
+     * Sends an asynchronous request to the media route service.
+     * <p>
+     * If an error occurs, it will be reported to the callback's {@link Callback#onError}
+     * method.
+     * </p>
+     *
+     * @param request The request name.
+     * @param args The request arguments, or null if none.
+     */
+    public void sendRequest(@NonNull String request, @Nullable Bundle args) {
+        if (TextUtils.isEmpty(request)) {
+            throw new IllegalArgumentException("request must not be null or empty");
+        }
+
+        synchronized (mLock) {
+            Message msg = Message.obtain();
+            msg.what = REQUEST_MSG_COMMAND;
+            msg.obj = request;
+            msg.setData(args);
+            sendSafelyLocked(msg);
+        }
+    }
+
+    private void sendSafelyLocked(Message msg) {
+        if (mRequestMessenger != null) {
+            try {
+                mRequestMessenger.send(msg);
+            } catch (RemoteException ex) {
+                postErrorLocked(ERROR_DISCONNECTED, null);
+            }
+        } else {
+            postErrorLocked(ERROR_DISCONNECTED, null);
+        }
+    }
+
+    private void postErrorLocked(final String error, final Bundle args) {
+        final Callback callback = mCallback;
+        if (callback != null) {
+            mCallbackHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (callback != null) {
+                        callback.onError(error, args);
+                    }
+                }
+            });
+        }
+    }
+
+    private void handleReply(Message msg) {
+        Callback callback = mCallback; // note: racy
+        if (callback != null) {
+            // ignore unrecognized messages in case of future protocol extension
+            if (msg.what == REPLY_MSG_ERROR && msg.obj instanceof String) {
+                mCallback.onError((String)msg.obj, msg.peekData());
+            } else if (msg.what == REPLY_MSG_EVENT && msg.obj instanceof String) {
+                mCallback.onEvent((String)msg.obj, msg.peekData());
+            }
+        }
+    }
+
+    /*
+     * Only use this handler to handle replies coming back from the media route service
+     * because the service can send any message it wants to it.
+     * Validate arguments carefully.
+     */
+    private static final class ReplyHandler extends Handler {
+        // break hard reference cycles through binder
+        private final WeakReference<MediaRouteProtocol> mProtocolRef;
+
+        public ReplyHandler(MediaRouteProtocol protocol, Looper looper) {
+            super(looper);
+            mProtocolRef = new WeakReference<MediaRouteProtocol>(protocol);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            MediaRouteProtocol protocol = mProtocolRef.get();
+            if (protocol != null) {
+                protocol.handleReply(msg);
+            }
+        }
+    }
+
+    /**
+     * Base class for application callbacks from the media route service.
+     * <p>
+     * Subclasses should extend this class to offer events for specialized protocols.
+     * </p>
+     */
+    public static abstract class Callback {
+        /**
+         * Called when an event is received from the media route service.
+         *
+         * @param event The event name.
+         * @param args The event arguments, or null if none.
+         */
+        public void onEvent(@NonNull String event, @Nullable Bundle args) { }
+
+        /**
+         * Called when an error occurs in the media route service.
+         *
+         * @param error The error name.
+         * @param args The error arguments, or null if none.
+         */
+        public void onError(@NonNull String error, @Nullable Bundle args) { }
+    }
+
+    /**
+     * Base class for a media route protocol stub implemented by a media route service.
+     * <p>
+     * Subclasses should extend this class to offer implementation for specialized
+     * protocols.
+     * </p><p>
+     * Instances of this class are thread-safe but requests will only be received
+     * on the handler that was specified in the constructor.
+     * </p>
+     */
+    public static abstract class Stub implements IInterface, Closeable {
+        private final Object mLock = new Object();
+
+        private final Messenger mRequestMessenger;
+        private Messenger mReplyMessenger;
+        private volatile boolean mClosed;
+
+        /**
+         * Creates an implementation of a media route protocol.
+         *
+         * @param handler The handler on which to receive requests, or null to use
+         * the current looper thread.
+         */
+        public Stub(@Nullable Handler handler) {
+            mRequestMessenger = new Messenger(new RequestHandler(this,
+                    handler != null ? handler.getLooper() : Looper.myLooper()));
+        }
+
+        /**
+         * Gets the underlying binder object for the stub.
+         */
+        @Override
+        public @NonNull IBinder asBinder() {
+            return mRequestMessenger.getBinder();
+        }
+
+        /**
+         * Closes the stub and prevents it from receiving any additional
+         * messages from the application.
+         */
+        @Override
+        public void close() {
+            synchronized (mLock) {
+                mClosed = true;
+                mReplyMessenger = null;
+            }
+        }
+
+        /**
+         * Called when the application sends a request to the media route service
+         * through this protocol.
+         * <p>
+         * The default implementation throws {@link UnsupportedOperationException}
+         * which is reported back to the application's error callback as
+         * {@link #ERROR_UNSUPPORTED_OPERATION}.
+         * </p>
+         *
+         * @param request The request name.
+         * @param args The request arguments, or null if none.
+         */
+        public void onRequest(@NonNull String request, @Nullable Bundle args)
+                throws UnsupportedOperationException {
+            throw new UnsupportedOperationException();
+        }
+
+        /**
+         * Called when the application attaches a callback to receive events and errors.
+         */
+        public void onClientAttached() {
+        }
+
+        /**
+         * Called when the application removes its callback and can no longer receive
+         * events and errors.
+         */
+        public void onClientDetached() {
+        }
+
+        /**
+         * Sends an error to the application.
+         *
+         * @param error The error name.
+         * @param args The error arguments, or null if none.
+         * @return True if the message was sent, or false if the client is not
+         * attached, cannot be reached, or if the stub has been closed.
+         */
+        public boolean sendError(@NonNull String error, @Nullable Bundle args) {
+            if (TextUtils.isEmpty(error)) {
+                throw new IllegalArgumentException("error must not be null or empty");
+            }
+
+            synchronized (mLock) {
+                Message msg = Message.obtain();
+                msg.what = REPLY_MSG_ERROR;
+                msg.obj = error;
+                msg.setData(args);
+                return replySafelyLocked(msg);
+            }
+        }
+
+        /**
+         * Sends an event to the application.
+         *
+         * @param event The event name.
+         * @param args The event arguments, or null if none.
+         * @return True if the message was sent, or false if the client is not
+         * attached, cannot be reached, or if the stub has been closed.
+         */
+        public boolean sendEvent(@NonNull String event, @Nullable Bundle args) {
+            if (TextUtils.isEmpty(event)) {
+                throw new IllegalArgumentException("event must not be null or empty");
+            }
+
+            synchronized (mLock) {
+                Message msg = Message.obtain();
+                msg.what = REPLY_MSG_EVENT;
+                msg.obj = event;
+                msg.setData(args);
+                return replySafelyLocked(msg);
+            }
+        }
+
+        private boolean replySafelyLocked(Message msg) {
+            if (mClosed) {
+                Log.w(TAG, "Could not send reply message because the stub has been closed: "
+                        + msg + ", in: " + getClass().getName());
+                return false;
+            }
+            if (mReplyMessenger == null) {
+                Log.w(TAG, "Could not send reply message because the client has not yet "
+                        + "attached a callback: " + msg + ", in: " + getClass().getName());
+                return false;
+            }
+
+            try {
+                mReplyMessenger.send(msg);
+                return true;
+            } catch (RemoteException ex) {
+                Log.w(TAG, "Could not send reply message because the client died: "
+                        + msg + ", in: " + getClass().getName());
+                return false;
+            }
+        }
+
+        private void handleRequest(Message msg) {
+            if (mClosed) { // note: racy
+                Log.w(TAG, "Dropping request because the media route service has "
+                        + "closed its end of the protocol: " + msg + ", in: " + getClass());
+                return;
+            }
+
+            // ignore unrecognized messages in case of future protocol extension
+            if (msg.what == REQUEST_MSG_COMMAND && msg.obj instanceof String) {
+                String command = (String)msg.obj;
+                try {
+                    onRequest(command, msg.peekData());
+                } catch (UnsupportedOperationException ex) {
+                    Log.w(TAG, "Client sent unsupported command request: "
+                            + msg + ", in: " + getClass());
+                    sendError(ERROR_UNSUPPORTED_OPERATION, null);
+                } catch (RuntimeException ex) {
+                    Log.e(TAG, "Stub threw runtime exception while processing command "
+                            + "request: " + msg + ", in: " + getClass());
+                    sendError(ERROR_UNKNOWN, null);
+                }
+            } else if (msg.what == REQUEST_MSG_SUBSCRIBE) {
+                synchronized (mLock) {
+                    if (mClosed) {
+                        return; // fix possible race if close() is called on another thread
+                    }
+                    mReplyMessenger = msg.replyTo;
+                }
+                if (msg.replyTo != null) {
+                    onClientAttached();
+                } else {
+                    onClientDetached();
+                }
+            }
+        }
+
+        /*
+         * Use this handler only to handle requests coming from the application
+         * because the application can send any message it wants to it.
+         */
+        private static final class RequestHandler extends Handler {
+            // break hard reference cycles through binder
+            private final WeakReference<Stub> mStubRef;
+
+            public RequestHandler(Stub stub, Looper looper) {
+                super(looper);
+                mStubRef = new WeakReference<Stub>(stub);
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                Stub stub = mStubRef.get();
+                if (stub != null) {
+                    stub.handleRequest(msg);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 83498d3..856c8d9 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,6 @@
+include ':support-annotations'
+project(':support-annotations').projectDir = new File(rootDir, 'annotations')
+
 include ':support-v4'
 project(':support-v4').projectDir = new File(rootDir, 'v4')
 
@@ -10,8 +13,17 @@
 include ':support-mediarouter-v7'
 project(':support-mediarouter-v7').projectDir = new File(rootDir, 'v7/mediarouter')
 
+include ':support-palette-v7'
+project(':support-palette-v7').projectDir = new File(rootDir, 'v7/palette')
+
+include ':support-recyclerview-v7'
+project(':support-recyclerview-v7').projectDir = new File(rootDir, 'v7/recyclerview')
+
+include ':support-cardview-v7'
+project(':support-cardview-v7').projectDir = new File(rootDir, 'v7/cardview')
+
 include ':support-v13'
 project(':support-v13').projectDir = new File(rootDir, 'v13')
 
-include ':support-annotations'
-project(':support-annotations').projectDir = new File(rootDir, 'annotations')
+include ':support-leanback-v17'
+project(':support-leanback-v17').projectDir = new File(rootDir, 'v17/leanback')
diff --git a/tests/Android.mk b/tests/Android.mk
index b14ca93..7bd3b00 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -5,7 +5,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, java)
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+LOCAL_STATIC_JAVA_LIBRARIES := mockito-target android-support-v4
 LOCAL_JAVA_LIBRARIES := android.test.runner
 LOCAL_PACKAGE_NAME := AndroidSupportTests
 
diff --git a/tests/java/android/support/v4/app/NotificationCompatWearableExtenderTest.java b/tests/java/android/support/v4/app/NotificationCompatWearableExtenderTest.java
new file mode 100644
index 0000000..c6fa124
--- /dev/null
+++ b/tests/java/android/support/v4/app/NotificationCompatWearableExtenderTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.tests.R;
+import android.test.AndroidTestCase;
+import android.view.Gravity;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for {@link android.support.v4.app.NotificationCompat.WearableExtender}.
+ */
+public class NotificationCompatWearableExtenderTest extends AndroidTestCase {
+    public static final int CUSTOM_CONTENT_HEIGHT_DP = 256;
+
+    private PendingIntent mPendingIntent;
+    private int mCustomContentHeightPx;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mPendingIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 0);
+
+        mCustomContentHeightPx = Math.round(getContext().getResources().getDisplayMetrics().density
+                * CUSTOM_CONTENT_HEIGHT_DP);
+    }
+
+    public void testEmptyEquals() throws Exception {
+        assertExtendersEqual(new Notification.WearableExtender(),
+                new NotificationCompat.WearableExtender());
+    }
+
+    public void testRealReadCompatEmptyValue() throws Exception {
+        NotificationCompat.WearableExtender compatExtender =
+                new NotificationCompat.WearableExtender();
+        Notification notif = new NotificationCompat.Builder(getContext())
+                .extend(compatExtender)
+                .build();
+
+        assertExtendersEqual(new Notification.WearableExtender(notif), compatExtender);
+        assertExtendersEqual(new Notification.WearableExtender(notif),
+                new NotificationCompat.WearableExtender(notif));
+    }
+
+    public void testCompatReadRealEmptyValue() throws Exception {
+        Notification.WearableExtender realExtender =
+                new Notification.WearableExtender();
+        Notification notif = new Notification.Builder(getContext())
+                .extend(realExtender)
+                .build();
+
+        assertExtendersEqual(realExtender, new NotificationCompat.WearableExtender(notif));
+        assertExtendersEqual(new Notification.WearableExtender(notif),
+                new NotificationCompat.WearableExtender(notif));
+    }
+
+    public void testRealReadCompatValue() throws Exception {
+        RemoteInput.Builder remoteInput = new RemoteInput.Builder("result_key1")
+                .setLabel("label")
+                .setChoices(new CharSequence[] {"choice 1", "choice 2"});
+        remoteInput.getExtras().putString("remoteinput_string", "test");
+        NotificationCompat.Action.Builder action2 = new NotificationCompat.Action.Builder(
+                R.drawable.action_icon, "Test title", mPendingIntent)
+                .addRemoteInput(remoteInput.build())
+                .extend(new NotificationCompat.Action.WearableExtender()
+                        .setAvailableOffline(false));
+        // Add an arbitrary key/value.
+        action2.getExtras().putFloat("action_float", 10.5f);
+
+        Notification page2 = new Notification.Builder(getContext())
+                .setContentTitle("page2 title")
+                .extend(new Notification.WearableExtender()
+                        .setContentIcon(R.drawable.content_icon))
+                .build();
+
+        NotificationCompat.WearableExtender compatExtender =
+                new NotificationCompat.WearableExtender()
+                        .addAction(new NotificationCompat.Action(R.drawable.action_icon2, "Action1",
+                                mPendingIntent))
+                        .addAction(action2.build())
+                        .setContentIntentAvailableOffline(false)
+                        .setHintHideIcon(true)
+                        .setHintShowBackgroundOnly(true)
+                        .setStartScrollBottom(true)
+                        .setDisplayIntent(mPendingIntent)
+                        .addPage(page2)
+                        .setContentIcon(R.drawable.content_icon2)
+                        .setContentIconGravity(Gravity.START)
+                        .setContentAction(5 /* arbitrary content action index */)
+                        .setCustomSizePreset(NotificationCompat.WearableExtender.SIZE_MEDIUM)
+                        .setCustomContentHeight(mCustomContentHeightPx)
+                        .setGravity(Gravity.TOP);
+
+        Notification notif = new NotificationCompat.Builder(getContext())
+                .extend(compatExtender).build();
+        assertExtendersEqual(new Notification.WearableExtender(notif), compatExtender);
+        assertExtendersEqual(new Notification.WearableExtender(notif),
+                new NotificationCompat.WearableExtender(notif));
+    }
+
+    public void testCompatReadRealValue() throws Exception {
+        android.app.RemoteInput.Builder remoteInput = new android.app.RemoteInput.Builder(
+                "result_key1")
+                .setLabel("label")
+                .setChoices(new CharSequence[] {"choice 1", "choice 2"});
+        remoteInput.getExtras().putString("remoteinput_string", "test");
+        Notification.Action.Builder action2 = new Notification.Action.Builder(
+                R.drawable.action_icon, "Test title", mPendingIntent)
+                .addRemoteInput(remoteInput.build())
+                .extend(new Notification.Action.WearableExtender()
+                        .setAvailableOffline(false));
+        // Add an arbitrary key/value.
+        action2.getExtras().putFloat("action_float", 10.5f);
+
+        Notification page2 = new Notification.Builder(getContext())
+                .setContentTitle("page2 title")
+                .extend(new Notification.WearableExtender()
+                        .setContentIcon(R.drawable.content_icon))
+                .build();
+
+        Notification.WearableExtender realExtender =
+                new Notification.WearableExtender()
+                        .addAction(new Notification.Action(R.drawable.action_icon2, "Action1",
+                                mPendingIntent))
+                        .addAction(action2.build())
+                        .setContentIntentAvailableOffline(false)
+                        .setHintHideIcon(true)
+                        .setHintShowBackgroundOnly(true)
+                        .setStartScrollBottom(true)
+                        .setDisplayIntent(mPendingIntent)
+                        .addPage(page2)
+                        .setContentIcon(R.drawable.content_icon2)
+                        .setContentIconGravity(Gravity.START)
+                        .setContentAction(5 /* arbitrary content action index */)
+                        .setCustomSizePreset(NotificationCompat.WearableExtender.SIZE_MEDIUM)
+                        .setCustomContentHeight(mCustomContentHeightPx)
+                        .setGravity(Gravity.TOP);
+
+        Notification notif = new Notification.Builder(getContext())
+                .extend(realExtender).build();
+        assertExtendersEqual(realExtender, new NotificationCompat.WearableExtender(notif));
+        assertExtendersEqual(new Notification.WearableExtender(notif),
+                new NotificationCompat.WearableExtender(notif));
+    }
+
+    private void assertExtendersEqual(Notification.WearableExtender real,
+            NotificationCompat.WearableExtender compat) {
+        assertActionsEquals(real.getActions(), compat.getActions());
+        assertEquals(real.getContentIntentAvailableOffline(),
+                compat.getContentIntentAvailableOffline());
+        assertEquals(real.getHintHideIcon(), compat.getHintHideIcon());
+        assertEquals(real.getHintShowBackgroundOnly(), compat.getHintShowBackgroundOnly());
+        assertEquals(real.getStartScrollBottom(), compat.getStartScrollBottom());
+        assertEquals(real.getDisplayIntent(), compat.getDisplayIntent());
+        assertPagesEquals(real.getPages(), compat.getPages());
+        assertEquals(real.getBackground(), compat.getBackground());
+        assertEquals(real.getContentIcon(), compat.getContentIcon());
+        assertEquals(real.getContentIconGravity(), compat.getContentIconGravity());
+        assertEquals(real.getContentAction(), compat.getContentAction());
+        assertEquals(real.getCustomSizePreset(), compat.getCustomSizePreset());
+        assertEquals(real.getCustomContentHeight(), compat.getCustomContentHeight());
+        assertEquals(real.getGravity(), compat.getGravity());
+    }
+
+    private void assertPagesEquals(List<Notification> pages1, List<Notification> pages2) {
+        assertEquals(pages1.size(), pages2.size());
+        for (int i = 0; i < pages1.size(); i++) {
+            assertNotificationsEqual(pages1.get(i), pages2.get(i));
+        }
+    }
+
+    private void assertNotificationsEqual(Notification n1, Notification n2) {
+        assertEquals(n1.icon, n2.icon);
+        assertBundlesEqual(n1.extras, n2.extras);
+        assertExtendersEqual(new Notification.WearableExtender(n1),
+                new NotificationCompat.WearableExtender(n2));
+    }
+
+    private void assertActionsEquals(List<Notification.Action> realArray,
+            List<NotificationCompat.Action> compatArray) {
+        assertEquals(realArray.size(), compatArray.size());
+        for (int i = 0; i < realArray.size(); i++) {
+            assertActionsEqual(realArray.get(i), compatArray.get(i));
+        }
+    }
+
+    private void assertActionsEqual(Notification.Action real, NotificationCompat.Action compat) {
+        assertEquals(real.icon, compat.icon);
+        assertEquals(real.title, compat.title);
+        assertEquals(real.actionIntent, compat.actionIntent);
+        assertRemoteInputsEquals(real.getRemoteInputs(), compat.getRemoteInputs());
+        assertBundlesEqual(real.getExtras(), compat.getExtras());
+    }
+
+    private void assertRemoteInputsEquals(android.app.RemoteInput[] realArray,
+            RemoteInput[] compatArray) {
+        assertEquals(realArray == null, compatArray == null);
+        if (realArray != null) {
+            assertEquals(realArray.length, compatArray.length);
+            for (int i = 0; i < realArray.length; i++) {
+                assertRemoteInputsEqual(realArray[i], compatArray[i]);
+            }
+        }
+    }
+
+    private void assertRemoteInputsEqual(android.app.RemoteInput real,
+            RemoteInput compat) {
+        assertEquals(real.getResultKey(), compat.getResultKey());
+        assertEquals(real.getLabel(), compat.getLabel());
+        assertCharSequencesEquals(real.getChoices(), compat.getChoices());
+        assertEquals(real.getAllowFreeFormInput(), compat.getAllowFreeFormInput());
+        assertBundlesEqual(real.getExtras(), compat.getExtras());
+    }
+
+    private void assertCharSequencesEquals(CharSequence[] array1, CharSequence[] array2) {
+        if (!Arrays.deepEquals(array1, array2)) {
+            fail("Arrays not equal: " + Arrays.toString(array1) + " != " + Arrays.toString(array2));
+        }
+    }
+
+    private void assertBundlesEqual(Bundle bundle1, Bundle bundle2) {
+        assertEquals(bundle1.size(), bundle2.size());
+        for (String key : bundle1.keySet()) {
+            assertEquals(bundle1.get(key), bundle2.get(key));
+        }
+    }
+}
diff --git a/tests/res/drawable/action_icon.xml b/tests/res/drawable/action_icon.xml
new file mode 100644
index 0000000..f38b709
--- /dev/null
+++ b/tests/res/drawable/action_icon.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" />
diff --git a/tests/res/drawable/action_icon2.xml b/tests/res/drawable/action_icon2.xml
new file mode 100644
index 0000000..f38b709
--- /dev/null
+++ b/tests/res/drawable/action_icon2.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" />
diff --git a/tests/res/drawable/content_icon.xml b/tests/res/drawable/content_icon.xml
new file mode 100644
index 0000000..f38b709
--- /dev/null
+++ b/tests/res/drawable/content_icon.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" />
diff --git a/tests/res/drawable/content_icon2.xml b/tests/res/drawable/content_icon2.xml
new file mode 100644
index 0000000..f38b709
--- /dev/null
+++ b/tests/res/drawable/content_icon2.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android" />
diff --git a/v13/AndroidManifest.xml b/v13/AndroidManifest.xml
new file mode 100644
index 0000000..c862c99
--- /dev/null
+++ b/v13/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.support.v13">
+    <application />
+</manifest>
diff --git a/v13/build.gradle b/v13/build.gradle
index 9f651ee..851f6ca 100644
--- a/v13/build.gradle
+++ b/v13/build.gradle
@@ -1,33 +1,119 @@
-apply plugin: 'java'
+apply plugin: 'android-library'
 
 archivesBaseName = 'support-v13'
 
-sourceSets {
-    main.java.srcDir 'java'
-    ics.java.srcDir 'ics'
-    icsmr1.java.srcDir 'ics-mr1'
-    k.java.srcDir 'k'
+// --------------------------
+// TO ADD NEW PLATFORM SPECIFIC CODE, UPDATE THIS:
+// create and configure the sourcesets/dependencies for platform-specific code.
+// values are: sourceset name, source folder name, api level, previous sourceset.
+
+ext.allSS = []
+
+def icsSS          = createApiSourceset('ics',          'ics',           '14',      null)
+def icsMr1SS       = createApiSourceset('icsmr1',       'ics-mr1',       '15',      icsSS)
+
+def createApiSourceset(String name, String folder, String apiLevel, SourceSet previousSource) {
+    def sourceSet = sourceSets.create(name)
+    sourceSet.java.srcDirs = [folder]
+
+    def configName = sourceSet.getCompileConfigurationName()
+
+    project.getDependencies().add(configName, getAndroidPrebuilt(apiLevel))
+    if (previousSource != null) {
+        setupDependencies(configName, previousSource)
+    }
+    ext.allSS.add(sourceSet)
+    return sourceSet
+}
+
+def setupDependencies(String configName, SourceSet previousSourceSet) {
+    project.getDependencies().add(configName, previousSourceSet.output)
+    project.getDependencies().add(configName, previousSourceSet.compileClasspath)
+}
+
+// create a jar task for the code above
+tasks.create(name: "internalJar", type: Jar) {
+    baseName "internal_impl"
+}
+
+ext.allSS.each { ss ->
+    internalJar.from ss.output
 }
 
 dependencies {
-    icsCompile getAndroidPrebuilt('14')
-
-    icsmr1Compile getAndroidPrebuilt('15')
-
-    kCompile getAndroidPrebuilt('current')
-
-    // order is important as we need the API 13 before the API 4 so that it uses the latest one.
-    compile getAndroidPrebuilt('13')
     compile project(':support-v4')
-    compile sourceSets.ics.output
-    compile sourceSets.icsmr1.output
-    compile sourceSets.k.output
+
+    // add the internal implementation as a dependency.
+    // this is not enough to make the regular compileJava task
+    // depend on the generation of this jar. This is done below
+    // when manipulating the libraryVariants.
+    compile files(internalJar.archivePath)
 }
 
-jar {
-    from sourceSets.ics.output
-    from sourceSets.icsmr1.output
-    from sourceSets.k.output
+android {
+    compileSdkVersion 13
+    buildToolsVersion "19.0.1"
+
+    defaultConfig {
+        minSdkVersion 13
+        // TODO: get target from branch
+        //targetSdkVersion 19
+    }
+
+
+    sourceSets {
+        main.manifest.srcFile 'AndroidManifest.xml'
+        main.java.srcDirs = ['java']
+        main.aidl.srcDirs = ['java']
+
+        androidTest.setRoot('tests')
+        androidTest.java.srcDir 'tests/java'
+    }
+
+    lintOptions {
+        // TODO: fix errors and reenable.
+        abortOnError false
+    }
+}
+
+android.libraryVariants.all { variant ->
+    variant.javaCompile.dependsOn internalJar
+
+    def name = variant.buildType.name
+
+    if (name.equals(com.android.builder.BuilderConstants.DEBUG)) {
+        return; // Skip debug builds.
+    }
+    def suffix = name.capitalize()
+
+    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
+        dependsOn variant.javaCompile
+        from variant.javaCompile.destinationDir
+        from 'LICENSE.txt'
+    }
+    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
+        source android.sourceSets.main.allJava
+        classpath = files(variant.javaCompile.classpath.files) + files(
+                "${android.plugin.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
+    }
+
+    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
+        classifier = 'javadoc'
+        from 'build/docs/javadoc'
+    }
+
+    def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
+        classifier = 'sources'
+        from android.sourceSets.main.allSource
+    }
+
+    project.ext.allSS.each { ss ->
+        javadocTask.source ss.allJava
+        sourcesJarTask.from ss.allSource
+    }
+
+    artifacts.add('archives', javadocJarTask);
+    artifacts.add('archives', sourcesJarTask);
 }
 
 uploadArchives {
@@ -64,32 +150,3 @@
         }
     }
 }
-
-// configuration for the javadoc to include all source sets.
-javadoc {
-    source    sourceSets.main.allJava
-    source    sourceSets.ics.allJava
-    source    sourceSets.icsmr1.allJava
-    source    sourceSets.k.allJava
-}
-
-// custom tasks for creating source/javadoc jars
-task sourcesJar(type: Jar, dependsOn:classes) {
-    classifier = 'sources'
-    from sourceSets.main.allSource
-    from sourceSets.ics.allSource
-    from sourceSets.icsmr1.allSource
-    from sourceSets.k.allSource
-}
-
-task javadocJar(type: Jar, dependsOn:javadoc) {
-    classifier         'javadoc'
-    from               javadoc.destinationDir
-}
-
-// add javadoc/source jar tasks as artifacts
-artifacts {
-    archives jar
-    archives sourcesJar
-    archives javadocJar
-}
diff --git a/v17/Android.mk b/v17/Android.mk
new file mode 100644
index 0000000..14ff0aa
--- /dev/null
+++ b/v17/Android.mk
@@ -0,0 +1,16 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/v17/leanback/.classpath b/v17/leanback/.classpath
new file mode 100644
index 0000000..7bc01d9
--- /dev/null
+++ b/v17/leanback/.classpath
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="gen"/>
+	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
+	<classpathentry kind="output" path="bin/classes"/>
+</classpath>
diff --git a/v17/leanback/.gitignore b/v17/leanback/.gitignore
new file mode 100644
index 0000000..c3c25e0
--- /dev/null
+++ b/v17/leanback/.gitignore
@@ -0,0 +1,4 @@
+.settings
+bin
+libs
+gen
diff --git a/v17/leanback/.project b/v17/leanback/.project
new file mode 100644
index 0000000..9d191cd
--- /dev/null
+++ b/v17/leanback/.project
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>android-support-v17-leanback</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ApkBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/v17/leanback/Android.mk b/v17/leanback/Android.mk
new file mode 100644
index 0000000..f64ca8e
--- /dev/null
+++ b/v17/leanback/Android.mk
@@ -0,0 +1,166 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+# Build the resources using the current SDK version.
+# We do this here because the final static library must be compiled with an older
+# SDK version than the resources.  The resources library and the R class that it
+# contains will not be linked into the final static library.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v17-leanback-res
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_AAPT_FLAGS := \
+        --auto-add-overlay
+LOCAL_JAR_EXCLUDE_FILES := none
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------------------------------------------------------------------
+
+#  Base sub-library contains classes both needed by api-level specific libraries
+#  (e.g. KitKat) and final static library.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v17-leanback-common
+LOCAL_SDK_VERSION := 17
+LOCAL_SRC_FILES := $(call all-java-files-under, common)
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------------------------------------------------------------------
+
+#  A helper sub-library that makes direct use of API 21.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v17-leanback-api21
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, api21)
+LOCAL_JAVA_LIBRARIES := android-support-v17-leanback-res android-support-v17-leanback-common
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------------------------------------------------------------------
+
+#  A helper sub-library that makes direct use of KitKat APIs.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v17-leanback-kitkat
+LOCAL_SDK_VERSION := 19
+LOCAL_SRC_FILES := $(call all-java-files-under, kitkat)
+LOCAL_JAVA_LIBRARIES := android-support-v17-leanback-res android-support-v17-leanback-common
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------------------------------------------------------------------
+
+#  A helper sub-library that makes direct use of JBMR2 APIs.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v17-leanback-jbmr2
+LOCAL_SDK_VERSION := 18
+LOCAL_SRC_FILES := $(call all-java-files-under, jbmr2)
+LOCAL_JAVA_LIBRARIES := android-support-v17-leanback-res android-support-v17-leanback-common
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------------------------------------------------------------------
+
+# Here is the final static library that apps can link against.
+# The R class is automatically excluded from the generated library.
+# Applications that use this library must specify LOCAL_RESOURCE_DIR
+# in their makefiles to include the resources in their package.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v17-leanback
+LOCAL_SDK_VERSION := 17
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v17-leanback-kitkat android-support-v17-leanback-jbmr2 \
+        android-support-v17-leanback-api21 android-support-v17-leanback-common
+LOCAL_JAVA_LIBRARIES := \
+        android-support-v4 \
+        android-support-v7-recyclerview \
+        android-support-v17-leanback-res
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+
+# ===========================================================
+# Common Droiddoc vars
+leanback.docs.src_files := \
+    $(call all-java-files-under, src) \
+    $(call all-html-files-under, src)
+leanback.docs.java_libraries := \
+    android-support-v4 \
+    android-support-v7-recyclerview \
+    android-support-v17-leanback-res \
+    android-support-v17-leanback
+
+# Documentation
+# ===========================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := android-support-v17-leanback
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_MODULE_TAGS := optional
+
+intermediates.COMMON := $(call intermediates-dir-for,$(LOCAL_MODULE_CLASS),android-support-v17-leanback,,COMMON)
+
+LOCAL_SRC_FILES := $(leanback.docs.src_files)
+LOCAL_ADDITONAL_JAVA_DIR := $(intermediates.COMMON)/src
+
+LOCAL_SDK_VERSION := 19
+LOCAL_IS_HOST_MODULE := false
+LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR := build/tools/droiddoc/templates-sdk
+
+LOCAL_JAVA_LIBRARIES := $(leanback.docs.java_libraries)
+
+LOCAL_DROIDDOC_OPTIONS := \
+    -offlinemode \
+    -hdf android.whichdoc offline \
+    -federate Android http://developer.android.com \
+    -federationapi Android prebuilts/sdk/api/17.txt \
+    -hide 113
+
+include $(BUILD_DROIDDOC)
+
+# Stub source files
+# ===========================================================
+
+leanback_internal_api_file := $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/android-support-v17-leanback_api.txt
+leanback.docs.stubpackages := android.support.v17.leanback:android.support.v17.leanback.app:android.support.v17.leanback.database:android.support.v17.leanback.widget
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := android-support-v17-leanback-stubs
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(leanback.docs.src_files)
+LOCAL_JAVA_LIBRARIES := $(leanback.docs.java_libraries)
+LOCAL_SDK_VERSION := current
+
+LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR := build/tools/droiddoc/templates-sdk
+LOCAL_UNINSTALLABLE_MODULE := true
+
+LOCAL_DROIDDOC_OPTIONS := \
+    -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android-support-v17-leanback-stubs_intermediates/src \
+    -stubpackages $(leanback.docs.stubpackages) \
+    -api $(leanback_internal_api_file) \
+    -hide 113 \
+    -nodocs
+
+include $(BUILD_DROIDDOC)
+leanback_stubs_stamp := $(full_target)
+$(leanback_internal_api_file) : $(full_target)
+
+# Cleanup temp vars
+# ===========================================================
+leanback.docs.src_files :=
+leanback.docs.java_libraries :=
+intermediates.COMMON :=
+leanback_internal_api_file :=
+leanback_stubs_stamp :=
+leanback.docs.stubpackages :=
diff --git a/v17/leanback/AndroidManifest.xml b/v17/leanback/AndroidManifest.xml
new file mode 100644
index 0000000..20ef094
--- /dev/null
+++ b/v17/leanback/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.support.v17.leanback">
+    <uses-sdk android:minSdkVersion="17"/>
+    <application />
+</manifest>
diff --git a/v17/leanback/README.txt b/v17/leanback/README.txt
new file mode 100644
index 0000000..f3dbe92
--- /dev/null
+++ b/v17/leanback/README.txt
@@ -0,0 +1 @@
+Library Project including Leanback framework support.
diff --git a/v17/leanback/api21/android/support/v17/leanback/transition/TransitionHelperApi21.java b/v17/leanback/api21/android/support/v17/leanback/transition/TransitionHelperApi21.java
new file mode 100644
index 0000000..6f18dd4
--- /dev/null
+++ b/v17/leanback/api21/android/support/v17/leanback/transition/TransitionHelperApi21.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.transition;
+
+import android.view.Window;
+
+final class TransitionHelperApi21 {
+
+    TransitionHelperApi21() {
+    }
+
+    public Object getSharedElementEnterTransition(Window window) {
+        return window.getSharedElementEnterTransition();
+    }
+
+    public Object getSharedElementReturnTransition(Window window) {
+        return window.getSharedElementReturnTransition();
+    }
+
+    public Object getSharedElementExitTransition(Window window) {
+        return window.getSharedElementExitTransition();
+    }
+
+    public Object getSharedElementReenterTransition(Window window) {
+        return window.getSharedElementReenterTransition();
+    }
+
+    public Object getEnterTransition(Window window) {
+        return window.getEnterTransition();
+    }
+
+    public Object getReturnTransition(Window window) {
+        return window.getReturnTransition();
+    }
+
+    public Object getExitTransition(Window window) {
+        return window.getExitTransition();
+    }
+
+    public Object getReenterTransition(Window window) {
+        return window.getReenterTransition();
+    }
+}
diff --git a/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java b/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java
new file mode 100644
index 0000000..4b96063
--- /dev/null
+++ b/v17/leanback/api21/android/support/v17/leanback/widget/ShadowHelperApi21.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.support.v17.leanback.R;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.Outline;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.ViewGroup;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+class ShadowHelperApi21 {
+
+    static int sNormalZ = Integer.MIN_VALUE;
+    static int sFocusedZ;
+    static final ViewOutlineProvider sOutlineProvider = new ViewOutlineProvider() {
+        @Override
+        public void getOutline(View view, Outline outline) {
+            outline.setRect(0, 0, view.getWidth(), view.getHeight());
+            outline.setAlpha(1.0f);
+        }
+    };
+
+    private static void initializeResources(Resources res) {
+        if (sNormalZ == Integer.MIN_VALUE) {
+            sNormalZ = (int) res.getDimension(R.dimen.lb_material_shadow_normal_z);
+            sFocusedZ = (int) res.getDimension(R.dimen.lb_material_shadow_focused_z);
+        }
+    }
+
+    /* add shadows and return a implementation detail object */
+    public static Object addShadow(ViewGroup shadowContainer) {
+        initializeResources(shadowContainer.getResources());
+        shadowContainer.setOutlineProvider(sOutlineProvider);
+        shadowContainer.setZ(sNormalZ);
+        return shadowContainer;
+    }
+
+    /* set shadow focus level 0 for unfocused 1 for fully focused */
+    public static void setShadowFocusLevel(Object impl, float level) {
+        ViewGroup shadowContainer = (ViewGroup) impl;
+        shadowContainer.setZ(sNormalZ + level * (sFocusedZ - sNormalZ));
+    }
+
+    public static void setZ(View view, float level) {
+        initializeResources(view.getResources());
+        view.setZ(sNormalZ + level * (sFocusedZ - sNormalZ));
+    }
+
+    public static void clearZ(View view) {
+        view.setZ(0);
+    }
+}
diff --git a/v17/leanback/build.gradle b/v17/leanback/build.gradle
new file mode 100644
index 0000000..f1e04a1
--- /dev/null
+++ b/v17/leanback/build.gradle
@@ -0,0 +1,103 @@
+apply plugin: 'android-library'
+
+archivesBaseName = 'leanback-v17'
+
+dependencies {
+    compile project(':support-v4')
+    compile project(':support-recyclerview-v7')
+}
+
+android {
+    // WARNING: should be 17
+    compileSdkVersion 'current'
+
+    buildToolsVersion "19.0.1"
+
+    defaultConfig {
+        minSdkVersion 17
+        // TODO: get target from branch
+        //targetSdkVersion 19
+    }
+
+    sourceSets {
+        main.manifest.srcFile 'AndroidManifest.xml'
+        main.java.srcDirs = ['common', 'jbmr2', 'kitkat', 'api21', 'src']
+        main.aidl.srcDirs = ['common', 'jbmr2', 'kitkat', 'api21', 'src']
+        main.res.srcDirs = ['res']
+
+        androidTest.setRoot('tests')
+        androidTest.java.srcDir 'tests/java'
+    }
+
+    lintOptions {
+        // TODO: fix errors and reenable.
+        abortOnError false
+    }
+}
+
+android.libraryVariants.all { variant ->
+    def name = variant.buildType.name
+
+    if (name.equals(com.android.builder.BuilderConstants.DEBUG)) {
+        return; // Skip debug builds.
+    }
+    def suffix = name.capitalize()
+
+    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
+        dependsOn variant.javaCompile
+        from variant.javaCompile.destinationDir
+        from 'LICENSE.txt'
+    }
+    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
+        source android.sourceSets.main.allJava
+        classpath = files(variant.javaCompile.classpath.files) + files(
+                "${android.plugin.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
+    }
+
+    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
+        classifier = 'javadoc'
+        from 'build/docs/javadoc'
+    }
+
+    def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
+        classifier = 'sources'
+        from android.sourceSets.main.allSource
+    }
+
+    artifacts.add('archives', javadocJarTask);
+    artifacts.add('archives', sourcesJarTask);
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri(rootProject.ext.supportRepoOut)) {
+            }
+
+            pom.project {
+                name 'Android Support Leanback v17'
+                description "Android Support Leanback v17"
+                url 'http://developer.android.com/tools/extras/support-library.html'
+                inceptionYear '2011'
+
+                licenses {
+                    license {
+                        name 'The Apache Software License, Version 2.0'
+                        url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+                        distribution 'repo'
+                    }
+                }
+
+                scm {
+                    url "http://source.android.com"
+                    connection "scm:git:https://android.googlesource.com/platform/frameworks/support"
+                }
+                developers {
+                    developer {
+                        name 'The Android Open Source Project'
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/v17/leanback/common/android/support/v17/leanback/transition/SlideCallback.java b/v17/leanback/common/android/support/v17/leanback/transition/SlideCallback.java
new file mode 100644
index 0000000..3a4529c
--- /dev/null
+++ b/v17/leanback/common/android/support/v17/leanback/transition/SlideCallback.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.transition;
+
+import android.view.View;
+
+/**
+ * Used by Slide to determine the slide edge and distance when it is about to
+ * create animator.
+ */
+interface SlideCallback {
+
+    /**
+     * Called when Slide is about to create animator for an appearing/disappearing view.
+     * Callback returns true to ask Slide to create animator, edge is returned
+     * in edge[0], distance in pixels is returned in distance[0].  Slide will not
+     * create animator if callback returns false.
+     */
+    public boolean getSlide(View view, boolean appear, int[] edge, float[] distance);
+
+}
diff --git a/v17/leanback/common/android/support/v17/leanback/transition/TransitionListener.java b/v17/leanback/common/android/support/v17/leanback/transition/TransitionListener.java
new file mode 100644
index 0000000..80f05ed
--- /dev/null
+++ b/v17/leanback/common/android/support/v17/leanback/transition/TransitionListener.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.transition;
+
+/**
+ * Listeners for transition start and stop.
+ * @hide
+ */
+public class TransitionListener {
+
+    public void onTransitionStart(Object transition) {
+    }
+
+    public void onTransitionEnd(Object transition) {
+    }
+
+}
diff --git a/v17/leanback/jbmr2/android/support/v17/leanback/widget/ShadowHelperJbmr2.java b/v17/leanback/jbmr2/android/support/v17/leanback/widget/ShadowHelperJbmr2.java
new file mode 100644
index 0000000..ad53425
--- /dev/null
+++ b/v17/leanback/jbmr2/android/support/v17/leanback/widget/ShadowHelperJbmr2.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.support.v17.leanback.R;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+class ShadowHelperJbmr2 {
+
+    static class ShadowImpl {
+        View mNormalShadow;
+        View mFocusShadow;
+    }
+
+    /* prepare parent for allowing shadows of a child */
+    public static void prepareParent(ViewGroup parent) {
+        parent.setLayoutMode(ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS);
+    }
+
+    /* add shadows and return a implementation detail object */
+    public static Object addShadow(ViewGroup shadowContainer) {
+        shadowContainer.setLayoutMode(ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS);
+        LayoutInflater inflater = LayoutInflater.from(shadowContainer.getContext());
+        inflater.inflate(R.layout.lb_shadow, shadowContainer, true);
+        ShadowImpl impl = new ShadowImpl();
+        impl.mNormalShadow = shadowContainer.findViewById(R.id.lb_shadow_normal);
+        impl.mFocusShadow = shadowContainer.findViewById(R.id.lb_shadow_focused);
+        return impl;
+    }
+
+    /* set shadow focus level 0 for unfocused 1 for fully focused */
+    public static void setShadowFocusLevel(Object impl, float level) {
+        ShadowImpl shadowImpl = (ShadowImpl) impl;
+        shadowImpl.mNormalShadow.setAlpha(1 - level);
+        shadowImpl.mFocusShadow.setAlpha(level);
+    }
+}
diff --git a/v17/leanback/kitkat/android/support/v17/leanback/transition/Slide.java b/v17/leanback/kitkat/android/support/v17/leanback/transition/Slide.java
new file mode 100644
index 0000000..1a557f6
--- /dev/null
+++ b/v17/leanback/kitkat/android/support/v17/leanback/transition/Slide.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v17.leanback.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.graphics.Rect;
+import android.util.Log;
+import android.util.Property;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.transition.Visibility;
+import android.transition.Transition;
+import android.transition.TransitionValues;
+
+/**
+ * Slide distance toward/from a edge.  The direction and distance are determined by
+ * {@link SlideCallback}.
+ */
+class Slide extends Visibility {
+    private static final String TAG = "Slide";
+
+    /**
+     * Move Views in or out of the left edge of the scene.
+     * @see #setSlideEdge(int)
+     */
+    public static final int LEFT = 0;
+
+    /**
+     * Move Views in or out of the top edge of the scene.
+     * @see #setSlideEdge(int)
+     */
+    public static final int TOP = 1;
+
+    /**
+     * Move Views in or out of the right edge of the scene.
+     * @see #setSlideEdge(int)
+     */
+    public static final int RIGHT = 2;
+
+    /**
+     * Move Views in or out of the bottom edge of the scene. This is the
+     * default slide direction.
+     * @see #setSlideEdge(int)
+     */
+    public static final int BOTTOM = 3;
+
+    private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
+    private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
+
+    private int[] mTempLoc = new int[2];
+    SlideCallback mCallback;
+    private int[] mTempEdge = new int[1];
+    private float[] mTempDistance = new float[1];
+
+    private interface CalculateSlide {
+        /** Returns the translation value for view when it out of the scene */
+        float getGone(float slide, View view);
+
+        /** Returns the translation value for view when it is in the scene */
+        float getHere(View view);
+
+        /** Returns the property to animate translation */
+        Property<View, Float> getProperty();
+    }
+
+    private static abstract class CalculateSlideHorizontal implements CalculateSlide {
+        @Override
+        public float getHere(View view) {
+            return view.getTranslationX();
+        }
+
+        @Override
+        public Property<View, Float> getProperty() {
+            return View.TRANSLATION_X;
+        }
+    }
+
+    private static abstract class CalculateSlideVertical implements CalculateSlide {
+        @Override
+        public float getHere(View view) {
+            return view.getTranslationY();
+        }
+
+        @Override
+        public Property<View, Float> getProperty() {
+            return View.TRANSLATION_Y;
+        }
+    }
+
+    private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() {
+        @Override
+        public float getGone(float distance, View view) {
+            return view.getTranslationX() - distance;
+        }
+    };
+
+    private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() {
+        @Override
+        public float getGone(float distance, View view) {
+            return view.getTranslationY() - distance;
+        }
+    };
+
+    private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() {
+        @Override
+        public float getGone(float distance, View view) {
+            return view.getTranslationX() + distance;
+        }
+    };
+
+    private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {
+        @Override
+        public float getGone(float distance, View view) {
+            return view.getTranslationY() + distance;
+        }
+    };
+
+    public Slide() {
+    }
+
+    public void setCallback(SlideCallback callback) {
+        mCallback = callback;
+    }
+
+    private CalculateSlide getSlideEdge(int slideEdge) {
+        switch (slideEdge) {
+            case LEFT:
+                return sCalculateLeft;
+            case TOP:
+                return sCalculateTop;
+            case RIGHT:
+                return sCalculateRight;
+            case BOTTOM:
+                return sCalculateBottom;
+            default:
+                throw new IllegalArgumentException("Invalid slide direction");
+        }
+    }
+
+    private Animator createAnimation(final View view, Property<View, Float> property,
+            float start, float end, float terminalValue, TimeInterpolator interpolator) {
+        view.setTranslationY(start);
+        if (start == end) {
+            return null;
+        }
+        final ObjectAnimator anim = ObjectAnimator.ofFloat(view, property, start, end);
+
+        SlideAnimatorListener listener = new SlideAnimatorListener(view, terminalValue, end);
+        anim.addListener(listener);
+        anim.addPauseListener(listener);
+        anim.setInterpolator(interpolator);
+        return anim;
+    }
+
+    @Override
+    public Animator onAppear(ViewGroup sceneRoot,
+            TransitionValues startValues, int startVisibility,
+            TransitionValues endValues, int endVisibility) {
+        View view = (endValues != null) ? endValues.view : null;
+        if (view == null) {
+            return null;
+        }
+        if (mCallback == null || !mCallback.getSlide(view, true, mTempEdge, mTempDistance)) {
+            return null;
+        }
+        final CalculateSlide slideCalculator = getSlideEdge(mTempEdge[0]);
+        float end = slideCalculator.getHere(view);
+        float start = slideCalculator.getGone(mTempDistance[0], view);
+        return createAnimation(view, slideCalculator.getProperty(), start, end, end, sDecelerate);
+    }
+
+    @Override
+    public Animator onDisappear(ViewGroup sceneRoot,
+            TransitionValues startValues, int startVisibility,
+            TransitionValues endValues, int endVisibility) {
+        View view = (startValues != null) ? startValues.view : null;
+        if (view == null) {
+            return null;
+        }
+        if (mCallback == null || !mCallback.getSlide(view, false, mTempEdge, mTempDistance)) {
+            return null;
+        }
+        final CalculateSlide slideCalculator = getSlideEdge(mTempEdge[0]);
+        float start = slideCalculator.getHere(view);
+        float end = slideCalculator.getGone(mTempDistance[0], view);
+
+        return createAnimation(view, slideCalculator.getProperty(), start, end, start,
+                sAccelerate);
+    }
+
+    private static class SlideAnimatorListener extends AnimatorListenerAdapter {
+        private boolean mCanceled = false;
+        private float mPausedY;
+        private final View mView;
+        private final float mEndY;
+        private final float mTerminalY;
+
+        public SlideAnimatorListener(View view, float terminalY, float endY) {
+            mView = view;
+            mTerminalY = terminalY;
+            mEndY = endY;
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animator) {
+            mView.setTranslationY(mTerminalY);
+            mCanceled = true;
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animator) {
+            if (!mCanceled) {
+                mView.setTranslationY(mTerminalY);
+            }
+        }
+
+        @Override
+        public void onAnimationPause(Animator animator) {
+            mPausedY = mView.getTranslationY();
+            mView.setTranslationY(mEndY);
+        }
+
+        @Override
+        public void onAnimationResume(Animator animator) {
+            mView.setTranslationY(mPausedY);
+        }
+    }
+}
\ No newline at end of file
diff --git a/v17/leanback/kitkat/android/support/v17/leanback/transition/TransitionHelperKitkat.java b/v17/leanback/kitkat/android/support/v17/leanback/transition/TransitionHelperKitkat.java
new file mode 100644
index 0000000..caa94d0
--- /dev/null
+++ b/v17/leanback/kitkat/android/support/v17/leanback/transition/TransitionHelperKitkat.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.transition;
+
+import android.animation.Animator;
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.transition.AutoTransition;
+import android.transition.ChangeBounds;
+import android.transition.Fade;
+import android.transition.Scene;
+import android.transition.Transition;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.transition.TransitionValues;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+final class TransitionHelperKitkat {
+
+    TransitionHelperKitkat() {
+    }
+
+    Object createScene(ViewGroup sceneRoot, Runnable enterAction) {
+        Scene scene = new Scene(sceneRoot);
+        scene.setEnterAction(enterAction);
+        return scene;
+    }
+
+    Object createTransitionSet(boolean sequential) {
+        TransitionSet set = new TransitionSet();
+        set.setOrdering(sequential ? TransitionSet.ORDERING_SEQUENTIAL :
+            TransitionSet.ORDERING_TOGETHER);
+        return set;
+    }
+
+    void addTransition(Object transitionSet, Object transition) {
+        ((TransitionSet) transitionSet).addTransition((Transition) transition);
+    }
+
+    Object createAutoTransition() {
+        return new AutoTransition();
+    }
+
+    Object createSlide(SlideCallback callback) {
+        Slide slide = new Slide();
+        slide.setCallback(callback);
+        return slide;
+    }
+
+    Object createFadeTransition(int fadingMode) {
+        Fade fade = new Fade(fadingMode);
+        return fade;
+    }
+
+    /**
+     * change bounds that support customized start delay.
+     */
+    static class CustomChangeBounds extends ChangeBounds {
+
+        int mDefaultStartDelay;
+        // View -> delay
+        final HashMap<View, Integer> mViewStartDelays = new HashMap<View, Integer>();
+        // id -> delay
+        final SparseIntArray mIdStartDelays = new SparseIntArray();
+        // Class.getName() -> delay
+        final HashMap<String, Integer> mClassStartDelays = new HashMap<String, Integer>();
+
+        private int getDelay(View view) {
+            Integer delay = mViewStartDelays.get(view);
+            if (delay != null) {
+                return delay;
+            }
+            int idStartDelay = mIdStartDelays.get(view.getId(), -1);
+            if (idStartDelay != -1) {
+                return idStartDelay;
+            }
+            delay = mClassStartDelays.get(view.getClass().getName());
+            if (delay != null) {
+                return delay;
+            }
+            return mDefaultStartDelay;
+        }
+
+        @Override
+        public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
+                TransitionValues endValues) {
+            Animator animator = super.createAnimator(sceneRoot, startValues, endValues);
+            if (animator != null && endValues != null && endValues.view != null) {
+                animator.setStartDelay(getDelay(endValues.view));
+            }
+            return animator;
+        }
+
+        public void setStartDelay(View view, int startDelay) {
+            mViewStartDelays.put(view, startDelay);
+        }
+
+        public void setStartDelay(int viewId, int startDelay) {
+            mIdStartDelays.put(viewId, startDelay);
+        }
+
+        public void setStartDelay(String className, int startDelay) {
+            mClassStartDelays.put(className, startDelay);
+        }
+
+        public void setDefaultStartDelay(int startDelay) {
+            mDefaultStartDelay = startDelay;
+        }
+    }
+
+    Object createChangeBounds(boolean reparent) {
+        CustomChangeBounds changeBounds = new CustomChangeBounds();
+        changeBounds.setReparent(reparent);
+        return changeBounds;
+    }
+
+    void setChangeBoundsStartDelay(Object changeBounds, int viewId, int startDelay) {
+        ((CustomChangeBounds) changeBounds).setStartDelay(viewId, startDelay);
+    }
+
+    void setChangeBoundsStartDelay(Object changeBounds, View view, int startDelay) {
+        ((CustomChangeBounds) changeBounds).setStartDelay(view, startDelay);
+    }
+
+    void setChangeBoundsStartDelay(Object changeBounds, String className, int startDelay) {
+        ((CustomChangeBounds) changeBounds).setStartDelay(className, startDelay);
+    }
+
+    void setChangeBoundsDefaultStartDelay(Object changeBounds, int startDelay) {
+        ((CustomChangeBounds) changeBounds).setDefaultStartDelay(startDelay);
+    }
+
+    void setStartDelay(Object transition, long startDelay) {
+        ((Transition)transition).setStartDelay(startDelay);
+    }
+
+    void setDuration(Object transition, long duration) {
+        ((Transition)transition).setDuration(duration);
+    }
+
+    void exclude(Object transition, int targetId, boolean exclude) {
+        ((Transition) transition).excludeTarget(targetId, exclude);
+    }
+
+    void exclude(Object transition, View targetView, boolean exclude) {
+        ((Transition) transition).excludeTarget(targetView, exclude);
+    }
+
+    void excludeChildren(Object transition, int targetId, boolean exclude) {
+        ((Transition) transition).excludeChildren(targetId, exclude);
+    }
+
+    void excludeChildren(Object transition, View targetView, boolean exclude) {
+        ((Transition) transition).excludeChildren(targetView, exclude);
+    }
+
+    void include(Object transition, int targetId) {
+        ((Transition) transition).addTarget(targetId);
+    }
+
+    void include(Object transition, View targetView) {
+        ((Transition) transition).addTarget(targetView);
+    }
+
+    public void setTransitionListener(Object transition, final TransitionListener listener) {
+        Transition t = (Transition) transition;
+        t.addListener(new Transition.TransitionListener() {
+
+            @Override
+            public void onTransitionStart(Transition transition) {
+                listener.onTransitionStart(transition);
+            }
+
+            @Override
+            public void onTransitionResume(Transition transition) {
+            }
+
+            @Override
+            public void onTransitionPause(Transition transition) {
+            }
+
+            @Override
+            public void onTransitionEnd(Transition transition) {
+                listener.onTransitionEnd(transition);
+            }
+
+            @Override
+            public void onTransitionCancel(Transition transition) {
+            }
+        });
+    }
+
+    void runTransition(Object scene, Object transition) {
+        TransitionManager.go((Scene) scene, (Transition) transition);
+    }
+
+    void setInterpolator(Object transition, Object timeInterpolator) {
+        ((Transition) transition).setInterpolator((TimeInterpolator) timeInterpolator);
+    }
+}
diff --git a/v17/leanback/project.properties b/v17/leanback/project.properties
new file mode 100644
index 0000000..91d2b02
--- /dev/null
+++ b/v17/leanback/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-19
+android.library=true
diff --git a/v17/leanback/res/animator-v21/lb_playback_bg_fade_in.xml b/v17/leanback/res/animator-v21/lb_playback_bg_fade_in.xml
new file mode 100644
index 0000000..bf68bc5
--- /dev/null
+++ b/v17/leanback/res/animator-v21/lb_playback_bg_fade_in.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<animator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:interpolator="@android:interpolator/linear_out_slow_in"
+    android:duration="@integer/lb_playback_bg_fade_in_ms"
+    android:valueFrom="0"
+    android:valueTo="255"
+    android:valueType="intType" />
diff --git a/v17/leanback/res/animator-v21/lb_playback_bg_fade_out.xml b/v17/leanback/res/animator-v21/lb_playback_bg_fade_out.xml
new file mode 100644
index 0000000..888acc9
--- /dev/null
+++ b/v17/leanback/res/animator-v21/lb_playback_bg_fade_out.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<animator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:interpolator="@android:interpolator/fast_out_linear_in"
+    android:duration="@integer/lb_playback_bg_fade_out_ms"
+    android:valueFrom="255"
+    android:valueTo="0"
+    android:valueType="intType" />
diff --git a/v17/leanback/res/animator-v21/lb_playback_description_fade_out.xml b/v17/leanback/res/animator-v21/lb_playback_description_fade_out.xml
new file mode 100644
index 0000000..6babde0
--- /dev/null
+++ b/v17/leanback/res/animator-v21/lb_playback_description_fade_out.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<animator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:interpolator="@android:interpolator/fast_out_linear_in"
+    android:duration="@integer/lb_playback_description_fade_out_ms"
+    android:valueFrom="1"
+    android:valueTo="0"
+    android:valueType="floatType" />
diff --git a/v17/leanback/res/animator/lb_playback_bg_fade_in.xml b/v17/leanback/res/animator/lb_playback_bg_fade_in.xml
new file mode 100644
index 0000000..f529509
--- /dev/null
+++ b/v17/leanback/res/animator/lb_playback_bg_fade_in.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<animator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:interpolator="@android:anim/linear_interpolator"
+    android:duration="@integer/lb_playback_bg_fade_in_ms"
+    android:valueFrom="0"
+    android:valueTo="255"
+    android:valueType="intType" />
diff --git a/v17/leanback/res/animator/lb_playback_bg_fade_out.xml b/v17/leanback/res/animator/lb_playback_bg_fade_out.xml
new file mode 100644
index 0000000..005a58f
--- /dev/null
+++ b/v17/leanback/res/animator/lb_playback_bg_fade_out.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<animator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:interpolator="@android:anim/linear_interpolator"
+    android:duration="@integer/lb_playback_bg_fade_out_ms"
+    android:valueFrom="255"
+    android:valueTo="0"
+    android:valueType="intType" />
diff --git a/v17/leanback/res/animator/lb_playback_controls_fade_in.xml b/v17/leanback/res/animator/lb_playback_controls_fade_in.xml
new file mode 100644
index 0000000..8ef6c27
--- /dev/null
+++ b/v17/leanback/res/animator/lb_playback_controls_fade_in.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<animator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@integer/lb_playback_controls_fade_in_ms"
+    android:valueFrom="0"
+    android:valueTo="1"
+    android:valueType="floatType" />
diff --git a/v17/leanback/res/animator/lb_playback_controls_fade_out.xml b/v17/leanback/res/animator/lb_playback_controls_fade_out.xml
new file mode 100644
index 0000000..1ac4f2a
--- /dev/null
+++ b/v17/leanback/res/animator/lb_playback_controls_fade_out.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<animator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@integer/lb_playback_controls_fade_out_ms"
+    android:valueFrom="1"
+    android:valueTo="0"
+    android:valueType="floatType" />
diff --git a/v17/leanback/res/animator/lb_playback_description_fade_in.xml b/v17/leanback/res/animator/lb_playback_description_fade_in.xml
new file mode 100644
index 0000000..dfa09d9
--- /dev/null
+++ b/v17/leanback/res/animator/lb_playback_description_fade_in.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<animator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@integer/lb_playback_description_fade_in_ms"
+    android:valueFrom="0"
+    android:valueTo="1"
+    android:valueType="floatType" />
diff --git a/v17/leanback/res/animator/lb_playback_description_fade_out.xml b/v17/leanback/res/animator/lb_playback_description_fade_out.xml
new file mode 100644
index 0000000..7fba44d
--- /dev/null
+++ b/v17/leanback/res/animator/lb_playback_description_fade_out.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<animator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:interpolator="@android:anim/linear_interpolator"
+    android:duration="@integer/lb_playback_description_fade_out_ms"
+    android:valueFrom="1"
+    android:valueTo="0"
+    android:valueType="floatType" />
diff --git a/v17/leanback/res/animator/lb_playback_rows_fade_in.xml b/v17/leanback/res/animator/lb_playback_rows_fade_in.xml
new file mode 100644
index 0000000..56bc700
--- /dev/null
+++ b/v17/leanback/res/animator/lb_playback_rows_fade_in.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<animator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@integer/lb_playback_rows_fade_in_ms"
+    android:startOffset="@integer/lb_playback_rows_fade_delay_ms"
+    android:valueFrom="0"
+    android:valueTo="1"
+    android:valueType="floatType" />
diff --git a/v17/leanback/res/animator/lb_playback_rows_fade_out.xml b/v17/leanback/res/animator/lb_playback_rows_fade_out.xml
new file mode 100644
index 0000000..eb066d2
--- /dev/null
+++ b/v17/leanback/res/animator/lb_playback_rows_fade_out.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<animator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="@integer/lb_playback_rows_fade_out_ms"
+    android:valueFrom="1"
+    android:valueTo="0"
+    android:valueType="floatType" />
diff --git a/v17/leanback/res/drawable-hdpi/lb_action_bg_focused.9.png b/v17/leanback/res/drawable-hdpi/lb_action_bg_focused.9.png
new file mode 100644
index 0000000..112b541
--- /dev/null
+++ b/v17/leanback/res/drawable-hdpi/lb_action_bg_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_card_shadow_focused.9.png b/v17/leanback/res/drawable-hdpi/lb_card_shadow_focused.9.png
new file mode 100644
index 0000000..7c59b7f
--- /dev/null
+++ b/v17/leanback/res/drawable-hdpi/lb_card_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_card_shadow_normal.9.png b/v17/leanback/res/drawable-hdpi/lb_card_shadow_normal.9.png
new file mode 100644
index 0000000..4abb20a
--- /dev/null
+++ b/v17/leanback/res/drawable-hdpi/lb_card_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_actions_right_arrow.png b/v17/leanback/res/drawable-hdpi/lb_ic_actions_right_arrow.png
new file mode 100644
index 0000000..e737a66
--- /dev/null
+++ b/v17/leanback/res/drawable-hdpi/lb_ic_actions_right_arrow.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_in_app_search.png b/v17/leanback/res/drawable-hdpi/lb_ic_in_app_search.png
new file mode 100644
index 0000000..542dd87
--- /dev/null
+++ b/v17/leanback/res/drawable-hdpi/lb_ic_in_app_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_sad_cloud.png b/v17/leanback/res/drawable-hdpi/lb_ic_sad_cloud.png
new file mode 100644
index 0000000..620aba6
--- /dev/null
+++ b/v17/leanback/res/drawable-hdpi/lb_ic_sad_cloud.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_search_mic.png b/v17/leanback/res/drawable-hdpi/lb_ic_search_mic.png
new file mode 100644
index 0000000..5db5420
--- /dev/null
+++ b/v17/leanback/res/drawable-hdpi/lb_ic_search_mic.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_search_mic_out.png b/v17/leanback/res/drawable-hdpi/lb_ic_search_mic_out.png
new file mode 100644
index 0000000..78e9b1e
--- /dev/null
+++ b/v17/leanback/res/drawable-hdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_in_app_search_bg.9.png b/v17/leanback/res/drawable-hdpi/lb_in_app_search_bg.9.png
new file mode 100644
index 0000000..d5c5dc1
--- /dev/null
+++ b/v17/leanback/res/drawable-hdpi/lb_in_app_search_bg.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_in_app_search_shadow_focused.9.png b/v17/leanback/res/drawable-hdpi/lb_in_app_search_shadow_focused.9.png
new file mode 100644
index 0000000..562ae9d
--- /dev/null
+++ b/v17/leanback/res/drawable-hdpi/lb_in_app_search_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_in_app_search_shadow_normal.9.png b/v17/leanback/res/drawable-hdpi/lb_in_app_search_shadow_normal.9.png
new file mode 100644
index 0000000..db44754
--- /dev/null
+++ b/v17/leanback/res/drawable-hdpi/lb_in_app_search_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_action_bg_focused.9.png b/v17/leanback/res/drawable-mdpi/lb_action_bg_focused.9.png
new file mode 100644
index 0000000..1d2b041
--- /dev/null
+++ b/v17/leanback/res/drawable-mdpi/lb_action_bg_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_card_shadow_focused.9.png b/v17/leanback/res/drawable-mdpi/lb_card_shadow_focused.9.png
new file mode 100644
index 0000000..39b220c
--- /dev/null
+++ b/v17/leanback/res/drawable-mdpi/lb_card_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_card_shadow_normal.9.png b/v17/leanback/res/drawable-mdpi/lb_card_shadow_normal.9.png
new file mode 100644
index 0000000..b9c3400
--- /dev/null
+++ b/v17/leanback/res/drawable-mdpi/lb_card_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_actions_right_arrow.png b/v17/leanback/res/drawable-mdpi/lb_ic_actions_right_arrow.png
new file mode 100644
index 0000000..aa6b44f
--- /dev/null
+++ b/v17/leanback/res/drawable-mdpi/lb_ic_actions_right_arrow.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_in_app_search.png b/v17/leanback/res/drawable-mdpi/lb_ic_in_app_search.png
new file mode 100644
index 0000000..ffdc8ec
--- /dev/null
+++ b/v17/leanback/res/drawable-mdpi/lb_ic_in_app_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_sad_cloud.png b/v17/leanback/res/drawable-mdpi/lb_ic_sad_cloud.png
new file mode 100644
index 0000000..5634efc
--- /dev/null
+++ b/v17/leanback/res/drawable-mdpi/lb_ic_sad_cloud.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_search_mic.png b/v17/leanback/res/drawable-mdpi/lb_ic_search_mic.png
new file mode 100644
index 0000000..7cc6d61
--- /dev/null
+++ b/v17/leanback/res/drawable-mdpi/lb_ic_search_mic.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_search_mic_out.png b/v17/leanback/res/drawable-mdpi/lb_ic_search_mic_out.png
new file mode 100644
index 0000000..9e36b2e
--- /dev/null
+++ b/v17/leanback/res/drawable-mdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_in_app_search_bg.9.png b/v17/leanback/res/drawable-mdpi/lb_in_app_search_bg.9.png
new file mode 100644
index 0000000..be062cc
--- /dev/null
+++ b/v17/leanback/res/drawable-mdpi/lb_in_app_search_bg.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_in_app_search_shadow_focused.9.png b/v17/leanback/res/drawable-mdpi/lb_in_app_search_shadow_focused.9.png
new file mode 100644
index 0000000..b63a57f
--- /dev/null
+++ b/v17/leanback/res/drawable-mdpi/lb_in_app_search_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_in_app_search_shadow_normal.9.png b/v17/leanback/res/drawable-mdpi/lb_in_app_search_shadow_normal.9.png
new file mode 100644
index 0000000..d91acee
--- /dev/null
+++ b/v17/leanback/res/drawable-mdpi/lb_in_app_search_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-v21/lb_action_bg.xml b/v17/leanback/res/drawable-v21/lb_action_bg.xml
new file mode 100644
index 0000000..b4b6a7b
--- /dev/null
+++ b/v17/leanback/res/drawable-v21/lb_action_bg.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight" >
+
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle" >
+            <corners android:radius="@dimen/lb_action_button_corner_radius" />
+            <solid android:color="?android:attr/colorButtonNormal" />
+        </shape>
+    </item>
+
+</ripple>
diff --git a/v17/leanback/res/drawable-v21/lb_card_foreground.xml b/v17/leanback/res/drawable-v21/lb_card_foreground.xml
new file mode 100644
index 0000000..3cd9699
--- /dev/null
+++ b/v17/leanback/res/drawable-v21/lb_card_foreground.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight" >
+</ripple>
\ No newline at end of file
diff --git a/v17/leanback/res/drawable-v21/lb_control_button_primary.xml b/v17/leanback/res/drawable-v21/lb_control_button_primary.xml
new file mode 100644
index 0000000..a006889
--- /dev/null
+++ b/v17/leanback/res/drawable-v21/lb_control_button_primary.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/lb_control_button_color" >
+
+    <item android:id="@android:id/mask">
+        <selector android:constantSize="true" >
+            <item android:state_focused="true">
+                <shape android:shape="oval" >
+
+                    <!-- Color not used since this is a ripple mask -->
+                    <solid android:color="#000000" />
+
+                    <size
+                        android:height="@dimen/lb_control_button_diameter"
+                        android:width="@dimen/lb_control_button_diameter" />
+                </shape>
+            </item>
+        </selector>
+    </item>
+
+</ripple>
\ No newline at end of file
diff --git a/v17/leanback/res/drawable-v21/lb_control_button_secondary.xml b/v17/leanback/res/drawable-v21/lb_control_button_secondary.xml
new file mode 100644
index 0000000..6623f6e
--- /dev/null
+++ b/v17/leanback/res/drawable-v21/lb_control_button_secondary.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="@color/lb_control_button_color" >
+
+    <item android:id="@android:id/mask">
+        <selector android:constantSize="true" >
+            <item android:state_focused="true">
+                <shape android:shape="oval" >
+
+                    <!-- Color not used since this is a ripple mask -->
+                    <solid android:color="#000000" />
+
+                    <size
+                        android:height="@dimen/lb_control_button_secondary_diameter"
+                        android:width="@dimen/lb_control_button_secondary_diameter" />
+                </shape>
+            </item>
+        </selector>
+    </item>
+
+</ripple>
\ No newline at end of file
diff --git a/v17/leanback/res/drawable-xhdpi/lb_action_bg_focused.9.png b/v17/leanback/res/drawable-xhdpi/lb_action_bg_focused.9.png
new file mode 100644
index 0000000..5e7c2be
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_action_bg_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_card_shadow_focused.9.png b/v17/leanback/res/drawable-xhdpi/lb_card_shadow_focused.9.png
new file mode 100644
index 0000000..599928b
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_card_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_card_shadow_normal.9.png b/v17/leanback/res/drawable-xhdpi/lb_card_shadow_normal.9.png
new file mode 100644
index 0000000..e5413a8
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_card_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_actions_right_arrow.png b/v17/leanback/res/drawable-xhdpi/lb_ic_actions_right_arrow.png
new file mode 100644
index 0000000..9796171
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_actions_right_arrow.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_card_info_text_fade.png b/v17/leanback/res/drawable-xhdpi/lb_ic_card_info_text_fade.png
new file mode 100644
index 0000000..1364a48
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_card_info_text_fade.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_cc.png b/v17/leanback/res/drawable-xhdpi/lb_ic_cc.png
new file mode 100644
index 0000000..518c063
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_cc.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_fast_forward.png b/v17/leanback/res/drawable-xhdpi/lb_ic_fast_forward.png
new file mode 100644
index 0000000..c5afb69
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_fast_forward.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_fast_rewind.png b/v17/leanback/res/drawable-xhdpi/lb_ic_fast_rewind.png
new file mode 100644
index 0000000..d9774ef
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_fast_rewind.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_hq.png b/v17/leanback/res/drawable-xhdpi/lb_ic_hq.png
new file mode 100644
index 0000000..f797d38
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_hq.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_in_app_search.png b/v17/leanback/res/drawable-xhdpi/lb_ic_in_app_search.png
new file mode 100644
index 0000000..1dd0d0f
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_in_app_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_loop.png b/v17/leanback/res/drawable-xhdpi/lb_ic_loop.png
new file mode 100644
index 0000000..988f572
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_loop.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_loop_one.png b/v17/leanback/res/drawable-xhdpi/lb_ic_loop_one.png
new file mode 100644
index 0000000..4ae9faa
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_loop_one.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_more.png b/v17/leanback/res/drawable-xhdpi/lb_ic_more.png
new file mode 100644
index 0000000..ef62a69
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_more.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_pause.png b/v17/leanback/res/drawable-xhdpi/lb_ic_pause.png
new file mode 100644
index 0000000..01e07f7
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_pause.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_play.png b/v17/leanback/res/drawable-xhdpi/lb_ic_play.png
new file mode 100644
index 0000000..1556076
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_play.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_playback_loop.png b/v17/leanback/res/drawable-xhdpi/lb_ic_playback_loop.png
new file mode 100644
index 0000000..f444ca4
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_playback_loop.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_replay.png b/v17/leanback/res/drawable-xhdpi/lb_ic_replay.png
new file mode 100644
index 0000000..a76d8fe
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_replay.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_sad_cloud.png b/v17/leanback/res/drawable-xhdpi/lb_ic_sad_cloud.png
new file mode 100644
index 0000000..e301816
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_sad_cloud.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic.png b/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic.png
new file mode 100644
index 0000000..c987c25
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic_out.png b/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic_out.png
new file mode 100644
index 0000000..5904a4f
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_shuffle.png b/v17/leanback/res/drawable-xhdpi/lb_ic_shuffle.png
new file mode 100644
index 0000000..32cf2dc
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_shuffle.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_skip_next.png b/v17/leanback/res/drawable-xhdpi/lb_ic_skip_next.png
new file mode 100644
index 0000000..2173375
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_skip_next.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_skip_previous.png b/v17/leanback/res/drawable-xhdpi/lb_ic_skip_previous.png
new file mode 100644
index 0000000..2c6035a
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_skip_previous.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_stop.png b/v17/leanback/res/drawable-xhdpi/lb_ic_stop.png
new file mode 100644
index 0000000..b96f297
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_stop.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down.png b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down.png
new file mode 100644
index 0000000..85dd902
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down_outline.png b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down_outline.png
new file mode 100644
index 0000000..2208fdf
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down_outline.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up.png b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up.png
new file mode 100644
index 0000000..b24b146
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up_outline.png b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up_outline.png
new file mode 100644
index 0000000..d6a5240
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up_outline.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_in_app_search_bg.9.png b/v17/leanback/res/drawable-xhdpi/lb_in_app_search_bg.9.png
new file mode 100644
index 0000000..8cc5438
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_in_app_search_bg.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_focused.9.png b/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_focused.9.png
new file mode 100644
index 0000000..f913c0f
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_normal.9.png b/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_normal.9.png
new file mode 100644
index 0000000..791ffd7
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_action_bg_focused.9.png b/v17/leanback/res/drawable-xxhdpi/lb_action_bg_focused.9.png
new file mode 100644
index 0000000..d2227b9
--- /dev/null
+++ b/v17/leanback/res/drawable-xxhdpi/lb_action_bg_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_focused.9.png b/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_focused.9.png
new file mode 100644
index 0000000..125bf12
--- /dev/null
+++ b/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_normal.9.png b/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_normal.9.png
new file mode 100644
index 0000000..887d24f
--- /dev/null
+++ b/v17/leanback/res/drawable-xxhdpi/lb_card_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_actions_right_arrow.png b/v17/leanback/res/drawable-xxhdpi/lb_ic_actions_right_arrow.png
new file mode 100644
index 0000000..6539869
--- /dev/null
+++ b/v17/leanback/res/drawable-xxhdpi/lb_ic_actions_right_arrow.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_in_app_search.png b/v17/leanback/res/drawable-xxhdpi/lb_ic_in_app_search.png
new file mode 100644
index 0000000..f47827f
--- /dev/null
+++ b/v17/leanback/res/drawable-xxhdpi/lb_ic_in_app_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_sad_cloud.png b/v17/leanback/res/drawable-xxhdpi/lb_ic_sad_cloud.png
new file mode 100644
index 0000000..8e7e111
--- /dev/null
+++ b/v17/leanback/res/drawable-xxhdpi/lb_ic_sad_cloud.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic.png b/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic.png
new file mode 100644
index 0000000..c8a0790
--- /dev/null
+++ b/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic_out.png b/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic_out.png
new file mode 100644
index 0000000..cb97131
--- /dev/null
+++ b/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_bg.9.png b/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_bg.9.png
new file mode 100644
index 0000000..b9e372e
--- /dev/null
+++ b/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_bg.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_focused.9.png b/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_focused.9.png
new file mode 100644
index 0000000..65f3a9e
--- /dev/null
+++ b/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_normal.9.png b/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_normal.9.png
new file mode 100644
index 0000000..9bc3f6f
--- /dev/null
+++ b/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable/lb_background.xml b/v17/leanback/res/drawable/lb_background.xml
new file mode 100644
index 0000000..4732bc1
--- /dev/null
+++ b/v17/leanback/res/drawable/lb_background.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item android:drawable="@color/lb_grey" android:id="@+id/background_theme"/>
+    <item android:drawable="@color/lb_grey" android:id="@+id/background_color"/>
+    <!-- Replaced at runtime with image to fade out -->
+    <item android:drawable="@color/lb_grey" android:id="@+id/background_imageout"/>
+    <!-- Replaced at runtime with image to fade in -->
+    <item android:drawable="@color/lb_grey" android:id="@+id/background_imagein"/>
+    <item android:drawable="@color/lb_background_protection" android:id="@+id/background_dim" />
+</layer-list>
diff --git a/v17/leanback/res/drawable/lb_card_foreground.xml b/v17/leanback/res/drawable/lb_card_foreground.xml
new file mode 100644
index 0000000..894a3eb
--- /dev/null
+++ b/v17/leanback/res/drawable/lb_card_foreground.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+</shape>
diff --git a/v17/leanback/res/drawable/lb_control_button_primary.xml b/v17/leanback/res/drawable/lb_control_button_primary.xml
new file mode 100644
index 0000000..4528f95
--- /dev/null
+++ b/v17/leanback/res/drawable/lb_control_button_primary.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android" android:constantSize="true" >
+    <item android:state_focused="true">
+        <shape android:shape="oval">
+            <solid android:color="@color/lb_control_button_color" />
+            <size
+                android:height="@dimen/lb_control_button_diameter"
+                android:width="@dimen/lb_control_button_diameter" />
+        </shape>
+    </item>
+</selector>
diff --git a/v17/leanback/res/drawable/lb_control_button_secondary.xml b/v17/leanback/res/drawable/lb_control_button_secondary.xml
new file mode 100644
index 0000000..9e52645
--- /dev/null
+++ b/v17/leanback/res/drawable/lb_control_button_secondary.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android" android:constantSize="true" >
+    <item android:state_focused="true">
+        <shape android:shape="oval">
+            <solid android:color="@color/lb_control_button_color" />
+            <size
+                android:height="@dimen/lb_control_button_secondary_diameter"
+                android:width="@dimen/lb_control_button_secondary_diameter" />
+        </shape>
+    </item>
+</selector>
diff --git a/v17/leanback/res/drawable/lb_playback_progress_bar.xml b/v17/leanback/res/drawable/lb_playback_progress_bar.xml
new file mode 100644
index 0000000..04e72b6
--- /dev/null
+++ b/v17/leanback/res/drawable/lb_playback_progress_bar.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <item
+        android:id="@android:id/background"
+        android:drawable="@color/lb_playback_background_progress_color"/>
+    <item android:id="@android:id/secondaryProgress">
+        <clip android:drawable="@color/lb_playback_secondary_progress_color" />
+    </item>
+    <item android:id="@android:id/progress">
+        <!--  Progress color set programmtically, this is just a placeholder. -->
+        <clip android:drawable="@color/lb_playback_progress_color_no_theme" />
+    </item>
+
+</layer-list>
diff --git a/v17/leanback/res/drawable/lb_search_orb.xml b/v17/leanback/res/drawable/lb_search_orb.xml
new file mode 100644
index 0000000..438099c
--- /dev/null
+++ b/v17/leanback/res/drawable/lb_search_orb.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+</shape>
diff --git a/v17/leanback/res/drawable/lb_speech_orb.xml b/v17/leanback/res/drawable/lb_speech_orb.xml
new file mode 100644
index 0000000..3b66f12
--- /dev/null
+++ b/v17/leanback/res/drawable/lb_speech_orb.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="oval">
+</shape>
diff --git a/v17/leanback/res/layout/lb_action_1_line.xml b/v17/leanback/res/layout/lb_action_1_line.xml
new file mode 100644
index 0000000..52d89e0
--- /dev/null
+++ b/v17/leanback/res/layout/lb_action_1_line.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/lb_action_button"
+    android:layout_width="wrap_content"
+    android:layout_height="@dimen/lb_action_1_line_height"
+    style="?attr/detailsActionButtonStyle"
+    android:lines="1"
+    />
diff --git a/v17/leanback/res/layout/lb_action_2_lines.xml b/v17/leanback/res/layout/lb_action_2_lines.xml
new file mode 100644
index 0000000..697074a
--- /dev/null
+++ b/v17/leanback/res/layout/lb_action_2_lines.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/lb_action_button"
+    android:layout_width="wrap_content"
+    android:layout_height="@dimen/lb_action_2_lines_height"
+    style="?attr/detailsActionButtonStyle"
+    android:lines="2"
+    />
diff --git a/v17/leanback/res/layout/lb_background_window.xml b/v17/leanback/res/layout/lb_background_window.xml
new file mode 100644
index 0000000..73c84d2
--- /dev/null
+++ b/v17/leanback/res/layout/lb_background_window.xml
@@ -0,0 +1,21 @@
+<!--?xml version="1.0" encoding="utf-8"?-->
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    />
diff --git a/v17/leanback/res/layout/lb_browse_fragment.xml b/v17/leanback/res/layout/lb_browse_fragment.xml
new file mode 100644
index 0000000..076a2f1
--- /dev/null
+++ b/v17/leanback/res/layout/lb_browse_fragment.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/browse_dummy"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <!-- BrowseFrameLayout serves as root of transition and manages switch between
+         left and right-->
+    <android.support.v17.leanback.app.BrowseFrameLayout
+        android:focusable="true"
+        android:focusableInTouchMode="true"
+        android:descendantFocusability="afterDescendants"
+        android:id="@+id/browse_frame"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        >
+        <android.support.v17.leanback.app.BrowseRowsFrameLayout
+            android:id="@+id/browse_container_dock"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+        <FrameLayout
+            android:id="@+id/browse_headers_dock"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent" />
+        <include layout="@layout/lb_browse_title" />
+    </android.support.v17.leanback.app.BrowseFrameLayout>
+</FrameLayout>
diff --git a/v17/leanback/res/layout/lb_browse_title.xml b/v17/leanback/res/layout/lb_browse_title.xml
new file mode 100644
index 0000000..75068d8
--- /dev/null
+++ b/v17/leanback/res/layout/lb_browse_title.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<android.support.v17.leanback.widget.TitleView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/browse_title_group"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingTop="?attr/browsePaddingTop"
+    android:paddingRight="?attr/browsePaddingRight"
+    android:paddingLeft="?attr/browsePaddingLeft"
+    android:paddingBottom="?attr/browsePaddingTop"
+    style="?attr/browseTitleViewStyle" />
+
diff --git a/v17/leanback/res/layout/lb_card_color_overlay.xml b/v17/leanback/res/layout/lb_card_color_overlay.xml
new file mode 100644
index 0000000..45a40e1
--- /dev/null
+++ b/v17/leanback/res/layout/lb_card_color_overlay.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<View
+      xmlns:android="http://schemas.android.com/apk/res/android"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent" />
diff --git a/v17/leanback/res/layout/lb_control_bar.xml b/v17/leanback/res/layout/lb_control_bar.xml
new file mode 100644
index 0000000..b7ad3a3
--- /dev/null
+++ b/v17/leanback/res/layout/lb_control_bar.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<android.support.v17.leanback.widget.ControlBar xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/control_bar"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal" />
diff --git a/v17/leanback/res/layout/lb_control_button_primary.xml b/v17/leanback/res/layout/lb_control_button_primary.xml
new file mode 100644
index 0000000..86e3e0d
--- /dev/null
+++ b/v17/leanback/res/layout/lb_control_button_primary.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content" >
+
+    <ImageView
+        android:id="@+id/button"
+        style="?attr/playbackControlsButtonStyle"
+        android:layout_width="@dimen/lb_control_button_diameter"
+        android:layout_height="@dimen/lb_control_button_height"
+        android:scaleType="center"
+        android:src="@drawable/lb_control_button_primary" />
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="@dimen/lb_control_icon_width"
+        android:layout_height="@dimen/lb_control_icon_height"
+        android:layout_gravity="center" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_control_button_secondary.xml b/v17/leanback/res/layout/lb_control_button_secondary.xml
new file mode 100644
index 0000000..4d0cafd
--- /dev/null
+++ b/v17/leanback/res/layout/lb_control_button_secondary.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content" >
+
+    <ImageView
+        android:id="@+id/button"
+        style="?attr/playbackControlsButtonStyle"
+        android:layout_width="@dimen/lb_control_button_secondary_diameter"
+        android:layout_height="@dimen/lb_control_button_secondary_height"
+        android:scaleType="center"
+        android:src="@drawable/lb_control_button_secondary" />
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="@dimen/lb_control_icon_width"
+        android:layout_height="@dimen/lb_control_icon_height"
+        android:layout_gravity="center" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_details_description.xml b/v17/leanback/res/layout/lb_details_description.xml
new file mode 100644
index 0000000..798504f
--- /dev/null
+++ b/v17/leanback/res/layout/lb_details_description.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    >
+
+    <!-- Top margins set programatically -->
+    <TextView
+        android:id="@+id/lb_details_description_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="?attr/detailsDescriptionTitleStyle"
+        />
+
+    <TextView
+        android:id="@+id/lb_details_description_subtitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="?attr/detailsDescriptionSubtitleStyle"
+        />
+
+    <TextView
+        android:id="@+id/lb_details_description_body"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="?attr/detailsDescriptionBodyStyle"
+        />
+</LinearLayout>
diff --git a/v17/leanback/res/layout/lb_details_fragment.xml b/v17/leanback/res/layout/lb_details_fragment.xml
new file mode 100644
index 0000000..92cf4b4
--- /dev/null
+++ b/v17/leanback/res/layout/lb_details_fragment.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/dummy"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <FrameLayout
+        android:id="@+id/fragment_dock"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent" />
+</FrameLayout>
diff --git a/v17/leanback/res/layout/lb_details_overview.xml b/v17/leanback/res/layout/lb_details_overview.xml
new file mode 100644
index 0000000..ce337e8
--- /dev/null
+++ b/v17/leanback/res/layout/lb_details_overview.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:lb="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingLeft="@dimen/lb_details_overview_margin_left"
+    android:paddingRight="@dimen/lb_details_overview_margin_right"
+    android:paddingBottom="@dimen/lb_details_overview_margin_bottom"
+    android:clipToPadding="false"
+    >
+
+    <!-- Background is applied to this inner layout -->
+    <LinearLayout
+        android:id="@+id/details_overview"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/lb_details_overview_height_large"
+        android:orientation="horizontal"
+         >
+
+        <ImageView
+            android:id="@+id/details_overview_image"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:adjustViewBounds="true"
+            android:scaleType="fitStart"
+            />
+
+        <LinearLayout
+            android:id="@+id/details_overview_right_panel"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:paddingBottom="@dimen/lb_details_overview_description_margin_bottom"
+            android:paddingTop="@dimen/lb_details_overview_description_margin_top"
+            android:orientation="vertical" >
+
+        <FrameLayout
+            android:id="@+id/details_overview_description"
+            android:layout_width="wrap_content"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:gravity="top"
+            android:layout_marginLeft="@dimen/lb_details_overview_description_margin_left"
+            android:layout_marginRight="@dimen/lb_details_overview_description_margin_right"
+            />
+
+        <android.support.v17.leanback.widget.HorizontalGridView
+            android:id="@+id/details_overview_actions"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            android:focusable="true"
+            android:focusableInTouchMode="true"
+            android:paddingLeft="@dimen/lb_details_overview_description_margin_left"
+            android:paddingRight="@dimen/lb_details_overview_description_margin_right"
+            lb:horizontalMargin="@dimen/lb_details_overview_action_items_margin"
+            lb:rowHeight="@dimen/lb_details_overview_actions_height" />
+
+        </LinearLayout>
+    </LinearLayout>
+
+</FrameLayout>
diff --git a/v17/leanback/res/layout/lb_error_fragment.xml b/v17/leanback/res/layout/lb_error_fragment.xml
new file mode 100644
index 0000000..2815560
--- /dev/null
+++ b/v17/leanback/res/layout/lb_error_fragment.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/error_frame"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
+    android:descendantFocusability="afterDescendants"
+    android:nextFocusLeft="@id/error_frame"
+    android:nextFocusRight="@id/error_frame"
+    android:nextFocusUp="@id/error_frame"
+    android:nextFocusDown="@id/error_frame">
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:orientation="vertical">
+
+        <ImageView
+            android:id="@+id/image"
+            android:layout_width="wrap_content"
+            android:layout_height="@dimen/lb_error_image_max_height"
+            android:layout_gravity="center"
+            android:visibility="gone"/>
+        <TextView
+            android:id="@+id/message"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:maxWidth="@dimen/lb_error_message_max_width"
+            android:visibility="gone"
+            style="?attr/errorMessageStyle"/>
+        <Button
+            android:id="@+id/button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:visibility="gone"
+            android:nextFocusLeft="@id/button"
+            android:nextFocusRight="@id/button"
+            android:nextFocusUp="@id/button"
+            android:nextFocusDown="@id/button"
+            style="?android:attr/buttonStyle"/>
+    </LinearLayout>
+
+    <include layout="@layout/lb_browse_title" />
+
+</FrameLayout>
diff --git a/v17/leanback/res/layout/lb_header.xml b/v17/leanback/res/layout/lb_header.xml
new file mode 100644
index 0000000..7437cf3
--- /dev/null
+++ b/v17/leanback/res/layout/lb_header.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<android.support.v17.leanback.widget.RowHeaderView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/row_header"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    style="?headerStyle"
+    />
diff --git a/v17/leanback/res/layout/lb_headers_fragment.xml b/v17/leanback/res/layout/lb_headers_fragment.xml
new file mode 100644
index 0000000..dbfbb8d
--- /dev/null
+++ b/v17/leanback/res/layout/lb_headers_fragment.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<android.support.v17.leanback.widget.VerticalGridView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:lb="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/browse_headers"
+    android:layout_width="@dimen/lb_browse_headers_width"
+    android:layout_height="match_parent"
+    style="?attr/headersVerticalGridStyle"/>
diff --git a/v17/leanback/res/layout/lb_image_card_view.xml b/v17/leanback/res/layout/lb_image_card_view.xml
new file mode 100644
index 0000000..70cfff5
--- /dev/null
+++ b/v17/leanback/res/layout/lb_image_card_view.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:lb="http://schemas.android.com/apk/res-auto">
+
+    <ImageView
+        android:id="@+id/main_image"
+        lb:layout_viewType="main"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:scaleType="centerCrop"
+        android:adjustViewBounds="true"
+        android:contentDescription="@null" />
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        lb:layout_viewType="info" >
+        <RelativeLayout
+            android:id="@+id/info_field"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/lb_basic_card_info_height"
+            android:padding="@dimen/lb_basic_card_info_padding"
+            android:layout_centerHorizontal="true" >
+            <TextView
+                android:id="@+id/title_text"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignParentStart="true"
+                android:layout_marginTop="@dimen/lb_basic_card_info_text_margin"
+                android:layout_marginLeft="@dimen/lb_basic_card_info_text_margin"
+                android:maxLines="1"
+                android:fontFamily="sans-serif-condensed"
+                android:textColor="@color/lb_basic_card_title_text_color"
+                android:textSize="@dimen/lb_basic_card_title_text_size"
+                android:ellipsize="end" />
+            <TextView
+                android:id="@+id/content_text"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignParentLeft="true"
+                android:layout_alignParentBottom="true"
+                android:layout_marginLeft="@dimen/lb_basic_card_info_text_margin"
+                android:layout_marginBottom="@dimen/lb_basic_card_info_text_margin"
+                android:maxLines="1"
+                android:fontFamily="sans-serif-condensed"
+                android:textColor="@color/lb_basic_card_content_text_color"
+                android:textSize="@dimen/lb_basic_card_content_text_size"
+                android:ellipsize="none" />
+            <ImageView
+                android:id="@+id/extra_badge"
+                android:layout_width="@dimen/lb_basic_card_info_badge_size"
+                android:layout_height="@dimen/lb_basic_card_info_badge_size"
+                android:layout_alignParentBottom="true"
+                android:layout_alignParentRight="true"
+                android:scaleType="fitCenter"
+                android:visibility="gone"
+                android:contentDescription="@null" />
+            <ImageView
+                android:id="@+id/fade_mask"
+                android:src="@drawable/lb_ic_card_info_text_fade"
+                android:layout_width="wrap_content"
+                android:layout_height="@dimen/lb_basic_card_info_badge_size"
+                android:layout_alignParentBottom="true"
+                android:layout_toStartOf="@id/extra_badge"
+                android:scaleType="fitCenter"
+                android:visibility="gone"
+                android:contentDescription="@null" />
+        </RelativeLayout>
+    </FrameLayout>
+</merge>
diff --git a/v17/leanback/res/layout/lb_list_row.xml b/v17/leanback/res/layout/lb_list_row.xml
new file mode 100644
index 0000000..80d7bef
--- /dev/null
+++ b/v17/leanback/res/layout/lb_list_row.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<android.support.v17.leanback.widget.HorizontalGridView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/row_content"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    style="?attr/rowHorizontalGridStyle" />
diff --git a/v17/leanback/res/layout/lb_list_row_hovercard.xml b/v17/leanback/res/layout/lb_list_row_hovercard.xml
new file mode 100644
index 0000000..a001dc9
--- /dev/null
+++ b/v17/leanback/res/layout/lb_list_row_hovercard.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/hovercard_panel"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="vertical" >
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="?attr/rowHoverCardTitleStyle" />
+    <TextView
+        android:id="@+id/description"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="?attr/rowHoverCardDescriptionStyle" />
+</LinearLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_playback_controls.xml b/v17/leanback/res/layout/lb_playback_controls.xml
new file mode 100644
index 0000000..e7ed643
--- /dev/null
+++ b/v17/leanback/res/layout/lb_playback_controls.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical" >
+
+    <ProgressBar
+        android:id="@+id/playback_progress"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:layout_width="match_parent"
+        android:layout_height="4dp"
+        android:maxHeight="4dp"
+        android:minHeight="4dp"
+        android:progressDrawable="@drawable/lb_playback_progress_bar" />
+
+    <FrameLayout
+        android:id="@+id/controls_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" >
+
+        <android.support.v17.leanback.widget.ControlBar
+            android:id="@+id/control_bar"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:orientation="horizontal" />
+
+        <FrameLayout
+            android:id="@+id/more_actions_dock"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="right" />
+
+        <TextView
+            android:id="@+id/current_time"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top|start"
+            android:layout_marginStart="@dimen/lb_playback_current_time_margin_start"
+            android:paddingTop="@dimen/lb_playback_time_padding_top"
+            style="?attr/playbackControlsTimeStyle" />
+
+        <TextView
+            android:id="@+id/total_time"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top|end"
+            android:layout_marginEnd="@dimen/lb_playback_total_time_margin_end"
+            android:paddingTop="@dimen/lb_playback_time_padding_top"
+            style="?attr/playbackControlsTimeStyle" />
+
+    </FrameLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_playback_controls_row.xml b/v17/leanback/res/layout/lb_playback_controls_row.xml
new file mode 100644
index 0000000..2875b51
--- /dev/null
+++ b/v17/leanback/res/layout/lb_playback_controls_row.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Note: clipChildren/clipToPadding false are needed to apply shadows to child
+     views with no padding of their own. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:paddingLeft="@dimen/lb_playback_controls_margin_left"
+    android:paddingRight="@dimen/lb_playback_controls_margin_right" >
+
+    <LinearLayout
+        android:id="@+id/controls_card"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/lb_playback_controls_card_height"
+        android:layout_marginBottom="@dimen/lb_playback_controls_margin_bottom"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:orientation="horizontal" >
+
+        <ImageView
+            android:id="@+id/image"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:adjustViewBounds="true"
+            android:scaleType="fitStart" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            android:orientation="vertical" >
+
+            <FrameLayout
+                android:id="@+id/description_dock"
+                android:layout_width="wrap_content"
+                android:layout_height="0dp"
+                android:layout_marginEnd="@dimen/lb_playback_description_margin_end"
+                android:layout_marginStart="@dimen/lb_playback_description_margin_start"
+                android:layout_marginTop="@dimen/lb_playback_description_margin_top"
+                android:layout_weight="1"
+                android:gravity="top" />
+
+            <!-- Space here prevents room only for title and subtitle above -->
+
+            <Space
+                android:id="@+id/spacer"
+                android:layout_width="match_parent"
+                android:layout_height="12dp" />
+
+            <FrameLayout
+                android:id="@+id/controls_dock"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginEnd="@dimen/lb_playback_description_margin_end"
+                android:layout_marginStart="@dimen/lb_playback_description_margin_start" />
+        </LinearLayout>
+    </LinearLayout>
+
+    <FrameLayout
+        android:id="@+id/secondary_controls_dock"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal" />
+
+    <Space
+        android:id="@+id/bottom_spacer"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/lb_playback_controls_margin_bottom" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_row_container.xml b/v17/leanback/res/layout/lb_row_container.xml
new file mode 100644
index 0000000..0f5dd5f
--- /dev/null
+++ b/v17/leanback/res/layout/lb_row_container.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+<LinearLayout
+    android:id="@+id/lb_row_container_header_dock"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:paddingLeft="?attr/browsePaddingLeft"
+    android:clipToPadding="false">
+</LinearLayout>
+</merge>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_row_header.xml b/v17/leanback/res/layout/lb_row_header.xml
new file mode 100644
index 0000000..69fac46
--- /dev/null
+++ b/v17/leanback/res/layout/lb_row_header.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<android.support.v17.leanback.widget.RowHeaderView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/row_header"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    style="?rowHeaderStyle"
+    />
diff --git a/v17/leanback/res/layout/lb_rows_fragment.xml b/v17/leanback/res/layout/lb_rows_fragment.xml
new file mode 100644
index 0000000..c4ffdc3
--- /dev/null
+++ b/v17/leanback/res/layout/lb_rows_fragment.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<android.support.v17.leanback.widget.VerticalGridView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/container_list"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    style="?attr/rowsVerticalGridStyle" />
+
diff --git a/v17/leanback/res/layout/lb_search_bar.xml b/v17/leanback/res/layout/lb_search_bar.xml
new file mode 100644
index 0000000..959d230
--- /dev/null
+++ b/v17/leanback/res/layout/lb_search_bar.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <android.support.v17.leanback.widget.SpeechOrbView
+            android:id="@+id/lb_search_bar_speech_orb"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:layout_marginLeft="@dimen/lb_search_bar_speech_orb_margin_left"
+            android:focusable="true"
+            android:focusableInTouchMode="true"
+            >
+        <requestFocus/>
+    </android.support.v17.leanback.widget.SpeechOrbView>
+
+    <RelativeLayout
+            android:id="@+id/lb_search_bar_items"
+            android:layout_width="@dimen/lb_search_bar_items_width"
+            android:layout_height="@dimen/lb_search_bar_items_height"
+            android:layout_toRightOf="@+id/lb_search_bar_speech_orb"
+            android:layout_centerVertical="true"
+            android:layout_marginLeft="@dimen/lb_search_bar_items_margin_left"
+            android:orientation="horizontal"
+            android:background="@drawable/lb_in_app_search_bg"
+            >
+
+        <ImageView
+                android:id="@+id/lb_search_bar_badge"
+                android:layout_width="@dimen/lb_search_bar_icon_width"
+                android:layout_height="@dimen/lb_search_bar_icon_height"
+                android:layout_gravity="center_vertical|left"
+                android:layout_centerVertical="true"
+                android:layout_marginLeft="@dimen/lb_search_bar_icon_margin_left"
+                android:src="@null"
+                android:visibility="gone"
+                style="?attr/browseTitleIconStyle"/>
+
+        <android.support.v17.leanback.widget.SearchEditText
+                    android:id="@+id/lb_search_text_editor"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_vertical|right"
+                    android:layout_marginLeft="@dimen/lb_search_bar_edit_text_margin_left"
+                    android:layout_centerVertical="true"
+                    android:cursorVisible="true"
+                    android:layout_toRightOf="@+id/lb_search_bar_badge"
+                    android:editable="true"
+                    android:background="@null"
+                    android:fontFamily="sans-serif"
+                    android:focusable="true"
+                    android:focusableInTouchMode="true"
+                    android:imeOptions="normal|flagNoExtractUi|actionSearch"
+                    android:inputType="text|textAutoComplete"
+                    android:singleLine="true"
+                    android:textColor="@color/lb_search_bar_text_color"
+                    android:textColorHint="@color/lb_search_bar_hint_color"
+                    android:textCursorDrawable="@null"
+                    android:hint="@string/lb_search_bar_hint"
+                    android:textSize="@dimen/lb_search_bar_text_size"/>
+    </RelativeLayout>
+</merge>
diff --git a/v17/leanback/res/layout/lb_search_fragment.xml b/v17/leanback/res/layout/lb_search_fragment.xml
new file mode 100644
index 0000000..57a46b4
--- /dev/null
+++ b/v17/leanback/res/layout/lb_search_fragment.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:id="@+id/lb_search_frame"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"
+             android:paddingTop="@dimen/lb_search_bar_padding_top"
+             android:clipToPadding="false"
+             android:clipChildren="false"
+        >
+        <FrameLayout
+                android:id="@+id/lb_results_frame"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"/>
+    <android.support.v17.leanback.widget.SearchBar
+            android:id="@+id/lb_search_bar"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:clipChildren="false"
+            />
+</FrameLayout>
diff --git a/v17/leanback/res/layout/lb_search_orb.xml b/v17/leanback/res/layout/lb_search_orb.xml
new file mode 100644
index 0000000..0eff71e
--- /dev/null
+++ b/v17/leanback/res/layout/lb_search_orb.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <View
+        android:id="@+id/search_orb"
+        android:layout_width="@dimen/lb_search_orb_size"
+        android:layout_height="@dimen/lb_search_orb_size"
+        android:background="@drawable/lb_search_orb" />
+
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:src="@drawable/lb_ic_in_app_search"
+        android:contentDescription="@string/orb_search_action" />
+
+</merge>
diff --git a/v17/leanback/res/layout/lb_shadow.xml b/v17/leanback/res/layout/lb_shadow.xml
new file mode 100644
index 0000000..b0aa0b1
--- /dev/null
+++ b/v17/leanback/res/layout/lb_shadow.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <View
+        android:id="@+id/lb_shadow_normal"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@drawable/lb_card_shadow_normal" />
+    <View
+        android:id="@+id/lb_shadow_focused"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@drawable/lb_card_shadow_focused"
+        android:alpha="0" />
+
+</merge>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_speech_orb.xml b/v17/leanback/res/layout/lb_speech_orb.xml
new file mode 100644
index 0000000..ff6129b
--- /dev/null
+++ b/v17/leanback/res/layout/lb_speech_orb.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <View
+            android:id="@+id/lb_speech_orb"
+            android:layout_width="@dimen/lb_search_bar_speech_orb_size"
+            android:layout_height="@dimen/lb_search_bar_speech_orb_size"
+            android:background="@drawable/lb_speech_orb" />
+
+    <ImageView
+            android:id="@+id/lb_speech_icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:src="@drawable/lb_ic_search_mic_out"
+            android:contentDescription="@string/orb_search_action" />
+
+</merge>
diff --git a/v17/leanback/res/layout/lb_title_view.xml b/v17/leanback/res/layout/lb_title_view.xml
new file mode 100644
index 0000000..590a683
--- /dev/null
+++ b/v17/leanback/res/layout/lb_title_view.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <ImageView
+        android:id="@+id/browse_badge"
+        android:layout_width="@dimen/lb_browse_title_text_width"
+        android:layout_height="@dimen/lb_browse_title_height"
+        android:layout_gravity="center_vertical|right"
+        android:src="@null"
+        android:visibility="gone"
+        style="?attr/browseTitleIconStyle"/>
+
+    <TextView
+        android:id="@+id/browse_title"
+        android:layout_width="@dimen/lb_browse_title_text_width"
+        android:layout_height="@dimen/lb_browse_title_height"
+        android:layout_gravity="center_vertical|right"
+        style="?attr/browseTitleTextStyle"/>
+
+    <android.support.v17.leanback.widget.SearchOrbView
+        android:id="@+id/browse_orb"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_gravity="center_vertical|left"
+        android:visibility="invisible" />
+
+</merge>
diff --git a/v17/leanback/res/layout/lb_vertical_grid.xml b/v17/leanback/res/layout/lb_vertical_grid.xml
new file mode 100644
index 0000000..7154e48
--- /dev/null
+++ b/v17/leanback/res/layout/lb_vertical_grid.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<android.support.v17.leanback.widget.VerticalGridView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/browse_grid"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:layout_gravity="center"
+    style="?attr/itemsVerticalGridStyle" />
diff --git a/v17/leanback/res/layout/lb_vertical_grid_fragment.xml b/v17/leanback/res/layout/lb_vertical_grid_fragment.xml
new file mode 100644
index 0000000..4cd3a22
--- /dev/null
+++ b/v17/leanback/res/layout/lb_vertical_grid_fragment.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/browse_dummy"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <android.support.v17.leanback.app.BrowseFrameLayout
+        android:id="@+id/browse_frame"
+        android:focusable="true"
+        android:focusableInTouchMode="true"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" >
+
+        <include layout="@layout/lb_browse_title" />
+
+        <FrameLayout
+            android:id="@+id/browse_grid_dock"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+
+    </android.support.v17.leanback.app.BrowseFrameLayout>
+</FrameLayout>
diff --git a/v17/leanback/res/raw/lb_voice_failure.ogg b/v17/leanback/res/raw/lb_voice_failure.ogg
new file mode 100644
index 0000000..d10b3d4
--- /dev/null
+++ b/v17/leanback/res/raw/lb_voice_failure.ogg
Binary files differ
diff --git a/v17/leanback/res/raw/lb_voice_no_input.ogg b/v17/leanback/res/raw/lb_voice_no_input.ogg
new file mode 100644
index 0000000..503eeab
--- /dev/null
+++ b/v17/leanback/res/raw/lb_voice_no_input.ogg
Binary files differ
diff --git a/v17/leanback/res/raw/lb_voice_open.ogg b/v17/leanback/res/raw/lb_voice_open.ogg
new file mode 100644
index 0000000..bf73679
--- /dev/null
+++ b/v17/leanback/res/raw/lb_voice_open.ogg
Binary files differ
diff --git a/v17/leanback/res/raw/lb_voice_success.ogg b/v17/leanback/res/raw/lb_voice_success.ogg
new file mode 100644
index 0000000..06afa23
--- /dev/null
+++ b/v17/leanback/res/raw/lb_voice_success.ogg
Binary files differ
diff --git a/v17/leanback/res/transition-v21/lb_enter_transition.xml b/v17/leanback/res/transition-v21/lb_enter_transition.xml
new file mode 100644
index 0000000..1572934
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_enter_transition.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+  <fade
+      android:interpolator="@android:interpolator/linear_out_slow_in"
+      android:startDelay="175"
+      android:duration="400"/>
+</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/transition-v21/lb_return_transition.xml b/v17/leanback/res/transition-v21/lb_return_transition.xml
new file mode 100644
index 0000000..0165a18
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_return_transition.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+  <fade
+      android:interpolator="@android:interpolator/linear_out_slow_in"
+      android:startDelay="75"
+      android:duration="250"/>
+</transitionSet>
diff --git a/v17/leanback/res/transition-v21/lb_shared_element_enter_transition.xml b/v17/leanback/res/transition-v21/lb_shared_element_enter_transition.xml
new file mode 100644
index 0000000..0ba4125
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_shared_element_enter_transition.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
+  android:duration="500" >
+  <fade
+      android:interpolator="@android:interpolator/linear_out_slow_in"
+      android:startDelay="325"
+      android:duration="150"/>
+  <changeBounds
+      android:interpolator="@android:interpolator/linear_out_slow_in">
+  </changeBounds>
+  <changeTransform
+      android:interpolator="@android:interpolator/linear_out_slow_in" />
+  <changeImageTransform
+      android:interpolator="@android:interpolator/linear_out_slow_in"/>
+</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/transition-v21/lb_shared_element_return_transition.xml b/v17/leanback/res/transition-v21/lb_shared_element_return_transition.xml
new file mode 100644
index 0000000..f97bb6d
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_shared_element_return_transition.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android" >
+  <fade
+      android:interpolator="@android:interpolator/linear_out_slow_in"
+      android:duration="75"/>
+  <changeBounds
+      android:interpolator="@android:interpolator/linear_out_slow_in"
+      android:duration="450">
+  </changeBounds>
+  <changeTransform
+      android:interpolator="@android:interpolator/linear_out_slow_in" 
+      android:duration="450"/>
+  <changeImageTransform
+      android:interpolator="@android:interpolator/linear_out_slow_in"
+      android:duration="450"/>
+</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/values-af/strings.xml b/v17/leanback/res/values-af/strings.xml
new file mode 100644
index 0000000..fa93257
--- /dev/null
+++ b/v17/leanback/res/values-af/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Soekhandeling"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Soek"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Praat om te soek"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Deursoek <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Praat om <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> te deursoek"</string>
+</resources>
diff --git a/v17/leanback/res/values-am/strings.xml b/v17/leanback/res/values-am/strings.xml
new file mode 100644
index 0000000..4b0f26f
--- /dev/null
+++ b/v17/leanback/res/values-am/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"እርምጃ ይፈልጉ"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"ይፈልጉ"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ለመፈለግ ይናገሩ"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> ፈልግ"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>ን ለመፈለግ ይናገሩ"</string>
+</resources>
diff --git a/v17/leanback/res/values-ar/strings.xml b/v17/leanback/res/values-ar/strings.xml
new file mode 100644
index 0000000..6540a3e
--- /dev/null
+++ b/v17/leanback/res/values-ar/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"إجراء البحث"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"بحث"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"التحدث  للبحث"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"بحث في <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"تحدّث للبحث في <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-bg/strings.xml b/v17/leanback/res/values-bg/strings.xml
new file mode 100644
index 0000000..9cd9b5c
--- /dev/null
+++ b/v17/leanback/res/values-bg/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Действие за търсене"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Търсете"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Говорете, за да търсите"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Търсете в/ъв <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Говорете, за да търсите в/ъв <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-bn-rBD/strings.xml b/v17/leanback/res/values-bn-rBD/strings.xml
new file mode 100644
index 0000000..400553a
--- /dev/null
+++ b/v17/leanback/res/values-bn-rBD/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"অনুসন্ধান অ্যাকশন"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"অনুসন্ধান"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"অনুসন্ধান করতে বলুন"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> অনুসন্ধান করুন"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> অনুসন্ধান করতে বলুন"</string>
+</resources>
diff --git a/v17/leanback/res/values-ca/strings.xml b/v17/leanback/res/values-ca/strings.xml
new file mode 100644
index 0000000..9087b56
--- /dev/null
+++ b/v17/leanback/res/values-ca/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Acció de cerca"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Cerca."</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Parla per fer una cerca."</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Cerca a <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>."</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Parla per cercar a <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>."</string>
+</resources>
diff --git a/v17/leanback/res/values-cs/strings.xml b/v17/leanback/res/values-cs/strings.xml
new file mode 100644
index 0000000..7c5b18c
--- /dev/null
+++ b/v17/leanback/res/values-cs/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Vyhledávání akce"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Vyhledávání"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Vyhledávejte hlasem"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Hledat <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Vyhledávejte v kontextu <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> hlasem"</string>
+</resources>
diff --git a/v17/leanback/res/values-da/strings.xml b/v17/leanback/res/values-da/strings.xml
new file mode 100644
index 0000000..51a195a
--- /dev/null
+++ b/v17/leanback/res/values-da/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Søgehandling"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Søg"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Tal for at søge"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Søg efter <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Tal for at søge efter <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-de/strings.xml b/v17/leanback/res/values-de/strings.xml
new file mode 100644
index 0000000..1b353a0
--- /dev/null
+++ b/v17/leanback/res/values-de/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Suchvorgang"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Suchen"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Zum Suchen sprechen"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"In <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> suchen"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Zum Suchen in <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> sprechen"</string>
+</resources>
diff --git a/v17/leanback/res/values-el/strings.xml b/v17/leanback/res/values-el/strings.xml
new file mode 100644
index 0000000..fc8c159
--- /dev/null
+++ b/v17/leanback/res/values-el/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Ενέργεια αναζήτησης"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Αναζήτηση"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Μιλήστε για να κάνετε αναζήτηση"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Αναζήτηση <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Μιλήστε για αναζήτηση <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-en-rGB/strings.xml b/v17/leanback/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..666fa5c
--- /dev/null
+++ b/v17/leanback/res/values-en-rGB/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Search Action"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Search"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Speak to search"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Search <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Speak to search <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-en-rIN/strings.xml b/v17/leanback/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..666fa5c
--- /dev/null
+++ b/v17/leanback/res/values-en-rIN/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Search Action"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Search"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Speak to search"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Search <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Speak to search <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-es-rUS/strings.xml b/v17/leanback/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..896535a
--- /dev/null
+++ b/v17/leanback/res/values-es-rUS/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Acción de búsqueda"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Búsqueda"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Habla para buscar"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Buscar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Habla para buscar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>."</string>
+</resources>
diff --git a/v17/leanback/res/values-es/strings.xml b/v17/leanback/res/values-es/strings.xml
new file mode 100644
index 0000000..9990ae6
--- /dev/null
+++ b/v17/leanback/res/values-es/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Buscar..."</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Buscar"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Buscar por voz"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Buscar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Buscar por voz <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-et-rEE/strings.xml b/v17/leanback/res/values-et-rEE/strings.xml
new file mode 100644
index 0000000..a097b5d
--- /dev/null
+++ b/v17/leanback/res/values-et-rEE/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Otsimistoiming"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Otsing"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Öelge otsimiseks"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Otsige teenusest <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Kõnelge teenusest <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> otsimiseks"</string>
+</resources>
diff --git a/v17/leanback/res/values-eu-rES/strings.xml b/v17/leanback/res/values-eu-rES/strings.xml
new file mode 100644
index 0000000..2690a84
--- /dev/null
+++ b/v17/leanback/res/values-eu-rES/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Bilaketa"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Bilatu"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Esan bilatu nahi duzuna"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Bilatu <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Esan bilatu nahi duzuna, bilaketa hemen egiteko: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-fa/strings.xml b/v17/leanback/res/values-fa/strings.xml
new file mode 100644
index 0000000..fa4bb0c
--- /dev/null
+++ b/v17/leanback/res/values-fa/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"عملکرد جستجو"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"جستجو"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"برای جستجو صحبت کنید"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"جستجوی <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"جستجو با گفتن <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-fi/strings.xml b/v17/leanback/res/values-fi/strings.xml
new file mode 100644
index 0000000..3193006
--- /dev/null
+++ b/v17/leanback/res/values-fi/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Hakutoiminto"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Haku"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Tee haku puhumalla"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Haku: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Puhehaku: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-fr-rCA/strings.xml b/v17/leanback/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..20595c3
--- /dev/null
+++ b/v17/leanback/res/values-fr-rCA/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Action de recherche"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Rechercher"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Énoncez votre recherche"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Rechercher dans <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Énoncez votre recherche dans <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-fr/strings.xml b/v17/leanback/res/values-fr/strings.xml
new file mode 100644
index 0000000..9a8d2c7
--- /dev/null
+++ b/v17/leanback/res/values-fr/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Commande de recherche"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Rechercher"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Énoncer la recherche"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Rechercher \"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>\""</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Énoncer la recherche \"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>\""</string>
+</resources>
diff --git a/v17/leanback/res/values-gl-rES/strings.xml b/v17/leanback/res/values-gl-rES/strings.xml
new file mode 100644
index 0000000..b85dcd1
--- /dev/null
+++ b/v17/leanback/res/values-gl-rES/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Acción de busca"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Buscar"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Fala para efectuar a busca"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Busca <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Fala para buscar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-hi/strings.xml b/v17/leanback/res/values-hi/strings.xml
new file mode 100644
index 0000000..0d3e72d
--- /dev/null
+++ b/v17/leanback/res/values-hi/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"खोज कार्रवाई"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"खोजें"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"खोजने के लिए बोलें"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> खोजें"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> खोजने के लिए बोलें"</string>
+</resources>
diff --git a/v17/leanback/res/values-hr/strings.xml b/v17/leanback/res/values-hr/strings.xml
new file mode 100644
index 0000000..89d8053
--- /dev/null
+++ b/v17/leanback/res/values-hr/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Radnja pretraživanja"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Pretražite"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Izgovorite upit za pretraživanje"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Tražite <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Izgovorite upit za pretraživanje <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-hu/strings.xml b/v17/leanback/res/values-hu/strings.xml
new file mode 100644
index 0000000..e6eaacb
--- /dev/null
+++ b/v17/leanback/res/values-hu/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Keresési művelet"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Keresés"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Beszéljen a keresés indításához"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Keresés itt: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Mondjon valamit, hogy itt keressen: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-hy-rAM/strings.xml b/v17/leanback/res/values-hy-rAM/strings.xml
new file mode 100644
index 0000000..de8561f
--- /dev/null
+++ b/v17/leanback/res/values-hy-rAM/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Որոնման հրամանը"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Որոնում"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Խոսեք՝ որոնելու համար"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Որոնեք <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Խոսեք՝ <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> որոնելու համար"</string>
+</resources>
diff --git a/v17/leanback/res/values-in/strings.xml b/v17/leanback/res/values-in/strings.xml
new file mode 100644
index 0000000..9be0a01
--- /dev/null
+++ b/v17/leanback/res/values-in/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Tindakan Penelusuran"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Telusuri"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Ucapkan untuk menelusuri"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Telusuri <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Ucapkan untuk menelusuri <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-is-rIS/strings.xml b/v17/leanback/res/values-is-rIS/strings.xml
new file mode 100644
index 0000000..29ccdc1
--- /dev/null
+++ b/v17/leanback/res/values-is-rIS/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Leitaraðgerð"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Leitaðu"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Talaðu til að leita"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Leita í <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Talaðu til að leita í <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-it/strings.xml b/v17/leanback/res/values-it/strings.xml
new file mode 100644
index 0000000..fa11e9d
--- /dev/null
+++ b/v17/leanback/res/values-it/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Azione di ricerca"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Ricerca"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Parla per cercare"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Cerca in <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Parla per cercare in <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-iw/strings.xml b/v17/leanback/res/values-iw/strings.xml
new file mode 100644
index 0000000..dacb052
--- /dev/null
+++ b/v17/leanback/res/values-iw/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"פעולת חיפוש"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"חפש"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"דבר כדי לחפש"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"חפש את <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"דבר כדי לחפש את <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-ja/strings.xml b/v17/leanback/res/values-ja/strings.xml
new file mode 100644
index 0000000..62b08b5
--- /dev/null
+++ b/v17/leanback/res/values-ja/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"検索操作"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"検索"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"音声検索"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>を検索"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>を音声検索"</string>
+</resources>
diff --git a/v17/leanback/res/values-ka-rGE/strings.xml b/v17/leanback/res/values-ka-rGE/strings.xml
new file mode 100644
index 0000000..ab9492c
--- /dev/null
+++ b/v17/leanback/res/values-ka-rGE/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"ძიების მოქმედება"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"ძიება"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"თქვით საძიებო ფრაზა"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>-ის ძიება"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"თქვით <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>-ის საძიებლად"</string>
+</resources>
diff --git a/v17/leanback/res/values-kk-rKZ/strings.xml b/v17/leanback/res/values-kk-rKZ/strings.xml
new file mode 100644
index 0000000..160a8e7
--- /dev/null
+++ b/v17/leanback/res/values-kk-rKZ/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Іздеу әрекеті"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Іздеу"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Іздеу үшін сөйлеу"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> іздеу"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> іздеу үшін сөйлеңіз"</string>
+</resources>
diff --git a/v17/leanback/res/values-km-rKH/strings.xml b/v17/leanback/res/values-km-rKH/strings.xml
new file mode 100644
index 0000000..92245b7
--- /dev/null
+++ b/v17/leanback/res/values-km-rKH/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"​ស្វែងរក​សកម្មភាព"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"ស្វែងរក"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"និយាយ​​ដើម្បី​ស្វែងរក"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"ស្វែងរក <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"និយាយ​ដើម្បី​ស្វែងរក <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-kn-rIN/strings.xml b/v17/leanback/res/values-kn-rIN/strings.xml
new file mode 100644
index 0000000..6ca527f
--- /dev/null
+++ b/v17/leanback/res/values-kn-rIN/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"ಹುಡುಕಾಟ ಕ್ರಿಯೆ"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"ಹುಡುಕಿ"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ಹುಡುಕಲು ಮಾತನಾಡಿ"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> ಹುಡುಕಿ"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> ಮಾತನಾಡಿ ಹುಡುಕಾಟ ನಡೆಸಿ"</string>
+</resources>
diff --git a/v17/leanback/res/values-ko/strings.xml b/v17/leanback/res/values-ko/strings.xml
new file mode 100644
index 0000000..af0130d
--- /dev/null
+++ b/v17/leanback/res/values-ko/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"검색 작업"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"검색"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"음성 검색"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> 검색"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> 음성 검색"</string>
+</resources>
diff --git a/v17/leanback/res/values-ky-rKG/strings.xml b/v17/leanback/res/values-ky-rKG/strings.xml
new file mode 100644
index 0000000..64a2e72
--- /dev/null
+++ b/v17/leanback/res/values-ky-rKG/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Издөө аракети"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Издөө"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Издөө үчүн сүйлөңүз"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> издөө"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> издөө үчүн сүйлөңүз"</string>
+</resources>
diff --git a/v17/leanback/res/values-lo-rLA/strings.xml b/v17/leanback/res/values-lo-rLA/strings.xml
new file mode 100644
index 0000000..107f989
--- /dev/null
+++ b/v17/leanback/res/values-lo-rLA/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"ຊອກ​ຫາ​ຄຳ​ສັ່ງ"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"ຊອກຫາ"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ເວົ້າ​ເພື່ອ​ຊອກ​ຫາ"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"ຊອກ​ຫາ <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"ເວົ້າ​ເພື່ອ​ຊອກ​ຫາ <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-lt/strings.xml b/v17/leanback/res/values-lt/strings.xml
new file mode 100644
index 0000000..f765d04
--- /dev/null
+++ b/v17/leanback/res/values-lt/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Paieškos veiksmas"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Paieška"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Pasakykite, kad ieškotumėte"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Ieškoti „<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>“"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Kalbėkite, kad ieškotumėte „<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>“"</string>
+</resources>
diff --git a/v17/leanback/res/values-lv/strings.xml b/v17/leanback/res/values-lv/strings.xml
new file mode 100644
index 0000000..677ae6b
--- /dev/null
+++ b/v17/leanback/res/values-lv/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Meklēšanas darbība"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Meklēt"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Runāt, lai meklētu"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Meklējiet <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Runājiet, lai meklētu <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-mk-rMK/strings.xml b/v17/leanback/res/values-mk-rMK/strings.xml
new file mode 100644
index 0000000..8ab8384
--- /dev/null
+++ b/v17/leanback/res/values-mk-rMK/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Акција на пребарување"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Пребарување"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Зборувајте за да пребарувате"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Пребарувај <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Кажете за да се пребарува <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-ml-rIN/strings.xml b/v17/leanback/res/values-ml-rIN/strings.xml
new file mode 100644
index 0000000..e31770d
--- /dev/null
+++ b/v17/leanback/res/values-ml-rIN/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"തിരയൽ പ്രവർത്തനം"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"തിരയുക"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ശബ്‌ദം ഉപയോഗിച്ച് തിരയുക"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> തിരയുക"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> തിരയുന്നതിന് സംസാരിക്കുക"</string>
+</resources>
diff --git a/v17/leanback/res/values-mn-rMN/strings.xml b/v17/leanback/res/values-mn-rMN/strings.xml
new file mode 100644
index 0000000..a58dfe9
--- /dev/null
+++ b/v17/leanback/res/values-mn-rMN/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Хайлтын үйлдэл"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Хайлт"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Ярьж хайх"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> Хайх"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> хайхын тулд ярина уу"</string>
+</resources>
diff --git a/v17/leanback/res/values-mr-rIN/strings.xml b/v17/leanback/res/values-mr-rIN/strings.xml
new file mode 100644
index 0000000..296b21c
--- /dev/null
+++ b/v17/leanback/res/values-mr-rIN/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"शोध क्रिया"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"शोधा"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"शोधण्यासाठी बोला"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> शोधा"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> शोधण्यासाठी बोला"</string>
+</resources>
diff --git a/v17/leanback/res/values-ms-rMY/strings.xml b/v17/leanback/res/values-ms-rMY/strings.xml
new file mode 100644
index 0000000..d915409
--- /dev/null
+++ b/v17/leanback/res/values-ms-rMY/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Tindakan Carian"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Carian"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Tutur untuk membuat carian"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Cari <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Sebut untuk mencari <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-my-rMM/strings.xml b/v17/leanback/res/values-my-rMM/strings.xml
new file mode 100644
index 0000000..76e70f2
--- /dev/null
+++ b/v17/leanback/res/values-my-rMM/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"ရှာဖွေရန် လုပ်ဆောင်ချက်"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"ရှာဖွေရန်"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ရှာဖွေရန် ပြောပါ"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>ကို ရှာရန်"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> ကို ရှာရန် ပြောပါ"</string>
+</resources>
diff --git a/v17/leanback/res/values-nb/strings.xml b/v17/leanback/res/values-nb/strings.xml
new file mode 100644
index 0000000..c1fe682
--- /dev/null
+++ b/v17/leanback/res/values-nb/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Søkehandling"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Søk"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Snakk for å søke"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Søk i <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Snakk for å søke i <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-ne-rNP/strings.xml b/v17/leanback/res/values-ne-rNP/strings.xml
new file mode 100644
index 0000000..81ce461
--- /dev/null
+++ b/v17/leanback/res/values-ne-rNP/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"कार्य खोजी गर्नुहोस्"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"खोजी गर्नुहोस्"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"खोजी गर्न बोल्नुहोस्"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> खोज्नुहोस्"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> खोजी गर्न बोल्नुहोस्"</string>
+</resources>
diff --git a/v17/leanback/res/values-nl/strings.xml b/v17/leanback/res/values-nl/strings.xml
new file mode 100644
index 0000000..5c0ba2e
--- /dev/null
+++ b/v17/leanback/res/values-nl/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Actie zoeken"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Zoeken"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Spreek om te zoeken"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> zoeken"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Spreek om <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> te zoeken"</string>
+</resources>
diff --git a/v17/leanback/res/values-pl/strings.xml b/v17/leanback/res/values-pl/strings.xml
new file mode 100644
index 0000000..9942bc7
--- /dev/null
+++ b/v17/leanback/res/values-pl/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Wyszukaj czynność"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Szukaj"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Powiedz, aby wyszukać"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Szukaj <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Powiedz, by wyszukać <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-pt-rPT/strings.xml b/v17/leanback/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..0f15262
--- /dev/null
+++ b/v17/leanback/res/values-pt-rPT/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Ação de pesquisa"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Pesquisar"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Fale para pesquisar"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Pesquisar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Fale para pesquisar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-pt/strings.xml b/v17/leanback/res/values-pt/strings.xml
new file mode 100644
index 0000000..0f15262
--- /dev/null
+++ b/v17/leanback/res/values-pt/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Ação de pesquisa"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Pesquisar"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Fale para pesquisar"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Pesquisar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Fale para pesquisar <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-ro/strings.xml b/v17/leanback/res/values-ro/strings.xml
new file mode 100644
index 0000000..fd354372
--- /dev/null
+++ b/v17/leanback/res/values-ro/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Acțiunea de căutare"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Căutați"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Rostiți pentru a căuta"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Căutați <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Vorbiți pentru a căuta <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-ru/strings.xml b/v17/leanback/res/values-ru/strings.xml
new file mode 100644
index 0000000..a06ebbc
--- /dev/null
+++ b/v17/leanback/res/values-ru/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Поиск"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Поиск"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Произнесите запрос"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Поиск здесь: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Произнесите запрос для поиска здесь: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-si-rLK/strings.xml b/v17/leanback/res/values-si-rLK/strings.xml
new file mode 100644
index 0000000..77742b5
--- /dev/null
+++ b/v17/leanback/res/values-si-rLK/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"සෙවීමේ ක්‍රියාව"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"සොයන්න"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"සෙවීමට කථා කරන්න"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> සොයන්න"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> සොයන්න කථා කරන්න"</string>
+</resources>
diff --git a/v17/leanback/res/values-sk/strings.xml b/v17/leanback/res/values-sk/strings.xml
new file mode 100644
index 0000000..8af51d8
--- /dev/null
+++ b/v17/leanback/res/values-sk/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Akcia vyhľadávania"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Hľadať"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Hovorením spustíte vyhľadávanie"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Vyhľadať výraz <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Vyslovením výrazu <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> spustíte jeho vyhľad."</string>
+</resources>
diff --git a/v17/leanback/res/values-sl/strings.xml b/v17/leanback/res/values-sl/strings.xml
new file mode 100644
index 0000000..d367572
--- /dev/null
+++ b/v17/leanback/res/values-sl/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Dejanje iskanja"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Iskanje"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Izgovorite, če želite iskati"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Iskanje: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Govorite, če želite iskati: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-sr/strings.xml b/v17/leanback/res/values-sr/strings.xml
new file mode 100644
index 0000000..771067c
--- /dev/null
+++ b/v17/leanback/res/values-sr/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Радња претраге"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Претражите"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Говорите да бисте претраживали"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Претражите <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Изговорите да бисте претражили <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-sv/strings.xml b/v17/leanback/res/values-sv/strings.xml
new file mode 100644
index 0000000..8b64837
--- /dev/null
+++ b/v17/leanback/res/values-sv/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Sökåtgärd"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Sök"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Säg det du söker efter"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Sök i <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Tala för att söka i <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-sw/strings.xml b/v17/leanback/res/values-sw/strings.xml
new file mode 100644
index 0000000..23d2641
--- /dev/null
+++ b/v17/leanback/res/values-sw/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Kitendo cha Kutafuta"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Utafutaji"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Tamka ili utafute"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Tafuta <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Tamka ili utafute <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-ta-rIN/strings.xml b/v17/leanback/res/values-ta-rIN/strings.xml
new file mode 100644
index 0000000..9533839
--- /dev/null
+++ b/v17/leanback/res/values-ta-rIN/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"செயலைத் தேடுக"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"தேடு"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"தேட, பேசவும்"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> ஐத் தேடுக"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> ஐத் தேட, பேசவும்"</string>
+</resources>
diff --git a/v17/leanback/res/values-te-rIN/strings.xml b/v17/leanback/res/values-te-rIN/strings.xml
new file mode 100644
index 0000000..2715f97
--- /dev/null
+++ b/v17/leanback/res/values-te-rIN/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"శోధన చర్య"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"శోధించండి"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"శోధించడానికి చదివి వినిపించండి"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>ని శోధించండి"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>ని శోధించడానికి చదివి వినిపించండి"</string>
+</resources>
diff --git a/v17/leanback/res/values-th/strings.xml b/v17/leanback/res/values-th/strings.xml
new file mode 100644
index 0000000..09922aa
--- /dev/null
+++ b/v17/leanback/res/values-th/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"การดำเนินการค้นหา"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"ค้นหา"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"พูดเพื่อค้นหา"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"ค้นหา <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"พูดเพื่อค้นหา <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-tl/strings.xml b/v17/leanback/res/values-tl/strings.xml
new file mode 100644
index 0000000..43eaad2
--- /dev/null
+++ b/v17/leanback/res/values-tl/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Pagkilos sa Paghahanap"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Maghanap"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Magsalita upang maghanap"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Hanapin ang <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Magsalita upang hanapin ang <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-tr/strings.xml b/v17/leanback/res/values-tr/strings.xml
new file mode 100644
index 0000000..cfa5167
--- /dev/null
+++ b/v17/leanback/res/values-tr/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Arama İşlemi"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Ara"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Arama yapmak için konuşun"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Ara: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Aramak için konuşun: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-uk/strings.xml b/v17/leanback/res/values-uk/strings.xml
new file mode 100644
index 0000000..c67bc8b
--- /dev/null
+++ b/v17/leanback/res/values-uk/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Команда пошуку"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Пошук"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Продиктуйте пошуковий запит"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Шукати: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Продиктуйте, щоб шукати: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-ur-rPK/strings.xml b/v17/leanback/res/values-ur-rPK/strings.xml
new file mode 100644
index 0000000..bcc9fde
--- /dev/null
+++ b/v17/leanback/res/values-ur-rPK/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"تلاش کی کارروائی"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"تلاش کریں"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"تلاش کرنے کیلئے بولیں"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> تلاش کریں"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> تلاش کرنے کیلئے بولیں"</string>
+</resources>
diff --git a/v17/leanback/res/values-uz-rUZ/strings.xml b/v17/leanback/res/values-uz-rUZ/strings.xml
new file mode 100644
index 0000000..95ae803
--- /dev/null
+++ b/v17/leanback/res/values-uz-rUZ/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Qidiruv amali"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Qidirish"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Qidirish uchun gapiring"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Qidirish: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Qidirish uchun ayting: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-v21/styles.xml b/v17/leanback/res/values-v21/styles.xml
new file mode 100644
index 0000000..70aa93a
--- /dev/null
+++ b/v17/leanback/res/values-v21/styles.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <style name="Widget.Leanback.DetailsActionButtonStyleBase" parent="android:Widget.Material.Button.Borderless">
+        <item name="android:background">@drawable/lb_action_bg</item>
+    </style>
+
+</resources>
diff --git a/v17/leanback/res/values-v21/themes.xml b/v17/leanback/res/values-v21/themes.xml
new file mode 100644
index 0000000..e8934a0
--- /dev/null
+++ b/v17/leanback/res/values-v21/themes.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <style name="Theme.LeanbackBase" parent="android:Theme.Material.NoActionBar">
+        <item name="playbackProgressPrimaryColor">?android:attr/colorAccent</item>
+        <item name="playbackControlsIconHighlightColor">?android:attr/colorAccent</item>
+    </style>
+</resources>
diff --git a/v17/leanback/res/values-vi/strings.xml b/v17/leanback/res/values-vi/strings.xml
new file mode 100644
index 0000000..2da6873
--- /dev/null
+++ b/v17/leanback/res/values-vi/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Tác vụ tìm kiếm"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Tìm kiếm"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Nói để tìm kiếm"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Tìm kiếm <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Nói để tìm kiếm <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values-zh-rCN/strings.xml b/v17/leanback/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..7f91918
--- /dev/null
+++ b/v17/leanback/res/values-zh-rCN/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"搜索操作"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"搜索"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"说话即可开始搜索"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"搜索<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"说话即可在<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>中搜索"</string>
+</resources>
diff --git a/v17/leanback/res/values-zh-rHK/strings.xml b/v17/leanback/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..6e32bf5
--- /dev/null
+++ b/v17/leanback/res/values-zh-rHK/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"搜尋動作"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"搜尋"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"使用語音搜尋"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"搜尋「<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>」"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"使用語音搜尋「<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>」"</string>
+</resources>
diff --git a/v17/leanback/res/values-zh-rTW/strings.xml b/v17/leanback/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..6e32bf5
--- /dev/null
+++ b/v17/leanback/res/values-zh-rTW/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"搜尋動作"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"搜尋"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"使用語音搜尋"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"搜尋「<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>」"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"使用語音搜尋「<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>」"</string>
+</resources>
diff --git a/v17/leanback/res/values-zu/strings.xml b/v17/leanback/res/values-zu/strings.xml
new file mode 100644
index 0000000..4168b19
--- /dev/null
+++ b/v17/leanback/res/values-zu/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="orb_search_action" msgid="5651268540267663887">"Isenzo sokusesha"</string>
+    <string name="lb_search_bar_hint" msgid="8325490927970116252">"Sesha"</string>
+    <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Khuluma ukuze useshe"</string>
+    <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Sesha i-<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+    <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Khuluma ukuze useshe i-<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+</resources>
diff --git a/v17/leanback/res/values/attrs.xml b/v17/leanback/res/values/attrs.xml
new file mode 100644
index 0000000..8cd6916
--- /dev/null
+++ b/v17/leanback/res/values/attrs.xml
@@ -0,0 +1,232 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <declare-styleable name="lbBaseGridView">
+        <!-- Allow DPAD key to navigate out at the front of the View (where position = 0),
+             default is false  -->
+        <attr name="focusOutFront" format="boolean" />
+        <!-- Allow DPAD key to navigate out at the end of the view, default is false -->
+        <attr name="focusOutEnd" format="boolean" />
+        <!-- Defining margin between two items horizontally -->
+        <attr name="horizontalMargin" format="dimension" />
+        <!-- Defining margin between two items vertically -->
+        <attr name="verticalMargin" format="dimension" />
+        <!-- Defining gravity of child view -->
+        <attr name="android:gravity" />
+    </declare-styleable>
+
+    <declare-styleable name="lbHorizontalGridView">
+        <!-- Defining height of each row of HorizontalGridView -->
+        <attr name="rowHeight" format="dimension" >
+            <enum name="wrap_content" value="-2" />
+        </attr>
+        <!-- Defining number of rows -->
+        <attr name="numberOfRows" format="integer" />
+    </declare-styleable>
+
+    <declare-styleable name="lbVerticalGridView">
+        <!-- Defining width of each column of VerticalGridView -->
+        <attr name="columnWidth" format="dimension" >
+            <enum name="wrap_content" value="-2" />
+        </attr>
+        <!-- Defining number of columns -->
+        <attr name="numberOfColumns" format="integer" />
+    </declare-styleable>
+
+    <declare-styleable name="lbBaseCardView">
+        <!-- Defines the type of the card layout -->
+        <attr name="cardType" format="enum">
+            <!-- A simple card layout with a single layout region. -->
+            <enum name="mainOnly" value="0" />
+            <!-- A card layout with two layout regions: a main area which is
+                 always visible, and an info region that appears over the lower
+                 area of the main region. -->
+            <enum name="infoOver" value="1" />
+            <!-- A card layout with two layout regions: a main area which is
+                 always visible, and an info region that appears below the main
+                 region. -->
+            <enum name="infoUnder" value="2" />
+            <!-- A card layout with three layout regions: a main area which is
+                 always visible, an info region that appears below the main
+                 region, and an extra region that appears below the info region
+                 after a small delay. -->
+            <enum name="infoUnderWithExtra" value="3" />
+        </attr>
+        <!-- Defines when the info region of a card layout is displayed. -->
+        <attr name="infoVisibility" format="enum">
+            <!-- Always display the info region. -->
+            <enum name="always" value="0"/>
+            <!-- Display the info region only when activated. -->
+            <enum name="activated" value="1"/>
+            <!-- Display the info region only when selected. -->
+            <enum name="selected" value="2"/>
+        </attr>
+        <!-- Defines when the extra region of a card layout is displayed.
+             Depends on infoVisibility, meaning the extra region never displays
+             if the info region is not displayed as well. -->
+        <attr name="extraVisibility" format="enum">
+            <!-- Always display the extra region. -->
+            <enum name="always" value="0"/>
+            <!-- Display the extra region only when activated. -->
+            <enum name="activated" value="1"/>
+            <!-- Display the extra region only when selected. -->
+            <enum name="selected" value="2"/>
+        </attr>
+        <!-- Defines the delay in milliseconds before the selection animation
+             runs for a card layout. -->
+        <attr name="selectedAnimationDelay" format="integer" />
+        <!-- Defines the duration in milliseconds of the selection animation for
+             a card layout. -->
+        <attr name="selectedAnimationDuration" format="integer" />
+        <!-- Defines the duration in milliseconds of the activated animation for
+             a card layout. -->
+        <attr name="activatedAnimationDuration" format="integer" />
+    </declare-styleable>
+
+    <!-- This is the basic set of layout attributes for elements within a card
+         layout. These attributes are specified with the rest of an elements's
+         normal attributes. -->
+    <declare-styleable name="lbBaseCardView_Layout">
+        <!-- The card layout region defined by this element. At most one of
+             element of each type should be specified as an immediate child of
+             the card layout. -->
+        <attr name="layout_viewType" format="enum">
+            <!-- The main region of the card. -->
+            <enum name="main" value="0"/>
+            <!-- The info region of the card. -->
+            <enum name="info" value="1"/>
+            <!-- The extra region of the card. -->
+            <enum name="extra" value="2"/>
+        </attr>
+    </declare-styleable>
+
+    <declare-styleable name="lbImageCardView">
+        <attr name="infoAreaBackground" format="reference|color"/>
+    </declare-styleable>
+
+    <declare-styleable name="lbSearchOrbView">
+        <!-- Defining icon of the search affordance -->
+        <attr name="searchOrbIcon" format="reference"/>
+        <!-- Defining icon tint color of the search affordance -->
+        <attr name="searchOrbIconColor" format="reference|color"/>
+        <!-- Defining color of the search affordance -->
+        <attr name="searchOrbColor" format="reference|color" />
+        <!-- Defining pulse color of the search affordance -->
+        <attr name="searchOrbBrightColor" format="reference|color" />
+    </declare-styleable>
+
+    <declare-styleable name="lbPlaybackControlsActionIcons">
+        <attr name="play" format="reference"/>
+        <attr name="pause" format="reference"/>
+        <attr name="fast_forward" format="reference"/>
+        <attr name="rewind" format="reference"/>
+        <attr name="skip_next" format="reference"/>
+        <attr name="skip_previous" format="reference"/>
+        <attr name="thumb_up_outline" format="reference"/>
+        <attr name="thumb_up" format="reference"/>
+        <attr name="thumb_down_outline" format="reference"/>
+        <attr name="thumb_down" format="reference"/>
+        <attr name="repeat" format="reference"/>
+        <attr name="repeat_one" format="reference"/>
+        <attr name="shuffle" format="reference"/>
+        <attr name="high_quality" format="reference"/>
+        <attr name="closed_captioning" format="reference"/>
+    </declare-styleable>
+
+    <declare-styleable name="LeanbackTheme">
+
+        <!-- left padding of BrowseFragment, RowsFragment, DetailsFragment -->
+        <attr name="browsePaddingLeft" format="dimension" />
+        <!-- right padding of BrowseFragment, RowsFragment, DetailsFragment -->
+        <attr name="browsePaddingRight" format="dimension" />
+        <!-- top padding of BrowseFragment -->
+        <attr name="browsePaddingTop" format="dimension" />
+        <!-- bottom padding of BrowseFragment -->
+        <attr name="browsePaddingBottom" format="dimension" />
+        <!-- start margin of RowsFragment inside BrowseFragment when HeadersFragment is visible -->
+        <attr name="browseRowsMarginStart" format="dimension" />
+        <!-- top margin of RowsFragment inside BrowseFragment when BrowseFragment title is visible -->
+        <attr name="browseRowsMarginTop" format="dimension" />
+        <!-- fading edge length of start of browse row when HeadersFragment is visible -->
+        <attr name="browseRowsFadingEdgeLength" format="dimension" />
+
+        <!-- BrowseFragment Title text style -->
+        <attr name="browseTitleTextStyle" format="reference" />
+
+        <!-- BrowseFragment Title icon style -->
+        <attr name="browseTitleIconStyle" format="reference" />
+
+        <!-- BrowseFragment TitleView style -->
+        <attr name="browseTitleViewStyle" format="reference" />
+
+        <!-- vertical grid style inside HeadersFragment -->
+        <attr name="headersVerticalGridStyle" format="reference" />
+        <!-- header style inside HeadersFragment -->
+        <attr name="headerStyle" format="reference" />
+
+        <!-- vertical grid style inside RowsFragment -->
+        <attr name="rowsVerticalGridStyle" format="reference" />
+
+        <!-- horizontal grid style inside a row -->
+        <attr name="rowHorizontalGridStyle" format="reference" />
+        <!-- header style inside a row -->
+        <attr name="rowHeaderStyle" format="reference" />
+
+        <!-- hover card title style -->
+        <attr name="rowHoverCardTitleStyle" format="reference" />
+        <!-- hover card description style -->
+        <attr name="rowHoverCardDescriptionStyle" format="reference" />
+
+        <!-- CardView styles -->
+        <attr name="baseCardViewStyle" format="reference" />
+        <attr name="imageCardViewStyle" format="reference" />
+
+        <!-- for details overviews -->
+        <attr name="detailsDescriptionTitleStyle" format="reference" />
+        <attr name="detailsDescriptionSubtitleStyle" format="reference" />
+        <attr name="detailsDescriptionBodyStyle" format="reference" />
+        <attr name="detailsActionButtonStyle" format="reference" />
+
+        <!-- for playback controls -->
+        <attr name="playbackControlsButtonStyle" format="reference" />
+        <attr name="playbackControlsTimeStyle" format="reference" />
+
+        <!-- style for a vertical grid of items -->
+        <attr name="itemsVerticalGridStyle" format="reference" />
+
+        <!-- for messages in the error fragment -->
+        <attr name="errorMessageStyle" format="reference" />
+
+        <!-- Default colors -->
+        <attr name="defaultBrandColor" format="reference|color" />
+        <attr name="defaultSearchColor" format="reference|color" />
+        <!-- Default color that search orb pulses to.  If not set, this color is determined programatically based on the defaultSearchColor -->
+        <attr name="defaultSearchBrightColor" format="reference|color" />
+
+        <!-- Style for searchOrb -->
+        <attr name="searchOrbViewStyle" format="reference"/>
+        <attr name="defaultSearchIcon" format="reference" />
+
+        <attr name="playbackProgressPrimaryColor" format="reference|color" />
+        <attr name="playbackControlsIconHighlightColor" format="reference|color" />
+        <attr name="playbackControlsActionIcons" format="reference" />
+
+    </declare-styleable>
+
+
+</resources>
diff --git a/v17/leanback/res/values/colors.xml b/v17/leanback/res/values/colors.xml
new file mode 100644
index 0000000..a5db609
--- /dev/null
+++ b/v17/leanback/res/values/colors.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <color name="lb_grey">#888888</color>
+
+    <color name="lb_browse_title_color">#EEEEEE</color>
+    <color name="lb_browse_header_color">#FFFFFF</color>
+
+    <color name="lb_list_item_unselected_text_color">#FFF1F1F1</color>
+    <color name="lb_background_protection">#99000000</color>
+
+    <color name="lb_view_dim_mask_color">#000000</color>
+    <item name="lb_view_dimmed_level" type="dimen">60%</item>
+
+    <color name="lb_details_overview_bg_color">#1B1B1B</color>
+    <color name="lb_details_description_color">#EEEEEE</color>
+    <color name="lb_details_description_body_color">#B2EEEEEE</color>
+
+    <color name="lb_error_background_color_opaque">#262626</color>
+    <color name="lb_error_background_color_translucent">#E6000000</color>
+    <color name="lb_error_message_color_on_opaque">#80EEEEEE</color>
+    <color name="lb_error_message_color_on_translucent">#80555555</color>
+
+    <color name="lb_action_text_color">#EEEEEE</color>
+
+    <color name="lb_search_bar_text_color">#FFEEEEEE</color>
+    <color name="lb_search_bar_text_speech_color">#FF444444</color>
+    <color name="lb_search_bar_hint_color">#66222222</color>
+
+    <color name="lb_speech_orb_not_recording">#33EEEEEE</color>
+    <color name="lb_speech_orb_recording">#33EE0000</color>
+
+    <color name="lb_basic_card_bg_color">#FF3B3B3B</color>
+    <color name="lb_basic_card_info_bg_color">#FF3B3B3B</color>
+    <color name="lb_basic_card_title_text_color">#FFEEEEEE</color>
+    <color name="lb_basic_card_content_text_color">#FF888888</color>
+
+    <color name="lb_default_brand_color">#FF455A64</color>
+    <color name="lb_default_search_color">#FFFFAA3F</color>
+
+    <color name="lb_control_button_color">#66EEEEEE</color>
+    <color name="lb_playback_progress_color_no_theme">#ff40c4ff</color>
+    <color name="lb_playback_icon_highlight_no_theme">#ff40c4ff</color>
+    <color name="lb_playback_secondary_progress_color">#33FFFFFF</color>
+    <color name="lb_playback_background_progress_color">#19FFFFFF</color>
+    <color name="lb_playback_controls_background_light">#80000000</color>
+    <color name="lb_playback_controls_background_dark">#c0000000</color>
+    <color name="lb_playback_controls_time_text_color">#B2EEEEEE</color>
+
+</resources>
diff --git a/v17/leanback/res/values/dimens.xml b/v17/leanback/res/values/dimens.xml
new file mode 100644
index 0000000..5c49911
--- /dev/null
+++ b/v17/leanback/res/values/dimens.xml
@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+    <dimen name="lb_list_row_height">224dp</dimen>
+
+    <dimen name="lb_browse_padding_left">56dp</dimen>
+    <dimen name="lb_browse_padding_top">27dp</dimen>
+    <dimen name="lb_browse_padding_right">56dp</dimen>
+    <dimen name="lb_browse_padding_bottom">48dp</dimen>
+    <dimen name="lb_browse_rows_margin_start">238dp</dimen>
+    <dimen name="lb_browse_rows_margin_top">167dp</dimen>
+    <dimen name="lb_browse_rows_fading_edge">16dp</dimen>
+    <dimen name="lb_vertical_grid_padding_bottom">87dp</dimen>
+
+    <dimen name="lb_browse_title_height">60dp</dimen>
+    <dimen name="lb_browse_title_icon_height">52dp</dimen>
+    <dimen name="lb_browse_title_icon_width">52dp</dimen>
+    <dimen name="lb_browse_title_icon_margin_right">52dp</dimen>
+    <dimen name="lb_browse_title_text_size">44sp</dimen>
+    <dimen name="lb_browse_title_text_width">584dp</dimen>
+
+    <dimen name="lb_browse_headers_width">270dp</dimen>
+    <integer name="lb_browse_headers_transition_delay">150</integer>
+    <integer name="lb_browse_headers_transition_duration">250</integer>
+
+    <integer name="lb_browse_rows_anim_duration">250</integer>
+
+    <!-- Derived from the ux spec of 48dp baseline to baseline -->
+    <dimen name="lb_browse_headers_vertical_margin">21dp</dimen>
+    <dimen name="lb_browse_header_text_size">20sp</dimen>
+    <dimen name="lb_browse_header_padding_right">24dp</dimen>
+    <dimen name="lb_browse_header_height">24dp</dimen>
+
+    <item name="lb_browse_header_select_duration" format="integer" type="dimen">150</item>
+    <item name="lb_browse_header_unselect_alpha" type="fraction">50%</item>
+    <item name="lb_browse_header_select_scale" format="float" type="dimen">1.2</item>
+
+    <dimen name="lb_browse_row_hovercard_max_width">400dp</dimen>
+    <dimen name="lb_browse_row_hovercard_title_font_size">18sp</dimen>
+    <dimen name="lb_browse_row_hovercard_description_font_size">14sp</dimen>
+    <dimen name="lb_browse_item_horizontal_margin">8dp</dimen>
+    <dimen name="lb_browse_item_vertical_margin">8dp</dimen>
+    <dimen name="lb_browse_selected_row_top_padding">20dp</dimen>
+    <dimen name="lb_browse_expanded_selected_row_top_padding">16dp</dimen>
+    <dimen name="lb_browse_expanded_row_no_hovercard_bottom_padding">28dp</dimen>
+
+    <item name="lb_focus_zoom_factor_small" type="fraction">106%</item>
+    <item name="lb_focus_zoom_factor_medium" type="fraction">110%</item>
+    <item name="lb_focus_zoom_factor_large" type="fraction">114%</item>
+
+    <dimen name="lb_details_overview_height_large">274dp</dimen>
+    <dimen name="lb_details_overview_height_small">159dp</dimen>
+    <dimen name="lb_details_overview_margin_left">132dp</dimen>
+    <dimen name="lb_details_overview_margin_right">132dp</dimen>
+    <dimen name="lb_details_overview_margin_bottom">40dp</dimen>
+
+    <dimen name="lb_details_overview_description_margin_top">24dp</dimen>
+    <dimen name="lb_details_overview_description_margin_left">24dp</dimen>
+    <dimen name="lb_details_overview_description_margin_right">24dp</dimen>
+    <dimen name="lb_details_overview_description_margin_bottom">12dp</dimen>
+    <dimen name="lb_details_overview_image_margin_horizontal">24dp</dimen>
+    <dimen name="lb_details_overview_image_margin_vertical">24dp</dimen>
+    <dimen name="lb_details_overview_action_items_margin">16dp</dimen>
+    <item name="lb_details_overview_action_select_duration" format="integer" type="dimen">150</item>
+    <dimen name="lb_details_overview_actions_padding_left">294dp</dimen>
+    <dimen name="lb_details_overview_actions_padding_right">132dp</dimen>
+    <dimen name="lb_details_overview_actions_height">56dp</dimen>
+    <dimen name="lb_details_overview_actions_fade_size">16dp</dimen>
+    <dimen name="lb_details_rows_align_top">167dp</dimen>
+
+    <dimen name="lb_details_description_title_text_size">34sp</dimen>
+    <dimen name="lb_details_description_subtitle_text_size">16sp</dimen>
+    <dimen name="lb_details_description_body_text_size">14sp</dimen>
+    <dimen name="lb_details_description_title_line_spacing">40dp</dimen>
+    <dimen name="lb_details_description_body_line_spacing">20dp</dimen>
+    <dimen name="lb_details_description_title_baseline">26dp</dimen>
+    <dimen name="lb_details_description_under_title_baseline_margin">32dp</dimen>
+    <dimen name="lb_details_description_under_subtitle_baseline_margin">32dp</dimen>
+
+    <integer name="lb_details_description_title_max_lines">2</integer>
+    <integer name="lb_details_description_subtitle_max_lines">1</integer>
+    <integer name="lb_details_description_body_max_lines">5</integer>
+    <integer name="lb_details_description_body_min_lines">3</integer>
+
+    <dimen name="lb_action_1_line_height">36dp</dimen>
+    <dimen name="lb_action_2_lines_height">56dp</dimen>
+    <dimen name="lb_action_padding_horizontal">24dp</dimen>
+    <dimen name="lb_action_with_icon_padding_left">14dp</dimen>
+    <dimen name="lb_action_with_icon_padding_right">20dp</dimen>
+    <dimen name="lb_action_icon_margin">12dp</dimen>
+    <dimen name="lb_action_text_size">16sp</dimen>
+    <dimen name="lb_action_button_corner_radius">2dp</dimen>
+
+    <dimen name="lb_playback_controls_align_bottom">58dp</dimen>
+    <dimen name="lb_playback_controls_padding_bottom">28dp</dimen>
+    <dimen name="lb_playback_major_fade_translate_y">200dp</dimen>
+    <dimen name="lb_playback_minor_fade_translate_y">16dp</dimen>
+    <dimen name="lb_playback_controls_card_height">176dp</dimen>
+    <dimen name="lb_playback_controls_margin_left">132dp</dimen>
+    <dimen name="lb_playback_controls_margin_right">132dp</dimen>
+    <dimen name="lb_playback_controls_margin_bottom">20dp</dimen>
+    <dimen name="lb_playback_description_margin_top">24dp</dimen>
+    <dimen name="lb_playback_description_margin_start">24dp</dimen>
+    <dimen name="lb_playback_description_margin_end">24dp</dimen>
+    <dimen name="lb_playback_controls_time_text_size">12sp</dimen>
+    <dimen name="lb_playback_current_time_margin_start">16dp</dimen>
+    <dimen name="lb_playback_total_time_margin_end">16dp</dimen>
+    <dimen name="lb_playback_time_padding_top">8dp</dimen>
+    <dimen name="lb_playback_controls_child_margin_default">48dp</dimen>
+    <dimen name="lb_playback_controls_child_margin_bigger">64dp</dimen>
+    <dimen name="lb_playback_controls_child_margin_biggest">88dp</dimen>
+
+    <dimen name="lb_control_button_diameter">90dp</dimen>
+    <dimen name="lb_control_button_height">64dp</dimen>
+    <dimen name="lb_control_button_secondary_diameter">48dp</dimen>
+    <dimen name="lb_control_button_secondary_height">48dp</dimen>
+    <dimen name="lb_control_icon_width">32dp</dimen>
+    <dimen name="lb_control_icon_height">32dp</dimen>
+
+    <dimen name="lb_error_image_max_height">120dp</dimen>
+    <integer name="lb_error_message_max_lines">1</integer>
+    <dimen name="lb_error_message_max_width">600dp</dimen>
+    <dimen name="lb_error_message_text_size">16sp</dimen>
+    <dimen name="lb_error_under_image_baseline_margin">36dp</dimen>
+    <dimen name="lb_error_under_message_baseline_margin">24dp</dimen>
+
+    <!-- Search bar -->
+    <dimen name="lb_search_bar_height">60dp</dimen>
+    <dimen name="lb_search_bar_padding_left">56dp</dimen>
+    <dimen name="lb_search_bar_padding_top">27dp</dimen>
+
+    <dimen name="lb_search_bar_text_size">22sp</dimen>
+    <dimen name="lb_search_bar_unfocused_text_size">18sp</dimen>
+    <dimen name="lb_search_bar_items_layout_margin_top">27dp</dimen>
+    <dimen name="lb_search_bar_items_width">600dp</dimen>
+    <dimen name="lb_search_bar_items_height">56dp</dimen>
+    <dimen name="lb_search_bar_items_margin_left">56dp</dimen>
+    <dimen name="lb_search_bar_icon_height">32dp</dimen>
+    <dimen name="lb_search_bar_icon_width">32dp</dimen>
+    <dimen name="lb_search_bar_icon_margin_left">16dp</dimen>
+    <dimen name="lb_search_bar_edit_text_margin_left">24dp</dimen>
+    <dimen name="lb_search_bar_hint_margin_left">52dp</dimen>
+
+
+    <!-- Search Fragment -->
+    <dimen name="lb_search_browse_rows_align_top">120dp</dimen>
+    <dimen name="lb_search_browse_row_padding_left">56dp</dimen>
+
+    <dimen name="lb_search_orb_size">52dp</dimen>
+    <item name="lb_search_orb_focused_zoom" type="fraction">120%</item>
+    <item name="lb_search_orb_pulse_duration_ms" type="integer">1000</item>
+    <item name="lb_search_orb_scale_duration_ms" type="integer">150</item>
+
+    <dimen name="lb_search_orb_margin_top">4dp</dimen>
+    <dimen name="lb_search_orb_margin_bottom">4dp</dimen>
+    <dimen name="lb_search_orb_margin_left">4dp</dimen>
+    <dimen name="lb_search_orb_margin_right">4dp</dimen>
+
+    <dimen name="lb_search_bar_speech_orb_size">52dp</dimen>
+    <item name="lb_search_bar_speech_orb_focused_zoom" type="fraction">120%</item>
+    <item name="lb_search_bar_speech_orb_max_level_zoom" type="fraction">144%</item>
+    <dimen name="lb_search_bar_speech_orb_margin_left">56dp</dimen>
+
+    <!-- BasicCardView -->
+    <dimen name="lb_basic_card_main_width">140dp</dimen>
+    <dimen name="lb_basic_card_main_height">188dp</dimen>
+    <dimen name="lb_basic_card_info_height">52dp</dimen>
+    <dimen name="lb_basic_card_info_padding">6dp</dimen>
+    <dimen name="lb_basic_card_info_text_margin">2dp</dimen>
+    <dimen name="lb_basic_card_title_text_size">16sp</dimen>
+    <dimen name="lb_basic_card_content_text_size">12sp</dimen>
+    <dimen name="lb_basic_card_info_badge_size">16dp</dimen>
+
+    <!-- z based shadow -->
+    <dimen name="lb_material_shadow_normal_z">2dp</dimen>
+    <dimen name="lb_material_shadow_focused_z">8dp</dimen>
+
+</resources>
diff --git a/v17/leanback/res/values/ids.xml b/v17/leanback/res/values/ids.xml
new file mode 100644
index 0000000..32dcbf7
--- /dev/null
+++ b/v17/leanback/res/values/ids.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+ <resources>
+     <item type="id" name="lb_focus_animator" />
+     <item type="id" name="lb_header_transition_position" />
+
+     <item type="id" name="lb_control_play_pause" />
+     <item type="id" name="lb_control_fast_forward" />
+     <item type="id" name="lb_control_fast_rewind" />
+     <item type="id" name="lb_control_skip_next" />
+     <item type="id" name="lb_control_skip_previous" />
+     <item type="id" name="lb_control_more_actions" />
+     <item type="id" name="lb_control_thumbs_up" />
+     <item type="id" name="lb_control_thumbs_down" />
+     <item type="id" name="lb_control_repeat" />
+     <item type="id" name="lb_control_shuffle" />
+     <item type="id" name="lb_control_high_quality" />
+     <item type="id" name="lb_control_closed_captioning" />
+
+ </resources>
diff --git a/v17/leanback/res/values/integers.xml b/v17/leanback/res/values/integers.xml
new file mode 100644
index 0000000..8547e22
--- /dev/null
+++ b/v17/leanback/res/values/integers.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <integer name="lb_card_selected_animation_delay">400</integer>
+    <integer name="lb_card_selected_animation_duration">150</integer>
+    <integer name="lb_card_activated_animation_duration">150</integer>
+    <integer name="lb_search_bar_text_mode_background_alpha">51</integer>
+    <integer name="lb_search_bar_speech_mode_background_alpha">179</integer>
+    <integer name="lb_playback_bg_fade_in_ms">325</integer>
+    <integer name="lb_playback_bg_fade_out_ms">500</integer>
+    <integer name="lb_playback_controls_fade_in_ms">250</integer>
+    <integer name="lb_playback_controls_fade_out_ms">325</integer>
+    <integer name="lb_playback_description_fade_in_ms">250</integer>
+    <integer name="lb_playback_description_fade_out_ms">200</integer>
+    <integer name="lb_playback_rows_fade_in_ms">150</integer>
+    <integer name="lb_playback_rows_fade_out_ms">250</integer>
+    <integer name="lb_playback_rows_fade_delay_ms">100</integer>
+    <integer name="lb_playback_controls_show_time_ms">3000</integer>
+</resources>
diff --git a/v17/leanback/res/values/strings.xml b/v17/leanback/res/values/strings.xml
new file mode 100644
index 0000000..bad8000
--- /dev/null
+++ b/v17/leanback/res/values/strings.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Image description for the call to search action visible when browsing content [CHAR LIMIT=40] -->
+    <string name="orb_search_action">Search Action</string>
+    <!-- Hint showing in the empty search bar [CHAR LIMIT=40] -->
+    <string name="lb_search_bar_hint">Search</string>
+    <!-- Hint showing in the empty search bar when using the voice input [CHAR LIMIT=40] -->
+    <string name="lb_search_bar_hint_speech">Speak to search</string>
+    <!-- Hint showing in the empty search bar using a provided context (usually the application name) [CHAR LIMIT=40] -->
+    <string name="lb_search_bar_hint_with_title">Search <xliff:g id="search context">%1$s</xliff:g></string>
+    <!-- Hint showing in the empty search bar using a provided context (usually the application name) while in voice input mode [CHAR LIMIT=40] -->
+    <string name="lb_search_bar_hint_with_title_speech">Speak to search <xliff:g id="search context">%1$s</xliff:g></string>
+</resources>
diff --git a/v17/leanback/res/values/styles.xml b/v17/leanback/res/values/styles.xml
new file mode 100644
index 0000000..c683a6e
--- /dev/null
+++ b/v17/leanback/res/values/styles.xml
@@ -0,0 +1,276 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="TextAppearance.Leanback" parent="android:TextAppearance.Holo">
+        <!-- Any text appearance overrides go here. -->
+        <item name="android:fontFamily">sans-serif-condensed</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.Title" parent="TextAppearance.Leanback">
+        <item name="android:fontFamily">sans-serif-light</item>
+        <item name="android:textSize">@dimen/lb_browse_title_text_size</item>
+        <item name="android:textColor">@color/lb_browse_title_color</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.Header" parent="TextAppearance.Leanback">
+        <item name="android:textSize">@dimen/lb_browse_header_text_size</item>
+        <item name="android:textColor">@color/lb_browse_header_color</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.Row.Header" parent="TextAppearance.Leanback.Header">
+    </style>
+
+    <style name="TextAppearance.Leanback.SearchTextEdit" parent="TextAppearance.Leanback">
+        <item name="android:textSize">@dimen/lb_search_bar_text_size</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.DetailsDescriptionTitle">
+        <item name="android:textSize">@dimen/lb_details_description_title_text_size</item>
+        <item name="android:textColor">@color/lb_details_description_color</item>
+        <item name="android:fontFamily">sans-serif-light</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.DetailsDescriptionSubtitle">
+        <item name="android:textSize">@dimen/lb_details_description_subtitle_text_size</item>
+        <item name="android:textColor">@color/lb_details_description_color</item>
+        <item name="android:fontFamily">sans-serif</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.DetailsDescriptionBody">
+        <item name="android:textSize">@dimen/lb_details_description_body_text_size</item>
+        <item name="android:textColor">@color/lb_details_description_body_color</item>
+        <item name="android:fontFamily">sans-serif</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.DetailsActionButton">
+        <item name="android:textSize">@dimen/lb_action_text_size</item>
+        <item name="android:textColor">@color/lb_action_text_color</item>
+        <item name="android:textAllCaps">true</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.PlaybackControlsTime">
+        <item name="android:textSize">@dimen/lb_playback_controls_time_text_size</item>
+        <item name="android:textColor">@color/lb_playback_controls_time_text_color</item>
+        <item name="android:fontFamily">sans-serif</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.ErrorMessage">
+        <item name="android:textSize">@dimen/lb_error_message_text_size</item>
+        <item name="android:textColor">@color/lb_error_message_color_on_opaque</item>
+        <item name="android:fontFamily">sans-serif</item>
+    </style>
+
+    <style name="Widget.Leanback" parent="android:Widget.Holo" />
+
+    <style name="Widget.Leanback.BaseCardViewStyle">
+        <item name="android:foreground">@drawable/lb_card_foreground</item>
+    </style>
+
+    <style name="Widget.Leanback.ImageCardViewStyle" parent="Widget.Leanback.BaseCardViewStyle">
+        <item name="cardType">infoUnder</item>
+        <item name="infoVisibility">activated</item>
+        <item name="android:background">@color/lb_basic_card_bg_color</item>
+        <item name="infoAreaBackground">@color/lb_basic_card_info_bg_color</item>
+    </style>
+
+    <style name="Widget.Leanback.TitleView" >
+    </style>
+
+    <style name="Widget.Leanback.Title" />
+
+    <style name="Widget.Leanback.Title.Text">
+        <item name="android:singleLine">true</item>
+        <item name="android:gravity">right</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.Title</item>
+    </style>
+
+    <style name="Widget.Leanback.Title.Icon">
+        <item name="android:scaleType">fitEnd</item>
+    </style>
+
+    <!-- HeadersFragment -->
+    <style name="Widget.Leanback.Headers" />
+
+    <!-- RowsFragment -->
+    <style name="Widget.Leanback.Rows" >
+    </style>
+
+    <!-- row view -->
+    <style name="Widget.Leanback.Row" >
+    </style>
+
+    <style name="Widget.Leanback.GridItems" />
+
+    <style name="Widget.Leanback.Headers.VerticalGridView" >
+        <item name="android:paddingLeft">?attr/browsePaddingLeft</item>
+        <item name="android:clipToPadding">false</item>
+        <item name="focusOutFront">true</item>
+        <item name="focusOutEnd">true</item>
+        <item name="verticalMargin">@dimen/lb_browse_headers_vertical_margin</item>
+        <item name="android:focusable">true</item>
+        <item name="android:focusableInTouchMode">true</item>
+    </style>
+
+    <style name="Widget.Leanback.Header" >
+        <item name="android:minHeight">@dimen/lb_browse_header_height</item>
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.Header</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:ellipsize">marquee</item>
+        <item name="android:marqueeRepeatLimit">0</item>
+        <item name="android:paddingRight">@dimen/lb_browse_header_padding_right</item>
+    </style>
+
+    <style name="Widget.Leanback.Rows.VerticalGridView" >
+        <item name="android:paddingBottom">?attr/browsePaddingBottom</item>
+        <item name="android:clipToPadding">false</item>
+        <item name="focusOutFront">true</item>
+        <item name="focusOutEnd">true</item>
+        <item name="android:focusable">true</item>
+        <item name="android:focusableInTouchMode">true</item>
+    </style>
+
+    <style name="Widget.Leanback.Row.HorizontalGridView">
+        <item name="android:clipToPadding">false</item>
+        <item name="android:focusable">true</item>
+        <item name="android:focusableInTouchMode">true</item>
+        <item name="android:paddingLeft">?attr/browsePaddingLeft</item>
+        <item name="android:paddingRight">?attr/browsePaddingRight</item>
+        <item name="android:paddingBottom">@dimen/lb_browse_item_vertical_margin</item>
+        <item name="android:paddingTop">@dimen/lb_browse_item_vertical_margin</item>
+        <item name="horizontalMargin">@dimen/lb_browse_item_horizontal_margin</item>
+        <item name="verticalMargin">@dimen/lb_browse_item_vertical_margin</item>
+        <item name="focusOutFront">true</item>
+        <item name="rowHeight">wrap_content</item>
+    </style>
+
+    <style name="Widget.Leanback.GridItems.VerticalGridView">
+        <item name="android:clipToPadding">false</item>
+        <item name="android:focusable">true</item>
+        <item name="android:focusableInTouchMode">true</item>
+        <item name="android:paddingLeft">?attr/browsePaddingLeft</item>
+        <item name="android:paddingRight">?attr/browsePaddingRight</item>
+        <item name="android:paddingBottom">@dimen/lb_vertical_grid_padding_bottom</item>
+        <item name="android:paddingTop">?attr/browseRowsMarginTop</item>
+        <item name="android:gravity">center_horizontal</item>
+        <item name="horizontalMargin">@dimen/lb_browse_item_horizontal_margin</item>
+        <item name="verticalMargin">@dimen/lb_browse_item_vertical_margin</item>
+        <item name="columnWidth">wrap_content</item>
+        <item name="focusOutFront">true</item>
+    </style>
+
+    <style name="Widget.Leanback.Row.Header" parent="Widget.Leanback.Header">
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.Row.Header</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.Row.HoverCardTitle" parent="TextAppearance.Leanback">
+        <item name="android:textSize">@dimen/lb_browse_row_hovercard_title_font_size</item>
+    </style>
+
+    <style name="TextAppearance.Leanback.Row.HoverCardDescription" parent="TextAppearance.Leanback">
+        <item name="android:textSize">@dimen/lb_browse_row_hovercard_description_font_size</item>
+    </style>
+
+    <style name="Widget.Leanback.Row.HoverCardTitle" >
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.Row.HoverCardTitle</item>
+        <item name="android:maxWidth">@dimen/lb_browse_row_hovercard_max_width</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:ellipsize">end</item>
+    </style>
+
+    <style name="Widget.Leanback.Row.HoverCardDescription" >
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.Row.HoverCardDescription</item>
+        <item name="android:maxWidth">@dimen/lb_browse_row_hovercard_max_width</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:maxLines">4</item>
+    </style>
+
+    <style name="Widget.Leanback.DetailsDescriptionTitleStyle">
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.DetailsDescriptionTitle</item>
+        <item name="android:maxLines">@integer/lb_details_description_title_max_lines</item>
+        <item name="android:includeFontPadding">false</item>
+        <item name="android:ellipsize">end</item>
+    </style>
+
+    <style name="Widget.Leanback.DetailsDescriptionSubtitleStyle">
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.DetailsDescriptionSubtitle</item>
+        <item name="android:maxLines">@integer/lb_details_description_subtitle_max_lines</item>
+        <item name="android:includeFontPadding">false</item>
+        <item name="android:ellipsize">end</item>
+    </style>
+
+    <style name="Widget.Leanback.DetailsDescriptionBodyStyle">
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.DetailsDescriptionBody</item>
+        <item name="android:includeFontPadding">false</item>
+        <item name="android:ellipsize">end</item>
+    </style>
+
+    <style name="Widget.Leanback.DetailsActionButtonStyleBase" parent="android:Widget.Holo.Button.Borderless">
+    </style>
+
+    <style name="Widget.Leanback.DetailsActionButtonStyle" parent="Widget.Leanback.DetailsActionButtonStyleBase">
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.DetailsActionButton</item>
+        <item name="android:includeFontPadding">false</item>
+        <item name="android:drawablePadding">@dimen/lb_action_icon_margin</item>
+        <item name="android:focusable">true</item>
+        <item name="android:focusableInTouchMode">true</item>
+        <item name="android:paddingLeft">@dimen/lb_action_padding_horizontal</item>
+        <item name="android:paddingRight">@dimen/lb_action_padding_horizontal</item>
+    </style>
+
+    <style name="Widget.Leanback.PlaybackControlsButtonStyle" >
+        <item name="android:focusable">true</item>
+        <item name="android:focusableInTouchMode">true</item>
+    </style>
+
+    <style name="Widget.Leanback.PlaybackControlsTimeStyle">
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackControlsTime</item>
+    </style>
+
+    <style name="Widget.Leanback.ErrorMessageStyle">
+        <item name="android:textAppearance">@style/TextAppearance.Leanback.ErrorMessage</item>
+        <item name="android:includeFontPadding">false</item>
+        <item name="android:maxLines">@integer/lb_error_message_max_lines</item>
+        <item name="android:ellipsize">end</item>
+    </style>
+
+    <style name="Widget.Leanback.SearchOrbViewStyle">
+        <item name="searchOrbIcon">?attr/defaultSearchIcon</item>
+        <item name="searchOrbColor">?attr/defaultSearchColor</item>
+        <item name="searchOrbBrightColor">?attr/defaultSearchBrightColor</item>
+    </style>
+
+    <style name="Widget.Leanback.PlaybackControlsActionIconsStyle">
+        <item name="play">@drawable/lb_ic_play</item>
+        <item name="pause">@drawable/lb_ic_pause</item>
+        <item name="fast_forward">@drawable/lb_ic_fast_forward</item>
+        <item name="rewind">@drawable/lb_ic_fast_rewind</item>
+        <item name="skip_next">@drawable/lb_ic_skip_next</item>
+        <item name="skip_previous">@drawable/lb_ic_skip_previous</item>
+        <item name="thumb_up_outline">@drawable/lb_ic_thumb_up_outline</item>
+        <item name="thumb_up">@drawable/lb_ic_thumb_up</item>
+        <item name="thumb_down_outline">@drawable/lb_ic_thumb_down_outline</item>
+        <item name="thumb_down">@drawable/lb_ic_thumb_down</item>
+        <item name="repeat">@drawable/lb_ic_loop</item>
+        <item name="repeat_one">@drawable/lb_ic_loop_one</item>
+        <item name="shuffle">@drawable/lb_ic_shuffle</item>
+        <item name="high_quality">@drawable/lb_ic_hq</item>
+        <item name="closed_captioning">@drawable/lb_ic_cc</item>
+    </style>
+
+</resources>
diff --git a/v17/leanback/res/values/themes.xml b/v17/leanback/res/values/themes.xml
new file mode 100644
index 0000000..5fa12d7
--- /dev/null
+++ b/v17/leanback/res/values/themes.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+
+    <!-- LeanbackBase may be overridden for specific api levels -->
+    <style name="Theme.LeanbackBase" parent="android:Theme.Holo.NoActionBar">
+        <item name="playbackProgressPrimaryColor">@color/lb_playback_progress_color_no_theme</item>
+        <item name="playbackControlsIconHighlightColor">@color/lb_playback_icon_highlight_no_theme</item>
+    </style>
+
+    <style name="Theme.Leanback" parent="Theme.LeanbackBase">
+
+        <item name="android:windowOverscan">true</item>
+
+        <item name="baseCardViewStyle">@style/Widget.Leanback.BaseCardViewStyle</item>
+        <item name="imageCardViewStyle">@style/Widget.Leanback.ImageCardViewStyle</item>
+
+        <item name="browsePaddingLeft">@dimen/lb_browse_padding_left</item>
+        <item name="browsePaddingRight">@dimen/lb_browse_padding_right</item>
+        <item name="browsePaddingTop">@dimen/lb_browse_padding_top</item>
+        <item name="browsePaddingBottom">@dimen/lb_browse_padding_bottom</item>
+        <item name="browseRowsMarginStart">@dimen/lb_browse_rows_margin_start</item>
+        <item name="browseRowsMarginTop">@dimen/lb_browse_rows_margin_top</item>
+        <item name="browseRowsFadingEdgeLength">@dimen/lb_browse_rows_fading_edge</item>
+
+        <item name="headersVerticalGridStyle">@style/Widget.Leanback.Headers.VerticalGridView</item>
+        <item name="headerStyle">@style/Widget.Leanback.Header</item>
+
+        <item name="rowsVerticalGridStyle">@style/Widget.Leanback.Rows.VerticalGridView</item>
+        <item name="rowHorizontalGridStyle">@style/Widget.Leanback.Row.HorizontalGridView</item>
+        <item name="itemsVerticalGridStyle">@style/Widget.Leanback.GridItems.VerticalGridView</item>
+
+        <item name="browseTitleTextStyle">@style/Widget.Leanback.Title.Text</item>
+        <item name="browseTitleIconStyle">@style/Widget.Leanback.Title.Icon</item>
+        <item name="browseTitleViewStyle">@style/Widget.Leanback.TitleView</item>
+
+        <item name="rowHeaderStyle">@style/Widget.Leanback.Row.Header</item>
+        <item name="rowHoverCardTitleStyle">@style/Widget.Leanback.Row.HoverCardTitle</item>
+        <item name="rowHoverCardDescriptionStyle">@style/Widget.Leanback.Row.HoverCardDescription</item>
+
+        <item name="searchOrbViewStyle">@style/Widget.Leanback.SearchOrbViewStyle</item>
+
+        <item name="detailsDescriptionTitleStyle">@style/Widget.Leanback.DetailsDescriptionTitleStyle</item>
+        <item name="detailsDescriptionSubtitleStyle">@style/Widget.Leanback.DetailsDescriptionSubtitleStyle</item>
+        <item name="detailsDescriptionBodyStyle">@style/Widget.Leanback.DetailsDescriptionBodyStyle</item>
+        <item name="detailsActionButtonStyle">@style/Widget.Leanback.DetailsActionButtonStyle</item>
+        <item name="playbackControlsButtonStyle">@style/Widget.Leanback.PlaybackControlsButtonStyle</item>
+        <item name="playbackControlsTimeStyle">@style/Widget.Leanback.PlaybackControlsTimeStyle</item>
+        <item name="playbackControlsActionIcons">@style/Widget.Leanback.PlaybackControlsActionIconsStyle</item>
+
+        <item name="errorMessageStyle">@style/Widget.Leanback.ErrorMessageStyle</item>
+
+        <!-- TODO should be null, and set programatically to avoid dependence on leanback theme -->
+        <item name="defaultBrandColor">@color/lb_default_brand_color</item>
+        <item name="defaultSearchColor">@null</item>
+        <item name="defaultSearchBrightColor">@null</item>
+        <item name="defaultSearchIcon">@null</item>
+
+        <item name="android:windowSharedElementEnterTransition">@transition/lb_shared_element_enter_transition</item>
+        <item name="android:windowEnterTransition">@transition/lb_enter_transition</item>
+        <item name="android:windowSharedElementReturnTransition">@transition/lb_shared_element_return_transition</item>
+        <item name="android:windowReturnTransition">@transition/lb_return_transition</item>
+        </style>
+
+</resources>
diff --git a/v17/leanback/src/.readme b/v17/leanback/src/.readme
new file mode 100644
index 0000000..4bcebad
--- /dev/null
+++ b/v17/leanback/src/.readme
@@ -0,0 +1,2 @@
+This hidden file is there to ensure there is an src folder.
+Once we support binary library this will go away.
\ No newline at end of file
diff --git a/v17/leanback/src/android/support/v17/leanback/animation/LogAccelerateInterpolator.java b/v17/leanback/src/android/support/v17/leanback/animation/LogAccelerateInterpolator.java
new file mode 100644
index 0000000..5ebfe57
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/animation/LogAccelerateInterpolator.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.animation;
+
+import android.animation.TimeInterpolator;
+
+/**
+ * @hide
+ */
+public class LogAccelerateInterpolator implements TimeInterpolator {
+
+    int mBase;
+    int mDrift;
+    final float mLogScale;
+
+    public LogAccelerateInterpolator(int base, int drift) {
+        mBase = base;
+        mDrift = drift;
+        mLogScale = 1f / computeLog(1, mBase, mDrift);
+    }
+
+    static float computeLog(float t, int base, int drift) {
+        return (float) -Math.pow(base, -t) + 1 + (drift * t);
+    }
+
+    @Override
+    public float getInterpolation(float t) {
+        return 1 - computeLog(1 - t, mBase, mDrift) * mLogScale;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/animation/LogDecelerateInterpolator.java b/v17/leanback/src/android/support/v17/leanback/animation/LogDecelerateInterpolator.java
new file mode 100644
index 0000000..9c9b552
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/animation/LogDecelerateInterpolator.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.animation;
+
+import android.animation.TimeInterpolator;
+
+/**
+ * @hide
+ */
+public class LogDecelerateInterpolator implements TimeInterpolator {
+
+    int mBase;
+    int mDrift;
+    final float mLogScale;
+
+    public LogDecelerateInterpolator(int base, int drift) {
+        mBase = base;
+        mDrift = drift;
+
+        mLogScale = 1f / computeLog(1, mBase, mDrift);
+    }
+
+    static float computeLog(float t, int base, int drift) {
+        return (float) -Math.pow(base, -t) + 1 + (drift * t);
+    }
+
+    @Override
+    public float getInterpolation(float t) {
+        return computeLog(t, mBase, mDrift) * mLogScale;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BackgroundFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BackgroundFragment.java
new file mode 100644
index 0000000..334dd9d
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BackgroundFragment.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.app.Fragment;
+
+/**
+ * Fragment used by the background manager.
+ * @hide
+ */
+public final class BackgroundFragment extends Fragment {
+    private BackgroundManager mBackgroundManager;
+
+    void setBackgroundManager(BackgroundManager backgroundManager) {
+        mBackgroundManager = backgroundManager;
+    }
+
+    BackgroundManager getBackgroundManager() {
+        return mBackgroundManager;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        // mBackgroundManager might be null:
+        // if BackgroundFragment is just restored by FragmentManager,
+        // and user does not call BackgroundManager.getInstance() yet.
+        if (mBackgroundManager != null) {
+            mBackgroundManager.onActivityResume();
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        // mBackgroundManager might be null:
+        // if BackgroundFragment is just restored by FragmentManager,
+        // and user does not call BackgroundManager.getInstance() yet.
+        if (mBackgroundManager != null) {
+            mBackgroundManager.detach();
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java b/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
new file mode 100644
index 0000000..80e6ef9
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BackgroundManager.java
@@ -0,0 +1,805 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v17.leanback.R;
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.Handler;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+
+/**
+ * Supports background image continuity between multiple Activities.
+ *
+ * <p>An Activity should instantiate a BackgroundManager and {@link #attach}
+ * to the Activity's window.  When the Activity is started, the background is
+ * initialized to the current background values stored in a continuity service.
+ * The background continuity service is updated as the background is updated.
+ *
+ * <p>At some point, for example when it is stopped, the Activity may release
+ * its background state.
+ *
+ * <p>When an Activity is resumed, if the BackgroundManager has not been
+ * released, the continuity service is updated from the BackgroundManager state.
+ * If the BackgroundManager was released, the BackgroundManager inherits the
+ * current state from the continuity service.
+ *
+ * <p>When the last Activity is destroyed, the background state is reset.
+ *
+ * <p>Backgrounds consist of several layers, from back to front:
+ * <ul>
+ *   <li>the background Drawable of the theme</li>
+ *   <li>a solid color (set via {@link #setColor})</li>
+ *   <li>two Drawables, previous and current (set via {@link #setBitmap} or
+ *   {@link #setDrawable}), which may be in transition</li>
+ * </ul>
+ *
+ * <p>BackgroundManager holds references to potentially large bitmap Drawables.
+ * Call {@link #release} to release these references when the Activity is not
+ * visible.
+ */
+// TODO: support for multiple app processes requires a proper android service
+// instead of the shared memory "service" implemented here. Such a service could
+// support continuity between fragments of different applications if desired.
+public final class BackgroundManager {
+    private static final String TAG = "BackgroundManager";
+    private static final boolean DEBUG = false;
+
+    private static final int FULL_ALPHA = 255;
+    private static final int DIM_ALPHA_ON_SOLID = (int) (0.8f * FULL_ALPHA);
+    private static final int CHANGE_BG_DELAY_MS = 500;
+    private static final int FADE_DURATION = 500;
+
+    /**
+     * Using a separate window for backgrounds can improve graphics performance by
+     * leveraging hardware display layers.
+     * TODO: support a leanback configuration option.
+     */
+    private static final boolean USE_SEPARATE_WINDOW = false;
+
+    private static final String WINDOW_NAME = "BackgroundManager";
+    private static final String FRAGMENT_TAG = BackgroundManager.class.getCanonicalName();
+
+    private Context mContext;
+    private Handler mHandler;
+    private Window mWindow;
+    private WindowManager mWindowManager;
+    private View mBgView;
+    private BackgroundContinuityService mService;
+    private int mThemeDrawableResourceId;
+
+    private int mHeightPx;
+    private int mWidthPx;
+    private Drawable mBackgroundDrawable;
+    private int mBackgroundColor;
+    private boolean mAttached;
+
+    private static class BitmapDrawable extends Drawable {
+
+        static class ConstantState extends Drawable.ConstantState {
+            Bitmap mBitmap;
+            Matrix mMatrix;
+            Paint mPaint;
+
+            @Override
+            public Drawable newDrawable() {
+                return new BitmapDrawable(null, mBitmap, mMatrix);
+            }
+
+            @Override
+            public int getChangingConfigurations() {
+                return 0;
+            }
+        }
+
+        private ConstantState mState = new ConstantState();
+
+        BitmapDrawable(Resources resources, Bitmap bitmap) {
+            this(resources, bitmap, null);
+        }
+
+        BitmapDrawable(Resources resources, Bitmap bitmap, Matrix matrix) {
+            mState.mBitmap = bitmap;
+            mState.mMatrix = matrix != null ? matrix : new Matrix();
+            mState.mPaint = new Paint();
+            mState.mPaint.setFilterBitmap(true);
+        }
+
+        Bitmap getBitmap() {
+            return mState.mBitmap;
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            if (mState.mBitmap == null) {
+                return;
+            }
+            canvas.drawBitmap(mState.mBitmap, mState.mMatrix, mState.mPaint);
+        }
+
+        @Override
+        public int getOpacity() {
+            return android.graphics.PixelFormat.OPAQUE;
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+            if (mState.mPaint.getAlpha() != alpha) {
+                mState.mPaint.setAlpha(alpha);
+                invalidateSelf();
+            }
+        }
+
+        @Override
+        public void setColorFilter(ColorFilter cf) {
+            // Abstract in Drawable, not implemented
+        }
+
+        @Override
+        public ConstantState getConstantState() {
+            return mState;
+        }
+    }
+
+    private static class DrawableWrapper {
+        protected int mAlpha;
+        protected Drawable mDrawable;
+        protected ValueAnimator mAnimator;
+        protected boolean mAnimationPending;
+
+        private final Interpolator mInterpolator = new LinearInterpolator();
+        private final ValueAnimator.AnimatorUpdateListener mAnimationUpdateListener =
+                new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                setAlpha((Integer) animation.getAnimatedValue());
+            }
+        };
+
+        public DrawableWrapper(Drawable drawable) {
+            mDrawable = drawable;
+            setAlpha(FULL_ALPHA);
+        }
+
+        public Drawable getDrawable() {
+            return mDrawable;
+        }
+        public void setAlpha(int alpha) {
+            mAlpha = alpha;
+            mDrawable.setAlpha(alpha);
+        }
+        public int getAlpha() {
+            return mAlpha;
+        }
+        public void setColor(int color) {
+            ((ColorDrawable) mDrawable).setColor(color);
+        }
+        public void fadeIn(int durationMs, int delayMs) {
+            fade(durationMs, delayMs, FULL_ALPHA);
+        }
+        public void fadeOut(int durationMs) {
+            fade(durationMs, 0, 0);
+        }
+        public void fade(int durationMs, int delayMs, int alpha) {
+            if (mAnimator != null && mAnimator.isStarted()) {
+                mAnimator.cancel();
+            }
+            mAnimator = ValueAnimator.ofInt(getAlpha(), alpha);
+            mAnimator.addUpdateListener(mAnimationUpdateListener);
+            mAnimator.setInterpolator(mInterpolator);
+            mAnimator.setDuration(durationMs);
+            mAnimator.setStartDelay(delayMs);
+            mAnimationPending = true;
+        }
+        public boolean isAnimationPending() {
+            return mAnimationPending;
+        }
+        public boolean isAnimationStarted() {
+            return mAnimator != null && mAnimator.isStarted();
+        }
+        public void startAnimation() {
+            startAnimation(null);
+        }
+        public void startAnimation(Animator.AnimatorListener listener) {
+            if (listener != null) {
+                mAnimator.addListener(listener);
+            }
+            mAnimator.start();
+            mAnimationPending = false;
+        }
+    }
+
+    private LayerDrawable mLayerDrawable;
+    private DrawableWrapper mLayerWrapper;
+    private DrawableWrapper mImageInWrapper;
+    private DrawableWrapper mImageOutWrapper;
+    private DrawableWrapper mColorWrapper;
+    private DrawableWrapper mDimWrapper;
+
+    private Drawable mThemeDrawable;
+    private ChangeBackgroundRunnable mChangeRunnable;
+
+    /**
+     * Shared memory continuity service.
+     */
+    private static class BackgroundContinuityService {
+        private static final String TAG = "BackgroundContinuityService";
+        private static boolean DEBUG = BackgroundManager.DEBUG;
+
+        private static BackgroundContinuityService sService = new BackgroundContinuityService();
+
+        private int mColor;
+        private Drawable mDrawable;
+        private int mCount;
+
+        private BackgroundContinuityService() {
+            reset();
+        }
+
+        private void reset() {
+            mColor = Color.TRANSPARENT;
+            mDrawable = null;
+        }
+
+        public static BackgroundContinuityService getInstance() {
+            final int count = sService.mCount++;
+            if (DEBUG) Log.v(TAG, "Returning instance with new count " + count);
+            return sService;
+        }
+
+        public void unref() {
+            if (mCount <= 0) throw new IllegalStateException("Can't unref, count " + mCount);
+            if (--mCount == 0) {
+                if (DEBUG) Log.v(TAG, "mCount is zero, resetting");
+                reset();
+            }
+        }
+        public int getColor() {
+            return mColor;
+        }
+        public Drawable getDrawable() {
+            return mDrawable;
+        }
+        public void setColor(int color) {
+            mColor = color;
+        }
+        public void setDrawable(Drawable drawable) {
+            mDrawable = drawable;
+        }
+    }
+
+    private Drawable getThemeDrawable() {
+        Drawable drawable = null;
+        if (mThemeDrawableResourceId != -1) {
+            drawable = mContext.getResources().getDrawable(mThemeDrawableResourceId).mutate();
+        }
+        if (drawable == null) {
+            drawable = createEmptyDrawable();
+        }
+        return drawable;
+    }
+
+    /**
+     * Get the BackgroundManager associated with the Activity.
+     * <p>
+     * The BackgroundManager will be created on-demand for each individual
+     * Activity. Subsequent calls will return the same BackgroundManager created
+     * for this Activity.
+     */
+    public static BackgroundManager getInstance(Activity activity) {
+        BackgroundFragment fragment = (BackgroundFragment) activity.getFragmentManager()
+                .findFragmentByTag(FRAGMENT_TAG);
+        if (fragment != null) {
+            BackgroundManager manager = fragment.getBackgroundManager();
+            if (manager != null) {
+                return manager;
+            }
+            // manager is null: this is a fragment restored by FragmentManager,
+            // fall through to create a BackgroundManager attach to it.
+        }
+        return new BackgroundManager(activity);
+    }
+
+    private BackgroundManager(Activity activity) {
+        mContext = activity;
+        mService = BackgroundContinuityService.getInstance();
+        mHeightPx = mContext.getResources().getDisplayMetrics().heightPixels;
+        mWidthPx = mContext.getResources().getDisplayMetrics().widthPixels;
+        mHandler = new Handler();
+
+        TypedArray ta = activity.getTheme().obtainStyledAttributes(new int[] {
+                android.R.attr.windowBackground });
+        mThemeDrawableResourceId = ta.getResourceId(0, -1);
+        if (mThemeDrawableResourceId < 0) {
+            if (DEBUG) Log.v(TAG, "BackgroundManager no window background resource!");
+        }
+        ta.recycle();
+
+        createFragment(activity);
+    }
+
+    private void createFragment(Activity activity) {
+        // Use a fragment to ensure the background manager gets detached properly.
+        BackgroundFragment fragment = (BackgroundFragment) activity.getFragmentManager()
+                .findFragmentByTag(FRAGMENT_TAG);
+        if (fragment == null) {
+            fragment = new BackgroundFragment();
+            activity.getFragmentManager().beginTransaction().add(fragment, FRAGMENT_TAG).commit();
+        } else {
+            if (fragment.getBackgroundManager() != null) {
+                throw new IllegalStateException("Created duplicated BackgroundManager for same " +
+                        "activity, please use getInstance() instead");
+            }
+        }
+        fragment.setBackgroundManager(this);
+    }
+
+    /**
+     * Synchronizes state when the owning Activity is resumed.
+     */
+    void onActivityResume() {
+        if (mService == null) {
+            return;
+        }
+        if (mLayerDrawable == null) {
+            if (DEBUG) Log.v(TAG, "onActivityResume " + this +
+                    " released state, syncing with service");
+            syncWithService();
+        } else {
+            if (DEBUG) Log.v(TAG, "onActivityResume " + this + " updating service color "
+                    + mBackgroundColor + " drawable " + mBackgroundDrawable);
+            mService.setColor(mBackgroundColor);
+            mService.setDrawable(mBackgroundDrawable);
+        }
+    }
+
+    private void syncWithService() {
+        int color = mService.getColor();
+        Drawable drawable = mService.getDrawable();
+
+        if (DEBUG) Log.v(TAG, "syncWithService color " + Integer.toHexString(color)
+                + " drawable " + drawable);
+
+        mBackgroundColor = color;
+        mBackgroundDrawable = drawable == null ? null :
+            drawable.getConstantState().newDrawable().mutate();
+
+        updateImmediate();
+    }
+
+    private void lazyInit() {
+        if (mLayerDrawable != null) {
+            return;
+        }
+
+        mLayerDrawable = (LayerDrawable) mContext.getResources().getDrawable(
+                R.drawable.lb_background).mutate();
+        mBgView.setBackground(mLayerDrawable);
+
+        mLayerDrawable.setDrawableByLayerId(R.id.background_imageout, createEmptyDrawable());
+
+        mDimWrapper = new DrawableWrapper(
+                mLayerDrawable.findDrawableByLayerId(R.id.background_dim));
+
+        mLayerWrapper = new DrawableWrapper(mLayerDrawable);
+
+        mColorWrapper = new DrawableWrapper(
+                mLayerDrawable.findDrawableByLayerId(R.id.background_color));
+    }
+
+    /**
+     * Make the background visible on the given Window.
+     */
+    public void attach(Window window) {
+        if (USE_SEPARATE_WINDOW) {
+            attachBehindWindow(window);
+        } else {
+            attachToView(window.getDecorView());
+        }
+    }
+
+    private void attachBehindWindow(Window window) {
+        if (DEBUG) Log.v(TAG, "attachBehindWindow " + window);
+        mWindow = window;
+        mWindowManager = window.getWindowManager();
+
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                // Media window sits behind the main application window
+                WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA,
+                // Avoid default to software format RGBA
+                WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+                android.graphics.PixelFormat.TRANSLUCENT);
+        params.setTitle(WINDOW_NAME);
+        params.width = ViewGroup.LayoutParams.MATCH_PARENT;
+        params.height = ViewGroup.LayoutParams.MATCH_PARENT;
+
+        View backgroundView = LayoutInflater.from(mContext).inflate(
+                R.layout.lb_background_window, null);
+        mWindowManager.addView(backgroundView, params);
+
+        attachToView(backgroundView);
+    }
+
+    private void attachToView(View sceneRoot) {
+        mBgView = sceneRoot;
+        mAttached = true;
+        syncWithService();
+    }
+
+    /**
+     * Release references to Drawables and put the BackgroundManager into the
+     * detached state. Called when the associated Activity is destroyed.
+     * @hide
+     */
+    void detach() {
+        if (DEBUG) Log.v(TAG, "detach " + this);
+        release();
+
+        if (mWindowManager != null && mBgView != null) {
+            mWindowManager.removeViewImmediate(mBgView);
+        }
+
+        mWindowManager = null;
+        mWindow = null;
+        mBgView = null;
+        mAttached = false;
+
+        if (mService != null) {
+            mService.unref();
+            mService = null;
+        }
+    }
+
+    /**
+     * Release references to Drawables. Typically called to reduce memory
+     * overhead when not visible.
+     * <p>
+     * When an Activity is resumed, if the BackgroundManager has not been
+     * released, the continuity service is updated from the BackgroundManager
+     * state. If the BackgroundManager was released, the BackgroundManager
+     * inherits the current state from the continuity service.
+     */
+    public void release() {
+        if (DEBUG) Log.v(TAG, "release " + this);
+        if (mLayerDrawable != null) {
+            mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable());
+            mLayerDrawable.setDrawableByLayerId(R.id.background_imageout, createEmptyDrawable());
+            mLayerDrawable = null;
+        }
+        mLayerWrapper = null;
+        mImageInWrapper = null;
+        mImageOutWrapper = null;
+        mColorWrapper = null;
+        mDimWrapper = null;
+        mThemeDrawable = null;
+        if (mChangeRunnable != null) {
+            mChangeRunnable.cancel();
+            mChangeRunnable = null;
+        }
+        releaseBackgroundBitmap();
+    }
+
+    private void releaseBackgroundBitmap() {
+        mBackgroundDrawable = null;
+    }
+
+    private void updateImmediate() {
+        lazyInit();
+
+        mColorWrapper.setColor(mBackgroundColor);
+        if (mDimWrapper != null) {
+            mDimWrapper.setAlpha(mBackgroundColor == Color.TRANSPARENT ? 0 : DIM_ALPHA_ON_SOLID);
+        }
+        showWallpaper(mBackgroundColor == Color.TRANSPARENT);
+
+        mThemeDrawable = getThemeDrawable();
+        mLayerDrawable.setDrawableByLayerId(R.id.background_theme, mThemeDrawable);
+
+        if (mBackgroundDrawable == null) {
+            mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable());
+        } else {
+            if (DEBUG) Log.v(TAG, "Background drawable is available");
+            mImageInWrapper = new DrawableWrapper(mBackgroundDrawable);
+            mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, mBackgroundDrawable);
+            if (mDimWrapper != null) {
+                mDimWrapper.setAlpha(FULL_ALPHA);
+            }
+        }
+    }
+
+    /**
+     * Set the background to the given color. The timing for when this becomes
+     * visible in the app is undefined and may take place after a small delay.
+     */
+    public void setColor(int color) {
+        if (DEBUG) Log.v(TAG, "setColor " + Integer.toHexString(color));
+
+        mBackgroundColor = color;
+        mService.setColor(mBackgroundColor);
+
+        if (mColorWrapper != null) {
+            mColorWrapper.setColor(mBackgroundColor);
+        }
+    }
+
+    /**
+     * Set the given drawable into the background. The provided Drawable will be
+     * used unmodified as the background, without any scaling or cropping
+     * applied to it. The timing for when this becomes visible in the app is
+     * undefined and may take place after a small delay.
+     */
+    public void setDrawable(Drawable drawable) {
+        if (DEBUG) Log.v(TAG, "setBackgroundDrawable " + drawable);
+        setDrawableInternal(drawable);
+    }
+
+    private void setDrawableInternal(Drawable drawable) {
+        if (!mAttached) {
+            throw new IllegalStateException("Must attach before setting background drawable");
+        }
+
+        if (mChangeRunnable != null) {
+            if (sameDrawable(drawable, mChangeRunnable.mDrawable)) {
+                if (DEBUG) Log.v(TAG, "setting same drawable");
+                return;
+            }
+            mChangeRunnable.cancel();
+        }
+        mChangeRunnable = new ChangeBackgroundRunnable(drawable);
+
+        if (mImageInWrapper != null && mImageInWrapper.isAnimationStarted()) {
+            if (DEBUG) Log.v(TAG, "animation in progress");
+        } else {
+            mHandler.postDelayed(mChangeRunnable, CHANGE_BG_DELAY_MS);
+        }
+    }
+
+    /**
+     * Set the given bitmap into the background. When using setBitmap to set the
+     * background, the provided bitmap will be scaled and cropped to correctly
+     * fit within the dimensions of the view. The timing for when this becomes
+     * visible in the app is undefined and may take place after a small delay.
+     */
+    public void setBitmap(Bitmap bitmap) {
+        if (DEBUG) {
+            Log.v(TAG, "setBitmap " + bitmap);
+        }
+
+        if (bitmap == null) {
+            setDrawableInternal(null);
+            return;
+        }
+
+        if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
+            if (DEBUG) {
+                Log.v(TAG, "invalid bitmap width or height");
+            }
+            return;
+        }
+
+        Matrix matrix = null;
+
+        if ((bitmap.getWidth() != mWidthPx || bitmap.getHeight() != mHeightPx)) {
+            int dwidth = bitmap.getWidth();
+            int dheight = bitmap.getHeight();
+            float scale;
+
+            // Scale proportionately to fit width and height.
+            if (dwidth * mHeightPx > mWidthPx * dheight) {
+                scale = (float) mHeightPx / (float) dheight;
+            } else {
+                scale = (float) mWidthPx / (float) dwidth;
+            }
+
+            int subX = Math.min((int) (mWidthPx / scale), dwidth);
+            int dx = Math.max(0, (dwidth - subX) / 2);
+
+            matrix = new Matrix();
+            matrix.setScale(scale, scale);
+            matrix.preTranslate(-dx, 0);
+
+            if (DEBUG) Log.v(TAG, "original image size " + bitmap.getWidth() + "x" + bitmap.getHeight() +
+                    " scale " + scale + " dx " + dx);
+        }
+
+        BitmapDrawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap, matrix);
+
+        setDrawableInternal(bitmapDrawable);
+    }
+
+    private void applyBackgroundChanges() {
+        if (!mAttached || mLayerWrapper == null) {
+            return;
+        }
+
+        if (DEBUG) Log.v(TAG, "applyBackgroundChanges drawable " + mBackgroundDrawable);
+
+        int dimAlpha = 0;
+
+        if (mImageOutWrapper != null && mImageOutWrapper.isAnimationPending()) {
+            if (DEBUG) Log.v(TAG, "mImageOutWrapper animation starting");
+            mImageOutWrapper.startAnimation();
+            mImageOutWrapper = null;
+            dimAlpha = DIM_ALPHA_ON_SOLID;
+        }
+
+        if (mImageInWrapper == null && mBackgroundDrawable != null) {
+            if (DEBUG) Log.v(TAG, "creating new imagein drawable");
+            mImageInWrapper = new DrawableWrapper(mBackgroundDrawable);
+            mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, mBackgroundDrawable);
+            if (DEBUG) Log.v(TAG, "mImageInWrapper animation starting");
+            mImageInWrapper.setAlpha(0);
+            mImageInWrapper.fadeIn(FADE_DURATION, 0);
+            mImageInWrapper.startAnimation(mImageInListener);
+            dimAlpha = FULL_ALPHA;
+        }
+
+        if (mDimWrapper != null && dimAlpha != 0) {
+            if (DEBUG) Log.v(TAG, "dimwrapper animation starting to " + dimAlpha);
+            mDimWrapper.fade(FADE_DURATION, 0, dimAlpha);
+            mDimWrapper.startAnimation();
+        }
+    }
+
+    private final Animator.AnimatorListener mImageInListener = new Animator.AnimatorListener() {
+        @Override
+        public void onAnimationStart(Animator animation) {
+        }
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+        }
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (mChangeRunnable != null) {
+                if (DEBUG) Log.v(TAG, "animation ended, found change runnable");
+                mChangeRunnable.run();
+            }
+        }
+        @Override
+        public void onAnimationCancel(Animator animation) {
+        }
+    };
+
+    /**
+     * Returns the current background color.
+     */
+    public final int getColor() {
+        return mBackgroundColor;
+    }
+
+    /**
+     * Returns the current background {@link Drawable}.
+     */
+    public Drawable getDrawable() {
+        return mBackgroundDrawable;
+    }
+
+    private boolean sameDrawable(Drawable first, Drawable second) {
+        if (first == null || second == null) {
+            return false;
+        }
+        if (first == second) {
+            return true;
+        }
+        if (first instanceof BitmapDrawable && second instanceof BitmapDrawable) {
+            if (((BitmapDrawable) first).getBitmap().sameAs(((BitmapDrawable) second).getBitmap())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Task which changes the background.
+     */
+    class ChangeBackgroundRunnable implements Runnable {
+        private Drawable mDrawable;
+        private boolean mCancel;
+
+        ChangeBackgroundRunnable(Drawable drawable) {
+            mDrawable = drawable;
+        }
+
+        public void cancel() {
+            mCancel = true;
+        }
+
+        @Override
+        public void run() {
+            if (!mCancel) {
+                runTask();
+            }
+        }
+
+        private void runTask() {
+            lazyInit();
+
+            if (sameDrawable(mDrawable, mBackgroundDrawable)) {
+                if (DEBUG) Log.v(TAG, "same bitmap detected");
+                return;
+            }
+
+            releaseBackgroundBitmap();
+
+            if (mImageInWrapper != null) {
+                mImageOutWrapper = new DrawableWrapper(mImageInWrapper.getDrawable());
+                mImageOutWrapper.setAlpha(mImageInWrapper.getAlpha());
+                mImageOutWrapper.fadeOut(FADE_DURATION);
+
+                // Order is important! Setting a drawable "removes" the
+                // previous one from the view
+                mLayerDrawable.setDrawableByLayerId(R.id.background_imagein, createEmptyDrawable());
+                mLayerDrawable.setDrawableByLayerId(R.id.background_imageout,
+                        mImageOutWrapper.getDrawable());
+                mImageInWrapper.setAlpha(0);
+                mImageInWrapper = null;
+            }
+
+            mBackgroundDrawable = mDrawable;
+            mService.setDrawable(mBackgroundDrawable);
+
+            applyBackgroundChanges();
+
+            mChangeRunnable = null;
+        }
+    }
+
+    private Drawable createEmptyDrawable() {
+        Bitmap bitmap = null;
+        return new BitmapDrawable(mContext.getResources(), bitmap);
+    }
+
+    private void showWallpaper(boolean show) {
+        if (mWindow == null) {
+            return;
+        }
+
+        WindowManager.LayoutParams layoutParams = mWindow.getAttributes();
+        if (show) {
+            if ((layoutParams.flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) {
+                return;
+            }
+            if (DEBUG) Log.v(TAG, "showing wallpaper");
+            layoutParams.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+        } else {
+            if ((layoutParams.flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) == 0) {
+                return;
+            }
+            if (DEBUG) Log.v(TAG, "hiding wallpaper");
+            layoutParams.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+        }
+
+        mWindow.setAttributes(layoutParams);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
new file mode 100644
index 0000000..2336690
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.OnChildSelectedListener;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * An internal base class for a fragment containing a list of rows.
+ */
+abstract class BaseRowFragment extends Fragment {
+    private ObjectAdapter mAdapter;
+    private VerticalGridView mVerticalGridView;
+    private PresenterSelector mPresenterSelector;
+    private ItemBridgeAdapter mBridgeAdapter;
+    private int mSelectedPosition = -1;
+    protected int mReparentHeaderId;
+    protected boolean mInTransition;
+
+    abstract protected int getLayoutResourceId();
+
+    private final OnChildSelectedListener mRowSelectedListener = new OnChildSelectedListener() {
+        @Override
+        public void onChildSelected(ViewGroup parent, View view, int position, long id) {
+            onRowSelected(parent, view, position, id);
+        }
+    };
+
+    protected void onRowSelected(ViewGroup parent, View view, int position, long id) {
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        mVerticalGridView = (VerticalGridView) inflater.inflate(getLayoutResourceId(), container, false);
+        return mVerticalGridView;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        if (mBridgeAdapter != null) {
+            mVerticalGridView.setAdapter(mBridgeAdapter);
+            if (mSelectedPosition != -1) {
+                mVerticalGridView.setSelectedPosition(mSelectedPosition);
+            }
+        }
+        mVerticalGridView.setOnChildSelectedListener(mRowSelectedListener);
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mVerticalGridView = null;
+    }
+
+    /**
+     * Set the presenter selector used to create and bind views.
+     */
+    public final void setPresenterSelector(PresenterSelector presenterSelector) {
+        mPresenterSelector = presenterSelector;
+        updateAdapter();
+    }
+
+    /**
+     * Get the presenter selector used to create and bind views.
+     */
+    public final PresenterSelector getPresenterSelector() {
+        return mPresenterSelector;
+    }
+
+    /**
+     * Sets the adapter for the fragment.
+     */
+    public final void setAdapter(ObjectAdapter rowsAdapter) {
+        mAdapter = rowsAdapter;
+        updateAdapter();
+    }
+
+    /**
+     * Returns the list of rows.
+     */
+    public final ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Returns the bridge adapter.
+     */
+    protected final ItemBridgeAdapter getBridgeAdapter() {
+        return mBridgeAdapter;
+    }
+
+    /**
+     * Set the selected item position.
+     */
+    public void setSelectedPosition(int position) {
+        mSelectedPosition = position;
+        if(mVerticalGridView != null && mVerticalGridView.getAdapter() != null) {
+            mVerticalGridView.setSelectedPositionSmooth(position);
+        }
+    }
+
+    final VerticalGridView getVerticalGridView() {
+        return mVerticalGridView;
+    }
+
+    protected void updateAdapter() {
+        mBridgeAdapter = null;
+
+        if (mAdapter != null) {
+            // If presenter selector is null, adapter ps will be used
+            mBridgeAdapter = new ItemBridgeAdapter(mAdapter, mPresenterSelector);
+        }
+        if (mVerticalGridView != null) {
+            mVerticalGridView.setAdapter(mBridgeAdapter);
+            if (mBridgeAdapter != null && mSelectedPosition != -1) {
+                mVerticalGridView.setSelectedPosition(mSelectedPosition);
+            }
+        }
+    }
+
+    protected Object getItem(Row row, int position) {
+        if (row instanceof ListRow) {
+            return ((ListRow) row).getAdapter().get(position);
+        } else {
+            return null;
+        }
+    }
+
+    void setReparentHeaderId(int reparentId) {
+        mReparentHeaderId = reparentId;
+    }
+
+    void onTransitionStart() {
+        mInTransition = true;
+        if (mVerticalGridView != null) {
+            mVerticalGridView.setAnimateChildLayout(false);
+            mVerticalGridView.setPruneChild(false);
+            mVerticalGridView.setFocusSearchDisabled(true);
+        }
+    }
+
+    void onTransitionEnd() {
+        if (mVerticalGridView != null) {
+            mVerticalGridView.setAnimateChildLayout(true);
+            mVerticalGridView.setPruneChild(true);
+            mVerticalGridView.setFocusSearchDisabled(false);
+        }
+        mInTransition = false;
+    }
+
+    void setItemAlignment() {
+        if (mVerticalGridView != null) {
+            // align the top edge of item
+            mVerticalGridView.setItemAlignmentOffset(0);
+            mVerticalGridView.setItemAlignmentOffsetPercent(
+                    VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+        }
+    }
+
+    void setWindowAlignmentFromTop(int alignedTop) {
+        if (mVerticalGridView != null) {
+            // align to a fixed position from top
+            mVerticalGridView.setWindowAlignmentOffset(alignedTop);
+            mVerticalGridView.setWindowAlignmentOffsetPercent(
+                    VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+            mVerticalGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
new file mode 100644
index 0000000..3f36e7c
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
@@ -0,0 +1,959 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.transition.TransitionListener;
+import android.support.v17.leanback.widget.HorizontalGridView;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.TitleView;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.SearchOrbView;
+import android.util.Log;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentManager.BackStackEntry;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+
+/**
+ * A fragment for creating Leanback browse screens. It is composed of a
+ * RowsFragment and a HeadersFragment.
+ * <p>
+ * A BrowseFragment renders the elements of its {@link ObjectAdapter} as a set
+ * of rows in a vertical list. The elements in this adapter must be subclasses
+ * of {@link Row}.
+ * <p>
+ * The HeadersFragment can be set to be either shown or hidden by default, or
+ * may be disabled entirely. See {@link #setHeadersState} for details.
+ * <p>
+ * By default the BrowseFragment includes support for returning to the headers
+ * when the user presses Back. For Activities that customize {@link
+ * Activity#onBackPressed()}, you must disable this default Back key support by
+ * calling {@link #setHeadersTransitionOnBackEnabled(boolean)} with false and
+ * use {@link BrowseFragment.BrowseTransitionListener} and
+ * {@link #startHeadersTransition(boolean)}.
+ */
+public class BrowseFragment extends Fragment {
+
+    final class BackStackListener implements FragmentManager.OnBackStackChangedListener {
+        int mLastEntryCount;
+        int mIndexOfHeadersBackStack;
+
+        BackStackListener() {
+            mLastEntryCount = getFragmentManager().getBackStackEntryCount();
+            mIndexOfHeadersBackStack = -1;
+        }
+
+        @Override
+        public void onBackStackChanged() {
+            if (getFragmentManager() == null) {
+                Log.w(TAG, "getFragmentManager() is null, stack:", new Exception());
+                return;
+            }
+            int count = getFragmentManager().getBackStackEntryCount();
+            // if backstack is growing and last pushed entry is "headers" backstack,
+            // remember the index of the entry.
+            if (count > mLastEntryCount) {
+                BackStackEntry entry = getFragmentManager().getBackStackEntryAt(count - 1);
+                if (mWithHeadersBackStackName.equals(entry.getName())) {
+                    mIndexOfHeadersBackStack = count - 1;
+                }
+            } else if (count < mLastEntryCount) {
+                // if popped "headers" backstack, initiate the show header transition if needed
+                if (mIndexOfHeadersBackStack >= count) {
+                    if (!mShowingHeaders) {
+                        startHeadersTransitionInternal(true);
+                    }
+                }
+            }
+            mLastEntryCount = count;
+        }
+    }
+
+    /**
+     * Listener for transitions between browse headers and rows.
+     */
+    public static class BrowseTransitionListener {
+        /**
+         * Callback when headers transition starts.
+         *
+         * @param withHeaders True if the transition will result in headers
+         *        being shown, false otherwise.
+         */
+        public void onHeadersTransitionStart(boolean withHeaders) {
+        }
+        /**
+         * Callback when headers transition stops.
+         *
+         * @param withHeaders True if the transition will result in headers
+         *        being shown, false otherwise.
+         */
+        public void onHeadersTransitionStop(boolean withHeaders) {
+        }
+    }
+
+    private static final String TAG = "BrowseFragment";
+
+    private static final String LB_HEADERS_BACKSTACK = "lbHeadersBackStack_";
+
+    private static boolean DEBUG = false;
+
+    /** The headers fragment is enabled and shown by default. */
+    public static final int HEADERS_ENABLED = 1;
+
+    /** The headers fragment is enabled and hidden by default. */
+    public static final int HEADERS_HIDDEN = 2;
+
+    /** The headers fragment is disabled and will never be shown. */
+    public static final int HEADERS_DISABLED = 3;
+
+    private static final float SLIDE_DISTANCE_FACTOR = 2;
+
+    private RowsFragment mRowsFragment;
+    private HeadersFragment mHeadersFragment;
+
+    private ObjectAdapter mAdapter;
+
+    private String mTitle;
+    private Drawable mBadgeDrawable;
+    private int mHeadersState = HEADERS_ENABLED;
+    private int mBrandColor = Color.TRANSPARENT;
+    private boolean mBrandColorSet;
+
+    private BrowseFrameLayout mBrowseFrame;
+    private TitleView mTitleView;
+    private boolean mShowingTitle = true;
+    private boolean mHeadersBackStackEnabled = true;
+    private String mWithHeadersBackStackName;
+    private boolean mShowingHeaders = true;
+    private boolean mCanShowHeaders = true;
+    private int mContainerListMarginLeft;
+    private int mContainerListAlignTop;
+    private SearchOrbView.Colors mSearchAffordanceColors;
+    private boolean mSearchAffordanceColorSet;
+    private OnItemSelectedListener mExternalOnItemSelectedListener;
+    private OnClickListener mExternalOnSearchClickedListener;
+    private OnItemClickedListener mOnItemClickedListener;
+    private OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
+    private OnItemViewClickedListener mOnItemViewClickedListener;
+    private int mSelectedPosition = -1;
+
+    private PresenterSelector mHeaderPresenterSelector;
+
+    // transition related:
+    private static TransitionHelper sTransitionHelper = TransitionHelper.getInstance();
+    private int mReparentHeaderId = View.generateViewId();
+    private Object mSceneWithTitle;
+    private Object mSceneWithoutTitle;
+    private Object mSceneWithHeaders;
+    private Object mSceneWithoutHeaders;
+    private Object mTitleUpTransition;
+    private Object mTitleDownTransition;
+    private Object mHeadersTransition;
+    private int mHeadersTransitionStartDelay;
+    private int mHeadersTransitionDuration;
+    private BackStackListener mBackStackChangedListener;
+    private BrowseTransitionListener mBrowseTransitionListener;
+
+    private static final String ARG_TITLE = BrowseFragment.class.getCanonicalName() + ".title";
+    private static final String ARG_BADGE_URI = BrowseFragment.class.getCanonicalName() + ".badge";
+    private static final String ARG_HEADERS_STATE =
+        BrowseFragment.class.getCanonicalName() + ".headersState";
+
+    /**
+     * Create arguments for a browse fragment.
+     *
+     * @param args The Bundle to place arguments into, or null if the method
+     *        should return a new Bundle.
+     * @param title The title of the BrowseFragment.
+     * @param headersState The initial state of the headers of the
+     *        BrowseFragment. Must be one of {@link #HEADERS_ENABLED}, {@link
+     *        #HEADERS_HIDDEN}, or {@link #HEADERS_DISABLED}.
+     * @return A Bundle with the given arguments for creating a BrowseFragment.
+     */
+    public static Bundle createArgs(Bundle args, String title, int headersState) {
+        if (args == null) {
+            args = new Bundle();
+        }
+        args.putString(ARG_TITLE, title);
+        args.putInt(ARG_HEADERS_STATE, headersState);
+        return args;
+    }
+
+    /**
+     * Sets the brand color for the browse fragment. The brand color is used as
+     * the primary color for UI elements in the browse fragment. For example,
+     * the background color of the headers fragment uses the brand color.
+     *
+     * @param color The color to use as the brand color of the fragment.
+     */
+    public void setBrandColor(int color) {
+        mBrandColor = color;
+        mBrandColorSet = true;
+
+        if (mHeadersFragment != null) {
+            mHeadersFragment.setBackgroundColor(mBrandColor);
+        }
+    }
+
+    /**
+     * Returns the brand color for the browse fragment.
+     * The default is transparent.
+     */
+    public int getBrandColor() {
+        return mBrandColor;
+    }
+
+    /**
+     * Sets the adapter containing the rows for the fragment.
+     *
+     * <p>The items referenced by the adapter must be be derived from
+     * {@link Row}. These rows will be used by the rows fragment and the headers
+     * fragment (if not disabled) to render the browse rows.
+     *
+     * @param adapter An ObjectAdapter for the browse rows. All items must
+     *        derive from {@link Row}.
+     */
+    public void setAdapter(ObjectAdapter adapter) {
+        mAdapter = adapter;
+        if (mRowsFragment != null) {
+            mRowsFragment.setAdapter(adapter);
+            mHeadersFragment.setAdapter(adapter);
+        }
+    }
+
+    /**
+     * Returns the adapter containing the rows for the fragment.
+     */
+    public ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Sets an item selection listener. This listener will be called when an
+     * item or row is selected by a user.
+     *
+     * @param listener The listener to call when an item or row is selected.
+     * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
+     */
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mExternalOnItemSelectedListener = listener;
+    }
+
+    /**
+     * Sets an item selection listener.
+     */
+    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        mExternalOnItemViewSelectedListener = listener;
+    }
+
+    /**
+     * Returns an item selection listener.
+     */
+    public OnItemViewSelectedListener getOnItemViewSelectedListener() {
+        return mExternalOnItemViewSelectedListener;
+    }
+
+    /**
+     * Sets an item clicked listener on the fragment.
+     *
+     * <p>OnItemClickedListener will override {@link View.OnClickListener} that
+     * an item presenter may set during
+     * {@link Presenter#onCreateViewHolder(ViewGroup)}. So in general, you
+     * should choose to use an {@link OnItemClickedListener} or a
+     * {@link View.OnClickListener} on your item views, but not both.
+     *
+     * @param listener The listener to call when an item is clicked.
+     * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)}
+     */
+    public void setOnItemClickedListener(OnItemClickedListener listener) {
+        mOnItemClickedListener = listener;
+        if (mRowsFragment != null) {
+            mRowsFragment.setOnItemClickedListener(listener);
+        }
+    }
+
+    /**
+     * Returns the item clicked listener.
+     * @deprecated Use {@link #getOnItemViewClickedListener()}
+     */
+    public OnItemClickedListener getOnItemClickedListener() {
+        return mOnItemClickedListener;
+    }
+
+    /**
+     * Sets an item clicked listener on the fragment.
+     * OnItemViewClickedListener will override {@link View.OnClickListener} that
+     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
+     * So in general,  developer should choose one of the listeners but not both.
+     */
+    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        mOnItemViewClickedListener = listener;
+        if (mRowsFragment != null) {
+            mRowsFragment.setOnItemViewClickedListener(listener);
+        }
+    }
+
+    /**
+     * Returns the item Clicked listener.
+     */
+    public OnItemViewClickedListener getOnItemViewClickedListener() {
+        return mOnItemViewClickedListener;
+    }
+
+    /**
+     * Sets a click listener for the search affordance.
+     *
+     * <p>The presence of a listener will change the visibility of the search
+     * affordance in the fragment title. When set to non-null, the title will
+     * contain an element that a user may click to begin a search.
+     *
+     * <p>The listener's {@link View.OnClickListener#onClick onClick} method
+     * will be invoked when the user clicks on the search element.
+     *
+     * @param listener The listener to call when the search element is clicked.
+     */
+    public void setOnSearchClickedListener(View.OnClickListener listener) {
+        mExternalOnSearchClickedListener = listener;
+        if (mTitleView != null) {
+            mTitleView.setOnSearchClickedListener(listener);
+        }
+    }
+
+    /**
+     * Sets the {@link SearchOrbView.Colors} used to draw the search affordance.
+     */
+    public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
+        mSearchAffordanceColors = colors;
+        mSearchAffordanceColorSet = true;
+        if (mTitleView != null) {
+            mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
+        }
+    }
+
+    /**
+     * Returns the {@link SearchOrbView.Colors} used to draw the search affordance.
+     */
+    public SearchOrbView.Colors getSearchAffordanceColors() {
+        if (mSearchAffordanceColorSet) {
+            return mSearchAffordanceColors;
+        }
+        if (mTitleView == null) {
+            throw new IllegalStateException("Fragment views not yet created");
+        }
+        return mTitleView.getSearchAffordanceColors();
+    }
+
+    /**
+     * Sets the color used to draw the search affordance.
+     * A default brighter color will be set by the framework.
+     *
+     * @param color The color to use for the search affordance.
+     */
+    public void setSearchAffordanceColor(int color) {
+        setSearchAffordanceColors(new SearchOrbView.Colors(color));
+    }
+
+    /**
+     * Returns the color used to draw the search affordance.
+     */
+    public int getSearchAffordanceColor() {
+        return getSearchAffordanceColors().color;
+    }
+
+    /**
+     * Start a headers transition.
+     *
+     * <p>This method will begin a transition to either show or hide the
+     * headers, depending on the value of withHeaders. If headers are disabled
+     * for this browse fragment, this method will throw an exception.
+     *
+     * @param withHeaders True if the headers should transition to being shown,
+     *        false if the transition should result in headers being hidden.
+     */
+    public void startHeadersTransition(boolean withHeaders) {
+        if (!mCanShowHeaders) {
+            throw new IllegalStateException("Cannot start headers transition");
+        }
+        if (isInHeadersTransition() || mShowingHeaders == withHeaders) {
+            return;
+        }
+        startHeadersTransitionInternal(withHeaders);
+    }
+
+    /**
+     * Returns true if the headers transition is currently running.
+     */
+    public boolean isInHeadersTransition() {
+        return mHeadersTransition != null;
+    }
+
+    /**
+     * Returns true if headers are shown.
+     */
+    public boolean isShowingHeaders() {
+        return mShowingHeaders;
+    }
+
+    /**
+     * Set a listener for browse fragment transitions.
+     *
+     * @param listener The listener to call when a browse headers transition
+     *        begins or ends.
+     */
+    public void setBrowseTransitionListener(BrowseTransitionListener listener) {
+        mBrowseTransitionListener = listener;
+    }
+
+    private void startHeadersTransitionInternal(boolean withHeaders) {
+        if (getFragmentManager().isDestroyed()) {
+            return;
+        }
+        mShowingHeaders = withHeaders;
+        mRowsFragment.onTransitionStart();
+        mHeadersFragment.onTransitionStart();
+        createHeadersTransition();
+        if (mBrowseTransitionListener != null) {
+            mBrowseTransitionListener.onHeadersTransitionStart(withHeaders);
+        }
+        sTransitionHelper.runTransition(withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders,
+                mHeadersTransition);
+        if (mHeadersBackStackEnabled) {
+            if (!withHeaders) {
+                getFragmentManager().beginTransaction()
+                        .addToBackStack(mWithHeadersBackStackName).commit();
+            } else {
+                int count = getFragmentManager().getBackStackEntryCount();
+                if (count > 0) {
+                    BackStackEntry entry = getFragmentManager().getBackStackEntryAt(count - 1);
+                    if (mWithHeadersBackStackName.equals(entry.getName())) {
+                        getFragmentManager().popBackStack();
+                    }
+                }
+            }
+        }
+    }
+
+    private boolean isVerticalScrolling() {
+        // don't run transition
+        return mHeadersFragment.getVerticalGridView().getScrollState()
+                != HorizontalGridView.SCROLL_STATE_IDLE
+                || mRowsFragment.getVerticalGridView().getScrollState()
+                != HorizontalGridView.SCROLL_STATE_IDLE;
+    }
+
+    private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener =
+            new BrowseFrameLayout.OnFocusSearchListener() {
+        @Override
+        public View onFocusSearch(View focused, int direction) {
+            // If headers fragment is disabled, just return null.
+            if (!mCanShowHeaders) return null;
+
+            final View searchOrbView = mTitleView.getSearchAffordanceView();
+            // if headers is running transition,  focus stays
+            if (isInHeadersTransition()) return focused;
+            if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
+            if (direction == View.FOCUS_LEFT) {
+                if (isVerticalScrolling() || mShowingHeaders) {
+                    return focused;
+                }
+                return mHeadersFragment.getVerticalGridView();
+            } else if (direction == View.FOCUS_RIGHT) {
+                if (isVerticalScrolling() || !mShowingHeaders) {
+                    return focused;
+                }
+                return mRowsFragment.getVerticalGridView();
+            } else if (focused == searchOrbView && direction == View.FOCUS_DOWN) {
+                return mShowingHeaders ? mHeadersFragment.getVerticalGridView() :
+                    mRowsFragment.getVerticalGridView();
+
+            } else if (focused != searchOrbView && searchOrbView.getVisibility() == View.VISIBLE
+                    && direction == View.FOCUS_UP) {
+                return searchOrbView;
+
+            } else {
+                return null;
+            }
+        }
+    };
+
+    private final BrowseFrameLayout.OnChildFocusListener mOnChildFocusListener =
+            new BrowseFrameLayout.OnChildFocusListener() {
+
+        @Override
+        public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+            // Make sure not changing focus when requestFocus() is called.
+            if (mCanShowHeaders && mShowingHeaders) {
+                if (mHeadersFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
+                    return true;
+                }
+            }
+            if (mRowsFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
+                return true;
+            }
+            return mTitleView.requestFocus(direction, previouslyFocusedRect);
+        };
+
+        @Override
+        public void onRequestChildFocus(View child, View focused) {
+            int childId = child.getId();
+            if (!mCanShowHeaders || isInHeadersTransition()) return;
+            if (childId == R.id.browse_container_dock && mShowingHeaders) {
+                startHeadersTransitionInternal(false);
+            } else if (childId == R.id.browse_headers_dock && !mShowingHeaders) {
+                startHeadersTransitionInternal(true);
+            }
+        }
+    };
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        TypedArray ta = getActivity().obtainStyledAttributes(R.styleable.LeanbackTheme);
+        mContainerListMarginLeft = (int) ta.getDimension(
+                R.styleable.LeanbackTheme_browseRowsMarginStart, 0);
+        mContainerListAlignTop = (int) ta.getDimension(
+                R.styleable.LeanbackTheme_browseRowsMarginTop, 0);
+        ta.recycle();
+
+        mHeadersTransitionStartDelay = getResources()
+                .getInteger(R.integer.lb_browse_headers_transition_delay);
+        mHeadersTransitionDuration = getResources()
+                .getInteger(R.integer.lb_browse_headers_transition_duration);
+
+        readArguments(getArguments());
+
+        if (mCanShowHeaders && mHeadersBackStackEnabled) {
+            mWithHeadersBackStackName = LB_HEADERS_BACKSTACK + this;
+            mBackStackChangedListener = new BackStackListener();
+            getFragmentManager().addOnBackStackChangedListener(mBackStackChangedListener);
+            if (!mShowingHeaders) {
+                getFragmentManager().beginTransaction()
+                        .addToBackStack(mWithHeadersBackStackName).commit();
+            }
+        }
+
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mBackStackChangedListener != null) {
+            getFragmentManager().removeOnBackStackChangedListener(mBackStackChangedListener);
+        }
+        super.onDestroy();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) {
+            mRowsFragment = new RowsFragment();
+            mHeadersFragment = new HeadersFragment();
+            getChildFragmentManager().beginTransaction()
+                    .replace(R.id.browse_headers_dock, mHeadersFragment)
+                    .replace(R.id.browse_container_dock, mRowsFragment).commit();
+        } else {
+            mHeadersFragment = (HeadersFragment) getChildFragmentManager()
+                    .findFragmentById(R.id.browse_headers_dock);
+            mRowsFragment = (RowsFragment) getChildFragmentManager()
+                    .findFragmentById(R.id.browse_container_dock);
+        }
+
+        mHeadersFragment.setHeadersGone(!mCanShowHeaders);
+
+        mRowsFragment.setAdapter(mAdapter);
+        if (mHeaderPresenterSelector != null) {
+            mHeadersFragment.setPresenterSelector(mHeaderPresenterSelector);
+        }
+        mHeadersFragment.setAdapter(mAdapter);
+
+        mRowsFragment.setOnItemSelectedListener(mRowSelectedListener);
+        mRowsFragment.setOnItemViewSelectedListener(mRowViewSelectedListener);
+        mHeadersFragment.setOnItemSelectedListener(mHeaderSelectedListener);
+        mHeadersFragment.setOnHeaderClickedListener(mHeaderClickedListener);
+        mRowsFragment.setOnItemClickedListener(mOnItemClickedListener);
+        mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
+
+        View root = inflater.inflate(R.layout.lb_browse_fragment, container, false);
+
+        mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
+        mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
+        mBrowseFrame.setOnChildFocusListener(mOnChildFocusListener);
+
+        mTitleView = (TitleView) root.findViewById(R.id.browse_title_group);
+        mTitleView.setTitle(mTitle);
+        mTitleView.setBadgeDrawable(mBadgeDrawable);
+        if (mSearchAffordanceColorSet) {
+            mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
+        }
+        if (mExternalOnSearchClickedListener != null) {
+            mTitleView.setOnSearchClickedListener(mExternalOnSearchClickedListener);
+        }
+
+        if (mBrandColorSet) {
+            mHeadersFragment.setBackgroundColor(mBrandColor);
+        }
+
+        mSceneWithTitle = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                TitleTransitionHelper.showTitle(mTitleView, true);
+            }
+        });
+        mSceneWithoutTitle = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                TitleTransitionHelper.showTitle(mTitleView, false);
+            }
+        });
+        mSceneWithHeaders = sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                showHeaders(true);
+            }
+        });
+        mSceneWithoutHeaders =  sTransitionHelper.createScene(mBrowseFrame, new Runnable() {
+            @Override
+            public void run() {
+                showHeaders(false);
+            }
+        });
+        mTitleUpTransition = TitleTransitionHelper.createTransitionTitleUp(sTransitionHelper);
+        mTitleDownTransition = TitleTransitionHelper.createTransitionTitleDown(sTransitionHelper);
+
+        sTransitionHelper.excludeChildren(mTitleUpTransition, R.id.browse_headers, true);
+        sTransitionHelper.excludeChildren(mTitleDownTransition, R.id.browse_headers, true);
+        sTransitionHelper.excludeChildren(mTitleUpTransition, R.id.container_list, true);
+        sTransitionHelper.excludeChildren(mTitleDownTransition, R.id.container_list, true);
+
+        return root;
+    }
+
+    private void createHeadersTransition() {
+        mHeadersTransition = sTransitionHelper.createTransitionSet(false);
+        sTransitionHelper.excludeChildren(mHeadersTransition, R.id.browse_title_group, true);
+        Object changeBounds = sTransitionHelper.createChangeBounds(false);
+        Object fadeIn = sTransitionHelper.createFadeTransition(TransitionHelper.FADE_IN);
+        Object fadeOut = sTransitionHelper.createFadeTransition(TransitionHelper.FADE_OUT);
+
+        sTransitionHelper.setDuration(fadeOut, mHeadersTransitionDuration);
+        sTransitionHelper.addTransition(mHeadersTransition, fadeOut);
+        if (mShowingHeaders) {
+            sTransitionHelper.setStartDelay(changeBounds, mHeadersTransitionStartDelay);
+        }
+        sTransitionHelper.setDuration(changeBounds, mHeadersTransitionDuration);
+        sTransitionHelper.addTransition(mHeadersTransition, changeBounds);
+        sTransitionHelper.setDuration(fadeIn, mHeadersTransitionDuration);
+        sTransitionHelper.setStartDelay(fadeIn, mHeadersTransitionStartDelay);
+        sTransitionHelper.addTransition(mHeadersTransition, fadeIn);
+
+        sTransitionHelper.setTransitionListener(mHeadersTransition, new TransitionListener() {
+            @Override
+            public void onTransitionStart(Object transition) {
+            }
+            @Override
+            public void onTransitionEnd(Object transition) {
+                mHeadersTransition = null;
+                mRowsFragment.onTransitionEnd();
+                mHeadersFragment.onTransitionEnd();
+                if (mShowingHeaders) {
+                    VerticalGridView headerGridView = mHeadersFragment.getVerticalGridView();
+                    if (headerGridView != null && !headerGridView.hasFocus()) {
+                        headerGridView.requestFocus();
+                    }
+                } else {
+                    VerticalGridView rowsGridView = mRowsFragment.getVerticalGridView();
+                    if (rowsGridView != null && !rowsGridView.hasFocus()) {
+                        rowsGridView.requestFocus();
+                    }
+                }
+                if (mBrowseTransitionListener != null) {
+                    mBrowseTransitionListener.onHeadersTransitionStop(mShowingHeaders);
+                }
+            }
+        });
+    }
+
+    /**
+     * Sets the {@link PresenterSelector} used to render the row headers.
+     *
+     * @param headerPresenterSelector The PresenterSelector that will determine
+     *        the Presenter for each row header.
+     */
+    public void setHeaderPresenterSelector(PresenterSelector headerPresenterSelector) {
+        mHeaderPresenterSelector = headerPresenterSelector;
+        if (mHeadersFragment != null) {
+            mHeadersFragment.setPresenterSelector(mHeaderPresenterSelector);
+        }
+    }
+
+    private void showHeaders(boolean show) {
+        if (DEBUG) Log.v(TAG, "showHeaders " + show);
+        mHeadersFragment.setHeadersEnabled(show);
+        MarginLayoutParams lp;
+        View containerList;
+
+        containerList = mRowsFragment.getView();
+        lp = (MarginLayoutParams) containerList.getLayoutParams();
+        lp.leftMargin = show ? mContainerListMarginLeft : 0;
+        containerList.setLayoutParams(lp);
+
+        containerList = mHeadersFragment.getView();
+        lp = (MarginLayoutParams) containerList.getLayoutParams();
+        lp.leftMargin = show ? 0 : -mContainerListMarginLeft;
+        containerList.setLayoutParams(lp);
+
+        mRowsFragment.setExpand(!show);
+    }
+
+    private HeadersFragment.OnHeaderClickedListener mHeaderClickedListener =
+        new HeadersFragment.OnHeaderClickedListener() {
+            @Override
+            public void onHeaderClicked() {
+                if (!mCanShowHeaders || !mShowingHeaders || isInHeadersTransition()) {
+                    return;
+                }
+                startHeadersTransitionInternal(false);
+                mRowsFragment.getVerticalGridView().requestFocus();
+            }
+        };
+
+    private OnItemViewSelectedListener mRowViewSelectedListener = new OnItemViewSelectedListener() {
+        @Override
+        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                RowPresenter.ViewHolder rowViewHolder, Row row) {
+            int position = mRowsFragment.getVerticalGridView().getSelectedPosition();
+            if (DEBUG) Log.v(TAG, "row selected position " + position);
+            onRowSelected(position);
+            if (mExternalOnItemViewSelectedListener != null) {
+                mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
+                        rowViewHolder, row);
+            }
+        }
+    };
+
+    private OnItemSelectedListener mRowSelectedListener = new OnItemSelectedListener() {
+        @Override
+        public void onItemSelected(Object item, Row row) {
+            if (mExternalOnItemSelectedListener != null) {
+                mExternalOnItemSelectedListener.onItemSelected(item, row);
+            }
+        }
+    };
+
+    private OnItemSelectedListener mHeaderSelectedListener = new OnItemSelectedListener() {
+        @Override
+        public void onItemSelected(Object item, Row row) {
+            int position = mHeadersFragment.getVerticalGridView().getSelectedPosition();
+            if (DEBUG) Log.v(TAG, "header selected position " + position);
+            onRowSelected(position);
+        }
+    };
+
+    private void onRowSelected(int position) {
+        if (position != mSelectedPosition) {
+            mSetSelectionRunnable.mPosition = position;
+            mBrowseFrame.getHandler().post(mSetSelectionRunnable);
+
+            if (getAdapter() == null || getAdapter().size() == 0 || position == 0) {
+                if (!mShowingTitle) {
+                    sTransitionHelper.runTransition(mSceneWithTitle, mTitleDownTransition);
+                    mShowingTitle = true;
+                }
+            } else if (mShowingTitle) {
+                sTransitionHelper.runTransition(mSceneWithoutTitle, mTitleUpTransition);
+                mShowingTitle = false;
+            }
+        }
+    }
+
+    private class SetSelectionRunnable implements Runnable {
+        int mPosition;
+        @Override
+        public void run() {
+            setSelection(mPosition);
+        }
+    }
+
+    private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
+
+    private void setSelection(int position) {
+        if (position != NO_POSITION) {
+            mRowsFragment.setSelectedPosition(position);
+            mHeadersFragment.setSelectedPosition(position);
+        }
+        mSelectedPosition = position;
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mHeadersFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
+        mHeadersFragment.setItemAlignment();
+        mRowsFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
+        mRowsFragment.setItemAlignment();
+
+        if (mCanShowHeaders && mShowingHeaders && mHeadersFragment.getView() != null) {
+            mHeadersFragment.getView().requestFocus();
+        } else if ((!mCanShowHeaders || !mShowingHeaders)
+                && mRowsFragment.getView() != null) {
+            mRowsFragment.getView().requestFocus();
+        }
+        if (mCanShowHeaders) {
+            showHeaders(mShowingHeaders);
+        }
+    }
+
+    /**
+     * Enable/disable headers transition on back key support. This is enabled by
+     * default. The BrowseFragment will add a back stack entry when headers are
+     * showing. Running a headers transition when the back key is pressed only
+     * works when the headers state is {@link #HEADERS_ENABLED} or
+     * {@link #HEADERS_HIDDEN}.
+     * <p>
+     * NOTE: If an Activity has its own onBackPressed() handling, you must
+     * disable this feature. You may use {@link #startHeadersTransition(boolean)}
+     * and {@link BrowseTransitionListener} in your own back stack handling.
+     */
+    public final void setHeadersTransitionOnBackEnabled(boolean headersBackStackEnabled) {
+        mHeadersBackStackEnabled = headersBackStackEnabled;
+    }
+
+    /**
+     * Returns true if headers transition on back key support is enabled.
+     */
+    public final boolean isHeadersTransitionOnBackEnabled() {
+        return mHeadersBackStackEnabled;
+    }
+
+    private void readArguments(Bundle args) {
+        if (args == null) {
+            return;
+        }
+        if (args.containsKey(ARG_TITLE)) {
+            setTitle(args.getString(ARG_TITLE));
+        }
+        if (args.containsKey(ARG_HEADERS_STATE)) {
+            setHeadersState(args.getInt(ARG_HEADERS_STATE));
+        }
+    }
+
+    /**
+     * Sets the drawable displayed in the browse fragment title.
+     *
+     * @param drawable The Drawable to display in the browse fragment title.
+     */
+    public void setBadgeDrawable(Drawable drawable) {
+        if (mBadgeDrawable != drawable) {
+            mBadgeDrawable = drawable;
+            if (mTitleView != null) {
+                mTitleView.setBadgeDrawable(drawable);
+            }
+        }
+    }
+
+    /**
+     * Returns the badge drawable used in the fragment title.
+     */
+    public Drawable getBadgeDrawable() {
+        return mBadgeDrawable;
+    }
+
+    /**
+     * Sets a title for the browse fragment.
+     *
+     * @param title The title of the browse fragment.
+     */
+    public void setTitle(String title) {
+        mTitle = title;
+        if (mTitleView != null) {
+            mTitleView.setTitle(title);
+        }
+    }
+
+    /**
+     * Returns the title for the browse fragment.
+     */
+    public String getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Sets the state for the headers column in the browse fragment. Must be one
+     * of {@link #HEADERS_ENABLED}, {@link #HEADERS_HIDDEN}, or
+     * {@link #HEADERS_DISABLED}.
+     *
+     * @param headersState The state of the headers for the browse fragment.
+     */
+    public void setHeadersState(int headersState) {
+        if (headersState < HEADERS_ENABLED || headersState > HEADERS_DISABLED) {
+            throw new IllegalArgumentException("Invalid headers state: " + headersState);
+        }
+        if (DEBUG) Log.v(TAG, "setHeadersState " + headersState);
+
+        if (headersState != mHeadersState) {
+            mHeadersState = headersState;
+            switch (headersState) {
+                case HEADERS_ENABLED:
+                    mCanShowHeaders = true;
+                    mShowingHeaders = true;
+                    break;
+                case HEADERS_HIDDEN:
+                    mCanShowHeaders = true;
+                    mShowingHeaders = false;
+                    break;
+                case HEADERS_DISABLED:
+                    mCanShowHeaders = false;
+                    mShowingHeaders = false;
+                    break;
+                default:
+                    Log.w(TAG, "Unknown headers state: " + headersState);
+                    break;
+            }
+            if (mHeadersFragment != null) {
+                mHeadersFragment.setHeadersGone(!mCanShowHeaders);
+            }
+        }
+    }
+
+    /**
+     * Returns the state of the headers column in the browse fragment.
+     */
+    public int getHeadersState() {
+        return mHeadersState;
+    }
+}
+
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseFrameLayout.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseFrameLayout.java
new file mode 100644
index 0000000..9b87305
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseFrameLayout.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+/**
+ * Top level implementation viewgroup for browse to manage transitions between
+ * browse sub fragments.
+ *
+ */
+class BrowseFrameLayout extends FrameLayout {
+
+    public interface OnFocusSearchListener {
+        public View onFocusSearch(View focused, int direction);
+    }
+
+    public interface OnChildFocusListener {
+        public boolean onRequestFocusInDescendants(int direction,
+                Rect previouslyFocusedRect);
+        public void onRequestChildFocus(View child, View focused);
+    }
+
+    public BrowseFrameLayout(Context context) {
+        this(context, null, 0);
+    }
+
+    public BrowseFrameLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public BrowseFrameLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    private OnFocusSearchListener mListener;
+    private OnChildFocusListener mOnChildFocusListener;
+
+    public void setOnFocusSearchListener(OnFocusSearchListener listener) {
+        mListener = listener;
+    }
+
+    public void setOnChildFocusListener(OnChildFocusListener listener) {
+        mOnChildFocusListener = listener;
+    }
+
+    @Override
+    protected boolean onRequestFocusInDescendants(int direction,
+            Rect previouslyFocusedRect) {
+        if (mOnChildFocusListener != null) {
+            return mOnChildFocusListener.onRequestFocusInDescendants(direction,
+                    previouslyFocusedRect);
+        }
+        return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
+    }
+
+    @Override
+    public View focusSearch(View focused, int direction) {
+        if (mListener != null) {
+            View view = mListener.onFocusSearch(focused, direction);
+            if (view != null) {
+                return view;
+            }
+        }
+        return super.focusSearch(focused, direction);
+    }
+
+    @Override
+    public void requestChildFocus(View child, View focused) {
+        super.requestChildFocus(child, focused);
+        if (mOnChildFocusListener != null) {
+            mOnChildFocusListener.onRequestChildFocus(child, focused);
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseRowsFrameLayout.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseRowsFrameLayout.java
new file mode 100644
index 0000000..3f10a63
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseRowsFrameLayout.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+/**
+ * Customized FrameLayout excludes margin of child from calculating the child size.
+ * So we can change left margin of rows while keep the width of rows unchanged without
+ * using hardcoded DIPS.
+ */
+class BrowseRowsFrameLayout extends FrameLayout {
+
+    public BrowseRowsFrameLayout(Context context) {
+        this(context ,null);
+    }
+
+    public BrowseRowsFrameLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public BrowseRowsFrameLayout(Context context, AttributeSet attrs,
+            int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void measureChildWithMargins(View child,
+            int parentWidthMeasureSpec, int widthUsed,
+            int parentHeightMeasureSpec, int heightUsed) {
+        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
+                getPaddingLeft() + getPaddingRight() + widthUsed, lp.width);
+        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
+                getPaddingTop() + getPaddingBottom() + heightUsed, lp.height);
+        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
new file mode 100644
index 0000000..da62244
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Wrapper fragment for leanback details screens.
+ */
+public class DetailsFragment extends Fragment {
+    private static final String TAG = "DetailsFragment";
+    private static boolean DEBUG = false;
+
+    private RowsFragment mRowsFragment;
+
+    private ObjectAdapter mAdapter;
+    private int mContainerListAlignTop;
+    private OnItemSelectedListener mExternalOnItemSelectedListener;
+    private OnItemClickedListener mOnItemClickedListener;
+    private OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
+    private OnItemViewClickedListener mOnItemViewClickedListener;
+    private int mSelectedPosition = -1;
+
+    /**
+     * Sets the list of rows for the fragment.
+     */
+    public void setAdapter(ObjectAdapter adapter) {
+        mAdapter = adapter;
+        if (mRowsFragment != null) {
+            mRowsFragment.setAdapter(adapter);
+        }
+    }
+
+    /**
+     * Returns the list of rows.
+     */
+    public ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Sets an item selection listener.
+     * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
+     */
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mExternalOnItemSelectedListener = listener;
+    }
+
+    /**
+     * Sets an item Clicked listener.
+     * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)}
+     */
+    public void setOnItemClickedListener(OnItemClickedListener listener) {
+        mOnItemClickedListener = listener;
+        if (mRowsFragment != null) {
+            mRowsFragment.setOnItemClickedListener(listener);
+        }
+    }
+
+    /**
+     * Sets an item selection listener.
+     */
+    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        mExternalOnItemViewSelectedListener = listener;
+    }
+
+    /**
+     * Sets an item Clicked listener.
+     */
+    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        mOnItemViewClickedListener = listener;
+        if (mRowsFragment != null) {
+            mRowsFragment.setOnItemViewClickedListener(listener);
+        }
+    }
+
+    /**
+     * Returns the item Clicked listener.
+     * @deprecated Use {@link #getOnItemViewClickedListener()}
+     */
+    public OnItemClickedListener getOnItemClickedListener() {
+        return mOnItemClickedListener;
+    }
+
+    /**
+     * Returns the item Clicked listener.
+     */
+    public OnItemViewClickedListener getOnItemViewClickedListener() {
+        return mOnItemViewClickedListener;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mContainerListAlignTop =
+            getResources().getDimensionPixelSize(R.dimen.lb_details_rows_align_top);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.lb_details_fragment, container, false);
+        mRowsFragment = (RowsFragment) getChildFragmentManager().findFragmentById(
+                R.id.fragment_dock); 
+        if (mRowsFragment == null) {
+            mRowsFragment = new RowsFragment();
+            getChildFragmentManager().beginTransaction()
+                    .replace(R.id.fragment_dock, mRowsFragment).commit();
+        }
+        mRowsFragment.setAdapter(mAdapter);
+        mRowsFragment.setOnItemViewSelectedListener(mRowSelectedListener);
+        mRowsFragment.setOnItemClickedListener(mOnItemClickedListener);
+        mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        return view;
+    }
+
+    private OnItemViewSelectedListener mRowSelectedListener = new OnItemViewSelectedListener() {
+        @Override
+        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                RowPresenter.ViewHolder rowViewHolder, Row row) {
+            if (mExternalOnItemSelectedListener != null) {
+                mExternalOnItemSelectedListener.onItemSelected(item, row);
+            }
+            if (mExternalOnItemViewSelectedListener != null) {
+                mExternalOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
+                        rowViewHolder, row);
+            }
+        }
+    };
+
+    void setVerticalGridViewLayout(VerticalGridView listview) {
+        // align the top edge of item to a fixed position
+        listview.setItemAlignmentOffset(0);
+        listview.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+        listview.setWindowAlignmentOffset(mContainerListAlignTop);
+        listview.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+    }
+
+    VerticalGridView getVerticalGridView() {
+        return mRowsFragment == null ? null : mRowsFragment.getVerticalGridView();
+    }
+
+    RowsFragment getRowsFragment() {
+        return mRowsFragment;
+    }
+
+    /**
+     * Setup dimensions that are only meaningful when the child Fragments are inside
+     * DetailsFragment.
+     */
+    private void setupChildFragmentLayout() {
+        setVerticalGridViewLayout(mRowsFragment.getVerticalGridView());
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        setupChildFragmentLayout();
+        mRowsFragment.getView().requestFocus();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/ErrorFragment.java b/v17/leanback/src/android/support/v17/leanback/app/ErrorFragment.java
new file mode 100644
index 0000000..cc7e560
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/ErrorFragment.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.os.Bundle;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.TitleView;
+import android.text.TextUtils;
+import android.util.Log;
+import android.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Paint.FontMetricsInt;
+import android.graphics.drawable.Drawable;
+
+/**
+ * A fragment for displaying an error indication.
+ */
+public class ErrorFragment extends Fragment {
+
+    private View mErrorFrame;
+    private String mTitle;
+    private Drawable mBadgeDrawable;
+    private TitleView mTitleView;
+    private ImageView mImageView;
+    private TextView mTextView;
+    private Button mButton;
+    private Drawable mDrawable;
+    private CharSequence mMessage;
+    private String mButtonText;
+    private View.OnClickListener mButtonClickListener;
+    private Drawable mBackgroundDrawable;
+    private boolean mIsBackgroundTranslucent = true;
+
+    /**
+     * Sets the drawable displayed in the browse fragment title.
+     *
+     * @param drawable The drawable to display in the browse fragment title.
+     */
+    public void setBadgeDrawable(Drawable drawable) {
+        mBadgeDrawable = drawable;
+        updateTitle();
+    }
+
+    /**
+     * Returns the badge drawable used in the fragment title.
+     */
+    public Drawable getBadgeDrawable() {
+        return mBadgeDrawable;
+    }
+
+    /**
+     * Sets a title for the browse fragment.
+     *
+     * @param title The title of the browse fragment.
+     */
+    public void setTitle(String title) {
+        mTitle = title;
+        updateTitle();
+    }
+
+    /**
+     * Returns the title for the browse fragment.
+     */
+    public String getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Sets the default background.
+     *
+     * @param translucent True to set a translucent background.
+     */
+    public void setDefaultBackground(boolean translucent) {
+        mBackgroundDrawable = null;
+        mIsBackgroundTranslucent = translucent;
+        updateBackground();
+        updateMessage();
+    }
+
+    /**
+     * Returns true if the background is translucent.
+     */
+    public boolean isBackgroundTranslucent() {
+        return mIsBackgroundTranslucent;
+    }
+
+    /**
+     * Sets a drawable for the fragment background.
+     *
+     * @param drawable The drawable used for the background.
+     */
+    public void setBackgroundDrawable(Drawable drawable) {
+        mBackgroundDrawable = drawable;
+        if (drawable != null) {
+            final int opacity = drawable.getOpacity();
+            mIsBackgroundTranslucent = (opacity == PixelFormat.TRANSLUCENT ||
+                    opacity == PixelFormat.TRANSPARENT);
+        }
+        updateBackground();
+        updateMessage();
+    }
+
+    /**
+     * Returns the background drawable.  May be null if a default is used.
+     */
+    public Drawable getBackgroundDrawable() {
+        return mBackgroundDrawable;
+    }
+
+    /**
+     * Sets the drawable to be used for the error image.
+     *
+     * @param drawable The drawable used for the error image.
+     */
+    public void setImageDrawable(Drawable drawable) {
+        mDrawable = drawable;
+        updateImageDrawable();
+    }
+
+    /**
+     * Returns the drawable used for the error image.
+     */
+    public Drawable getImageDrawable() {
+        return mDrawable;
+    }
+
+    /**
+     * Sets the error message.
+     *
+     * @param message The error message.
+     */
+    public void setMessage(CharSequence message) {
+        mMessage = message;
+        updateMessage();
+    }
+
+    /**
+     * Returns the error message.
+     */
+    public CharSequence getMessage() {
+        return mMessage;
+    }
+
+    /**
+     * Sets the button text.
+     *
+     * @param text The button text.
+     */
+    public void setButtonText(String text) {
+        mButtonText = text;
+        updateButton();
+    }
+
+    /**
+     * Returns the button text.
+     */
+    public String getButtonText() {
+        return mButtonText;
+    }
+
+    /**
+     * Set the button click listener.
+     *
+     * @param clickListener The click listener for the button.
+     */
+    public void setButtonClickListener(View.OnClickListener clickListener) {
+        mButtonClickListener = clickListener;
+        updateButton();
+    }
+
+    /**
+     * Returns the button click listener.
+     */
+    public View.OnClickListener getButtonClickListener() {
+        return mButtonClickListener;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View root = inflater.inflate(R.layout.lb_error_fragment, container, false);
+
+        mErrorFrame = root.findViewById(R.id.error_frame);
+        updateBackground();
+
+        mImageView = (ImageView) root.findViewById(R.id.image);
+        updateImageDrawable();
+
+        mTextView = (TextView) root.findViewById(R.id.message);
+        updateMessage();
+
+        mButton = (Button) root.findViewById(R.id.button);
+        updateButton();
+
+        mTitleView = (TitleView) root.findViewById(R.id.browse_title_group);
+        updateTitle();
+
+        FontMetricsInt metrics = getFontMetricsInt(mTextView);
+        int underImageBaselineMargin = container.getResources().getDimensionPixelSize(
+                R.dimen.lb_error_under_image_baseline_margin);
+        setTopMargin(mTextView, underImageBaselineMargin + metrics.ascent);
+
+        int underMessageBaselineMargin = container.getResources().getDimensionPixelSize(
+                R.dimen.lb_error_under_message_baseline_margin);
+        setTopMargin(mButton, underMessageBaselineMargin - metrics.descent);
+
+        return root;
+    }
+
+    private void updateBackground() {
+        if (mErrorFrame != null) {
+            if (mBackgroundDrawable != null) {
+                mErrorFrame.setBackground(mBackgroundDrawable);
+            } else {
+                mErrorFrame.setBackgroundColor(mErrorFrame.getResources().getColor(
+                        mIsBackgroundTranslucent ?
+                        R.color.lb_error_background_color_translucent :
+                        R.color.lb_error_background_color_opaque));
+            }
+        }
+    }
+
+    private void updateTitle() {
+        if (mTitleView != null) {
+            mTitleView.setTitle(mTitle);
+            mTitleView.setBadgeDrawable(mBadgeDrawable);
+        }
+    }
+
+    private void updateMessage() {
+        if (mTextView != null) {
+            mTextView.setText(mMessage);
+            mTextView.setTextColor(mTextView.getResources().getColor(mIsBackgroundTranslucent ?
+                    R.color.lb_error_message_color_on_translucent :
+                    R.color.lb_error_message_color_on_opaque));
+            mTextView.setVisibility(TextUtils.isEmpty(mMessage) ? View.GONE : View.VISIBLE);
+        }
+    }
+
+    private void updateImageDrawable() {
+        if (mImageView != null) {
+            mImageView.setImageDrawable(mDrawable);
+            mImageView.setVisibility(mDrawable == null ? View.GONE : View.VISIBLE);
+        }
+    }
+
+    private void updateButton() {
+        if (mButton != null) {
+            mButton.setText(mButtonText);
+            mButton.setOnClickListener(mButtonClickListener);
+            mButton.setVisibility(TextUtils.isEmpty(mButtonText) ? View.GONE : View.VISIBLE);
+            mButton.requestFocus();
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mErrorFrame.requestFocus();
+    }
+
+    private static FontMetricsInt getFontMetricsInt(TextView textView) {
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        paint.setTextSize(textView.getTextSize());
+        paint.setTypeface(textView.getTypeface());
+        return paint.getFontMetricsInt();
+    }
+
+    private static void setTopMargin(TextView textView, int topMargin) {
+        ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) textView.getLayoutParams();
+        lp.topMargin = topMargin;
+        textView.setLayoutParams(lp);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java b/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
new file mode 100644
index 0000000..5d71b2e
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.support.v17.leanback.app;
+
+import android.os.Bundle;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.FocusHighlightHelper;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowHeaderPresenter;
+import android.support.v17.leanback.widget.SinglePresenterSelector;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnLayoutChangeListener;
+import android.widget.FrameLayout;
+
+/**
+ * An internal fragment containing a list of row headers.
+ */
+public class HeadersFragment extends BaseRowFragment {
+
+    interface OnHeaderClickedListener {
+        void onHeaderClicked();
+    }
+
+    private OnItemSelectedListener mOnItemSelectedListener;
+    private OnHeaderClickedListener mOnHeaderClickedListener;
+    private boolean mHeadersEnabled = true;
+    private boolean mHeadersGone = false;
+    private int mBackgroundColor;
+    private boolean mBackgroundColorSet;
+
+    private static final PresenterSelector sHeaderPresenter = new SinglePresenterSelector(
+            new RowHeaderPresenter(R.layout.lb_header));
+
+    public HeadersFragment() {
+        setPresenterSelector(sHeaderPresenter);
+    }
+
+    public void setOnHeaderClickedListener(OnHeaderClickedListener listener) {
+        mOnHeaderClickedListener = listener;
+    }
+
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mOnItemSelectedListener = listener;
+    }
+
+    @Override
+    protected void onRowSelected(ViewGroup parent, View view, int position, long id) {
+        if (mOnItemSelectedListener != null) {
+            if (position >= 0) {
+                Row row = (Row) getAdapter().get(position);
+                mOnItemSelectedListener.onItemSelected(null, row);
+            } else {
+                mOnItemSelectedListener.onItemSelected(null, null);
+            }
+        }
+    }
+
+    private final ItemBridgeAdapter.AdapterListener mAdapterListener =
+            new ItemBridgeAdapter.AdapterListener() {
+        @Override
+        public void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) {
+            View headerView = viewHolder.getViewHolder().view;
+            headerView.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (mOnHeaderClickedListener != null) {
+                        mOnHeaderClickedListener.onHeaderClicked();
+                    }
+                }
+            });
+            headerView.setFocusable(true);
+            headerView.setFocusableInTouchMode(true);
+            if (mWrapper != null) {
+                viewHolder.itemView.addOnLayoutChangeListener(sLayoutChangeListener);
+            } else {
+                headerView.addOnLayoutChangeListener(sLayoutChangeListener);
+            }
+        }
+
+    };
+
+    private static OnLayoutChangeListener sLayoutChangeListener = new OnLayoutChangeListener() {
+        @Override
+        public void onLayoutChange(View v, int left, int top, int right, int bottom,
+            int oldLeft, int oldTop, int oldRight, int oldBottom) {
+            v.setPivotX(0);
+            v.setPivotY(v.getMeasuredHeight() / 2);
+        }
+    };
+
+    @Override
+    protected int getLayoutResourceId() {
+        return R.layout.lb_headers_fragment;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        final VerticalGridView listView = getVerticalGridView();
+        if (listView == null) {
+            return;
+        }
+        if (getBridgeAdapter() != null) {
+            FocusHighlightHelper.setupHeaderItemFocusHighlight(listView);
+        }
+        listView.setBackgroundColor(getBackgroundColor());
+        listView.setVisibility(mHeadersGone ? View.GONE : View.VISIBLE);
+        listView.setLayoutEnabled(mHeadersEnabled);
+    }
+
+    void setHeadersEnabled(boolean enabled) {
+        mHeadersEnabled = enabled;
+        final VerticalGridView listView = getVerticalGridView();
+        if (listView != null) {
+            listView.setLayoutEnabled(mHeadersEnabled);
+        }
+    }
+
+    void setHeadersGone(boolean gone) {
+        mHeadersGone = gone;
+        final VerticalGridView listView = getVerticalGridView();
+        if (listView != null) {
+            listView.setVisibility(mHeadersGone ? View.GONE : View.VISIBLE);
+        }
+    }
+
+    // Wrapper needed because of conflict between RecyclerView's use of alpha
+    // for ADD animations, and RowHeaderPresnter's use of alpha for selected level.
+    private final ItemBridgeAdapter.Wrapper mWrapper = new ItemBridgeAdapter.Wrapper() {
+        @Override
+        public void wrap(View wrapper, View wrapped) {
+            ((FrameLayout) wrapper).addView(wrapped);
+        }
+
+        @Override
+        public View createWrapper(View root) {
+            return new FrameLayout(root.getContext());
+        }
+    };
+    @Override
+    protected void updateAdapter() {
+        super.updateAdapter();
+        ItemBridgeAdapter adapter = getBridgeAdapter();
+        if (adapter != null) {
+            adapter.setAdapterListener(mAdapterListener);
+            adapter.setWrapper(mWrapper);
+        }
+        if (adapter != null && getVerticalGridView() != null) {
+            FocusHighlightHelper.setupHeaderItemFocusHighlight(getVerticalGridView());
+        }
+    }
+
+    void setBackgroundColor(int color) {
+        mBackgroundColor = color;
+        mBackgroundColorSet = true;
+
+        if (getVerticalGridView() != null) {
+            getVerticalGridView().setBackgroundColor(mBackgroundColor);
+        }
+    }
+
+    int getBackgroundColor() {
+        if (getActivity() == null) {
+            throw new IllegalStateException("Activity must be attached");
+        }
+
+        if (mBackgroundColorSet) {
+            return mBackgroundColor;
+        }
+
+        TypedValue outValue = new TypedValue();
+        getActivity().getTheme().resolveAttribute(R.attr.defaultBrandColor, outValue, true);
+        return getResources().getColor(outValue.resourceId);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
new file mode 100644
index 0000000..a916fc6
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
@@ -0,0 +1,627 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.view.animation.AccelerateInterpolator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v7.widget.RecyclerView;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.animation.LogAccelerateInterpolator;
+import android.support.v17.leanback.animation.LogDecelerateInterpolator;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+
+
+/**
+ * A fragment for displaying playback controls and related content.
+ * The {@link android.support.v17.leanback.widget.PlaybackControlsRow} is expected to be
+ * at position 0 in the adapter.
+ */
+public class PlaybackOverlayFragment extends DetailsFragment {
+
+    /**
+     * No background.
+     */
+    public static final int BG_NONE = 0;
+
+    /**
+     * A dark translucent background.
+     */
+    public static final int BG_DARK = 1;
+
+    /**
+     * A light translucent background.
+     */
+    public static final int BG_LIGHT = 2;
+
+    public static class OnFadeCompleteListener {
+        public void onFadeInComplete() {
+        }
+        public void onFadeOutComplete() {
+        }
+    }
+
+    private static final String TAG = "PlaybackOverlayFragment";
+    private static final boolean DEBUG = false;
+    private static final int ANIMATION_MULTIPLIER = 1;
+
+    private static int START_FADE_OUT = 1;
+
+    // Fading status
+    private static final int IDLE = 0;
+    private static final int IN = 1;
+    private static final int OUT = 2;
+
+    private int mAlignPosition;
+    private int mPaddingBottom;
+    private View mRootView;
+    private int mBackgroundType = BG_DARK;
+    private int mBgDarkColor;
+    private int mBgLightColor;
+    private int mShowTimeMs;
+    private int mMajorFadeTranslateY, mMinorFadeTranslateY;
+    private int mAnimationTranslateY;
+    private OnFadeCompleteListener mFadeCompleteListener;
+    private boolean mFadingEnabled = true;
+    private int mFadingStatus = IDLE;
+    private int mBgAlpha;
+    private ValueAnimator mBgFadeInAnimator, mBgFadeOutAnimator;
+    private ValueAnimator mControlRowFadeInAnimator, mControlRowFadeOutAnimator;
+    private ValueAnimator mDescriptionFadeInAnimator, mDescriptionFadeOutAnimator;
+    private ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator;
+    private boolean mTranslateAnimationEnabled;
+    private boolean mResetControlsToPrimaryActionsPending;
+    private RecyclerView.ItemAnimator mItemAnimator;
+
+    private final Animator.AnimatorListener mFadeListener =
+            new Animator.AnimatorListener() {
+        @Override
+        public void onAnimationStart(Animator animation) {
+            enableVerticalGridAnimations(false);
+        }
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+        }
+        @Override
+        public void onAnimationCancel(Animator animation) {
+        }
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (DEBUG) Log.v(TAG, "onAnimationEnd " + mBgAlpha);
+            if (mBgAlpha > 0) {
+                enableVerticalGridAnimations(true);
+                startFadeTimer();
+                if (mFadeCompleteListener != null) {
+                    mFadeCompleteListener.onFadeInComplete();
+                }
+            } else {
+                if (getVerticalGridView() != null) {
+                    // Reset focus to the controls row
+                    getVerticalGridView().setSelectedPosition(0);
+                    resetControlsToPrimaryActions(null);
+                }
+                if (mFadeCompleteListener != null) {
+                    mFadeCompleteListener.onFadeOutComplete();
+                }
+            }
+            mFadingStatus = IDLE;
+        }
+    };
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message message) {
+            if (message.what == START_FADE_OUT && mFadingEnabled) {
+                fade(false);
+            }
+        }
+    };
+
+    private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener =
+            new VerticalGridView.OnTouchInterceptListener() {
+        public boolean onInterceptTouchEvent(MotionEvent event) {
+            return onInterceptInputEvent();
+        }
+    };
+
+    private final VerticalGridView.OnMotionInterceptListener mOnMotionInterceptListener =
+            new VerticalGridView.OnMotionInterceptListener() {
+        public boolean onInterceptMotionEvent(MotionEvent event) {
+            return onInterceptInputEvent();
+        }
+    };
+
+    private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener =
+            new VerticalGridView.OnKeyInterceptListener() {
+        public boolean onInterceptKeyEvent(KeyEvent event) {
+            return onInterceptInputEvent();
+        }
+    };
+
+    private void setBgAlpha(int alpha) {
+        mBgAlpha = alpha;
+        if (mRootView != null) {
+            mRootView.getBackground().setAlpha(alpha);
+        }
+    }
+
+    private void enableVerticalGridAnimations(boolean enable) {
+        if (getVerticalGridView() != null) {
+            getVerticalGridView().setAnimateChildLayout(enable);
+        }
+    }
+
+    private void resetControlsToPrimaryActions(ItemBridgeAdapter.ViewHolder vh) {
+        if (vh == null && getVerticalGridView() != null) {
+            vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView().findViewHolderForPosition(0);
+        }
+        if (vh == null) {
+            mResetControlsToPrimaryActionsPending = true;
+        } else if (vh.getPresenter() instanceof PlaybackControlsRowPresenter) {
+            mResetControlsToPrimaryActionsPending = false;
+            ((PlaybackControlsRowPresenter) vh.getPresenter()).showPrimaryActions(
+                    (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder());
+        }
+    }
+
+    /**
+     * Enables or disables view fading.  If enabled,
+     * the view will be faded in when the fragment starts,
+     * and will fade out after a time period.  The timeout
+     * period is reset each time {@link #tickle} is called.
+     *
+     */
+    public void setFadingEnabled(boolean enabled) {
+        if (DEBUG) Log.v(TAG, "setFadingEnabled " + enabled);
+        if (enabled != mFadingEnabled) {
+            mFadingEnabled = enabled;
+            if (isResumed()) {
+                if (mFadingEnabled) {
+                    if (mFadingStatus == IDLE && !mHandler.hasMessages(START_FADE_OUT)) {
+                        startFadeTimer();
+                    }
+                } else {
+                    // Ensure fully opaque
+                    mHandler.removeMessages(START_FADE_OUT);
+                    fade(true);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns true if view fading is enabled.
+     */
+    public boolean isFadingEnabled() {
+        return mFadingEnabled;
+    }
+
+    /**
+     * Sets the listener to be called when fade in or out has completed.
+     */
+    public void setFadeCompleteListener(OnFadeCompleteListener listener) {
+        mFadeCompleteListener = listener;
+    }
+
+    /**
+     * Returns the listener to be called when fade in or out has completed.
+     */
+    public OnFadeCompleteListener getFadeCompleteListener() {
+        return mFadeCompleteListener;
+    }
+
+    /**
+     * Tickles the playback controls.  Fades in the view if it was faded out,
+     * otherwise resets the fade out timer.  Tickling on input events is handled
+     * by the fragment.
+     */
+    public void tickle() {
+        if (DEBUG) Log.v(TAG, "tickle enabled " + mFadingEnabled + " isResumed " + isResumed());
+        if (!mFadingEnabled || !isResumed()) {
+            return;
+        }
+        if (mHandler.hasMessages(START_FADE_OUT)) {
+            // Restart the timer
+            startFadeTimer();
+        } else {
+            fade(true);
+        }
+    }
+
+    private boolean onInterceptInputEvent() {
+        if (DEBUG) Log.v(TAG, "onInterceptInputEvent status " + mFadingStatus);
+        boolean consumeEvent = (mFadingStatus == IDLE && mBgAlpha == 0);
+        tickle();
+        return consumeEvent;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (mFadingEnabled) {
+            setBgAlpha(0);
+            fade(true);
+        }
+        getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
+        getVerticalGridView().setOnMotionInterceptListener(mOnMotionInterceptListener);
+        getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
+    }
+
+    private void startFadeTimer() {
+        if (mHandler != null) {
+            mHandler.removeMessages(START_FADE_OUT);
+            mHandler.sendEmptyMessageDelayed(START_FADE_OUT, mShowTimeMs);
+        }
+    }
+
+    private static ValueAnimator loadAnimator(Context context, int resId) {
+        ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(context, resId);
+        animator.setDuration(animator.getDuration() * ANIMATION_MULTIPLIER);
+        return animator;
+    }
+
+    private void loadBgAnimator() {
+        AnimatorUpdateListener listener = new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator arg0) {
+                setBgAlpha((Integer) arg0.getAnimatedValue());
+            }
+        };
+
+        mBgFadeInAnimator = loadAnimator(getActivity(), R.animator.lb_playback_bg_fade_in);
+        mBgFadeInAnimator.addUpdateListener(listener);
+        mBgFadeInAnimator.addListener(mFadeListener);
+
+        mBgFadeOutAnimator = loadAnimator(getActivity(), R.animator.lb_playback_bg_fade_out);
+        mBgFadeOutAnimator.addUpdateListener(listener);
+        mBgFadeOutAnimator.addListener(mFadeListener);
+    }
+
+    private TimeInterpolator mLogDecelerateInterpolator = new LogDecelerateInterpolator(100,0);
+    private TimeInterpolator mLogAccelerateInterpolator = new LogAccelerateInterpolator(100,0);
+
+    private void loadControlRowAnimator() {
+        AnimatorUpdateListener listener = new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator arg0) {
+                if (getVerticalGridView() == null) {
+                    return;
+                }
+                RecyclerView.ViewHolder vh = getVerticalGridView().findViewHolderForPosition(0);
+                if (vh != null) {
+                    final float fraction = (Float) arg0.getAnimatedValue();
+                    if (DEBUG) Log.v(TAG, "fraction " + fraction);
+                    vh.itemView.setAlpha(fraction);
+                    vh.itemView.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
+                }
+            }
+        };
+
+        mControlRowFadeInAnimator = loadAnimator(
+                getActivity(), R.animator.lb_playback_controls_fade_in);
+        mControlRowFadeInAnimator.addUpdateListener(listener);
+        mControlRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
+
+        mControlRowFadeOutAnimator = loadAnimator(
+                getActivity(), R.animator.lb_playback_controls_fade_out);
+        mControlRowFadeOutAnimator.addUpdateListener(listener);
+        mControlRowFadeOutAnimator.setInterpolator(mLogAccelerateInterpolator);
+    }
+
+    private void loadOtherRowAnimator() {
+        AnimatorUpdateListener listener = new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator arg0) {
+                if (getVerticalGridView() == null) {
+                    return;
+                }
+                final float fraction = (Float) arg0.getAnimatedValue();
+                final int count = getVerticalGridView().getChildCount();
+                for (int i = 0; i < count; i++) {
+                    View view = getVerticalGridView().getChildAt(i);
+                    if (getVerticalGridView().getChildPosition(view) > 0) {
+                        view.setAlpha(fraction);
+                        view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
+                    }
+                }
+            }
+        };
+
+        mOtherRowFadeInAnimator = loadAnimator(
+                getActivity(), R.animator.lb_playback_controls_fade_in);
+        mOtherRowFadeInAnimator.addUpdateListener(listener);
+        mOtherRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
+
+        mOtherRowFadeOutAnimator = loadAnimator(
+                getActivity(), R.animator.lb_playback_controls_fade_out);
+        mOtherRowFadeOutAnimator.addUpdateListener(listener);
+        mOtherRowFadeOutAnimator.setInterpolator(new AccelerateInterpolator());
+    }
+
+    private void loadDescriptionAnimator() {
+        AnimatorUpdateListener listener = new AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator arg0) {
+                if (getVerticalGridView() == null) {
+                    return;
+                }
+                ItemBridgeAdapter.ViewHolder adapterVh = (ItemBridgeAdapter.ViewHolder)
+                        getVerticalGridView().findViewHolderForPosition(0);
+                if (adapterVh != null && adapterVh.getViewHolder()
+                        instanceof PlaybackControlsRowPresenter.ViewHolder) {
+                    final Presenter.ViewHolder vh = ((PlaybackControlsRowPresenter.ViewHolder)
+                            adapterVh.getViewHolder()).mDescriptionViewHolder;
+                    vh.view.setAlpha((Float) arg0.getAnimatedValue());
+                }
+            }
+        };
+
+        mDescriptionFadeInAnimator = loadAnimator(
+                getActivity(), R.animator.lb_playback_description_fade_in);
+        mDescriptionFadeInAnimator.addUpdateListener(listener);
+        mDescriptionFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
+
+        mDescriptionFadeOutAnimator = loadAnimator(
+                getActivity(), R.animator.lb_playback_description_fade_out);
+        mDescriptionFadeOutAnimator.addUpdateListener(listener);
+    }
+
+    private void fade(boolean fadeIn) {
+        if (DEBUG) Log.v(TAG, "fade " + fadeIn);
+        if (getView() == null) {
+            return;
+        }
+        if ((fadeIn && mFadingStatus == IN) || (!fadeIn && mFadingStatus == OUT)) {
+            if (DEBUG) Log.v(TAG, "requested fade in progress");
+            return;
+        }
+        if ((fadeIn && mBgAlpha == 255) || (!fadeIn && mBgAlpha == 0)) {
+            if (DEBUG) Log.v(TAG, "fade is no-op");
+            return;
+        }
+
+        mAnimationTranslateY = getVerticalGridView().getSelectedPosition() == 0 ?
+                mMajorFadeTranslateY : mMinorFadeTranslateY;
+
+        if (mFadingStatus == IDLE) {
+            if (fadeIn) {
+                mBgFadeInAnimator.start();
+                mControlRowFadeInAnimator.start();
+                mOtherRowFadeInAnimator.start();
+                mDescriptionFadeInAnimator.start();
+            } else {
+                mBgFadeOutAnimator.start();
+                mControlRowFadeOutAnimator.start();
+                mOtherRowFadeOutAnimator.start();
+                mDescriptionFadeOutAnimator.start();
+            }
+        } else {
+            if (fadeIn) {
+                mBgFadeOutAnimator.reverse();
+                mControlRowFadeOutAnimator.reverse();
+                mOtherRowFadeOutAnimator.reverse();
+                mDescriptionFadeOutAnimator.reverse();
+            } else {
+                mBgFadeInAnimator.reverse();
+                mControlRowFadeInAnimator.reverse();
+                mOtherRowFadeInAnimator.reverse();
+                mDescriptionFadeInAnimator.reverse();
+            }
+        }
+
+        // If fading in while control row is focused, set initial translationY so
+        // views slide in from below.
+        if (fadeIn && mFadingStatus == IDLE) {
+            final int count = getVerticalGridView().getChildCount();
+            for (int i = 0; i < count; i++) {
+                getVerticalGridView().getChildAt(i).setTranslationY(mAnimationTranslateY);
+            }
+        }
+
+        mFadingStatus = fadeIn ? IN : OUT;
+    }
+
+    /**
+     * Sets the list of rows for the fragment.
+     */
+    @Override
+    public void setAdapter(ObjectAdapter adapter) {
+        if (getAdapter() != null) {
+            getAdapter().unregisterObserver(mObserver);
+        }
+        super.setAdapter(adapter);
+        if (adapter != null) {
+            adapter.registerObserver(mObserver);
+        }
+    }
+
+    @Override
+    void setVerticalGridViewLayout(VerticalGridView listview) {
+        if (listview == null) {
+            return;
+        }
+        // Padding affects alignment when last row is focused
+        // (last is first when there's only one row).
+        setBottomPadding(listview, mPaddingBottom);
+
+        // Item alignment affects focused row that isn't the last.
+        listview.setItemAlignmentOffset(mAlignPosition);
+        listview.setItemAlignmentOffsetPercent(100);
+
+        // Push rows to the bottom.
+        listview.setWindowAlignmentOffset(0);
+        listview.setWindowAlignmentOffsetPercent(100);
+        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE);
+    }
+
+    private static void setBottomPadding(View view, int padding) {
+        view.setPadding(view.getPaddingLeft(), view.getPaddingTop(),
+                view.getPaddingRight(), padding);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mAlignPosition =
+                getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_align_bottom);
+        mPaddingBottom =
+                getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_bottom);
+        mBgDarkColor =
+                getResources().getColor(R.color.lb_playback_controls_background_dark);
+        mBgLightColor =
+                getResources().getColor(R.color.lb_playback_controls_background_light);
+        mShowTimeMs =
+                getResources().getInteger(R.integer.lb_playback_controls_show_time_ms);
+        mMajorFadeTranslateY =
+                getResources().getDimensionPixelSize(R.dimen.lb_playback_major_fade_translate_y);
+        mMinorFadeTranslateY =
+                getResources().getDimensionPixelSize(R.dimen.lb_playback_minor_fade_translate_y);
+
+        loadBgAnimator();
+        loadControlRowAnimator();
+        loadOtherRowAnimator();
+        loadDescriptionAnimator();
+    }
+
+    /**
+     * Sets the background type.
+     *
+     * @param type One of BG_LIGHT, BG_DARK, or BG_NONE.
+     */
+    public void setBackgroundType(int type) {
+        switch (type) {
+        case BG_LIGHT:
+        case BG_DARK:
+        case BG_NONE:
+            if (type != mBackgroundType) {
+                mBackgroundType = type;
+                updateBackground();
+            }
+            break;
+        default:
+            throw new IllegalArgumentException("Invalid background type");
+        }
+    }
+
+    /**
+     * Returns the background type.
+     */
+    public int getBackgroundType() {
+        return mBackgroundType;
+    }
+
+    private void updateBackground() {
+        if (mRootView != null) {
+            int color = mBgDarkColor;
+            switch (mBackgroundType) {
+                case BG_DARK: break;
+                case BG_LIGHT: color = mBgLightColor; break;
+                case BG_NONE: color = Color.TRANSPARENT; break;
+            }
+            mRootView.setBackground(new ColorDrawable(color));
+        }
+    }
+
+    private void updateControlsBottomSpace(ItemBridgeAdapter.ViewHolder vh) {
+        // Add extra space between rows 0 and 1
+        if (vh == null && getVerticalGridView() != null) {
+            vh = (ItemBridgeAdapter.ViewHolder)
+                    getVerticalGridView().findViewHolderForPosition(0);
+        }
+        if (vh != null && vh.getPresenter() instanceof PlaybackControlsRowPresenter) {
+            final int adapterSize = getAdapter() == null ? 0 : getAdapter().size();
+            ((PlaybackControlsRowPresenter) vh.getPresenter()).showBottomSpace(
+                    (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder(),
+                    adapterSize > 1);
+        }
+    }
+
+    private final ItemBridgeAdapter.AdapterListener mAdapterListener =
+            new ItemBridgeAdapter.AdapterListener() {
+        @Override
+        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
+            if (DEBUG) Log.v(TAG, "onAttachedToWindow " + vh.getViewHolder().view);
+            if ((mFadingStatus == IDLE && mBgAlpha == 0) || mFadingStatus == OUT) {
+                if (DEBUG) Log.v(TAG, "setting alpha to 0");
+                vh.getViewHolder().view.setAlpha(0);
+            }
+            if (vh.getPosition() == 0 && mResetControlsToPrimaryActionsPending) {
+                resetControlsToPrimaryActions(vh);
+            }
+        }
+        @Override
+        public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
+            if (DEBUG) Log.v(TAG, "onDetachedFromWindow " + vh.getViewHolder().view);
+            // Reset animation state
+            vh.getViewHolder().view.setAlpha(1f);
+            vh.getViewHolder().view.setTranslationY(0);
+            if (vh.getViewHolder() instanceof PlaybackControlsRowPresenter.ViewHolder) {
+                ((PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder())
+                        .mDescriptionViewHolder.view.setAlpha(1f);
+            }
+        }
+        @Override
+        public void onBind(ItemBridgeAdapter.ViewHolder vh) {
+            if (vh.getPosition() == 0) {
+                updateControlsBottomSpace(vh);
+            }
+        }
+    };
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        mRootView = super.onCreateView(inflater, container, savedInstanceState);
+        mBgAlpha = 255;
+        updateBackground();
+        getRowsFragment().setExternalAdapterListener(mAdapterListener);
+        return mRootView;
+    }
+
+    @Override
+    public void onDestroyView() {
+        mRootView = null;
+        super.onDestroyView();
+    }
+
+    private final DataObserver mObserver = new DataObserver() {
+        public void onChanged() {
+            updateControlsBottomSpace(null);
+        }
+    };
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
new file mode 100644
index 0000000..d1224f9
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import java.util.ArrayList;
+
+import android.animation.TimeAnimator;
+import android.animation.TimeAnimator.TimeListener;
+import android.graphics.Canvas;
+import android.os.Bundle;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.graphics.ColorOverlayDimmer;
+import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.RowPresenter.ViewHolder;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.HorizontalGridView;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+/**
+ * An ordered set of rows of leanback widgets.
+ */
+public class RowsFragment extends BaseRowFragment {
+
+    /**
+     * Internal helper class that manages row select animation and apply a default
+     * dim to each row.
+     */
+    final class RowViewHolderExtra implements TimeListener {
+        final RowPresenter mRowPresenter;
+        final Presenter.ViewHolder mRowViewHolder;
+
+        final TimeAnimator mSelectAnimator = new TimeAnimator();
+        final ColorOverlayDimmer mColorDimmer;
+
+        int mSelectAnimatorDurationInUse;
+        Interpolator mSelectAnimatorInterpolatorInUse;
+        float mSelectLevelAnimStart;
+        float mSelectLevelAnimDelta;
+
+        RowViewHolderExtra(ItemBridgeAdapter.ViewHolder ibvh) {
+            mRowPresenter = (RowPresenter) ibvh.getPresenter();
+            mRowViewHolder = ibvh.getViewHolder();
+            mSelectAnimator.setTimeListener(this);
+            if (mRowPresenter.getSelectEffectEnabled()
+                    && mRowPresenter.isUsingDefaultSelectEffect()) {
+                mColorDimmer = ColorOverlayDimmer.createDefault(ibvh.itemView.getContext());
+            } else {
+                mColorDimmer = null;
+            }
+        }
+
+        @Override
+        public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
+            if (mSelectAnimator.isRunning()) {
+                updateSelect(totalTime, deltaTime);
+            }
+        }
+
+        void updateSelect(long totalTime, long deltaTime) {
+            float fraction;
+            if (totalTime >= mSelectAnimatorDurationInUse) {
+                fraction = 1;
+                mSelectAnimator.end();
+            } else {
+                fraction = (float) (totalTime / (double) mSelectAnimatorDurationInUse);
+            }
+            if (mSelectAnimatorInterpolatorInUse != null) {
+                fraction = mSelectAnimatorInterpolatorInUse.getInterpolation(fraction);
+            }
+            float level =  mSelectLevelAnimStart + fraction * mSelectLevelAnimDelta;
+            if (mColorDimmer != null) {
+                mColorDimmer.setActiveLevel(level);
+                if (getVerticalGridView() != null) {
+                    getVerticalGridView().invalidate();
+                }
+            }
+            mRowPresenter.setSelectLevel(mRowViewHolder, level);
+        }
+
+        void animateSelect(boolean select, boolean immediate) {
+            endSelectAnimation();
+            final float end = select ? 1 : 0;
+            if (immediate) {
+                mRowPresenter.setSelectLevel(mRowViewHolder, end);
+                if (mColorDimmer != null) {
+                    mColorDimmer.setActiveLevel(end);
+                }
+            } else if (mRowPresenter.getSelectLevel(mRowViewHolder) != end) {
+                mSelectAnimatorDurationInUse = mSelectAnimatorDuration;
+                mSelectAnimatorInterpolatorInUse = mSelectAnimatorInterpolator;
+                mSelectLevelAnimStart = mRowPresenter.getSelectLevel(mRowViewHolder);
+                mSelectLevelAnimDelta = end - mSelectLevelAnimStart;
+                mSelectAnimator.start();
+            }
+        }
+
+        void endAnimations() {
+            endSelectAnimation();
+        }
+
+        void endSelectAnimation() {
+            mSelectAnimator.end();
+        }
+
+        void drawDimForSelection(Canvas c) {
+            if (mColorDimmer != null) {
+                mColorDimmer.drawColorOverlay(c, mRowViewHolder.view, false);
+            }
+        }
+    }
+
+    private static final String TAG = "RowsFragment";
+    private static final boolean DEBUG = false;
+
+    private ItemBridgeAdapter.ViewHolder mSelectedViewHolder;
+    private boolean mExpand = true;
+    private boolean mViewsCreated;
+
+    private OnItemSelectedListener mOnItemSelectedListener;
+    private OnItemViewSelectedListener mOnItemViewSelectedListener;
+    private OnItemClickedListener mOnItemClickedListener;
+    private OnItemViewClickedListener mOnItemViewClickedListener;
+
+    // Select animation and interpolator are not intended to be
+    // exposed at this moment. They might be synced with vertical scroll
+    // animation later.
+    int mSelectAnimatorDuration;
+    Interpolator mSelectAnimatorInterpolator = new DecelerateInterpolator(2);
+
+    private RecyclerView.RecycledViewPool mRecycledViewPool;
+    private ArrayList<Presenter> mPresenterMapper;
+
+    private ItemBridgeAdapter.AdapterListener mExternalAdapterListener;
+
+    /**
+     * Sets an item clicked listener on the fragment.
+     * OnItemClickedListener will override {@link View.OnClickListener} that
+     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
+     * So in general,  developer should choose one of the listeners but not both.
+     * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)}
+     */
+    public void setOnItemClickedListener(OnItemClickedListener listener) {
+        mOnItemClickedListener = listener;
+        if (mViewsCreated) {
+            throw new IllegalStateException(
+                    "Item clicked listener must be set before views are created");
+        }
+    }
+
+    /**
+     * Returns the item clicked listener.
+     * @deprecated Use {@link #getOnItemClickedListener()}
+     */
+    public OnItemClickedListener getOnItemClickedListener() {
+        return mOnItemClickedListener;
+    }
+
+    /**
+     * Sets an item clicked listener on the fragment.
+     * OnItemViewClickedListener will override {@link View.OnClickListener} that
+     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
+     * So in general,  developer should choose one of the listeners but not both.
+     */
+    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        mOnItemViewClickedListener = listener;
+        if (mViewsCreated) {
+            throw new IllegalStateException(
+                    "Item clicked listener must be set before views are created");
+        }
+    }
+
+    /**
+     * Returns the item clicked listener.
+     */
+    public OnItemViewClickedListener getOnItemViewClickedListener() {
+        return mOnItemViewClickedListener;
+    }
+
+    /**
+     * Set the visibility of titles/hovercard of browse rows.
+     */
+    public void setExpand(boolean expand) {
+        mExpand = expand;
+        VerticalGridView listView = getVerticalGridView();
+        if (listView != null) {
+            final int count = listView.getChildCount();
+            if (DEBUG) Log.v(TAG, "setExpand " + expand + " count " + count);
+            for (int i = 0; i < count; i++) {
+                View view = listView.getChildAt(i);
+                ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) listView.getChildViewHolder(view);
+                setRowViewExpanded(vh, mExpand);
+            }
+        }
+    }
+
+    /**
+     * Sets an item selection listener.
+     * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
+     */
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mOnItemSelectedListener = listener;
+        VerticalGridView listView = getVerticalGridView();
+        if (listView != null) {
+            final int count = listView.getChildCount();
+            for (int i = 0; i < count; i++) {
+                View view = listView.getChildAt(i);
+                ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
+                        listView.getChildViewHolder(view);
+                setOnItemSelectedListener(vh, mOnItemSelectedListener);
+            }
+        }
+    }
+
+    /**
+     * Sets an item selection listener.
+     */
+    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        mOnItemViewSelectedListener = listener;
+        VerticalGridView listView = getVerticalGridView();
+        if (listView != null) {
+            final int count = listView.getChildCount();
+            for (int i = 0; i < count; i++) {
+                View view = listView.getChildAt(i);
+                ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder)
+                        listView.getChildViewHolder(view);
+                setOnItemViewSelectedListener(vh, mOnItemViewSelectedListener);
+            }
+        }
+    }
+
+    /**
+     * Returns an item selection listener.
+     */
+    public OnItemViewSelectedListener getOnItemViewSelectedListener() {
+        return mOnItemViewSelectedListener;
+    }
+
+    @Override
+    protected void onRowSelected(ViewGroup parent, View view, int position, long id) {
+        VerticalGridView listView = getVerticalGridView();
+        if (listView == null) {
+            return;
+        }
+        ItemBridgeAdapter.ViewHolder vh = (view == null) ? null :
+            (ItemBridgeAdapter.ViewHolder) listView.getChildViewHolder(view);
+
+        if (mSelectedViewHolder != vh) {
+            if (DEBUG) Log.v(TAG, "new row selected position " + position + " view " + view);
+
+            if (mSelectedViewHolder != null) {
+                setRowViewSelected(mSelectedViewHolder, false, false);
+            }
+            mSelectedViewHolder = vh;
+            if (mSelectedViewHolder != null) {
+                setRowViewSelected(mSelectedViewHolder, true, false);
+            }
+        }
+    }
+
+    @Override
+    protected int getLayoutResourceId() {
+        return R.layout.lb_rows_fragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mSelectAnimatorDuration = getResources().getInteger(R.integer.lb_browse_rows_anim_duration);
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        if (DEBUG) Log.v(TAG, "onViewCreated");
+        super.onViewCreated(view, savedInstanceState);
+        // Align the top edge of child with id row_content.
+        // Need set this for directly using RowsFragment.
+        getVerticalGridView().setItemAlignmentViewId(R.id.row_content);
+        getVerticalGridView().addItemDecoration(mItemDecoration);
+
+        mRecycledViewPool = null;
+        mPresenterMapper = null;
+    }
+
+    @Override
+    void setItemAlignment() {
+        super.setItemAlignment();
+        if (getVerticalGridView() != null) {
+            getVerticalGridView().setItemAlignmentOffsetWithPadding(true);
+        }
+    }
+
+    void setExternalAdapterListener(ItemBridgeAdapter.AdapterListener listener) {
+        mExternalAdapterListener = listener;
+    }
+
+    private RecyclerView.ItemDecoration mItemDecoration = new RecyclerView.ItemDecoration() {
+        @Override
+        public void onDrawOver(Canvas c, RecyclerView parent) {
+            final int count = parent.getChildCount();
+            for (int i = 0; i < count; i++) {
+                ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
+                        parent.getChildViewHolder(parent.getChildAt(i));
+                RowViewHolderExtra extra = (RowViewHolderExtra) ibvh.getExtraObject();
+                extra.drawDimForSelection(c);
+            }
+        }
+    };
+
+    private static void setRowViewExpanded(ItemBridgeAdapter.ViewHolder vh, boolean expanded) {
+        ((RowPresenter) vh.getPresenter()).setRowViewExpanded(vh.getViewHolder(), expanded);
+    }
+
+    private static void setRowViewSelected(ItemBridgeAdapter.ViewHolder vh, boolean selected,
+            boolean immediate) {
+        RowViewHolderExtra extra = (RowViewHolderExtra) vh.getExtraObject();
+        extra.animateSelect(selected, immediate);
+        ((RowPresenter) vh.getPresenter()).setRowViewSelected(vh.getViewHolder(), selected);
+    }
+
+    private static void setOnItemSelectedListener(ItemBridgeAdapter.ViewHolder vh,
+            OnItemSelectedListener listener) {
+        ((RowPresenter) vh.getPresenter()).setOnItemSelectedListener(listener);
+    }
+
+    private static void setOnItemViewSelectedListener(ItemBridgeAdapter.ViewHolder vh,
+            OnItemViewSelectedListener listener) {
+        ((RowPresenter) vh.getPresenter()).setOnItemViewSelectedListener(listener);
+    }
+
+    private final ItemBridgeAdapter.AdapterListener mBridgeAdapterListener =
+            new ItemBridgeAdapter.AdapterListener() {
+        @Override
+        public void onAddPresenter(Presenter presenter, int type) {
+            ((RowPresenter) presenter).setOnItemClickedListener(mOnItemClickedListener);
+            ((RowPresenter) presenter).setOnItemViewClickedListener(mOnItemViewClickedListener);
+            if (mExternalAdapterListener != null) {
+                mExternalAdapterListener.onAddPresenter(presenter, type);
+            }
+        }
+        @Override
+        public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
+            VerticalGridView listView = getVerticalGridView();
+            if (listView != null && ((RowPresenter) vh.getPresenter()).canDrawOutOfBounds()) {
+                listView.setClipChildren(false);
+            }
+            setupSharedViewPool(vh);
+            mViewsCreated = true;
+            vh.setExtraObject(new RowViewHolderExtra(vh));
+            // selected state is initialized to false, then driven by grid view onChildSelected
+            // events.  When there is rebind, grid view fires onChildSelected event properly.
+            // So we don't need do anything special later in onBind or onAttachedToWindow.
+            setRowViewSelected(vh, false, true);
+            if (mExternalAdapterListener != null) {
+                mExternalAdapterListener.onCreate(vh);
+            }
+        }
+        @Override
+        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
+            if (DEBUG) Log.v(TAG, "onAttachToWindow");
+            // All views share the same mExpand value.  When we attach a view to grid view,
+            // we should make sure it pick up the latest mExpand value we set early on other
+            // attached views.  For no-structure-change update,  the view is rebound to new data,
+            // but again it should use the unchanged mExpand value,  so we don't need do any
+            // thing in onBind.
+            setRowViewExpanded(vh, mExpand);
+            setOnItemSelectedListener(vh, mOnItemSelectedListener);
+            setOnItemViewSelectedListener(vh, mOnItemViewSelectedListener);
+            if (mExternalAdapterListener != null) {
+                mExternalAdapterListener.onAttachedToWindow(vh);
+            }
+        }
+        @Override
+        public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
+            if (mExternalAdapterListener != null) {
+                mExternalAdapterListener.onDetachedFromWindow(vh);
+            }
+        }
+        @Override
+        public void onBind(ItemBridgeAdapter.ViewHolder vh) {
+            if (mExternalAdapterListener != null) {
+                mExternalAdapterListener.onBind(vh);
+            }
+        }
+        @Override
+        public void onUnbind(ItemBridgeAdapter.ViewHolder vh) {
+            RowViewHolderExtra extra = (RowViewHolderExtra) vh.getExtraObject();
+            extra.endAnimations();
+            if (mExternalAdapterListener != null) {
+                mExternalAdapterListener.onUnbind(vh);
+            }
+        }
+    };
+
+    private void setupSharedViewPool(ItemBridgeAdapter.ViewHolder bridgeVh) {
+        RowPresenter rowPresenter = (RowPresenter) bridgeVh.getPresenter();
+        RowPresenter.ViewHolder rowVh = rowPresenter.getRowViewHolder(bridgeVh.getViewHolder());
+
+        if (rowVh instanceof ListRowPresenter.ViewHolder) {
+            HorizontalGridView view = ((ListRowPresenter.ViewHolder) rowVh).getGridView();
+            // Recycled view pool is shared between all list rows
+            if (mRecycledViewPool == null) {
+                mRecycledViewPool = view.getRecycledViewPool();
+            } else {
+                view.setRecycledViewPool(mRecycledViewPool);
+            }
+
+            ItemBridgeAdapter bridgeAdapter =
+                    ((ListRowPresenter.ViewHolder) rowVh).getBridgeAdapter();
+            if (mPresenterMapper == null) {
+                mPresenterMapper = bridgeAdapter.getPresenterMapper();
+            } else {
+                bridgeAdapter.setPresenterMapper(mPresenterMapper);
+            }
+        }
+    }
+
+    @Override
+    protected void updateAdapter() {
+        super.updateAdapter();
+        mSelectedViewHolder = null;
+        mViewsCreated = false;
+
+        ItemBridgeAdapter adapter = getBridgeAdapter();
+        if (adapter != null) {
+            adapter.setAdapterListener(mBridgeAdapterListener);
+        }
+    }
+
+    @Override
+    void onTransitionStart() {
+        super.onTransitionStart();
+        freezeRows(true);
+    }
+
+    @Override
+    void onTransitionEnd() {
+        super.onTransitionEnd();
+        freezeRows(false);
+    }
+
+    private void freezeRows(boolean freeze) {
+        VerticalGridView verticalView = getVerticalGridView();
+        final int count = verticalView.getChildCount();
+        for (int i = 0; i < count; i++) {
+            ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
+                    verticalView.getChildViewHolder(verticalView.getChildAt(i));
+            RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
+            RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
+            rowPresenter.freeze(vh, freeze);
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java b/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
new file mode 100644
index 0000000..9d4f43b
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.app.Fragment;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.speech.SpeechRecognizer;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.SearchBar;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.Presenter.ViewHolder;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.support.v17.leanback.R;
+
+import java.util.List;
+
+/**
+ * A fragment to handle searches. An application will supply an implementation
+ * of the {@link SearchResultProvider} interface to handle the search and return
+ * an {@link ObjectAdapter} containing the results. The results are rendered
+ * into a {@link RowsFragment}, in the same way that they are in a {@link
+ * BrowseFragment}.
+ *
+ * <p>Note: Your application will need to request android.permission.RECORD_AUDIO.
+ */
+public class SearchFragment extends Fragment {
+    private static final String TAG = SearchFragment.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final String ARG_PREFIX = SearchFragment.class.getCanonicalName();
+    private static final String ARG_QUERY =  ARG_PREFIX + ".query";
+    private static final String ARG_TITLE = ARG_PREFIX  + ".title";
+
+    /**
+     * Search API to be provided by the application.
+     */
+    public static interface SearchResultProvider {
+        /**
+         * <p>Method invoked some time prior to the first call to onQueryTextChange to retrieve
+         * an ObjectAdapter that will contain the results to future updates of the search query.</p>
+         *
+         * <p>As results are retrieved, the application should use the data set notification methods
+         * on the ObjectAdapter to instruct the SearchFragment to update the results.</p>
+         *
+         * @return ObjectAdapter The result object adapter.
+         */
+        public ObjectAdapter getResultsAdapter();
+
+        /**
+         * <p>Method invoked when the search query is updated.</p>
+         *
+         * <p>This is called as soon as the query changes; it is up to the application to add a
+         * delay before actually executing the queries if needed.
+         *
+         * <p>This method might not always be called before onQueryTextSubmit gets called, in
+         * particular for voice input.
+         *
+         * @param newQuery The current search query.
+         * @return whether the results changed as a result of the new query.
+         */
+        public boolean onQueryTextChange(String newQuery);
+
+        /**
+         * Method invoked when the search query is submitted, either by dismissing the keyboard,
+         * pressing search or next on the keyboard or when voice has detected the end of the query.
+         *
+         * @param query The query entered.
+         * @return whether the results changed as a result of the query.
+         */
+        public boolean onQueryTextSubmit(String query);
+    }
+
+    private RowsFragment mRowsFragment;
+    private final Handler mHandler = new Handler();
+
+    private SearchBar mSearchBar;
+    private SearchResultProvider mProvider;
+    private String mPendingQuery = null;
+
+    private OnItemSelectedListener mOnItemSelectedListener;
+    private OnItemClickedListener mOnItemClickedListener;
+    private OnItemViewSelectedListener mOnItemViewSelectedListener;
+    private OnItemViewClickedListener mOnItemViewClickedListener;
+    private ObjectAdapter mResultAdapter;
+
+    private String mTitle;
+    private Drawable mBadgeDrawable;
+
+    private SpeechRecognizer mSpeechRecognizer;
+
+    /**
+     * @param args Bundle to use for the arguments, if null a new Bundle will be created.
+     */
+    public static Bundle createArgs(Bundle args, String query) {
+        return createArgs(args, query, null);
+    }
+
+    public static Bundle createArgs(Bundle args, String query, String title)  {
+        if (args == null) {
+            args = new Bundle();
+        }
+        args.putString(ARG_QUERY, query);
+        args.putString(ARG_TITLE, title);
+        return args;
+    }
+
+    /**
+     * Create a search fragment with a given search query.
+     *
+     * <p>You should only use this if you need to start the search fragment with a
+     * pre-filled query.
+     *
+     * @param query The search query to begin with.
+     * @return A new SearchFragment.
+     */
+    public static SearchFragment newInstance(String query) {
+        SearchFragment fragment = new SearchFragment();
+        Bundle args = createArgs(null, query);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View root = inflater.inflate(R.layout.lb_search_fragment, container, false);
+
+        FrameLayout searchFrame = (FrameLayout) root.findViewById(R.id.lb_search_frame);
+        mSearchBar = (SearchBar) searchFrame.findViewById(R.id.lb_search_bar);
+        mSearchBar.setSearchBarListener(new SearchBar.SearchBarListener() {
+            @Override
+            public void onSearchQueryChange(String query) {
+                if (DEBUG) Log.v(TAG, String.format("onSearchQueryChange %s", query));
+                if (null != mProvider) {
+                    retrieveResults(query);
+                } else {
+                    mPendingQuery = query;
+                }
+            }
+
+            @Override
+            public void onSearchQuerySubmit(String query) {
+                if (DEBUG) Log.v(TAG, String.format("onSearchQuerySubmit %s", query));
+                mRowsFragment.setSelectedPosition(0);
+                mRowsFragment.getVerticalGridView().requestFocus();
+                if (null != mProvider) {
+                    mProvider.onQueryTextSubmit(query);
+                }
+            }
+
+            @Override
+            public void onKeyboardDismiss(String query) {
+                if (DEBUG) Log.v(TAG, String.format("onKeyboardDismiss %s", query));
+                mRowsFragment.setSelectedPosition(0);
+                mRowsFragment.getVerticalGridView().requestFocus();
+            }
+        });
+
+        readArguments(getArguments());
+        if (null != mBadgeDrawable) {
+            setBadgeDrawable(mBadgeDrawable);
+        }
+        if (null != mTitle) {
+            setTitle(mTitle);
+        }
+
+        // Inject the RowsFragment in the results container
+        if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) {
+            mRowsFragment = new RowsFragment();
+            getChildFragmentManager().beginTransaction()
+                    .replace(R.id.lb_results_frame, mRowsFragment).commit();
+        } else {
+            mRowsFragment = (RowsFragment) getChildFragmentManager()
+                    .findFragmentById(R.id.browse_container_dock);
+        }
+        mRowsFragment.setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
+            @Override
+            public void onItemSelected(ViewHolder itemViewHolder, Object item,
+                    RowPresenter.ViewHolder rowViewHolder, Row row) {
+                int position = mRowsFragment.getVerticalGridView().getSelectedPosition();
+                if (DEBUG) Log.v(TAG, String.format("onItemSelected %d", position));
+                mSearchBar.setVisibility(0 >= position ? View.VISIBLE : View.GONE);
+                if (null != mOnItemSelectedListener) {
+                    mOnItemSelectedListener.onItemSelected(item, row);
+                }
+                if (null != mOnItemViewSelectedListener) {
+                    mOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
+                            rowViewHolder, row);
+                }
+            }
+        });
+        mRowsFragment.setOnItemViewClickedListener(new OnItemViewClickedListener() {
+            @Override
+            public void onItemClicked(ViewHolder itemViewHolder, Object item,
+                    RowPresenter.ViewHolder rowViewHolder, Row row) {
+                int position = mRowsFragment.getVerticalGridView().getSelectedPosition();
+                if (DEBUG) Log.v(TAG, String.format("onItemClicked %d", position));
+                if (null != mOnItemClickedListener) {
+                    mOnItemClickedListener.onItemClicked(item, row);
+                }
+                if (null != mOnItemViewClickedListener) {
+                    mOnItemViewClickedListener.onItemClicked(itemViewHolder, item,
+                            rowViewHolder, row);
+                }
+            }
+        });
+        mRowsFragment.setExpand(true);
+        if (null != mProvider) {
+            onSetSearchResultProvider();
+        }
+        return root;
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        VerticalGridView list = mRowsFragment.getVerticalGridView();
+        int mContainerListAlignTop =
+                getResources().getDimensionPixelSize(R.dimen.lb_search_browse_rows_align_top);
+        list.setItemAlignmentOffset(0);
+        list.setItemAlignmentOffsetPercent(VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+        list.setWindowAlignmentOffset(mContainerListAlignTop);
+        list.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+        list.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (null == mSpeechRecognizer) {
+            mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(getActivity());
+            mSearchBar.setSpeechRecognizer(mSpeechRecognizer);
+        }
+    }
+
+    @Override
+    public void onPause() {
+        if (null != mSpeechRecognizer) {
+            mSearchBar.setSpeechRecognizer(null);
+            mSpeechRecognizer.destroy();
+            mSpeechRecognizer = null;
+        }
+        super.onPause();
+    }
+
+    /**
+     * Set the search provider that is responsible for returning results for the
+     * search query.
+     */
+    public void setSearchResultProvider(SearchResultProvider searchResultProvider) {
+        mProvider = searchResultProvider;
+        onSetSearchResultProvider();
+    }
+
+    /**
+     * Sets an item selection listener for the results.
+     *
+     * @param listener The item selection listener to be invoked when an item in
+     *        the search results is selected.
+     * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
+     */
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mOnItemSelectedListener = listener;
+    }
+
+    /**
+     * Sets an item clicked listener for the results.
+     *
+     * @param listener The item clicked listener to be invoked when an item in
+     *        the search results is clicked.
+     * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)}
+     */
+    public void setOnItemClickedListener(OnItemClickedListener listener) {
+        mOnItemClickedListener = listener;
+    }
+
+    /**
+     * Sets an item selection listener for the results.
+     *
+     * @param listener The item selection listener to be invoked when an item in
+     *        the search results is selected.
+     */
+    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        mOnItemViewSelectedListener = listener;
+    }
+
+    /**
+     * Sets an item clicked listener for the results.
+     *
+     * @param listener The item clicked listener to be invoked when an item in
+     *        the search results is clicked.
+     */
+    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        mOnItemViewClickedListener = listener;
+    }
+
+    /**
+     * Sets the title string to be be shown in an empty search bar. The title
+     * may be placed in a call-to-action, such as "Search <i>title</i>" or
+     * "Speak to search <i>title</i>".
+     */
+    public void setTitle(String title) {
+        mTitle = title;
+        if (null != mSearchBar) {
+            mSearchBar.setTitle(title);
+        }
+    }
+
+    /**
+     * Returns the title set in the search bar.
+     */
+    public String getTitle() {
+        if (null != mSearchBar) {
+            return mSearchBar.getTitle();
+        }
+        return null;
+    }
+
+    /**
+     * Sets the badge drawable that will be shown inside the search bar next to
+     * the title.
+     */
+    public void setBadgeDrawable(Drawable drawable) {
+        mBadgeDrawable = drawable;
+        if (null != mSearchBar) {
+            mSearchBar.setBadgeDrawable(drawable);
+        }
+    }
+
+    /**
+     * Returns the badge drawable in the search bar.
+     */
+    public Drawable getBadgeDrawable() {
+        if (null != mSearchBar) {
+            return mSearchBar.getBadgeDrawable();
+        }
+        return null;
+    }
+
+    /**
+     * Display the completions shown by the IME. An application may provide
+     * a list of query completions that the system will show in the IME.
+     *
+     * @param completions A list of completions to show in the IME. Setting to
+     *        null or empty will clear the list.
+     */
+    public void displayCompletions(List<String> completions) {
+        mSearchBar.displayCompletions(completions);
+    }
+
+    private void retrieveResults(String searchQuery) {
+        if (DEBUG) Log.v(TAG, String.format("retrieveResults %s", searchQuery));
+        mProvider.onQueryTextChange(searchQuery);
+    }
+
+    private void onSetSearchResultProvider() {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                // Retrieve the result adapter
+                mResultAdapter = mProvider.getResultsAdapter();
+                if (null != mRowsFragment) {
+                    mRowsFragment.setAdapter(mResultAdapter);
+                    executePendingQuery();
+                }
+            }
+        });
+    }
+
+    private void executePendingQuery() {
+        if (null != mPendingQuery && null != mResultAdapter) {
+            String query = mPendingQuery;
+            mPendingQuery = null;
+            retrieveResults(query);
+        }
+    }
+
+    private void readArguments(Bundle args) {
+        if (null == args) {
+            return;
+        }
+        if (args.containsKey(ARG_QUERY)) {
+            setSearchQuery(args.getString(ARG_QUERY));
+        }
+
+        if (args.containsKey(ARG_TITLE)) {
+            setTitle(args.getString(ARG_TITLE));
+        }
+    }
+
+    private void setSearchQuery(String query) {
+        mSearchBar.setSearchQuery(query);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/TitleTransitionHelper.java b/v17/leanback/src/android/support/v17/leanback/app/TitleTransitionHelper.java
new file mode 100644
index 0000000..82b210d
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/TitleTransitionHelper.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.widget.TitleView;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+class TitleTransitionHelper {
+
+    private static Interpolator createTransitionInterpolatorUp() {
+        return new DecelerateInterpolator(4);
+    }
+
+    private static Interpolator createTransitionInterpolatorDown() {
+        return new DecelerateInterpolator();
+    }
+
+    static public Object createTransitionTitleUp(TransitionHelper helper) {
+        Object transition = helper.createChangeBounds(false);
+        helper.setInterpolator(transition, createTransitionInterpolatorUp());
+        return transition;
+    }
+
+    static public Object createTransitionTitleDown(TransitionHelper helper) {
+        Object transition = helper.createChangeBounds(false);
+        helper.setInterpolator(transition, createTransitionInterpolatorDown());
+        return transition;
+    }
+
+    static public void showTitle(TitleView view, boolean show) {
+        MarginLayoutParams lp = (MarginLayoutParams) view.getLayoutParams();
+        lp.topMargin = show ? 0 : -view.getHeight();
+        view.setLayoutParams(lp);
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
new file mode 100644
index 0000000..f55d473
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.TitleView;
+import android.support.v17.leanback.widget.VerticalGridPresenter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemClickedListener;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.SearchOrbView;
+import android.app.Fragment;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * A fragment for creating leanback vertical grids.
+ *
+ * <p>Renders a vertical grid of objects given a {@link VerticalGridPresenter} and
+ * an {@link ObjectAdapter}.
+ */
+public class VerticalGridFragment extends Fragment {
+    private static final String TAG = "VerticalGridFragment";
+    private static boolean DEBUG = false;
+
+    private BrowseFrameLayout mBrowseFrame;
+    private String mTitle;
+    private Drawable mBadgeDrawable;
+    private ObjectAdapter mAdapter;
+    private VerticalGridPresenter mGridPresenter;
+    private VerticalGridPresenter.ViewHolder mGridViewHolder;
+    private OnItemSelectedListener mOnItemSelectedListener;
+    private OnItemClickedListener mOnItemClickedListener;
+    private OnItemViewSelectedListener mOnItemViewSelectedListener;
+    private OnItemViewClickedListener mOnItemViewClickedListener;
+    private View.OnClickListener mExternalOnSearchClickedListener;
+    private int mSelectedPosition = -1;
+
+    private TitleView mTitleView;
+    private SearchOrbView.Colors mSearchAffordanceColors;
+    private boolean mSearchAffordanceColorSet;
+    private boolean mShowingTitle = true;
+
+    // transition related
+    private static TransitionHelper sTransitionHelper = TransitionHelper.getInstance();
+    private Object mTitleUpTransition;
+    private Object mTitleDownTransition;
+    private Object mSceneWithTitle;
+    private Object mSceneWithoutTitle;
+
+    /**
+     * Sets the badge drawable displayed in the title area.
+     */
+    public void setBadgeDrawable(Drawable drawable) {
+        if (drawable != mBadgeDrawable) {
+            mBadgeDrawable = drawable;
+            if (mTitleView != null) {
+                mTitleView.setBadgeDrawable(drawable);
+            }
+        }
+    }
+
+    /**
+     * Returns the badge drawable.
+     */
+    public Drawable getBadgeDrawable() {
+        return mBadgeDrawable;
+    }
+
+    /**
+     * Sets a title for the fragment.
+     */
+    public void setTitle(String title) {
+        mTitle = title;
+        if (mTitleView != null) {
+            mTitleView.setTitle(mTitle);
+        }
+    }
+
+    /**
+     * Returns the title for the fragment.
+     */
+    public String getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Sets the grid presenter.
+     */
+    public void setGridPresenter(VerticalGridPresenter gridPresenter) {
+        if (gridPresenter == null) {
+            throw new IllegalArgumentException("Grid presenter may not be null");
+        }
+        mGridPresenter = gridPresenter;
+        mGridPresenter.setOnItemViewSelectedListener(mRowSelectedListener);
+        if (mOnItemViewClickedListener != null) {
+            mGridPresenter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        }
+        if (mOnItemClickedListener != null) {
+            mGridPresenter.setOnItemClickedListener(mOnItemClickedListener);
+        }
+    }
+
+    /**
+     * Returns the grid presenter.
+     */
+    public VerticalGridPresenter getGridPresenter() {
+        return mGridPresenter;
+    }
+
+    /**
+     * Sets the object adapter for the fragment.
+     */
+    public void setAdapter(ObjectAdapter adapter) {
+        mAdapter = adapter;
+        updateAdapter();
+    }
+
+    /**
+     * Returns the object adapter.
+     */
+    public ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    final private OnItemViewSelectedListener mRowSelectedListener =
+            new OnItemViewSelectedListener() {
+        @Override
+        public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                RowPresenter.ViewHolder rowViewHolder, Row row) {
+            int position = mGridViewHolder.getGridView().getSelectedPosition();
+            if (DEBUG) Log.v(TAG, "row selected position " + position);
+            onRowSelected(position);
+            if (mOnItemSelectedListener != null) {
+                mOnItemSelectedListener.onItemSelected(item, row);
+            }
+            if (mOnItemViewSelectedListener != null) {
+                mOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
+                        rowViewHolder, row);
+            }
+        }
+    };
+
+    /**
+     * Sets an item selection listener.
+     * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
+     */
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mOnItemSelectedListener = listener;
+    }
+
+    /**
+     * Sets an item selection listener.
+     */
+    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        mOnItemViewSelectedListener = listener;
+    }
+
+    private void onRowSelected(int position) {
+        if (position != mSelectedPosition) {
+            if (!mGridViewHolder.getGridView().hasPreviousViewInSameRow(position)) {
+                // if has no sibling in front of it,  show title
+                if (!mShowingTitle) {
+                    sTransitionHelper.runTransition(mSceneWithTitle, mTitleDownTransition);
+                    mShowingTitle = true;
+                }
+            } else if (mShowingTitle) {
+                sTransitionHelper.runTransition(mSceneWithoutTitle, mTitleUpTransition);
+                mShowingTitle = false;
+            }
+            mSelectedPosition = position;
+        }
+    }
+
+    /**
+     * Sets an item clicked listener.
+     * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)}
+     */
+    public void setOnItemClickedListener(OnItemClickedListener listener) {
+        mOnItemClickedListener = listener;
+        if (mGridPresenter != null) {
+            mGridPresenter.setOnItemClickedListener(mOnItemClickedListener);
+        }
+    }
+
+    /**
+     * Returns the item clicked listener.
+     * @deprecated Use {@link #getOnItemViewClickedListener()}
+     */
+    public OnItemClickedListener getOnItemClickedListener() {
+        return mOnItemClickedListener;
+    }
+
+    /**
+     * Sets an item clicked listener.
+     */
+    public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        mOnItemViewClickedListener = listener;
+        if (mGridPresenter != null) {
+            mGridPresenter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+        }
+    }
+
+    /**
+     * Returns the item clicked listener.
+     */
+    public OnItemViewClickedListener getOnItemViewClickedListener() {
+        return mOnItemViewClickedListener;
+    }
+
+    /**
+     * Sets a click listener for the search affordance.
+     *
+     * <p>The presence of a listener will change the visibility of the search
+     * affordance in the title area. When set to non-null, the title area will
+     * contain a call to search action.
+     *
+     * <p>The listener's onClick method will be invoked when the user clicks on
+     * the search action.
+     *
+     * @param listener The listener to invoke when the search affordance is
+     *        clicked, or null to hide the search affordance.
+     */
+    public void setOnSearchClickedListener(View.OnClickListener listener) {
+        mExternalOnSearchClickedListener = listener;
+        if (mTitleView != null) {
+            mTitleView.setOnSearchClickedListener(listener);
+        }
+    }
+
+    /**
+     * Sets the {@link SearchOrbView.Colors} used to draw the search affordance.
+     */
+    public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
+        mSearchAffordanceColors = colors;
+        mSearchAffordanceColorSet = true;
+        if (mTitleView != null) {
+            mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
+        }
+    }
+
+    /**
+     * Returns the {@link SearchOrbView.Colors} used to draw the search affordance.
+     */
+    public SearchOrbView.Colors getSearchAffordanceColors() {
+        if (mSearchAffordanceColorSet) {
+            return mSearchAffordanceColors;
+        }
+        if (mTitleView == null) {
+            throw new IllegalStateException("Fragment views not yet created");
+        }
+        return mTitleView.getSearchAffordanceColors();
+    }
+
+    /**
+     * Sets the color used to draw the search affordance.
+     * A default brighter color will be set by the framework.
+     *
+     * @param color The color to use for the search affordance.
+     */
+    public void setSearchAffordanceColor(int color) {
+        setSearchAffordanceColors(new SearchOrbView.Colors(color));
+    }
+
+    /**
+     * Returns the color used to draw the search affordance.
+     */
+    public int getSearchAffordanceColor() {
+        return getSearchAffordanceColors().color;
+    }
+
+    private final BrowseFrameLayout.OnFocusSearchListener mOnFocusSearchListener =
+            new BrowseFrameLayout.OnFocusSearchListener() {
+        @Override
+        public View onFocusSearch(View focused, int direction) {
+            if (DEBUG) Log.v(TAG, "onFocusSearch focused " + focused + " + direction " + direction);
+
+            final View searchOrbView = mTitleView.getSearchAffordanceView();
+            if (focused == searchOrbView && (
+                    direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT)) {
+                return mGridViewHolder.view;
+
+            } else if (focused != searchOrbView && searchOrbView.getVisibility() == View.VISIBLE
+                    && direction == View.FOCUS_UP) {
+                return searchOrbView;
+
+            } else {
+                return null;
+            }
+        }
+    };
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        ViewGroup root = (ViewGroup) inflater.inflate(R.layout.lb_vertical_grid_fragment,
+                container, false);
+
+        mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
+        mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
+
+        mTitleView = (TitleView) root.findViewById(R.id.browse_title_group);
+        mTitleView.setBadgeDrawable(mBadgeDrawable);
+        mTitleView.setTitle(mTitle);
+        if (mSearchAffordanceColorSet) {
+            mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
+        }
+        if (mExternalOnSearchClickedListener != null) {
+            mTitleView.setOnSearchClickedListener(mExternalOnSearchClickedListener);
+        }
+
+        mSceneWithTitle = sTransitionHelper.createScene(root, new Runnable() {
+            @Override
+            public void run() {
+                TitleTransitionHelper.showTitle(mTitleView, true);
+            }
+        });
+        mSceneWithoutTitle = sTransitionHelper.createScene(root, new Runnable() {
+            @Override
+            public void run() {
+                TitleTransitionHelper.showTitle(mTitleView, false);
+            }
+        });
+        mTitleUpTransition = TitleTransitionHelper.createTransitionTitleUp(sTransitionHelper);
+        mTitleDownTransition = TitleTransitionHelper.createTransitionTitleDown(sTransitionHelper);
+        sTransitionHelper.excludeChildren(mTitleUpTransition, R.id.browse_grid_dock, true);
+        sTransitionHelper.excludeChildren(mTitleDownTransition, R.id.browse_grid_dock, true);
+
+        return root;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        ViewGroup gridDock = (ViewGroup) view.findViewById(R.id.browse_grid_dock);
+        mGridViewHolder = mGridPresenter.onCreateViewHolder(gridDock);
+        gridDock.addView(mGridViewHolder.view);
+
+        updateAdapter();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mGridViewHolder.getGridView().requestFocus();
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mGridViewHolder = null;
+    }
+
+    /**
+     * Sets the selected item position.
+     */
+    public void setSelectedPosition(int position) {
+        mSelectedPosition = position;
+        if(mGridViewHolder != null && mGridViewHolder.getGridView().getAdapter() != null) {
+            mGridViewHolder.getGridView().setSelectedPositionSmooth(position);
+        }
+    }
+
+    private void updateAdapter() {
+        if (mGridViewHolder != null) {
+            mGridPresenter.onBindViewHolder(mGridViewHolder, mAdapter);
+            if (mSelectedPosition != -1) {
+                mGridViewHolder.getGridView().setSelectedPosition(mSelectedPosition);
+            }
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/database/CursorMapper.java b/v17/leanback/src/android/support/v17/leanback/database/CursorMapper.java
new file mode 100644
index 0000000..20b4a36
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/database/CursorMapper.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.database;
+
+import android.database.Cursor;
+
+/**
+ * Abstract class used to convert the current {@link Cursor} row to a single
+ * object.
+ */
+public abstract class CursorMapper {
+
+    private Cursor mCursor;
+
+    /**
+     * Called once when the associated {@link Cursor} is changed. A subclass
+     * should bind column indexes to column names in this method. This method is
+     * not intended to be called outside of CursorMapper.
+     */
+    protected abstract void bindColumns(Cursor cursor);
+
+    /**
+     * A subclass should implement this method to create a single object using
+     * binding information. This method is not intended to be called
+     * outside of CursorMapper.
+     */
+    protected abstract Object bind(Cursor cursor);
+
+    /**
+     * Convert a {@link Cursor} at its current position to an Object.
+     */
+    public Object convert(Cursor cursor) {
+        if (cursor != mCursor) {
+            mCursor = cursor;
+            bindColumns(mCursor);
+        }
+        return bind(mCursor);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/graphics/ColorFilterCache.java b/v17/leanback/src/android/support/v17/leanback/graphics/ColorFilterCache.java
new file mode 100644
index 0000000..872d282
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/graphics/ColorFilterCache.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.graphics;
+
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.util.SparseArray;
+
+/**
+ * Cache of {@link ColorFilter}s for a given color at different alpha levels.
+ */
+public final class ColorFilterCache {
+
+    private static final SparseArray<ColorFilterCache> sColorToFiltersMap =
+            new SparseArray<ColorFilterCache>();
+
+    private final PorterDuffColorFilter[] mFilters = new PorterDuffColorFilter[0x100];
+
+    /**
+     * Get a ColorDimmer for a given color.  Only the RGB values are used; the 
+     * alpha channel is ignored in color. Subsequent calls to this method
+     * with the same color value will return the same cache.
+     *
+     * @param color The color to use for the color filters.
+     * @return A cache of ColorFilters at different alpha levels for the color.
+     */
+    public static ColorFilterCache getColorFilterCache(int color) {
+        final int r = Color.red(color);
+        final int g = Color.green(color);
+        final int b = Color.blue(color);
+        color = Color.rgb(r, g, b);
+        ColorFilterCache filters = sColorToFiltersMap.get(color);
+        if (filters == null) {
+            filters = new ColorFilterCache(r, g, b);
+            sColorToFiltersMap.put(color, filters);
+        }
+        return filters;
+    }
+
+    private ColorFilterCache(int r, int g, int b) {
+        // Pre cache all 256 filter levels
+        for (int i = 0x00; i <= 0xFF; i++) {
+            int color = Color.argb(i, r, g, b);
+            mFilters[i] = new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP);
+        }
+    }
+
+    /**
+     * Returns a ColorFilter for a given alpha level between 0 and 1.0.
+     *
+     * @param level The alpha level the filter should apply.
+     * @return A ColorFilter at the alpha level for the color represented by the
+     *         cache.
+     */
+    public ColorFilter getFilterForLevel(float level) {
+        if (level >= 0 && level <= 1.0) {
+            int filterIndex = (int) (0xFF * level);
+            return mFilters[filterIndex];
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/graphics/ColorFilterDimmer.java b/v17/leanback/src/android/support/v17/leanback/graphics/ColorFilterDimmer.java
new file mode 100644
index 0000000..d64a098
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/graphics/ColorFilterDimmer.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.graphics;
+
+import android.content.Context;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.view.View;
+import android.support.v17.leanback.R;
+
+/**
+ * Helper class for applying a dim level to a View.  The ColorFilterDimmer
+ * uses a ColorFilter in a Paint object to dim the view according to the
+ * currently active level.
+ */
+public final class ColorFilterDimmer {
+
+    private final ColorFilterCache mColorDimmer;
+
+    private final float mActiveLevel;
+    private final float mDimmedLevel;
+
+    private final Paint mPaint;
+    private ColorFilter mFilter;
+
+    /**
+     * Creates a default ColorFilterDimmer. Uses the default color and level for
+     * the dimmer.
+     *
+     * @param context A Context used to retrieve Resources.
+     * @return A ColorFilterDimmer with the default dim color and levels.
+     */
+    public static ColorFilterDimmer createDefault(Context context) {
+        return new ColorFilterDimmer(ColorFilterCache.getColorFilterCache(
+                context.getResources().getColor(R.color.lb_view_dim_mask_color)),
+                0, context.getResources().getFraction(R.dimen.lb_view_dimmed_level, 1, 1));
+    }
+
+    /**
+     * Creates a ColorFilterDimmer for the given color and levels..
+     *
+     * @param dimmer      The ColorFilterCache for dim color.
+     * @param activeLevel The level of dimming when the View is in its active
+     *                    state. Must be a float value between 0.0 and 1.0.
+     * @param dimmedLevel The level of dimming when the View is in its dimmed
+     *                    state. Must be a float value between 0.0 and 1.0.
+     */
+    public static ColorFilterDimmer create(ColorFilterCache dimmer,
+            float activeLevel, float dimmedLevel) {
+        return new ColorFilterDimmer(dimmer, activeLevel, dimmedLevel);
+    }
+
+    private ColorFilterDimmer(ColorFilterCache dimmer, float activeLevel, float dimmedLevel) {
+        mColorDimmer = dimmer;
+        if (activeLevel > 1.0f) activeLevel = 1.0f;
+        if (activeLevel < 0.0f) activeLevel = 0.0f;
+        if (dimmedLevel > 1.0f) dimmedLevel = 1.0f;
+        if (dimmedLevel < 0.0f) dimmedLevel = 0.0f;
+        mActiveLevel = activeLevel;
+        mDimmedLevel = dimmedLevel;
+        mPaint = new Paint();
+    }
+
+    /**
+     * Apply current the ColorFilter to a View. This method will set the
+     * hardware layer of the view when applying a filter, and remove it when not
+     * applying a filter.
+     *
+     * @param view The View to apply the ColorFilter to.
+     */
+    public void applyFilterToView(View view) {
+        if (mFilter != null) {
+            view.setLayerType(View.LAYER_TYPE_HARDWARE, mPaint);
+        } else {
+            view.setLayerType(View.LAYER_TYPE_NONE, null);
+        }
+        // FIXME: Current framework has bug that not triggering invalidate when change layer
+        // paint.  Will add conditional sdk version check once bug is fixed in released
+        // framework.
+        view.invalidate();
+    }
+
+    /**
+     * Sets the active level of the dimmer. Updates the ColorFilter based on the
+     * level.
+     *
+     * @param level A float between 0 (fully dim) and 1 (fully active).
+     */
+    public void setActiveLevel(float level) {
+        if (level < 0.0f) level = 0.0f;
+        if (level > 1.0f) level = 1.0f;
+        mFilter = mColorDimmer.getFilterForLevel(
+                mDimmedLevel + level * (mActiveLevel - mDimmedLevel));
+        mPaint.setColorFilter(mFilter);
+    }
+
+    /**
+     * Gets the ColorFilter set to the current dim level.
+     *
+     * @return The current ColorFilter.
+     */
+    public ColorFilter getColorFilter() {
+        return mFilter;
+    }
+
+    /**
+     * Gets the Paint object set to the current dim level.
+     *
+     * @return The current Paint object.
+     */
+    public Paint getPaint() {
+        return mPaint;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/graphics/ColorOverlayDimmer.java b/v17/leanback/src/android/support/v17/leanback/graphics/ColorOverlayDimmer.java
new file mode 100644
index 0000000..17a185b
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/graphics/ColorOverlayDimmer.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.graphics;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.support.v17.leanback.R;
+import android.view.View;
+
+/**
+ * Helper class for assigning a dim color to Paint. It holds the alpha value for
+ * the current active level.
+ */
+public final class ColorOverlayDimmer {
+
+    private final float mActiveLevel;
+    private final float mDimmedLevel;
+
+    private final Paint mPaint;
+
+    private int mAlpha;
+    private float mAlphaFloat;
+
+    /**
+     * Creates a default ColorOverlayDimmer.
+     */
+    public static ColorOverlayDimmer createDefault(Context context) {
+        return new ColorOverlayDimmer(
+                context.getResources().getColor(R.color.lb_view_dim_mask_color), 0,
+                context.getResources().getFraction(R.dimen.lb_view_dimmed_level, 1, 1));
+    }
+
+    /**
+     * Creates a ColorOverlayDimmer for the given color and levels.
+     *
+     * @param dimColor    The color for fully dimmed. Only the RGB values are
+     *                    used; the alpha channel is ignored.
+     * @param activeLevel The level of dimming when the View is in its active
+     *                    state. Must be a float value between 0.0 and 1.0.
+     * @param dimmedLevel The level of dimming when the View is in its dimmed
+     *                    state. Must be a float value between 0.0 and 1.0.
+     */
+    public static ColorOverlayDimmer createColorOverlayDimmer(int dimColor, float activeLevel,
+            float dimmedLevel) {
+        return new ColorOverlayDimmer(dimColor, activeLevel, dimmedLevel);
+    }
+
+    private ColorOverlayDimmer(int dimColor, float activeLevel, float dimmedLevel) {
+        if (activeLevel > 1.0f) activeLevel = 1.0f;
+        if (activeLevel < 0.0f) activeLevel = 0.0f;
+        if (dimmedLevel > 1.0f) dimmedLevel = 1.0f;
+        if (dimmedLevel < 0.0f) dimmedLevel = 0.0f;
+        mPaint = new Paint();
+        dimColor = Color.rgb(Color.red(dimColor), Color.green(dimColor), Color.blue(dimColor));
+        mPaint.setColor(dimColor);
+        mActiveLevel = activeLevel;
+        mDimmedLevel = dimmedLevel;
+        setActiveLevel(1);
+    }
+
+    /**
+     * Sets the active level of the dimmer. Updates the alpha value based on the
+     * level.
+     *
+     * @param level A float between 0 (fully dim) and 1 (fully active).
+     */
+    public void setActiveLevel(float level) {
+        mAlphaFloat = (mDimmedLevel + level * (mActiveLevel - mDimmedLevel));
+        mAlpha = (int) (255 * mAlphaFloat);
+        mPaint.setAlpha(mAlpha);
+    }
+
+    /**
+     * Returns whether the dimmer needs to draw.
+     */
+    public boolean needsDraw() {
+        return mAlpha != 0;
+    }
+
+    /**
+     * Returns the alpha value for the dimmer.
+     */
+    public int getAlpha() {
+        return mAlpha;
+    }
+
+    /**
+     * Returns the float value between 0 and 1 corresponding to alpha between
+     * 0 and 255.
+     */
+    public float getAlphaFloat() {
+        return mAlphaFloat;
+    }
+
+    /**
+     * Returns the Paint object set to the current alpha value.
+     */
+    public Paint getPaint() {
+        return mPaint;
+    }
+
+    /**
+     * Change the RGB of the color according to current dim level. Maintains the
+     * alpha value of the color.
+     *
+     * @param color The color to apply the dim level to.
+     * @return A color with the RGB values adjusted by the alpha of the current
+     *         dim level.
+     */
+    public int applyToColor(int color) {
+        float f = 1 - mAlphaFloat;
+        return Color.argb(Color.alpha(color),
+                (int)(Color.red(color) * f),
+                (int)(Color.green(color) * f),
+                (int)(Color.blue(color) * f));
+    }
+
+    /**
+     * Draw a dim color overlay on top of a child View inside the canvas of
+     * the parent View.
+     *
+     * @param c Canvas of the parent View.
+     * @param v A child of the parent View.
+     * @param includePadding Set to true to draw overlay on padding area of the
+     *        View.
+     */
+    public void drawColorOverlay(Canvas c, View v, boolean includePadding) {
+        c.save();
+        float dx = v.getLeft() + v.getTranslationX();
+        float dy = v.getTop() + v.getTranslationY();
+        c.translate(dx, dy);
+        c.concat(v.getMatrix());
+        c.translate(-dx, -dy);
+        if (includePadding) {
+            c.drawRect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom(), mPaint);
+        } else {
+            c.drawRect(v.getLeft() + v.getPaddingLeft(),
+                    v.getTop() + v.getPaddingTop(),
+                    v.getRight() - v.getPaddingRight(),
+                    v.getBottom() - v.getPaddingBottom(), mPaint);
+        }
+        c.restore();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java b/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java
new file mode 100644
index 0000000..c6bf59d
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java
@@ -0,0 +1,623 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.transition;
+
+import android.os.Build;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+
+/**
+ * Helper for view transitions.
+ * @hide
+ */
+public final class TransitionHelper {
+
+    public static final int FADE_IN = 0x1;
+    public static final int FADE_OUT = 0x2;
+
+    public static final int SLIDE_LEFT = 0;
+    public static final int SLIDE_TOP = 1;
+    public static final int SLIDE_RIGHT = 2;
+    public static final int SLIDE_BOTTOM = 3;
+
+    private final static TransitionHelper sHelper = new TransitionHelper();
+    TransitionHelperVersionImpl mImpl;
+
+    /**
+     * Gets whether the system supports Transition animations.
+     *
+     * @return True if Transition animations are supported.
+     */
+    public static boolean systemSupportsTransitions() {
+        if (Build.VERSION.SDK_INT >= 19) {
+            // Supported on Android 4.4 or later.
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Interface implemented by classes that support Transition animations.
+     */
+    static interface TransitionHelperVersionImpl {
+
+        public Object getSharedElementEnterTransition(Window window);
+
+        public Object getSharedElementReturnTransition(Window window);
+
+        public Object getSharedElementExitTransition(Window window);
+
+        public Object getSharedElementReenterTransition(Window window);
+
+        public Object getEnterTransition(Window window);
+
+        public Object getReturnTransition(Window window);
+
+        public Object getExitTransition(Window window);
+
+        public Object getReenterTransition(Window window);
+
+        public Object createScene(ViewGroup sceneRoot, Runnable r);
+
+        public Object createAutoTransition();
+
+        public Object createSlide(SlideCallback callback);
+
+        public Object createFadeTransition(int fadingMode);
+
+        public Object createChangeBounds(boolean reparent);
+
+        public void setChangeBoundsStartDelay(Object changeBounds, View view, int startDelay);
+
+        public void setChangeBoundsStartDelay(Object changeBounds, int viewId, int startDelay);
+
+        public void setChangeBoundsStartDelay(Object changeBounds, String className,
+                int startDelay);
+
+        public void setChangeBoundsDefaultStartDelay(Object changeBounds, int startDelay);
+
+        public Object createTransitionSet(boolean sequential);
+
+        public void addTransition(Object transitionSet, Object transition);
+
+        public void setTransitionListener(Object transition, TransitionListener listener);
+
+        public void runTransition(Object scene, Object transition);
+
+        public void exclude(Object transition, int targetId, boolean exclude);
+
+        public void exclude(Object transition, View targetView, boolean exclude);
+
+        public void excludeChildren(Object transition, int targetId, boolean exclude);
+
+        public void excludeChildren(Object transition, View target, boolean exclude);
+
+        public void include(Object transition, int targetId);
+
+        public void include(Object transition, View targetView);
+
+        public void setStartDelay(Object transition, long startDelay);
+
+        public void setDuration(Object transition, long duration);
+
+        public void setInterpolator(Object transition, Object timeInterpolator);
+
+    }
+
+    /**
+     * Interface used when we do not support Transition animations.
+     */
+    private static final class TransitionHelperStubImpl implements TransitionHelperVersionImpl {
+
+        private static class TransitionStub {
+            TransitionListener mTransitionListener;
+        }
+
+        @Override
+        public Object getSharedElementEnterTransition(Window window) {
+            return null;
+        }
+
+        @Override
+        public Object getSharedElementReturnTransition(Window window) {
+            return null;
+        }
+
+        @Override
+        public Object getSharedElementExitTransition(Window window) {
+            return null;
+        }
+
+        @Override
+        public Object getSharedElementReenterTransition(Window window) {
+            return null;
+        }
+
+        @Override
+        public Object getEnterTransition(Window window) {
+            return null;
+        }
+
+        @Override
+        public Object getReturnTransition(Window window) {
+            return null;
+        }
+
+        @Override
+        public Object getExitTransition(Window window) {
+            return null;
+        }
+
+        @Override
+        public Object getReenterTransition(Window window) {
+            return null;
+        }
+
+        @Override
+        public Object createScene(ViewGroup sceneRoot, Runnable r) {
+            return r;
+        }
+
+        @Override
+        public Object createAutoTransition() {
+            return new TransitionStub();
+        }
+
+        @Override
+        public Object createFadeTransition(int fadingMode) {
+            return new TransitionStub();
+        }
+
+        @Override
+        public Object createChangeBounds(boolean reparent) {
+            return new TransitionStub();
+        }
+
+        @Override
+        public Object createSlide(SlideCallback callback) {
+            return new TransitionStub();
+        }
+
+        @Override
+        public void setChangeBoundsStartDelay(Object changeBounds, View view, int startDelay) {
+        }
+
+        @Override
+        public void setChangeBoundsStartDelay(Object changeBounds, int viewId, int startDelay) {
+        }
+
+        @Override
+        public void setChangeBoundsStartDelay(Object changeBounds, String className,
+                int startDelay) {
+        }
+
+        @Override
+        public void setChangeBoundsDefaultStartDelay(Object changeBounds, int startDelay) {
+        }
+
+        @Override
+        public Object createTransitionSet(boolean sequential) {
+            return new TransitionStub();
+        }
+
+        @Override
+        public void addTransition(Object transitionSet, Object transition) {
+        }
+
+        @Override
+        public void exclude(Object transition, int targetId, boolean exclude) {
+        }
+
+        @Override
+        public void exclude(Object transition, View targetView, boolean exclude) {
+        }
+
+        @Override
+        public void excludeChildren(Object transition, int targetId, boolean exclude) {
+        }
+
+        @Override
+        public void excludeChildren(Object transition, View targetView, boolean exclude) {
+        }
+
+        @Override
+        public void include(Object transition, int targetId) {
+        }
+
+        @Override
+        public void include(Object transition, View targetView) {
+        }
+
+        @Override
+        public void setStartDelay(Object transition, long startDelay) {
+        }
+
+        @Override
+        public void setDuration(Object transition, long duration) {
+        }
+
+        @Override
+        public void setTransitionListener(Object transition, TransitionListener listener) {
+            ((TransitionStub) transition).mTransitionListener = listener;
+        }
+
+        @Override
+        public void runTransition(Object scene, Object transition) {
+            TransitionStub transitionStub = (TransitionStub) transition;
+            if (transitionStub != null && transitionStub.mTransitionListener != null) {
+                transitionStub.mTransitionListener.onTransitionStart(transition);
+            }
+            Runnable r = ((Runnable) scene);
+            if (r != null) {
+                r.run();
+            }
+            if (transitionStub != null && transitionStub.mTransitionListener != null) {
+                transitionStub.mTransitionListener.onTransitionEnd(transition);
+            }
+        }
+
+        @Override
+        public void setInterpolator(Object transition, Object timeInterpolator) {
+        }
+    }
+
+    /**
+     * Implementation used on KitKat (and above).
+     */
+    private static class TransitionHelperKitkatImpl implements TransitionHelperVersionImpl {
+        private final TransitionHelperKitkat mTransitionHelper;
+
+        TransitionHelperKitkatImpl() {
+            mTransitionHelper = new TransitionHelperKitkat();
+        }
+
+        @Override
+        public Object getSharedElementEnterTransition(Window window) {
+            return null;
+        }
+
+        @Override
+        public Object getSharedElementReturnTransition(Window window) {
+            return null;
+        }
+
+        @Override
+        public Object getSharedElementExitTransition(Window window) {
+            return null;
+        }
+
+        @Override
+        public Object getSharedElementReenterTransition(Window window) {
+            return null;
+        }
+
+        @Override
+        public Object getEnterTransition(Window window) {
+            return null;
+        }
+
+        @Override
+        public Object getReturnTransition(Window window) {
+            return null;
+        }
+
+        @Override
+        public Object getExitTransition(Window window) {
+            return null;
+        }
+
+        @Override
+        public Object getReenterTransition(Window window) {
+            return null;
+        }
+
+        @Override
+        public Object createScene(ViewGroup sceneRoot, Runnable r) {
+            return mTransitionHelper.createScene(sceneRoot, r);
+        }
+
+        @Override
+        public Object createAutoTransition() {
+            return mTransitionHelper.createAutoTransition();
+        }
+
+        @Override
+        public Object createFadeTransition(int fadingMode) {
+            return mTransitionHelper.createFadeTransition(fadingMode);
+        }
+
+        @Override
+        public Object createChangeBounds(boolean reparent) {
+            return mTransitionHelper.createChangeBounds(reparent);
+        }
+
+        @Override
+        public Object createSlide(SlideCallback callback) {
+            return mTransitionHelper.createSlide(callback);
+        }
+
+        @Override
+        public void setChangeBoundsStartDelay(Object changeBounds, View view, int startDelay) {
+            mTransitionHelper.setChangeBoundsStartDelay(changeBounds, view, startDelay);
+        }
+
+        @Override
+        public void setChangeBoundsStartDelay(Object changeBounds, int viewId, int startDelay) {
+            mTransitionHelper.setChangeBoundsStartDelay(changeBounds, viewId, startDelay);
+        }
+
+        @Override
+        public void setChangeBoundsStartDelay(Object changeBounds, String className,
+                int startDelay) {
+            mTransitionHelper.setChangeBoundsStartDelay(changeBounds, className, startDelay);
+        }
+
+        @Override
+        public void setChangeBoundsDefaultStartDelay(Object changeBounds, int startDelay) {
+            mTransitionHelper.setChangeBoundsDefaultStartDelay(changeBounds, startDelay);
+        }
+
+        @Override
+        public Object createTransitionSet(boolean sequential) {
+            return mTransitionHelper.createTransitionSet(sequential);
+        }
+
+        @Override
+        public void addTransition(Object transitionSet, Object transition) {
+            mTransitionHelper.addTransition(transitionSet, transition);
+        }
+
+        @Override
+        public void exclude(Object transition, int targetId, boolean exclude) {
+            mTransitionHelper.exclude(transition, targetId, exclude);
+        }
+
+        @Override
+        public void exclude(Object transition, View targetView, boolean exclude) {
+            mTransitionHelper.exclude(transition, targetView, exclude);
+        }
+
+        @Override
+        public void excludeChildren(Object transition, int targetId, boolean exclude) {
+            mTransitionHelper.excludeChildren(transition, targetId, exclude);
+        }
+
+        @Override
+        public void excludeChildren(Object transition, View targetView, boolean exclude) {
+            mTransitionHelper.excludeChildren(transition, targetView, exclude);
+        }
+
+        @Override
+        public void include(Object transition, int targetId) {
+            mTransitionHelper.include(transition, targetId);
+        }
+
+        @Override
+        public void include(Object transition, View targetView) {
+            mTransitionHelper.include(transition, targetView);
+        }
+
+        @Override
+        public void setStartDelay(Object transition, long startDelay) {
+            mTransitionHelper.setStartDelay(transition, startDelay);
+        }
+
+        @Override
+        public void setDuration(Object transition, long duration) {
+            mTransitionHelper.setDuration(transition, duration);
+        }
+
+        @Override
+        public void setTransitionListener(Object transition, TransitionListener listener) {
+            mTransitionHelper.setTransitionListener(transition, listener);
+        }
+
+        @Override
+        public void runTransition(Object scene, Object transition) {
+            mTransitionHelper.runTransition(scene, transition);
+        }
+
+        @Override
+        public void setInterpolator(Object transition, Object timeInterpolator) {
+            mTransitionHelper.setInterpolator(transition, timeInterpolator);
+        }
+    }
+
+    private static final class TransitionHelperApi21Impl extends TransitionHelperKitkatImpl {
+        private final TransitionHelperApi21 mTransitionHelper;
+
+        TransitionHelperApi21Impl() {
+            mTransitionHelper = new TransitionHelperApi21();
+        }
+
+        @Override
+        public Object getSharedElementEnterTransition(Window window) {
+            return mTransitionHelper.getSharedElementEnterTransition(window);
+        }
+
+        @Override
+        public Object getSharedElementReturnTransition(Window window) {
+            return mTransitionHelper.getSharedElementReturnTransition(window);
+        }
+
+        @Override
+        public Object getSharedElementExitTransition(Window window) {
+            return mTransitionHelper.getSharedElementExitTransition(window);
+        }
+
+        @Override
+        public Object getSharedElementReenterTransition(Window window) {
+            return mTransitionHelper.getSharedElementReenterTransition(window);
+        }
+
+        @Override
+        public Object getEnterTransition(Window window) {
+            return mTransitionHelper.getEnterTransition(window);
+        }
+
+        @Override
+        public Object getReturnTransition(Window window) {
+            return mTransitionHelper.getReturnTransition(window);
+        }
+
+        @Override
+        public Object getExitTransition(Window window) {
+            return mTransitionHelper.getExitTransition(window);
+        }
+
+        @Override
+        public Object getReenterTransition(Window window) {
+            return mTransitionHelper.getReenterTransition(window);
+        }
+    }
+
+    /**
+     * Returns the TransitionHelper that can be used to perform Transition
+     * animations.
+     */
+    public static TransitionHelper getInstance() {
+        return sHelper;
+    }
+
+    private TransitionHelper() {
+        if ("L".equals(Build.VERSION.RELEASE)) {
+            mImpl = new TransitionHelperApi21Impl();
+        } else  if (systemSupportsTransitions()) {
+            mImpl = new TransitionHelperKitkatImpl();
+        } else {
+            mImpl = new TransitionHelperStubImpl();
+        }
+    }
+
+    public Object getSharedElementEnterTransition(Window window) {
+        return mImpl.getSharedElementEnterTransition(window);
+    }
+
+    public Object getSharedElementReturnTransition(Window window) {
+        return mImpl.getSharedElementReturnTransition(window);
+    }
+
+    public Object getSharedElementExitTransition(Window window) {
+        return mImpl.getSharedElementExitTransition(window);
+    }
+
+    public Object getSharedElementReenterTransition(Window window) {
+        return mImpl.getSharedElementReenterTransition(window);
+    }
+
+    public Object getEnterTransition(Window window) {
+        return mImpl.getEnterTransition(window);
+    }
+
+    public Object getReturnTransition(Window window) {
+        return mImpl.getReturnTransition(window);
+    }
+
+    public Object getExitTransition(Window window) {
+        return mImpl.getExitTransition(window);
+    }
+
+    public Object getReenterTransition(Window window) {
+        return mImpl.getReenterTransition(window);
+    }
+
+    public Object createScene(ViewGroup sceneRoot, Runnable r) {
+        return mImpl.createScene(sceneRoot, r);
+    }
+
+    public Object createChangeBounds(boolean reparent) {
+        return mImpl.createChangeBounds(reparent);
+    }
+
+    public void setChangeBoundsStartDelay(Object changeBounds, View view, int startDelay) {
+        mImpl.setChangeBoundsStartDelay(changeBounds, view, startDelay);
+    }
+
+    public void setChangeBoundsStartDelay(Object changeBounds, int viewId, int startDelay) {
+        mImpl.setChangeBoundsStartDelay(changeBounds, viewId, startDelay);
+    }
+
+    public void setChangeBoundsStartDelay(Object changeBounds, String className, int startDelay) {
+        mImpl.setChangeBoundsStartDelay(changeBounds, className, startDelay);
+    }
+
+    public void setChangeBoundsDefaultStartDelay(Object changeBounds, int startDelay) {
+        mImpl.setChangeBoundsDefaultStartDelay(changeBounds, startDelay);
+    }
+
+    public Object createTransitionSet(boolean sequential) {
+        return mImpl.createTransitionSet(sequential);
+    }
+
+    public Object createSlide(SlideCallback callback) {
+        return mImpl.createSlide(callback);
+    }
+
+    public void addTransition(Object transitionSet, Object transition) {
+        mImpl.addTransition(transitionSet, transition);
+    }
+
+    public void exclude(Object transition, int targetId, boolean exclude) {
+        mImpl.exclude(transition, targetId, exclude);
+    }
+
+    public void exclude(Object transition, View targetView, boolean exclude) {
+        mImpl.exclude(transition, targetView, exclude);
+    }
+
+    public void excludeChildren(Object transition, int targetId, boolean exclude) {
+        mImpl.excludeChildren(transition, targetId, exclude);
+    }
+
+    public void excludeChildren(Object transition, View targetView, boolean exclude) {
+        mImpl.excludeChildren(transition, targetView, exclude);
+    }
+
+    public void include(Object transition, int targetId) {
+        mImpl.include(transition, targetId);
+    }
+
+    public void include(Object transition, View targetView) {
+        mImpl.include(transition, targetView);
+    }
+
+    public void setStartDelay(Object transition, long startDelay) {
+        mImpl.setStartDelay(transition, startDelay);
+    }
+
+    public void setDuration(Object transition, long duration) {
+        mImpl.setDuration(transition, duration);
+    }
+
+    public Object createAutoTransition() {
+        return mImpl.createAutoTransition();
+    }
+
+    public Object createFadeTransition(int fadeMode) {
+        return mImpl.createFadeTransition(fadeMode);
+    }
+
+    public void setTransitionListener(Object transition, TransitionListener listener) {
+        mImpl.setTransitionListener(transition, listener);
+    }
+
+    public void runTransition(Object scene, Object transition) {
+        mImpl.runTransition(scene, transition);
+    }
+
+    public void setInterpolator(Object transition, Object timeInterpolator) {
+        mImpl.setInterpolator(transition, timeInterpolator);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter.java
new file mode 100644
index 0000000..1fd6ea2
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/AbstractDetailsDescriptionPresenter.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetricsInt;
+import android.support.v17.leanback.R;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/**
+ * An abstract {@link Presenter} for rendering a detailed description of an
+ * item. Typically this Presenter will be used in a DetailsOveriewRowPresenter.
+ *
+ * <p>Subclasses will override {@link #onBindDescription} to implement the data
+ * binding for this Presenter.
+ */
+public abstract class AbstractDetailsDescriptionPresenter extends Presenter {
+
+    public static class ViewHolder extends Presenter.ViewHolder {
+        private final TextView mTitle;
+        private final TextView mSubtitle;
+        private final TextView mBody;
+        private final int mTitleMargin;
+        private final int mUnderTitleBaselineMargin;
+        private final int mUnderSubtitleBaselineMargin;
+        private final int mTitleLineSpacing;
+        private final int mBodyLineSpacing;
+        private final int mBodyMaxLines;
+        private final int mBodyMinLines;
+        private final FontMetricsInt mTitleFontMetricsInt;
+        private final FontMetricsInt mSubtitleFontMetricsInt;
+        private final FontMetricsInt mBodyFontMetricsInt;
+
+        public ViewHolder(View view) {
+            super(view);
+            mTitle = (TextView) view.findViewById(R.id.lb_details_description_title);
+            mSubtitle = (TextView) view.findViewById(R.id.lb_details_description_subtitle);
+            mBody = (TextView) view.findViewById(R.id.lb_details_description_body);
+
+            FontMetricsInt titleFontMetricsInt = getFontMetricsInt(mTitle);
+            final int titleAscent = view.getResources().getDimensionPixelSize(
+                    R.dimen.lb_details_description_title_baseline);
+            // Ascent is negative
+            mTitleMargin = titleAscent + titleFontMetricsInt.ascent;
+
+            mUnderTitleBaselineMargin = view.getResources().getDimensionPixelSize(
+                    R.dimen.lb_details_description_under_title_baseline_margin);
+            mUnderSubtitleBaselineMargin = view.getResources().getDimensionPixelSize(
+                    R.dimen.lb_details_description_under_subtitle_baseline_margin);
+
+            mTitleLineSpacing = view.getResources().getDimensionPixelSize(
+                    R.dimen.lb_details_description_title_line_spacing);
+            mBodyLineSpacing = view.getResources().getDimensionPixelSize(
+                    R.dimen.lb_details_description_body_line_spacing);
+
+            mBodyMaxLines = view.getResources().getInteger(
+                    R.integer.lb_details_description_body_max_lines);
+            mBodyMinLines = view.getResources().getInteger(
+                    R.integer.lb_details_description_body_min_lines);
+
+            mTitleFontMetricsInt = getFontMetricsInt(mTitle);
+            mSubtitleFontMetricsInt = getFontMetricsInt(mSubtitle);
+            mBodyFontMetricsInt = getFontMetricsInt(mBody);
+
+            mTitle.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+                @Override
+                public void onLayoutChange(View v, int left, int top, int right,
+                        int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                    mBody.setMaxLines(mTitle.getLineCount() > 1 ? mBodyMinLines : mBodyMaxLines);
+                }
+            });
+        }
+
+        public TextView getTitle() {
+            return mTitle;
+        }
+
+        public TextView getSubtitle() {
+            return mSubtitle;
+        }
+
+        public TextView getBody() {
+            return mBody;
+        }
+
+        private FontMetricsInt getFontMetricsInt(TextView textView) {
+            Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            paint.setTextSize(textView.getTextSize());
+            paint.setTypeface(textView.getTypeface());
+            return paint.getFontMetricsInt();
+        }
+    }
+
+    @Override
+    public final ViewHolder onCreateViewHolder(ViewGroup parent) {
+        View v = LayoutInflater.from(parent.getContext())
+            .inflate(R.layout.lb_details_description, parent, false);
+        return new ViewHolder(v);
+    }
+
+    @Override
+    public final void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+        ViewHolder vh = (ViewHolder) viewHolder;
+        onBindDescription(vh, item);
+
+        boolean hasTitle = true;
+        if (TextUtils.isEmpty(vh.mTitle.getText())) {
+            vh.mTitle.setVisibility(View.GONE);
+            hasTitle = false;
+        } else {
+            vh.mTitle.setVisibility(View.VISIBLE);
+            vh.mTitle.setLineSpacing(vh.mTitleLineSpacing - vh.mTitle.getLineHeight() +
+                    vh.mTitle.getLineSpacingExtra(), vh.mTitle.getLineSpacingMultiplier());
+        }
+        setTopMargin(vh.mTitle, vh.mTitleMargin);
+
+        boolean hasSubtitle = true;
+        if (TextUtils.isEmpty(vh.mSubtitle.getText())) {
+            vh.mSubtitle.setVisibility(View.GONE);
+            hasSubtitle = false;
+        } else {
+            vh.mSubtitle.setVisibility(View.VISIBLE);
+            if (hasTitle) {
+                setTopMargin(vh.mSubtitle, vh.mUnderTitleBaselineMargin +
+                        vh.mSubtitleFontMetricsInt.ascent - vh.mTitleFontMetricsInt.descent);
+            } else {
+                setTopMargin(vh.mSubtitle, 0);
+            }
+        }
+
+        if (TextUtils.isEmpty(vh.mBody.getText())) {
+            vh.mBody.setVisibility(View.GONE);
+        } else {
+            vh.mBody.setVisibility(View.VISIBLE);
+            vh.mBody.setLineSpacing(vh.mBodyLineSpacing - vh.mBody.getLineHeight() +
+                    vh.mBody.getLineSpacingExtra(), vh.mBody.getLineSpacingMultiplier());
+
+            if (hasSubtitle) {
+                setTopMargin(vh.mBody, vh.mUnderSubtitleBaselineMargin +
+                        vh.mBodyFontMetricsInt.ascent - vh.mSubtitleFontMetricsInt.descent);
+            } else if (hasTitle) {
+                setTopMargin(vh.mBody, vh.mUnderTitleBaselineMargin +
+                        vh.mBodyFontMetricsInt.ascent - vh.mTitleFontMetricsInt.descent);
+            } else {
+                setTopMargin(vh.mBody, 0);
+            }
+        }
+    }
+
+    /**
+     * Binds the data from the item referenced in the DetailsOverviewRow to the
+     * ViewHolder.
+     *
+     * @param vh The ViewHolder for this details description view.
+     * @param item The item from the DetailsOverviewRow being presented.
+     */
+    protected abstract void onBindDescription(ViewHolder vh, Object item);
+
+    @Override
+    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {}
+
+    private void setTopMargin(TextView textView, int topMargin) {
+        ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) textView.getLayoutParams();
+        lp.topMargin = topMargin;
+        textView.setLayoutParams(lp);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Action.java b/v17/leanback/src/android/support/v17/leanback/widget/Action.java
new file mode 100644
index 0000000..deb36c4
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/Action.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+
+import static android.support.v17.leanback.widget.ObjectAdapter.NO_ID;
+
+/**
+ * An action that can be shown on a details page. It contains one or two lines
+ * of text and an optional image.
+ */
+public class Action {
+
+    private long mId = NO_ID;
+    private Drawable mIcon;
+    private CharSequence mLabel1;
+    private CharSequence mLabel2;
+
+    /**
+     * Constructor for an Action.
+     *
+     * @param id The id of the Action.
+     */
+    public Action(long id) {
+        this(id, "");
+    }
+
+    /**
+     * Constructor for an Action.
+     *
+     * @param id The id of the Action.
+     * @param label The label to display for the Action.
+     */
+    public Action(long id, CharSequence label) {
+        this(id, label, null);
+    }
+
+    /**
+     * Constructor for an Action.
+     *
+     * @param id The id of the Action.
+     * @param label1 The label to display on the first line of the Action.
+     * @param label2 The label to display on the second line of the Action.
+     */
+    public Action(long id, CharSequence label1, CharSequence label2) {
+        this(id, label1, label2, null);
+    }
+
+    /**
+     * Constructor for an Action.
+     *
+     * @param id The id of the Action.
+     * @param label1 The label to display on the first line of the Action.
+     * @param label2 The label to display on the second line of the Action.
+     * @param icon The icon to display for the Action.
+     */
+    public Action(long id, CharSequence label1, CharSequence label2, Drawable icon) {
+        setId(id);
+        setLabel1(label1);
+        setLabel2(label2);
+        setIcon(icon);
+    }
+
+    /**
+     * Set id for this Action.
+     */
+    public final void setId(long id) {
+        mId = id;
+    }
+
+    /**
+     * Returns the id for this Action.
+     */
+    public final long getId() {
+        return mId;
+    }
+
+    /**
+     * Set the first line label for this Action.
+     */
+    public final void setLabel1(CharSequence label) {
+        mLabel1 = label;
+    }
+
+    /**
+     * Returns the first line label for this Action.
+     */
+    public final CharSequence getLabel1() {
+        return mLabel1;
+    }
+
+    /**
+     * Set the second line label for this Action.
+     */
+    public final void setLabel2(CharSequence label) {
+        mLabel2 = label;
+    }
+
+    /**
+     * Returns the second line label for this Action.
+     */
+    public final CharSequence getLabel2() {
+        return mLabel2;
+    }
+
+    /**
+     * Set the icon drawable for this Action.
+     */
+    public final void setIcon(Drawable icon) {
+        mIcon = icon;
+    }
+
+    /**
+     * Returns the icon drawable for this Action.
+     */
+    public final Drawable getIcon() {
+        return mIcon;
+    }
+
+    @Override
+    public String toString(){
+        StringBuilder sb = new StringBuilder();
+        if (!TextUtils.isEmpty(mLabel1)) {
+            sb.append(mLabel1);
+        }
+        if (!TextUtils.isEmpty(mLabel2)) {
+            if (!TextUtils.isEmpty(mLabel1)) {
+                sb.append(" ");
+            }
+            sb.append(mLabel2);
+        }
+        if (mIcon != null && sb.length() == 0) {
+            sb.append("(action icon)");
+        }
+        return sb.toString();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java b/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java
new file mode 100644
index 0000000..bd87e14
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ActionPresenterSelector.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.support.v17.leanback.R;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+class ActionPresenterSelector extends PresenterSelector {
+
+    private final Presenter mOneLineActionPresenter = new OneLineActionPresenter();
+    private final Presenter mTwoLineActionPresenter = new TwoLineActionPresenter();
+    private OnActionClickedListener mOnActionClickedListener;
+
+    @Override
+    public Presenter getPresenter(Object item) {
+        Action action = (Action) item;
+        if (TextUtils.isEmpty(action.getLabel2())) {
+            return mOneLineActionPresenter;
+        } else {
+            return mTwoLineActionPresenter;
+        }
+    }
+
+    public final void setOnActionClickedListener(OnActionClickedListener listener) {
+        mOnActionClickedListener = listener;
+    }
+
+    public final OnActionClickedListener getOnActionClickedListener() {
+        return mOnActionClickedListener;
+    }
+
+    static class ActionViewHolder extends Presenter.ViewHolder {
+        Action mAction;
+        Button mButton;
+
+        public ActionViewHolder(View view) {
+            super(view);
+            mButton = (Button) view.findViewById(R.id.lb_action_button);
+        }
+    }
+
+    class OneLineActionPresenter extends Presenter {
+        @Override
+        public ViewHolder onCreateViewHolder(ViewGroup parent) {
+            View v = LayoutInflater.from(parent.getContext())
+                .inflate(R.layout.lb_action_1_line, parent, false);
+            final ActionViewHolder vh = new ActionViewHolder(v);
+            v.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        if (ActionPresenterSelector.this.mOnActionClickedListener != null &&
+                            vh.mAction != null) {
+                            ActionPresenterSelector.this.mOnActionClickedListener.onActionClicked(vh.mAction);
+                        }
+                    }
+            });
+            return vh;
+        }
+
+        @Override
+        public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+            Action action = (Action) item;
+            ActionViewHolder vh = (ActionViewHolder) viewHolder;
+            vh.mAction = action;
+            vh.mButton.setText(action.getLabel1());
+        }
+
+        @Override
+        public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+            ((ActionViewHolder) viewHolder).mAction = null;
+        }
+    }
+
+    class TwoLineActionPresenter extends Presenter {
+        @Override
+        public ViewHolder onCreateViewHolder(ViewGroup parent) {
+            View v = LayoutInflater.from(parent.getContext())
+                .inflate(R.layout.lb_action_2_lines, parent, false);
+            final ActionViewHolder vh = new ActionViewHolder(v);
+            v.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        if (ActionPresenterSelector.this.mOnActionClickedListener != null &&
+                            vh.mAction != null) {
+                            ActionPresenterSelector.this.mOnActionClickedListener.onActionClicked(vh.mAction);
+                        }
+                    }
+            });
+            return vh;
+        }
+
+        @Override
+        public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+            Action action = (Action) item;
+            ActionViewHolder vh = (ActionViewHolder) viewHolder;
+            vh.mAction = action;
+
+            if (action.getIcon() != null) {
+                final int leftPadding = vh.view.getResources()
+                        .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_left);
+                final int rightPadding = vh.view.getResources()
+                        .getDimensionPixelSize(R.dimen.lb_action_with_icon_padding_right);
+                vh.view.setPadding(leftPadding, 0, rightPadding, 0);
+            } else {
+                final int padding = vh.view.getResources()
+                        .getDimensionPixelSize(R.dimen.lb_action_padding_horizontal);
+                vh.view.setPadding(padding, 0, padding, 0);
+            }
+            vh.mButton.setCompoundDrawablesWithIntrinsicBounds(action.getIcon(), null, null, null);
+
+            CharSequence line1 = action.getLabel1();
+            CharSequence line2 = action.getLabel2();
+            if (TextUtils.isEmpty(line1)) {
+                vh.mButton.setText(line2);
+            } else if (TextUtils.isEmpty(line2)) {
+                vh.mButton.setText(line1);
+            } else {
+                vh.mButton.setText(line1 + "\n" + line2);
+            }
+        }
+
+        @Override
+        public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+            ActionViewHolder vh = (ActionViewHolder) viewHolder;
+            vh.mButton.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+            vh.view.setPadding(0, 0, 0, 0);
+            vh.mAction = null;
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
new file mode 100644
index 0000000..6c4ee28
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ArrayObjectAdapter.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * An ObjectAdapter implemented with an {@link ArrayList}.
+ */
+public class ArrayObjectAdapter extends ObjectAdapter {
+
+    private ArrayList<Object> mItems = new ArrayList<Object>();
+
+    /**
+     * Construct an adapter with the given {@link PresenterSelector}.
+     */
+    public ArrayObjectAdapter(PresenterSelector presenterSelector) {
+        super(presenterSelector);
+    }
+
+    /**
+     * Construct an adapter that uses the given {@link Presenter} for all items.
+     */
+    public ArrayObjectAdapter(Presenter presenter) {
+        super(presenter);
+    }
+
+    /**
+     * Construct an adapter.
+     */
+    public ArrayObjectAdapter() {
+        super();
+    }
+
+    @Override
+    public int size() {
+        return mItems.size();
+    }
+
+    @Override
+    public Object get(int index) {
+        return mItems.get(index);
+    }
+
+    /**
+     * Returns the index for the first occurrence of item in the adapter, or -1 if
+     * not found.
+     *
+     * @param item  The item to find in the list.
+     * @return Index of the first occurrence of the item in the adapter, or -1
+     *         if not found.
+     */
+    public int indexOf(Object item) {
+        return mItems.indexOf(item);
+    }
+
+    /**
+     * Notify that the content of a range of items changed. Note that this is
+     * not same as items being added or removed.
+     *
+     * @param positionStart The position of first item that has changed.
+     * @param itemCount The count of how many items have changed.
+     */
+    public void notifyArrayItemRangeChanged(int positionStart, int itemCount) {
+        notifyItemRangeChanged(positionStart, itemCount);
+    }
+
+    /**
+     * Adds an item to the end of the adapter.
+     *
+     * @param item The item to add to the end of the adapter.
+     */
+    public void add(Object item) {
+        add(mItems.size(), item);
+    }
+
+    /**
+     * Inserts an item into this adapter at the specified index.
+     *
+     * @param index The index at which the item should be inserted.
+     * @param item The item to insert into the adapter.
+     */
+    public void add(int index, Object item) {
+        mItems.add(index, item);
+        notifyItemRangeInserted(index, 1);
+    }
+
+    /**
+     * Adds the objects in the given collection to the adapter, starting at the
+     * given index.
+     *
+     * @param index The index at which the items should be inserted.
+     * @param items A {@link Collection} of items to insert.
+     */
+    public void addAll(int index, Collection items) {
+        int itemsCount = items.size();
+        mItems.addAll(index, items);
+        notifyItemRangeInserted(index, itemsCount);
+    }
+
+    /**
+     * Removes the first occurrence of the given item from the adapter.
+     *
+     * @param item The item to remove from the adapter.
+     * @return True if the item was found and thus removed from the adapter.
+     */
+    public boolean remove(Object item) {
+        int index = mItems.indexOf(item);
+        if (index >= 0) {
+            mItems.remove(index);
+            notifyItemRangeRemoved(index, 1);
+        }
+        return index >= 0;
+    }
+
+    /**
+     * Removes a range of items from the adapter. The range is specified by giving
+     * the starting position and the number of elements to remove.
+     *
+     * @param position The index of the first item to remove.
+     * @param count The number of items to remove.
+     * @return The number of items removed.
+     */
+    public int removeItems(int position, int count) {
+        int itemsToRemove = Math.min(count, mItems.size() - position);
+
+        for (int i = 0; i < itemsToRemove; i++) {
+            mItems.remove(position);
+        }
+        notifyItemRangeRemoved(position, itemsToRemove);
+        return itemsToRemove;
+    }
+
+    /**
+     * Removes all items from this adapter, leaving it empty.
+     */
+    public void clear() {
+        int itemCount = mItems.size();
+        mItems.clear();
+        notifyItemRangeRemoved(0, itemCount);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java
new file mode 100644
index 0000000..4d0b540
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java
@@ -0,0 +1,918 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.v17.leanback.R;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Transformation;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+
+/**
+ * A card style layout that responds to certain state changes. It arranges its
+ * children in a vertical column, with different regions becoming visible at
+ * different times.
+ *
+ * <p>
+ * A BaseCardView will draw its children based on its type, the region
+ * visibilities of the child types, and the state of the widget. A child may be
+ * marked as belonging to one of three regions: main, info, or extra. The main
+ * region is always visible, while the info and extra regions can be set to
+ * display based on the activated or selected state of the View. The card states
+ * are set by calling {@link #setActivated(boolean) setActivated} and
+ * {@link #setSelected(boolean) setSelected}.
+ * <p>
+ * See {@link BaseCardView.LayoutParams} for layout attributes.
+ * </p>
+ */
+public class BaseCardView extends FrameLayout {
+    private static final String TAG = "BaseCardView";
+    private static final boolean DEBUG = false;
+
+    /**
+     * A simple card type with a single layout area. This card type does not
+     * change its layout or size as it transitions between
+     * Activated/Not-Activated or Selected/Unselected states.
+     *
+     * @see #getCardType()
+     */
+    public static final int CARD_TYPE_MAIN_ONLY = 0;
+
+    /**
+     * A Card type with 2 layout areas: A main area which is always visible, and
+     * an info area that fades in over the main area when it is visible.
+     * The card height will not change.
+     *
+     * @see #getCardType()
+     */
+    public static final int CARD_TYPE_INFO_OVER = 1;
+
+    /**
+     * A Card type with 2 layout areas: A main area which is always visible, and
+     * an info area that appears below the main area. When the info area is visible
+     * the total card height will change.
+     *
+     * @see #getCardType()
+     */
+    public static final int CARD_TYPE_INFO_UNDER = 2;
+
+    /**
+     * A Card type with 3 layout areas: A main area which is always visible; an
+     * info area which will appear below the main area, and an extra area that
+     * only appears after a short delay. The info area appears below the main
+     * area, causing the total card height to change. The extra area animates in
+     * at the bottom of the card, shifting up the info view without affecting
+     * the card height.
+     *
+     * @see #getCardType()
+     */
+    public static final int CARD_TYPE_INFO_UNDER_WITH_EXTRA = 3;
+
+    /**
+     * Indicates that a card region is always visible.
+     */
+    public static final int CARD_REGION_VISIBLE_ALWAYS = 0;
+
+    /**
+     * Indicates that a card region is visible when the card is activated.
+     */
+    public static final int CARD_REGION_VISIBLE_ACTIVATED = 1;
+
+    /**
+     * Indicates that a card region is visible when the card is selected.
+     */
+    public static final int CARD_REGION_VISIBLE_SELECTED = 2;
+
+    private static final int CARD_TYPE_INVALID = 4;
+
+    private int mCardType;
+    private int mInfoVisibility;
+    private int mExtraVisibility;
+
+    private ArrayList<View> mMainViewList;
+    private ArrayList<View> mInfoViewList;
+    private ArrayList<View> mExtraViewList;
+
+    private int mMeasuredWidth;
+    private int mMeasuredHeight;
+    private boolean mDelaySelectedAnim;
+    private int mSelectedAnimationDelay;
+    private final int mActivatedAnimDuration;
+    private final int mSelectedAnimDuration;
+
+    private float mInfoOffset;
+    private float mInfoVisFraction;
+    private float mInfoAlpha = 1.0f;
+    private Animation mAnim;
+
+    private final static int[] LB_PRESSED_STATE_SET = new int[]{
+        android.R.attr.state_pressed};
+
+    private final Runnable mAnimationTrigger = new Runnable() {
+        @Override
+        public void run() {
+            animateInfoOffset(true);
+        }
+    };
+
+    public BaseCardView(Context context) {
+        this(context, null);
+    }
+
+    public BaseCardView(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.baseCardViewStyle);
+    }
+
+    public BaseCardView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseCardView, defStyle, 0);
+
+        try {
+            mCardType = a.getInteger(R.styleable.lbBaseCardView_cardType, CARD_TYPE_MAIN_ONLY);
+            mInfoVisibility = a.getInteger(R.styleable.lbBaseCardView_infoVisibility,
+                    CARD_REGION_VISIBLE_ACTIVATED);
+            mExtraVisibility = a.getInteger(R.styleable.lbBaseCardView_extraVisibility,
+                    CARD_REGION_VISIBLE_SELECTED);
+            // Extra region should never show before info region.
+            if (mExtraVisibility < mInfoVisibility) {
+                mExtraVisibility = mInfoVisibility;
+            }
+
+            mSelectedAnimationDelay = a.getInteger(
+                    R.styleable.lbBaseCardView_selectedAnimationDelay,
+                    getResources().getInteger(R.integer.lb_card_selected_animation_delay));
+
+            mSelectedAnimDuration = a.getInteger(
+                    R.styleable.lbBaseCardView_selectedAnimationDuration,
+                    getResources().getInteger(R.integer.lb_card_selected_animation_duration));
+
+            mActivatedAnimDuration =
+                    a.getInteger(R.styleable.lbBaseCardView_activatedAnimationDuration,
+                    getResources().getInteger(R.integer.lb_card_activated_animation_duration));
+        } finally {
+            a.recycle();
+        }
+
+        mDelaySelectedAnim = true;
+
+        mMainViewList = new ArrayList<View>();
+        mInfoViewList = new ArrayList<View>();
+        mExtraViewList = new ArrayList<View>();
+
+        mInfoOffset = 0.0f;
+        mInfoVisFraction = 0.0f;
+    }
+
+    /**
+     * Sets a flag indicating if the Selected animation (if the selected card
+     * type implements one) should run immediately after the card is selected,
+     * or if it should be delayed. The default behavior is to delay this
+     * animation. This is a one-shot override. If set to false, after the card
+     * is selected and the selected animation is triggered, this flag is
+     * automatically reset to true. This is useful when you want to change the
+     * default behavior, and have the selected animation run immediately. One
+     * such case could be when focus moves from one row to the other, when
+     * instead of delaying the selected animation until the user pauses on a
+     * card, it may be desirable to trigger the animation for that card
+     * immediately.
+     *
+     * @param delay True (default) if the selected animation should be delayed
+     *            after the card is selected, or false if the animation should
+     *            run immediately the next time the card is Selected.
+     */
+    public void setSelectedAnimationDelayed(boolean delay) {
+        mDelaySelectedAnim = delay;
+    }
+
+    /**
+     * Returns a boolean indicating if the selected animation will run
+     * immediately or be delayed the next time the card is Selected.
+     *
+     * @return true if this card is set to delay the selected animation the next
+     *         time it is selected, or false if the selected animation will run
+     *         immediately the next time the card is selected.
+     */
+    public boolean isSelectedAnimationDelayed() {
+        return mDelaySelectedAnim;
+    }
+
+    /**
+     * Sets the type of this Card.
+     *
+     * @param type The desired card type.
+     */
+    public void setCardType(int type) {
+        if (mCardType != type) {
+            if (type >= CARD_TYPE_MAIN_ONLY && type < CARD_TYPE_INVALID) {
+                // Valid card type
+                mCardType = type;
+            } else {
+                Log.e(TAG, "Invalid card type specified: " + type +
+                        ". Defaulting to type CARD_TYPE_MAIN_ONLY.");
+                mCardType = CARD_TYPE_MAIN_ONLY;
+            }
+            requestLayout();
+        }
+    }
+
+    /**
+     * Returns the type of this Card.
+     *
+     * @return The type of this card.
+     */
+    public int getCardType() {
+        return mCardType;
+    }
+
+    public void setInfoVisibility(int visibility) {
+        if (mInfoVisibility != visibility) {
+            mInfoVisibility = visibility;
+            if (mInfoVisibility == CARD_REGION_VISIBLE_SELECTED && isSelected()) {
+                mInfoVisFraction = 1.0f;
+            } else {
+                mInfoVisFraction = 0.0f;
+            }
+            requestLayout();
+        }
+    }
+
+    public int getInfoVisibility() {
+        return mInfoVisibility;
+    }
+
+    public void setExtraVisibility(int visibility) {
+        if (mExtraVisibility != visibility) {
+            mExtraVisibility = visibility;
+            requestLayout();
+        }
+    }
+
+    public int getExtraVisibility() {
+        return mExtraVisibility;
+    }
+
+    /**
+     * Sets the Activated state of this Card. This can trigger changes in the
+     * card layout, resulting in views to become visible or hidden. A card is
+     * normally set to Activated state when its parent container (like a Row)
+     * receives focus, and then activates all of its children.
+     *
+     * @param activated True if the card is ACTIVE, or false if INACTIVE.
+     * @see #isActivated()
+     */
+    @Override
+    public void setActivated(boolean activated) {
+        if (activated != isActivated()) {
+            super.setActivated(activated);
+            applyActiveState(isActivated());
+        }
+    }
+
+    /**
+     * Sets the Selected state of this Card. This can trigger changes in the
+     * card layout, resulting in views to become visible or hidden. A card is
+     * normally set to Selected state when it receives input focus.
+     *
+     * @param selected True if the card is Selected, or false otherwise.
+     * @see #isSelected()
+     */
+    @Override
+    public void setSelected(boolean selected) {
+        if (selected != isSelected()) {
+            super.setSelected(selected);
+            applySelectedState(isSelected());
+        }
+    }
+
+    @Override
+    public boolean shouldDelayChildPressedState() {
+        return false;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        mMeasuredWidth = 0;
+        mMeasuredHeight = 0;
+        int state = 0;
+        int mainHeight = 0;
+        int infoHeight = 0;
+        int extraHeight = 0;
+
+        findChildrenViews();
+
+        final int unspecifiedSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        // MAIN is always present
+        for (int i = 0; i < mMainViewList.size(); i++) {
+            View mainView = mMainViewList.get(i);
+            if (mainView.getVisibility() != View.GONE) {
+                measureChild(mainView, unspecifiedSpec, unspecifiedSpec);
+                mMeasuredWidth = Math.max(mMeasuredWidth, mainView.getMeasuredWidth());
+                mainHeight += mainView.getMeasuredHeight();
+                state = View.combineMeasuredStates(state, mainView.getMeasuredState());
+            }
+        }
+        setPivotX(mMeasuredWidth / 2);
+        setPivotY(mainHeight / 2);
+
+
+        // The MAIN area determines the card width
+        int cardWidthMeasureSpec = MeasureSpec.makeMeasureSpec(mMeasuredWidth, MeasureSpec.EXACTLY);
+
+        if (hasInfoRegion()) {
+            for (int i = 0; i < mInfoViewList.size(); i++) {
+                View infoView = mInfoViewList.get(i);
+                if (infoView.getVisibility() != View.GONE) {
+                    measureChild(infoView, cardWidthMeasureSpec, unspecifiedSpec);
+                    if (mCardType != CARD_TYPE_INFO_OVER) {
+                        infoHeight += infoView.getMeasuredHeight();
+                    }
+                    state = View.combineMeasuredStates(state, infoView.getMeasuredState());
+                }
+            }
+
+            if (hasExtraRegion()) {
+                for (int i = 0; i < mExtraViewList.size(); i++) {
+                    View extraView = mExtraViewList.get(i);
+                    if (extraView.getVisibility() != View.GONE) {
+                        measureChild(extraView, cardWidthMeasureSpec, unspecifiedSpec);
+                        extraHeight += extraView.getMeasuredHeight();
+                        state = View.combineMeasuredStates(state, extraView.getMeasuredState());
+                    }
+                }
+            }
+        }
+
+        boolean infoAnimating = hasInfoRegion() && mInfoVisibility == CARD_REGION_VISIBLE_SELECTED;
+        mMeasuredHeight = (int) (mainHeight +
+                (infoAnimating ? (infoHeight * mInfoVisFraction) : infoHeight)
+                + extraHeight - (infoAnimating ? 0 : mInfoOffset));
+
+        // Report our final dimensions.
+        setMeasuredDimension(View.resolveSizeAndState(mMeasuredWidth + getPaddingLeft() +
+                getPaddingRight(), widthMeasureSpec, state),
+                View.resolveSizeAndState(mMeasuredHeight + getPaddingTop() + getPaddingBottom(),
+                        heightMeasureSpec, state << View.MEASURED_HEIGHT_STATE_SHIFT));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        float currBottom = getPaddingTop();
+
+        // MAIN is always present
+        for (int i = 0; i < mMainViewList.size(); i++) {
+            View mainView = mMainViewList.get(i);
+            if (mainView.getVisibility() != View.GONE) {
+                mainView.layout(getPaddingLeft(),
+                        (int) currBottom,
+                                mMeasuredWidth + getPaddingLeft(),
+                        (int) (currBottom + mainView.getMeasuredHeight()));
+                currBottom += mainView.getMeasuredHeight();
+            }
+        }
+
+        if (hasInfoRegion()) {
+            float infoHeight = 0f;
+            for (int i = 0; i < mInfoViewList.size(); i++) {
+                infoHeight += mInfoViewList.get(i).getMeasuredHeight();
+            }
+
+            if (mCardType == CARD_TYPE_INFO_OVER) {
+                // retract currBottom to overlap the info views on top of main
+                currBottom -= infoHeight;
+                if (currBottom < 0) {
+                    currBottom = 0;
+                }
+            } else if (mCardType == CARD_TYPE_INFO_UNDER) {
+                if (mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) {
+                    infoHeight = infoHeight * mInfoVisFraction;
+                }
+            } else {
+                currBottom -= mInfoOffset;
+            }
+
+            for (int i = 0; i < mInfoViewList.size(); i++) {
+                View infoView = mInfoViewList.get(i);
+                if (infoView.getVisibility() != View.GONE) {
+                    int viewHeight = infoView.getMeasuredHeight();
+                    if (viewHeight > infoHeight) {
+                        viewHeight = (int) infoHeight;
+                    }
+                    infoView.layout(getPaddingLeft(),
+                            (int) currBottom,
+                                    mMeasuredWidth + getPaddingLeft(),
+                            (int) (currBottom + viewHeight));
+                    currBottom += viewHeight;
+                    infoHeight -= viewHeight;
+                    if (infoHeight <= 0) {
+                        break;
+                    }
+                }
+            }
+
+            if (hasExtraRegion()) {
+                for (int i = 0; i < mExtraViewList.size(); i++) {
+                    View extraView = mExtraViewList.get(i);
+                    if (extraView.getVisibility() != View.GONE) {
+                        extraView.layout(getPaddingLeft(),
+                                (int) currBottom,
+                                        mMeasuredWidth + getPaddingLeft(),
+                                (int) (currBottom + extraView.getMeasuredHeight()));
+                        currBottom += extraView.getMeasuredHeight();
+                    }
+                }
+            }
+        }
+        // Force update drawable bounds.
+        onSizeChanged(0, 0, right - left, bottom - top);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        removeCallbacks(mAnimationTrigger);
+        cancelAnimations();
+        mInfoOffset = 0.0f;
+        mInfoVisFraction = 0.0f;
+    }
+
+    private boolean hasInfoRegion() {
+        return mCardType != CARD_TYPE_MAIN_ONLY;
+    }
+
+    private boolean hasExtraRegion() {
+        return mCardType == CARD_TYPE_INFO_UNDER_WITH_EXTRA;
+    }
+
+    private boolean isRegionVisible(int regionVisibility) {
+        switch (regionVisibility) {
+            case CARD_REGION_VISIBLE_ALWAYS:
+                return true;
+            case CARD_REGION_VISIBLE_ACTIVATED:
+                return isActivated();
+            case CARD_REGION_VISIBLE_SELECTED:
+                return isActivated() && isSelected();
+            default:
+                if (DEBUG) Log.e(TAG, "invalid region visibility state: " + regionVisibility);
+                return false;
+        }
+    }
+
+    private void findChildrenViews() {
+        mMainViewList.clear();
+        mInfoViewList.clear();
+        mExtraViewList.clear();
+
+        final int count = getChildCount();
+
+        boolean infoVisible = isRegionVisible(mInfoVisibility);
+        boolean extraVisible = hasExtraRegion() && mInfoOffset > 0f;
+
+        if (mCardType == CARD_TYPE_INFO_UNDER && mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) {
+            infoVisible = infoVisible && mInfoVisFraction > 0f;
+        }
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+
+            if (child == null) {
+                continue;
+            }
+
+            BaseCardView.LayoutParams lp = (BaseCardView.LayoutParams) child
+                    .getLayoutParams();
+            if (lp.viewType == LayoutParams.VIEW_TYPE_INFO) {
+                mInfoViewList.add(child);
+                child.setVisibility(infoVisible ? View.VISIBLE : View.GONE);
+            } else if (lp.viewType == LayoutParams.VIEW_TYPE_EXTRA) {
+                mExtraViewList.add(child);
+                child.setVisibility(extraVisible ? View.VISIBLE : View.GONE);
+            } else {
+                // Default to MAIN
+                mMainViewList.add(child);
+                child.setVisibility(View.VISIBLE);
+            }
+        }
+
+    }
+
+    @Override
+    protected int[] onCreateDrawableState(int extraSpace) {
+        // filter out focus states,  since leanback does not fade foreground on focus.
+        final int[] s = super.onCreateDrawableState(extraSpace);
+        final int N = s.length;
+        boolean pressed = false;
+        boolean enabled = false;
+        for (int i = 0; i < N; i++) {
+            if (s[i] == android.R.attr.state_pressed) {
+                pressed = true;
+            }
+            if (s[i] == android.R.attr.state_enabled) {
+                enabled = true;
+            }
+        }
+        if (pressed && enabled) {
+            return View.PRESSED_ENABLED_STATE_SET;
+        } else if (pressed) {
+            return LB_PRESSED_STATE_SET;
+        } else if (enabled) {
+            return View.ENABLED_STATE_SET;
+        } else {
+            return View.EMPTY_STATE_SET;
+        }
+    }
+
+    private void applyActiveState(boolean active) {
+        if (hasInfoRegion() && mInfoVisibility <= CARD_REGION_VISIBLE_ACTIVATED) {
+            setInfoViewVisibility(active);
+        }
+        if (hasExtraRegion() && mExtraVisibility <= CARD_REGION_VISIBLE_ACTIVATED) {
+            //setExtraVisibility(active);
+        }
+    }
+
+    private void setInfoViewVisibility(boolean visible) {
+        if (mCardType == CARD_TYPE_INFO_UNDER_WITH_EXTRA) {
+            // Active state changes for card type
+            // CARD_TYPE_INFO_UNDER_WITH_EXTRA
+            if (visible) {
+                for (int i = 0; i < mInfoViewList.size(); i++) {
+                    mInfoViewList.get(i).setVisibility(View.VISIBLE);
+                }
+            } else {
+                for (int i = 0; i < mInfoViewList.size(); i++) {
+                    mInfoViewList.get(i).setVisibility(View.GONE);
+                }
+                for (int i = 0; i < mExtraViewList.size(); i++) {
+                    mExtraViewList.get(i).setVisibility(View.GONE);
+                }
+                mInfoOffset = 0.0f;
+            }
+        } else if (mCardType == CARD_TYPE_INFO_UNDER) {
+            // Active state changes for card type CARD_TYPE_INFO_UNDER
+            if (mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) {
+                animateInfoHeight(visible);
+            } else {
+                for (int i = 0; i < mInfoViewList.size(); i++) {
+                    mInfoViewList.get(i).setVisibility(visible ? View.VISIBLE : View.GONE);
+                }
+            }
+        } else if (mCardType == CARD_TYPE_INFO_OVER) {
+            // Active state changes for card type CARD_TYPE_INFO_OVER
+            animateInfoAlpha(visible);
+        }
+    }
+
+    private void applySelectedState(boolean focused) {
+        removeCallbacks(mAnimationTrigger);
+
+        if (mCardType == CARD_TYPE_INFO_UNDER_WITH_EXTRA) {
+            // Focus changes for card type CARD_TYPE_INFO_UNDER_WITH_EXTRA
+            if (focused) {
+                if (!mDelaySelectedAnim) {
+                    post(mAnimationTrigger);
+                    mDelaySelectedAnim = true;
+                } else {
+                    postDelayed(mAnimationTrigger, mSelectedAnimationDelay);
+                }
+            } else {
+                animateInfoOffset(false);
+            }
+        } else if (mInfoVisibility == CARD_REGION_VISIBLE_SELECTED) {
+            setInfoViewVisibility(focused);
+        }
+    }
+
+    private void cancelAnimations() {
+        if (mAnim != null) {
+            mAnim.cancel();
+            mAnim = null;
+        }
+    }
+
+    // This animation changes the Y offset of the info and extra views,
+    // so that they animate UP to make the extra info area visible when a
+    // card is selected.
+    private void animateInfoOffset(boolean shown) {
+        cancelAnimations();
+
+        int extraHeight = 0;
+        if (shown) {
+            int widthSpec = MeasureSpec.makeMeasureSpec(mMeasuredWidth, MeasureSpec.EXACTLY);
+            int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+            for (int i = 0; i < mExtraViewList.size(); i++) {
+                View extraView = mExtraViewList.get(i);
+                extraView.setVisibility(View.VISIBLE);
+                extraView.measure(widthSpec, heightSpec);
+                extraHeight = Math.max(extraHeight, extraView.getMeasuredHeight());
+            }
+        }
+
+        mAnim = new InfoOffsetAnimation(mInfoOffset, shown ? extraHeight : 0);
+        mAnim.setDuration(mSelectedAnimDuration);
+        mAnim.setInterpolator(new AccelerateDecelerateInterpolator());
+        mAnim.setAnimationListener(new Animation.AnimationListener() {
+            @Override
+            public void onAnimationStart(Animation animation) {
+            }
+
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                if (mInfoOffset == 0f) {
+                    for (int i = 0; i < mExtraViewList.size(); i++) {
+                        mExtraViewList.get(i).setVisibility(View.GONE);
+                    }
+                }
+            }
+
+                @Override
+            public void onAnimationRepeat(Animation animation) {
+            }
+
+        });
+        startAnimation(mAnim);
+    }
+
+    // This animation changes the visible height of the info views,
+    // so that they animate in and out of view.
+    private void animateInfoHeight(boolean shown) {
+        cancelAnimations();
+
+        int extraHeight = 0;
+        if (shown) {
+            int widthSpec = MeasureSpec.makeMeasureSpec(mMeasuredWidth, MeasureSpec.EXACTLY);
+            int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+            for (int i = 0; i < mExtraViewList.size(); i++) {
+                View extraView = mExtraViewList.get(i);
+                extraView.setVisibility(View.VISIBLE);
+                extraView.measure(widthSpec, heightSpec);
+                extraHeight = Math.max(extraHeight, extraView.getMeasuredHeight());
+            }
+        }
+
+        mAnim = new InfoHeightAnimation(mInfoVisFraction, shown ? 1.0f : 0f);
+        mAnim.setDuration(mSelectedAnimDuration);
+        mAnim.setInterpolator(new AccelerateDecelerateInterpolator());
+        mAnim.setAnimationListener(new Animation.AnimationListener() {
+                @Override
+            public void onAnimationStart(Animation animation) {
+            }
+
+                @Override
+            public void onAnimationEnd(Animation animation) {
+                if (mInfoOffset == 0f) {
+                    for (int i = 0; i < mExtraViewList.size(); i++) {
+                        mExtraViewList.get(i).setVisibility(View.GONE);
+                    }
+                }
+            }
+
+            @Override
+            public void onAnimationRepeat(Animation animation) {
+            }
+
+        });
+        startAnimation(mAnim);
+    }
+
+    // This animation changes the alpha of the info views, so they animate in
+    // and out. It's meant to be used when the info views are overlaid on top of
+    // the main view area. It gets triggered by a change in the Active state of
+    // the card.
+    private void animateInfoAlpha(boolean shown) {
+        cancelAnimations();
+
+        if (shown) {
+            for (int i = 0; i < mInfoViewList.size(); i++) {
+                mInfoViewList.get(i).setVisibility(View.VISIBLE);
+            }
+        }
+
+        mAnim = new InfoAlphaAnimation(mInfoAlpha, shown ? 1.0f : 0.0f);
+        mAnim.setDuration(mActivatedAnimDuration);
+        mAnim.setInterpolator(new DecelerateInterpolator());
+        mAnim.setAnimationListener(new Animation.AnimationListener() {
+            @Override
+            public void onAnimationStart(Animation animation) {
+            }
+
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                if (mInfoAlpha == 0.0) {
+                    for (int i = 0; i < mInfoViewList.size(); i++) {
+                        mInfoViewList.get(i).setVisibility(View.GONE);
+                    }
+                }
+            }
+
+            @Override
+            public void onAnimationRepeat(Animation animation) {
+            }
+
+        });
+        startAnimation(mAnim);
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new BaseCardView.LayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        return new BaseCardView.LayoutParams(
+                LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+        if (lp instanceof LayoutParams) {
+            return new LayoutParams((LayoutParams) lp);
+        } else {
+            return new LayoutParams(lp);
+        }
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof BaseCardView.LayoutParams;
+    }
+
+    /**
+     * Per-child layout information associated with BaseCardView.
+     */
+    public static class LayoutParams extends FrameLayout.LayoutParams {
+        public static final int VIEW_TYPE_MAIN = 0;
+        public static final int VIEW_TYPE_INFO = 1;
+        public static final int VIEW_TYPE_EXTRA = 2;
+
+        /**
+         * Card component type for the view associated with these LayoutParams.
+         */
+        @ViewDebug.ExportedProperty(category = "layout", mapping = {
+                @ViewDebug.IntToString(from = VIEW_TYPE_MAIN, to = "MAIN"),
+                @ViewDebug.IntToString(from = VIEW_TYPE_INFO, to = "INFO"),
+                @ViewDebug.IntToString(from = VIEW_TYPE_EXTRA, to = "EXTRA")
+        })
+        public int viewType = VIEW_TYPE_MAIN;
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.lbBaseCardView_Layout);
+
+            viewType = a.getInt(
+                    R.styleable.lbBaseCardView_Layout_layout_viewType, VIEW_TYPE_MAIN);
+
+            a.recycle();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(ViewGroup.LayoutParams p) {
+            super(p);
+        }
+
+        /**
+         * Copy constructor. Clones the width, height, and View Type of the
+         * source.
+         *
+         * @param source The layout params to copy from.
+         */
+        public LayoutParams(LayoutParams source) {
+            super(source);
+
+            this.viewType = source.viewType;
+        }
+    }
+
+    // Helper animation class used in the animation of the info and extra
+    // fields vertically within the card
+    private class InfoOffsetAnimation extends Animation {
+        private float mStartValue;
+        private float mDelta;
+
+        public InfoOffsetAnimation(float start, float end) {
+            mStartValue = start;
+            mDelta = end - start;
+        }
+
+        @Override
+        protected void applyTransformation(float interpolatedTime, Transformation t) {
+            mInfoOffset = mStartValue + (interpolatedTime * mDelta);
+            requestLayout();
+        }
+    }
+
+    // Helper animation class used in the animation of the visible height
+    // for the info fields.
+    private class InfoHeightAnimation extends Animation {
+        private float mStartValue;
+        private float mDelta;
+
+        public InfoHeightAnimation(float start, float end) {
+            mStartValue = start;
+            mDelta = end - start;
+        }
+
+        @Override
+        protected void applyTransformation(float interpolatedTime, Transformation t) {
+            mInfoVisFraction = mStartValue + (interpolatedTime * mDelta);
+            requestLayout();
+        }
+    }
+
+    // Helper animation class used to animate the alpha for the info views
+    // when they are fading in or out of view.
+    private class InfoAlphaAnimation extends Animation {
+        private float mStartValue;
+        private float mDelta;
+
+        public InfoAlphaAnimation(float start, float end) {
+            mStartValue = start;
+            mDelta = end - start;
+        }
+
+        @Override
+        protected void applyTransformation(float interpolatedTime, Transformation t) {
+            mInfoAlpha = mStartValue + (interpolatedTime * mDelta);
+            for (int i = 0; i < mInfoViewList.size(); i++) {
+                mInfoViewList.get(i).setAlpha(mInfoAlpha);
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        if (DEBUG) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(this.getClass().getSimpleName()).append(" : ");
+            sb.append("cardType=");
+            switch(mCardType) {
+                case CARD_TYPE_MAIN_ONLY:
+                    sb.append("MAIN_ONLY");
+                    break;
+                case CARD_TYPE_INFO_OVER:
+                    sb.append("INFO_OVER");
+                    break;
+                case CARD_TYPE_INFO_UNDER:
+                    sb.append("INFO_UNDER");
+                    break;
+                case CARD_TYPE_INFO_UNDER_WITH_EXTRA:
+                    sb.append("INFO_UNDER_WITH_EXTRA");
+                    break;
+                default:
+                    sb.append("INVALID");
+                    break;
+            }
+            sb.append(" : ");
+            sb.append(mMainViewList.size()).append(" main views, ");
+            sb.append(mInfoViewList.size()).append(" info views, ");
+            sb.append(mExtraViewList.size()).append(" extra views : ");
+            sb.append("infoVisibility=").append(mInfoVisibility).append(" ");
+            sb.append("extraVisibility=").append(mExtraVisibility).append(" ");
+            sb.append("isActivated=").append(isActivated());
+            sb.append(" : ");
+            sb.append("isSelected=").append(isSelected());
+            return sb.toString();
+        } else {
+            return super.toString();
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
new file mode 100644
index 0000000..3fdf87c
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
@@ -0,0 +1,602 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.support.v17.leanback.R;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * Base class for vertically and horizontally scrolling lists. The items come
+ * from the {@link RecyclerView.Adapter} associated with this view.
+ * @hide
+ */
+abstract class BaseGridView extends RecyclerView {
+
+    /**
+     * Always keep focused item at a aligned position.  Developer can use
+     * WINDOW_ALIGN_XXX and ITEM_ALIGN_XXX to define how focused item is aligned.
+     * In this mode, the last focused position will be remembered and restored when focus
+     * is back to the view.
+     */
+    public final static int FOCUS_SCROLL_ALIGNED = 0;
+
+    /**
+     * Scroll to make the focused item inside client area.
+     */
+    public final static int FOCUS_SCROLL_ITEM = 1;
+
+    /**
+     * Scroll a page of items when focusing to item outside the client area.
+     * The page size matches the client area size of RecyclerView.
+     */
+    public final static int FOCUS_SCROLL_PAGE = 2;
+
+    /**
+     * The first item is aligned with the low edge of the viewport. When
+     * navigating away from the first item, the focus maintains a middle
+     * location.
+     * <p>
+     * The middle location is calculated by "windowAlignOffset" and
+     * "windowAlignOffsetPercent"; if neither of these two is defined, the
+     * default value is 1/2 of the size.
+     */
+    public final static int WINDOW_ALIGN_LOW_EDGE = 1;
+
+    /**
+     * The last item is aligned with the high edge of the viewport when
+     * navigating to the end of list. When navigating away from the end, the
+     * focus maintains a middle location.
+     * <p>
+     * The middle location is calculated by "windowAlignOffset" and
+     * "windowAlignOffsetPercent"; if neither of these two is defined, the
+     * default value is 1/2 of the size.
+     */
+    public final static int WINDOW_ALIGN_HIGH_EDGE = 1 << 1;
+
+    /**
+     * The first item and last item are aligned with the two edges of the
+     * viewport. When navigating in the middle of list, the focus maintains a
+     * middle location.
+     * <p>
+     * The middle location is calculated by "windowAlignOffset" and
+     * "windowAlignOffsetPercent"; if neither of these two is defined, the
+     * default value is 1/2 of the size.
+     */
+    public final static int WINDOW_ALIGN_BOTH_EDGE =
+            WINDOW_ALIGN_LOW_EDGE | WINDOW_ALIGN_HIGH_EDGE;
+
+    /**
+     * The focused item always stays in a middle location.
+     * <p>
+     * The middle location is calculated by "windowAlignOffset" and
+     * "windowAlignOffsetPercent"; if neither of these two is defined, the
+     * default value is 1/2 of the size.
+     */
+    public final static int WINDOW_ALIGN_NO_EDGE = 0;
+
+    /**
+     * Value indicates that percent is not used.
+     */
+    public final static float WINDOW_ALIGN_OFFSET_PERCENT_DISABLED = -1;
+
+    /**
+     * Value indicates that percent is not used.
+     */
+    public final static float ITEM_ALIGN_OFFSET_PERCENT_DISABLED = -1;
+
+    /**
+     * Listener for intercepting touch dispatch events.
+     */
+    public interface OnTouchInterceptListener {
+        /**
+         * Returns true if the touch dispatch event should be consumed.
+         */
+        public boolean onInterceptTouchEvent(MotionEvent event);
+    }
+
+    /**
+     * Listener for intercepting generic motion dispatch events.
+     */
+    public interface OnMotionInterceptListener {
+        /**
+         * Returns true if the touch dispatch event should be consumed.
+         */
+        public boolean onInterceptMotionEvent(MotionEvent event);
+    }
+
+    /**
+     * Listener for intercepting key dispatch events.
+     */
+    public interface OnKeyInterceptListener {
+        /**
+         * Returns true if the key dispatch event should be consumed.
+         */
+        public boolean onInterceptKeyEvent(KeyEvent event);
+    }
+
+    protected final GridLayoutManager mLayoutManager;
+
+    /**
+     * Animate layout changes from a child resizing or adding/removing a child.
+     */
+    private boolean mAnimateChildLayout = true;
+
+    private RecyclerView.ItemAnimator mSavedItemAnimator;
+
+    private OnTouchInterceptListener mOnTouchInterceptListener;
+    private OnMotionInterceptListener mOnMotionInterceptListener;
+    private OnKeyInterceptListener mOnKeyInterceptListener;
+
+    public BaseGridView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mLayoutManager = new GridLayoutManager(this);
+        setLayoutManager(mLayoutManager);
+        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
+        setHasFixedSize(true);
+        setChildrenDrawingOrderEnabled(true);
+    }
+
+    protected void initBaseGridViewAttributes(Context context, AttributeSet attrs) {
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseGridView);
+        boolean throughFront = a.getBoolean(R.styleable.lbBaseGridView_focusOutFront, false);
+        boolean throughEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutEnd, false);
+        mLayoutManager.setFocusOutAllowed(throughFront, throughEnd);
+        mLayoutManager.setVerticalMargin(
+                a.getDimensionPixelSize(R.styleable.lbBaseGridView_verticalMargin, 0));
+        mLayoutManager.setHorizontalMargin(
+                a.getDimensionPixelSize(R.styleable.lbBaseGridView_horizontalMargin, 0));
+        if (a.hasValue(R.styleable.lbBaseGridView_android_gravity)) {
+            setGravity(a.getInt(R.styleable.lbBaseGridView_android_gravity, Gravity.NO_GRAVITY));
+        }
+        a.recycle();
+    }
+
+    /**
+     * Set the strategy used to scroll in response to item focus changing:
+     * <ul>
+     * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li>
+     * <li>{@link #FOCUS_SCROLL_ITEM}</li>
+     * <li>{@link #FOCUS_SCROLL_PAGE}</li>
+     * </ul>
+     */
+    public void setFocusScrollStrategy(int scrollStrategy) {
+        if (scrollStrategy != FOCUS_SCROLL_ALIGNED && scrollStrategy != FOCUS_SCROLL_ITEM
+            && scrollStrategy != FOCUS_SCROLL_PAGE) {
+            throw new IllegalArgumentException("Invalid scrollStrategy");
+        }
+        mLayoutManager.setFocusScrollStrategy(scrollStrategy);
+        requestLayout();
+    }
+
+    /**
+     * Returns the strategy used to scroll in response to item focus changing.
+     * <ul>
+     * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li>
+     * <li>{@link #FOCUS_SCROLL_ITEM}</li>
+     * <li>{@link #FOCUS_SCROLL_PAGE}</li>
+     * </ul>
+     */
+    public int getFocusScrollStrategy() {
+        return mLayoutManager.getFocusScrollStrategy();
+    }
+
+    /**
+     * Set how the focused item gets aligned in the view.
+     *
+     * @param windowAlignment {@link #WINDOW_ALIGN_BOTH_EDGE},
+     *        {@link #WINDOW_ALIGN_LOW_EDGE}, {@link #WINDOW_ALIGN_HIGH_EDGE} or
+     *        {@link #WINDOW_ALIGN_NO_EDGE}.
+     */
+    public void setWindowAlignment(int windowAlignment) {
+        mLayoutManager.setWindowAlignment(windowAlignment);
+        requestLayout();
+    }
+
+    /**
+     * Get how the focused item gets aligned in the view.
+     *
+     * @return {@link #WINDOW_ALIGN_BOTH_EDGE}, {@link #WINDOW_ALIGN_LOW_EDGE},
+     *         {@link #WINDOW_ALIGN_HIGH_EDGE} or {@link #WINDOW_ALIGN_NO_EDGE}.
+     */
+    public int getWindowAlignment() {
+        return mLayoutManager.getWindowAlignment();
+    }
+
+    /**
+     * Set the absolute offset in pixels for window alignment.
+     *
+     * @param offset The number of pixels to offset. Can be negative for
+     *        alignment from the high edge, or positive for alignment from the
+     *        low edge.
+     */
+    public void setWindowAlignmentOffset(int offset) {
+        mLayoutManager.setWindowAlignmentOffset(offset);
+        requestLayout();
+    }
+
+    /**
+     * Get the absolute offset in pixels for window alignment.
+     *
+     * @return The number of pixels to offset. Will be negative for alignment
+     *         from the high edge, or positive for alignment from the low edge.
+     *         Default value is 0.
+     */
+    public int getWindowAlignmentOffset() {
+        return mLayoutManager.getWindowAlignmentOffset();
+    }
+
+    /**
+     * Set offset percent for window alignment in addition to {@link
+     * #getWindowAlignmentOffset()}.
+     *
+     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
+     *        width from low edge. Use
+     *        {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
+     */
+    public void setWindowAlignmentOffsetPercent(float offsetPercent) {
+        mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent);
+        requestLayout();
+    }
+
+    /**
+     * Get offset percent for window alignment in addition to
+     * {@link #getWindowAlignmentOffset()}.
+     *
+     * @return Percentage to offset. E.g., 40 means 40% of the width from the 
+     *         low edge, or {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} if
+     *         disabled. Default value is 50.
+     */
+    public float getWindowAlignmentOffsetPercent() {
+        return mLayoutManager.getWindowAlignmentOffsetPercent();
+    }
+
+    /**
+     * Set the absolute offset in pixels for item alignment.
+     *
+     * @param offset The number of pixels to offset. Can be negative for
+     *        alignment from the high edge, or positive for alignment from the
+     *        low edge.
+     */
+    public void setItemAlignmentOffset(int offset) {
+        mLayoutManager.setItemAlignmentOffset(offset);
+        requestLayout();
+    }
+
+    /**
+     * Get the absolute offset in pixels for item alignment.
+     *
+     * @return The number of pixels to offset. Will be negative for alignment
+     *         from the high edge, or positive for alignment from the low edge.
+     *         Default value is 0.
+     */
+    public int getItemAlignmentOffset() {
+        return mLayoutManager.getItemAlignmentOffset();
+    }
+
+    /**
+     * Set to true if include padding in calculating item align offset.
+     *
+     * @param withPadding When it is true: we include left/top padding for positive
+     *          item offset, include right/bottom padding for negative item offset.
+     */
+    public void setItemAlignmentOffsetWithPadding(boolean withPadding) {
+        mLayoutManager.setItemAlignmentOffsetWithPadding(withPadding);
+        requestLayout();
+    }
+
+    /**
+     * Returns true if include padding in calculating item align offset.
+     */
+    public boolean isItemAlignmentOffsetWithPadding() {
+        return mLayoutManager.isItemAlignmentOffsetWithPadding();
+    }
+
+    /**
+     * Set offset percent for item alignment in addition to {@link
+     * #getItemAlignmentOffset()}.
+     *
+     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
+     *        width from the low edge. Use
+     *        {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
+     */
+    public void setItemAlignmentOffsetPercent(float offsetPercent) {
+        mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent);
+        requestLayout();
+    }
+
+    /**
+     * Get offset percent for item alignment in addition to {@link
+     * #getItemAlignmentOffset()}.
+     *
+     * @return Percentage to offset. E.g., 40 means 40% of the width from the
+     *         low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if
+     *         disabled. Default value is 50.
+     */
+    public float getItemAlignmentOffsetPercent() {
+        return mLayoutManager.getItemAlignmentOffsetPercent();
+    }
+
+    /**
+     * Set the id of the view to align with. Use zero (default) for the item
+     * view itself.
+     */
+    public void setItemAlignmentViewId(int viewId) {
+        mLayoutManager.setItemAlignmentViewId(viewId);
+    }
+
+    /**
+     * Get the id of the view to align with, or zero for the item view itself.
+     */
+    public int getItemAlignmentViewId() {
+        return mLayoutManager.getItemAlignmentViewId();
+    }
+
+    /**
+     * Set the margin in pixels between two child items.
+     */
+    public void setItemMargin(int margin) {
+        mLayoutManager.setItemMargin(margin);
+        requestLayout();
+    }
+
+    /**
+     * Set the margin in pixels between two child items vertically.
+     */
+    public void setVerticalMargin(int margin) {
+        mLayoutManager.setVerticalMargin(margin);
+        requestLayout();
+    }
+
+    /**
+     * Get the margin in pixels between two child items vertically.
+     */
+    public int getVerticalMargin() {
+        return mLayoutManager.getVerticalMargin();
+    }
+
+    /**
+     * Set the margin in pixels between two child items horizontally.
+     */
+    public void setHorizontalMargin(int margin) {
+        mLayoutManager.setHorizontalMargin(margin);
+        requestLayout();
+    }
+
+    /**
+     * Get the margin in pixels between two child items horizontally.
+     */
+    public int getHorizontalMargin() {
+        return mLayoutManager.getHorizontalMargin();
+    }
+
+    /**
+     * Register a callback to be invoked when an item in BaseGridView has
+     * been selected.
+     */
+    public void setOnChildSelectedListener(OnChildSelectedListener listener) {
+        mLayoutManager.setOnChildSelectedListener(listener);
+    }
+
+    /**
+     * Change the selected item immediately without animation.
+     */
+    public void setSelectedPosition(int position) {
+        mLayoutManager.setSelection(this, position);
+    }
+
+    /**
+     * Change the selected item and run an animation to scroll to the target
+     * position.
+     */
+    public void setSelectedPositionSmooth(int position) {
+        mLayoutManager.setSelectionSmooth(this, position);
+    }
+
+    /**
+     * Get the selected item position.
+     */
+    public int getSelectedPosition() {
+        return mLayoutManager.getSelection();
+    }
+
+    /**
+     * Set if an animation should run when a child changes size or when adding
+     * or removing a child.
+     * <p><i>Unstable API, might change later.</i>
+     */
+    public void setAnimateChildLayout(boolean animateChildLayout) {
+        if (mAnimateChildLayout != animateChildLayout) {
+            mAnimateChildLayout = animateChildLayout;
+            if (!mAnimateChildLayout) {
+                mSavedItemAnimator = getItemAnimator();
+                super.setItemAnimator(null);
+            } else {
+                super.setItemAnimator(mSavedItemAnimator);
+            }
+        }
+    }
+
+    /**
+     * Return true if an animation will run when a child changes size or when
+     * adding or removing a child.
+     * <p><i>Unstable API, might change later.</i>
+     */
+    public boolean isChildLayoutAnimated() {
+        return mAnimateChildLayout;
+    }
+
+    /**
+     * Describes how the child views are positioned. Defaults to
+     * GRAVITY_TOP|GRAVITY_LEFT.
+     *
+     * @param gravity See {@link android.view.Gravity}
+     */
+    public void setGravity(int gravity) {
+        mLayoutManager.setGravity(gravity);
+        requestLayout();
+    }
+
+    @Override
+    public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+        return mLayoutManager.gridOnRequestFocusInDescendants(this, direction,
+                previouslyFocusedRect);
+    }
+
+    /**
+     * Get the x/y offsets to final position from current position if the view
+     * is selected.
+     *
+     * @param view The view to get offsets.
+     * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of
+     *        Y.
+     */
+    public void getViewSelectedOffsets(View view, int[] offsets) {
+        mLayoutManager.getViewSelectedOffsets(view, offsets);
+    }
+
+    @Override
+    public int getChildDrawingOrder(int childCount, int i) {
+        return mLayoutManager.getChildDrawingOrder(this, childCount, i);
+    }
+
+    final boolean isChildrenDrawingOrderEnabledInternal() {
+        return isChildrenDrawingOrderEnabled();
+    }
+
+    /**
+     * Disable or enable focus search.
+     */
+    public final void setFocusSearchDisabled(boolean disabled) {
+        mLayoutManager.setFocusSearchDisabled(disabled);
+    }
+
+    /**
+     * Return true if focus search is disabled.
+     */
+    public final boolean isFocusSearchDisabled() {
+        return mLayoutManager.isFocusSearchDisabled();
+    }
+
+    /**
+     * Enable or disable layout.  All children will be removed when layout is
+     * disabled.
+     */
+    public void setLayoutEnabled(boolean layoutEnabled) {
+        mLayoutManager.setLayoutEnabled(layoutEnabled);
+    }
+
+    /**
+     * Enable or disable pruning child.  Disable is useful during transition.
+     */
+    public void setPruneChild(boolean pruneChild) {
+        mLayoutManager.setPruneChild(pruneChild);
+    }
+
+    /**
+     * Enable or disable scrolling.  Disable is useful during transition.
+     */
+    public void setScrollEnabled(boolean scrollEnabled) {
+        mLayoutManager.setScrollEnabled(scrollEnabled);
+    }
+
+    /**
+     * Returns true if scrolling is enabled.
+     */
+    public boolean isScrollEnabled() {
+        return mLayoutManager.isScrollEnabled();
+    }
+
+    /**
+     * Returns true if the view at the given position has a same row sibling
+     * in front of it.
+     *
+     * @param position Position in adapter.
+     */
+    public boolean hasPreviousViewInSameRow(int position) {
+        return mLayoutManager.hasPreviousViewInSameRow(position);
+    }
+
+    /**
+     * Enable or disable the default "focus draw at last" order rule.
+     */
+    public void setFocusDrawingOrderEnabled(boolean enabled) {
+        super.setChildrenDrawingOrderEnabled(enabled);
+    }
+
+    /**
+     * Returns true if default "focus draw at last" order rule is enabled.
+     */
+    public boolean isFocusDrawingOrderEnabled() {
+        return super.isChildrenDrawingOrderEnabled();
+    }
+
+    /**
+     * Sets the touch intercept listener.
+     */
+    public void setOnTouchInterceptListener(OnTouchInterceptListener listener) {
+        mOnTouchInterceptListener = listener;
+    }
+
+    /**
+     * Sets the generic motion intercept listener.
+     */
+    public void setOnMotionInterceptListener(OnMotionInterceptListener listener) {
+        mOnMotionInterceptListener = listener;
+    }
+
+    /**
+     * Sets the key intercept listener.
+     */
+    public void setOnKeyInterceptListener(OnKeyInterceptListener listener) {
+        mOnKeyInterceptListener = listener;
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (mOnKeyInterceptListener != null) {
+            if (mOnKeyInterceptListener.onInterceptKeyEvent(event)) {
+                return true;
+            }
+        }
+        return super.dispatchKeyEvent(event);
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        if (mOnTouchInterceptListener != null) {
+            if (mOnTouchInterceptListener.onInterceptTouchEvent(event)) {
+                return true;
+            }
+        }
+        return super.dispatchTouchEvent(event);
+    }
+
+    @Override
+    public boolean dispatchGenericFocusedEvent(MotionEvent event) {
+        if (mOnMotionInterceptListener != null) {
+            if (mOnMotionInterceptListener.onInterceptMotionEvent(event)) {
+                return true;
+            }
+        }
+        return super.dispatchGenericFocusedEvent(event);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ClassPresenterSelector.java b/v17/leanback/src/android/support/v17/leanback/widget/ClassPresenterSelector.java
new file mode 100644
index 0000000..64d2270
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ClassPresenterSelector.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import java.util.HashMap;
+
+/**
+ * A ClassPresenterSelector selects a {@link Presenter} based on the item's
+ * Java class.
+ */
+public final class ClassPresenterSelector extends PresenterSelector {
+
+    private final HashMap<Class<?>, Presenter> mClassMap = new HashMap<Class<?>, Presenter>();
+
+    public void addClassPresenter(Class<?> cls, Presenter presenter) {
+        mClassMap.put(cls, presenter);
+    }
+
+    @Override
+    public Presenter getPresenter(Object item) {
+        Class<?> cls = item.getClass();
+        Presenter presenter = null;
+
+        do {
+            presenter = mClassMap.get(cls);
+            cls = cls.getSuperclass();
+        } while (presenter == null && cls != null);
+
+        return presenter;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ControlBar.java b/v17/leanback/src/android/support/v17/leanback/widget/ControlBar.java
new file mode 100644
index 0000000..7146371
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ControlBar.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+
+class ControlBar extends LinearLayout {
+
+    public interface OnChildFocusedListener {
+        public void onChildFocusedListener(View child, View focused);
+    }
+
+    private int mChildMarginFromCenter;
+    private OnChildFocusedListener mOnChildFocusedListener;
+
+    public ControlBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public ControlBar(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+        if (getChildCount() > 0) {
+            if (getChildAt(getChildCount() / 2).requestFocus(direction, previouslyFocusedRect)) {
+                return true;
+            }
+        }
+        return super.requestFocus(direction, previouslyFocusedRect);
+    }
+
+    public void setOnChildFocusedListener(OnChildFocusedListener listener) {
+        mOnChildFocusedListener = listener;
+    }
+
+    public void setChildMarginFromCenter(int marginFromCenter) {
+        mChildMarginFromCenter = marginFromCenter;
+    }
+
+    @Override
+    public void requestChildFocus (View child, View focused) {
+        super.requestChildFocus(child, focused);
+        if (mOnChildFocusedListener != null) {
+            mOnChildFocusedListener.onChildFocusedListener(child, focused);
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        if (mChildMarginFromCenter <= 0) {
+            return;
+        }
+
+        int totalExtraMargin = 0;
+        for (int i = 0; i < getChildCount() - 1; i++) {
+            View first = getChildAt(i);
+            View second = getChildAt(i+1);
+            int measuredWidth = first.getMeasuredWidth() + second.getMeasuredWidth();
+            int marginStart = mChildMarginFromCenter - measuredWidth / 2;
+            LayoutParams lp = (LayoutParams) second.getLayoutParams();
+            int extraMargin = marginStart - lp.getMarginStart();
+            lp.setMarginStart(marginStart);
+            second.setLayoutParams(lp);
+            totalExtraMargin += extraMargin;
+        }
+        setMeasuredDimension(getMeasuredWidth() + totalExtraMargin, getMeasuredHeight());
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ControlBarPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/ControlBarPresenter.java
new file mode 100644
index 0000000..46e0e00
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ControlBarPresenter.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.support.v17.leanback.R;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+/**
+ * A presenter that assumes a LinearLayout container for a series
+ * of control buttons backed by objects of type {@link Action}.
+ *
+ * Different layouts may be passed to the presenter constructor.
+ * The layout must contain a view with id control_bar.
+ */
+class ControlBarPresenter extends Presenter {
+
+    private static final int MAX_CONTROLS = 7;
+
+    /**
+     * The data type expected by this presenter.
+     */
+    static class BoundData {
+        /**
+         * Adapter containing objects of type {@link Action}.
+         */
+        ObjectAdapter adapter;
+
+        /**
+         * The presenter to be used for the adapter objects.
+         */
+        Presenter presenter;
+    }
+
+    /**
+     * A ViewHolder for an actions bar.
+     */
+    class ViewHolder extends Presenter.ViewHolder {
+        ObjectAdapter mAdapter;
+        Presenter mPresenter;
+        ControlBar mControlBar;
+        SparseArray<Presenter.ViewHolder> mViewHolders =
+                new SparseArray<Presenter.ViewHolder>();
+        ObjectAdapter.DataObserver mDataObserver;
+
+        /**
+         * Constructor for the ViewHolder.
+         */
+        ViewHolder(View rootView) {
+            super(rootView);
+            mControlBar = (ControlBar) rootView.findViewById(R.id.control_bar);
+            if (mControlBar == null) {
+                throw new IllegalStateException("Couldn't find control_bar");
+            }
+            mControlBar.setOnChildFocusedListener(new ControlBar.OnChildFocusedListener() {
+                @Override
+                public void onChildFocusedListener(View child, View focused) {
+                    if (mOnItemViewSelectedListener == null) {
+                        return;
+                    }
+                    for (int position = 0; position < mViewHolders.size(); position++) {
+                        if (mViewHolders.get(position).view == child) {
+                            mOnItemViewSelectedListener.onItemSelected(
+                                    mViewHolders.get(position), getDisplayedAdapter().get(position),
+                                            null, null);
+                            break;
+                        }
+                    }
+                }
+            });
+            mDataObserver = new ObjectAdapter.DataObserver() {
+                @Override
+                public void onChanged() {
+                    if (mAdapter == getDisplayedAdapter()) {
+                        showControls(mPresenter);
+                    }
+                }
+                @Override
+                public void onItemRangeChanged(int positionStart, int itemCount) {
+                    if (mAdapter == getDisplayedAdapter()) {
+                        for (int i = 0; i < itemCount; i++) {
+                            bindControlToAction(positionStart + i, mPresenter);
+                        }
+                    }
+                }
+            };
+        }
+
+        int getChildMarginFromCenter(Context context, int numControls) {
+            // Includes margin between icons plus two times half the icon width.
+            return getChildMarginDefault(context) + getControlIconWidth(context);
+        }
+
+        void showControls(Presenter presenter) {
+            View focusedChild = mControlBar.getFocusedChild();
+            ObjectAdapter adapter = getDisplayedAdapter();
+            mControlBar.removeAllViews();
+            for (int position = 0; position < adapter.size() && position < MAX_CONTROLS;
+                    position++) {
+                bindControlToAction(position, adapter, presenter);
+            }
+            if (focusedChild != null) {
+                focusedChild.requestFocus();
+            }
+            mControlBar.setChildMarginFromCenter(
+                    getChildMarginFromCenter(mControlBar.getContext(), adapter.size()));
+        }
+
+        void bindControlToAction(int position, Presenter presenter) {
+            bindControlToAction(position, getDisplayedAdapter(), presenter);
+        }
+
+        private void bindControlToAction(final int position,
+                ObjectAdapter adapter, Presenter presenter) {
+            Presenter.ViewHolder vh = mViewHolders.get(position);
+            Object item = adapter.get(position);
+            if (vh == null) {
+                vh = presenter.onCreateViewHolder(mControlBar);
+                mViewHolders.put(position, vh);
+                presenter.setOnClickListener(vh, new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        Object item = getDisplayedAdapter().get(position);
+                        if (mOnActionClickedListener != null && item instanceof Action) {
+                            mOnActionClickedListener.onActionClicked((Action) item);
+                        }
+                    }
+                });
+            }
+            if (vh.view.getParent() == null) {
+                mControlBar.addView(vh.view);
+            }
+            presenter.onBindViewHolder(vh, item);
+        }
+
+        /**
+         * Returns the adapter currently bound to the displayed controls.
+         * May be overridden in a subclass.
+         */
+        ObjectAdapter getDisplayedAdapter() {
+            return mAdapter;
+        }
+    }
+
+    private OnActionClickedListener mOnActionClickedListener;
+    private OnItemViewSelectedListener mOnItemViewSelectedListener;
+    private int mLayoutResourceId;
+    private static int sChildMarginDefault;
+    private static int sControlIconWidth;
+
+    /**
+     * Constructor for a ControlBarPresenter.
+     *
+     * @param layoutResourceId The resource id of the layout for this presenter.
+     */
+    public ControlBarPresenter(int layoutResourceId) {
+        mLayoutResourceId = layoutResourceId;
+    }
+
+    /**
+     * Returns the layout resource id.
+     */
+    public int getLayoutResourceId() {
+        return mLayoutResourceId;
+    }
+
+    /**
+     * Sets the listener for {@link Action} click events.
+     */
+    public void setOnActionClickedListener(OnActionClickedListener listener) {
+        mOnActionClickedListener = listener;
+    }
+
+    /**
+     * Gets the listener for {@link Action} click events.
+     */
+    public OnActionClickedListener getOnActionClickedListener() {
+        return mOnActionClickedListener;
+    }
+
+    /**
+     * Sets the listener for item selection.  When this listener is invoked,
+     *  the rowViewHolder and row are always null.
+     */
+    public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        mOnItemViewSelectedListener = listener;
+    }
+
+    /**
+     * Gets the listener for item selection.
+     */
+    public OnItemViewSelectedListener getOnItemViewSelectedListener() {
+        return mOnItemViewSelectedListener;
+    }
+
+    @Override
+    public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
+        View v = LayoutInflater.from(parent.getContext())
+            .inflate(getLayoutResourceId(), parent, false);
+        return new ViewHolder(v);
+    }
+
+    @Override
+    public void onBindViewHolder(Presenter.ViewHolder holder, Object item) {
+        ViewHolder vh = (ViewHolder) holder;
+        BoundData data = (BoundData) item;
+        if (vh.mAdapter != data.adapter) {
+            vh.mAdapter = data.adapter;
+            vh.mAdapter.registerObserver(vh.mDataObserver);
+        }
+        vh.mPresenter = data.presenter;
+        vh.showControls(vh.mPresenter);
+    }
+
+    @Override
+    public void onUnbindViewHolder(Presenter.ViewHolder holder) {
+        ViewHolder vh = (ViewHolder) holder;
+        vh.mAdapter.unregisterObserver(vh.mDataObserver);
+        vh.mAdapter = null;
+    }
+
+    int getChildMarginDefault(Context context) {
+        if (sChildMarginDefault == 0) {
+            sChildMarginDefault = context.getResources().getDimensionPixelSize(
+                    R.dimen.lb_playback_controls_child_margin_default);
+        }
+        return sChildMarginDefault;
+    }
+
+    int getControlIconWidth(Context context) {
+        if (sControlIconWidth == 0) {
+            sControlIconWidth = context.getResources().getDimensionPixelSize(
+                    R.dimen.lb_control_icon_width);
+        }
+        return sControlIconWidth;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ControlButtonPresenterSelector.java b/v17/leanback/src/android/support/v17/leanback/widget/ControlButtonPresenterSelector.java
new file mode 100644
index 0000000..de95729
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ControlButtonPresenterSelector.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.support.v17.leanback.R;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+/**
+ * ControlButtonPresenterSelector displays primary and secondary
+ * controls for a {@link PlaybackControlsRow}.
+ *
+ * Binds to items of type {@link Action}.
+ */
+public class ControlButtonPresenterSelector extends PresenterSelector {
+
+    private final Presenter mPrimaryPresenter =
+            new ControlButtonPresenter(R.layout.lb_control_button_primary);
+    private final Presenter mSecondaryPresenter =
+            new ControlButtonPresenter(R.layout.lb_control_button_secondary);
+
+    /**
+     * Returns the presenter for primary controls.
+     */
+    public Presenter getPrimaryPresenter() {
+        return mPrimaryPresenter;
+    }
+
+    /**
+     * Returns the presenter for secondary controls.
+     */
+    public Presenter getSecondaryPresenter() {
+        return mSecondaryPresenter;
+    }
+
+    /**
+     * Always returns the presenter for primary controls.
+     */
+    public Presenter getPresenter(Object item) {
+        return mPrimaryPresenter;
+    }
+
+    static class ActionViewHolder extends Presenter.ViewHolder {
+        ImageView mIcon;
+        View mFocusableView;
+
+        public ActionViewHolder(View view) {
+            super(view);
+            mIcon = (ImageView) view.findViewById(R.id.icon);
+            mFocusableView = view.findViewById(R.id.button);
+        }
+    }
+
+    static class ControlButtonPresenter extends Presenter {
+        private int mLayoutResourceId;
+
+        ControlButtonPresenter(int layoutResourceId) {
+            mLayoutResourceId = layoutResourceId;
+        }
+
+        @Override
+        public ViewHolder onCreateViewHolder(ViewGroup parent) {
+            View v = LayoutInflater.from(parent.getContext())
+                    .inflate(mLayoutResourceId, parent, false);
+            return new ActionViewHolder(v);
+        }
+
+        @Override
+        public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+            Action action = (Action) item;
+            ActionViewHolder vh = (ActionViewHolder) viewHolder;
+            vh.mIcon.setImageDrawable(action.getIcon());
+        }
+
+        @Override
+        public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+            ActionViewHolder vh = (ActionViewHolder) viewHolder;
+            vh.mIcon.setImageDrawable(null);
+        }
+
+        @Override
+        public void setOnClickListener(Presenter.ViewHolder viewHolder,
+                View.OnClickListener listener) {
+            ((ActionViewHolder) viewHolder).mFocusableView.setOnClickListener(listener);
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/CursorObjectAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/CursorObjectAdapter.java
new file mode 100644
index 0000000..8f3cf2c
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/CursorObjectAdapter.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.database.Cursor;
+import android.support.v17.leanback.database.CursorMapper;
+import android.util.LruCache;
+
+/**
+ * An ObjectAdapter implemented with a {@link Cursor}.
+ */
+public class CursorObjectAdapter extends ObjectAdapter {
+    private static final int CACHE_SIZE = 100;
+    private Cursor mCursor;
+    private CursorMapper mMapper;
+    private final LruCache<Integer, Object> mItemCache = new LruCache<Integer, Object>(CACHE_SIZE);
+
+    /**
+     * Construct an adapter with the given {@link PresenterSelector}.
+     */
+    public CursorObjectAdapter(PresenterSelector presenterSelector) {
+        super(presenterSelector);
+    }
+
+    /**
+     * Construct an adapter that uses the given {@link Presenter} for all items.
+     */
+    public CursorObjectAdapter(Presenter presenter) {
+        super(presenter);
+    }
+
+    /**
+     * Construct an adapter.
+     */
+    public CursorObjectAdapter() {
+        super();
+    }
+
+    /**
+     * Change the underlying cursor to a new cursor. If there is
+     * an existing cursor it will be closed if it is different than the new
+     * cursor.
+     *
+     * @param cursor The new cursor to be used.
+     */
+    public void changeCursor(Cursor cursor) {
+        if (cursor == mCursor) {
+            return;
+        }
+        if (mCursor != null) {
+            mCursor.close();
+        }
+        mCursor = cursor;
+        mItemCache.trimToSize(0);
+        onCursorChanged();
+    }
+
+    /**
+     * Swap in a new Cursor, returning the old Cursor. Unlike changeCursor(Cursor),
+     * the returned old Cursor is not closed.
+     *
+     * @param cursor The new cursor to be used.
+     */
+    public Cursor swapCursor(Cursor cursor) {
+        if (cursor == mCursor) {
+            return mCursor;
+        }
+        Cursor oldCursor = mCursor;
+        mCursor = cursor;
+        mItemCache.trimToSize(0);
+        onCursorChanged();
+        return oldCursor;
+    }
+
+    /**
+     * Called whenever the cursor changes.
+     */
+    protected void onCursorChanged() {
+        notifyChanged();
+    }
+
+    /**
+     * Gets the {@link Cursor} backing the adapter.
+     */
+     public final Cursor getCursor() {
+        return mCursor;
+    }
+
+    /**
+     * Sets the {@link CursorMapper} used to convert {@link Cursor} rows into
+     * Objects.
+     */
+    public final void setMapper(CursorMapper mapper) {
+        boolean changed = mMapper != mapper;
+        mMapper = mapper;
+
+        if (changed) {
+            onMapperChanged();
+        }
+    }
+
+    /**
+     * Called when {@link #setMapper(CursorMapper)} is called and a different
+     * mapper is provided.
+     */
+    protected void onMapperChanged() {
+    }
+
+    /**
+     * Gets the {@link CursorMapper} used to convert {@link Cursor} rows into
+     * Objects.
+     */
+    public final CursorMapper getMapper() {
+        return mMapper;
+    }
+
+    @Override
+    public int size() {
+        if (mCursor == null) {
+            return 0;
+        }
+        return mCursor.getCount();
+    }
+
+    @Override
+    public Object get(int index) {
+        if (mCursor == null) {
+            return null;
+        }
+        if (!mCursor.moveToPosition(index)) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        Object item = mItemCache.get(index);
+        if (item != null) {
+            return item;
+        }
+        item = mMapper.convert(mCursor);
+        mItemCache.put(index, item);
+        return item;
+    }
+
+    /**
+     * Closes this adapter, closing the backing {@link Cursor} as well.
+     */
+    public void close() {
+        if (mCursor != null) {
+            mCursor.close();
+            mCursor = null;
+        }
+    }
+
+    /**
+     * Checks whether the adapter, and hence the backing {@link Cursor}, is closed.
+     */
+    public boolean isClosed() {
+        return mCursor == null || mCursor.isClosed();
+    }
+
+    /**
+     * Remove an item from the cache. This will force the item to be re-read
+     * from the data source the next time (@link #get(int)} is called.
+     */
+    protected final void invalidateCache(int index) {
+        mItemCache.remove(index);
+    }
+
+    /**
+     * Remove {@code count} items starting at {@code index}.
+     */
+    protected final void invalidateCache(int index, int count) {
+        for (int limit = count + index; index < limit; index++) {
+            invalidateCache(index);
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRow.java b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRow.java
new file mode 100644
index 0000000..60fe6be
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRow.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * An overview row for a details fragment. This row consists of an image, a
+ * description view, and optionally a series of {@link Action}s that can be taken for
+ * the item.
+ */
+public class DetailsOverviewRow extends Row {
+
+    private Object mItem;
+    private Drawable mImageDrawable;
+    private ArrayList<Action> mActions = new ArrayList<Action>();
+    private boolean mImageScaleUpAllowed = true;
+
+    /**
+     * Constructor for a DetailsOverviewRow.
+     *
+     * @param item The main item for the details page.
+     */
+    public DetailsOverviewRow(Object item) {
+        super(null);
+        mItem = item;
+        verify();
+    }
+
+    /**
+     * Gets the main item for the details page.
+     */
+    public final Object getItem() {
+        return mItem;
+    }
+
+    /**
+     * Sets a drawable as the image of this details overview.
+     *
+     * @param drawable The drawable to set.
+     */
+    public final void setImageDrawable(Drawable drawable) {
+        mImageDrawable = drawable;
+    }
+
+    /**
+     * Sets a Bitmap as the image of this details overview.
+     *
+     * @param context The context to retrieve display metrics from.
+     * @param bm The bitmap to set.
+     */
+    public final void setImageBitmap(Context context, Bitmap bm) {
+        mImageDrawable = new BitmapDrawable(context.getResources(), bm);
+    }
+
+    /**
+     * Gets the image drawable of this details overview.
+     *
+     * @return The overview's image drawable, or null if no drawable has been
+     *         assigned.
+     */
+    public final Drawable getImageDrawable() {
+        return mImageDrawable;
+    }
+
+    /**
+     * Allows or disallows scaling up of images.
+     * Images will always be scaled down if necessary.
+     */
+    public void setImageScaleUpAllowed(boolean allowed) {
+        mImageScaleUpAllowed = allowed;
+    }
+
+    /**
+     * Returns true if the image may be scaled up; false otherwise.
+     */
+    public boolean isImageScaleUpAllowed() {
+        return mImageScaleUpAllowed;
+    }
+
+    /**
+     * Add an Action to the overview.
+     *
+     * @param action The Action to add.
+     */
+    public final void addAction(Action action) {
+        mActions.add(action);
+    }
+
+    /**
+     * Add an Action to the overview at the specified position.
+     *
+     * @param pos The position to insert the Action.
+     * @param action The Action to add.
+     */
+    public final void addAction(int pos, Action action) {
+        mActions.add(pos, action);
+    }
+
+    /**
+     * Remove the given Action from the overview.
+     *
+     * @param action The Action to remove.
+     * @return true if the overview contained the specified Action.
+     */
+    public final boolean removeAction(Action action) {
+        return mActions.remove(action);
+    }
+
+    /**
+     * Gets a read-only view of the list of Actions of this details overview.
+     *
+     * @return An unmodifiable view of the list of Actions.
+     */
+    public final List<Action> getActions() {
+        return Collections.unmodifiableList(mActions);
+    }
+
+    private void verify() {
+        if (mItem == null) {
+            throw new IllegalArgumentException("Object cannot be null");
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
new file mode 100644
index 0000000..541d37a
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRowPresenter.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.support.v17.leanback.R;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import java.util.Collection;
+
+/**
+ * A DetailsOverviewRowPresenter renders a {@link DetailsOverviewRow} to display an
+ * overview of an item. Typically this row will be the first row in a fragment
+ * such as the {@link android.support.v17.leanback.app.DetailsFragment
+ * DetailsFragment}.  View created by DetailsOverviewRowPresenter is made in three parts:
+ * ImageView on the left, action list view on the bottom and a customizable detailed
+ * description view on the right.
+ *
+ * <p>The detailed description is rendered using a {@link Presenter} passed in
+ * {@link #DetailsOverviewRowPresenter(Presenter)}.  User can access detailed description
+ * ViewHolder from {@link ViewHolder#mDetailsDescriptionViewHolder}.
+ * </p>
+ *
+ * <p>
+ * To participate in activity transition, call {@link #setSharedElementEnterTransition(Activity,
+ * String)} during Activity's onCreate().
+ * </p>
+ *
+ * <p>
+ * Because transition support and layout are fully controlled by DetailsOverviewRowPresenter,
+ * developer can not override DetailsOverviewRowPresenter.ViewHolder for adding/replacing views
+ * of DetailsOverviewRowPresenter.  If developer wants more customization beyond replacing
+ * detailed description , he/she should write a new presenter class for row object.
+ * </p>
+ */
+public class DetailsOverviewRowPresenter extends RowPresenter {
+
+    private static final String TAG = "DetailsOverviewRowPresenter";
+    private static final boolean DEBUG = false;
+
+    private static final int MORE_ACTIONS_FADE_MS = 100;
+
+    /**
+     * A ViewHolder for the DetailsOverviewRow.
+     */
+    public final static class ViewHolder extends RowPresenter.ViewHolder {
+        final ViewGroup mOverviewView;
+        final ImageView mImageView;
+        final ViewGroup mRightPanel;
+        final FrameLayout mDetailsDescriptionFrame;
+        final HorizontalGridView mActionsRow;
+        public final Presenter.ViewHolder mDetailsDescriptionViewHolder;
+        int mNumItems;
+        boolean mShowMoreRight;
+        boolean mShowMoreLeft;
+
+        void bind(ItemBridgeAdapter bridgeAdapter) {
+            mNumItems = bridgeAdapter.getItemCount();
+            bridgeAdapter.setAdapterListener(mAdapterListener);
+
+            mShowMoreRight = false;
+            mShowMoreLeft = true;
+            showMoreLeft(false);
+        }
+
+        final View.OnLayoutChangeListener mLayoutChangeListener =
+                new View.OnLayoutChangeListener() {
+
+            @Override
+            public void onLayoutChange(View v, int left, int top, int right,
+                    int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                if (DEBUG) Log.v(TAG, "onLayoutChange " + v);
+                checkFirstAndLastPosition(false);
+            }
+        };
+
+        final ItemBridgeAdapter.AdapterListener mAdapterListener =
+                new ItemBridgeAdapter.AdapterListener() {
+
+            @Override
+            public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
+                // Remove first to ensure we don't add ourselves more than once.
+                viewHolder.itemView.removeOnLayoutChangeListener(mLayoutChangeListener);
+                viewHolder.itemView.addOnLayoutChangeListener(mLayoutChangeListener);
+            }
+            @Override
+            public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
+                viewHolder.itemView.removeOnLayoutChangeListener(mLayoutChangeListener);
+                checkFirstAndLastPosition(false);
+            }
+        };
+
+        final RecyclerView.OnScrollListener mScrollListener =
+                new RecyclerView.OnScrollListener() {
+
+            @Override
+            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+            }
+            @Override
+            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+                checkFirstAndLastPosition(true);
+            }
+        };
+
+        private int getViewCenter(View view) {
+            return (view.getRight() - view.getLeft()) / 2;
+        }
+
+        private void checkFirstAndLastPosition(boolean fromScroll) {
+            RecyclerView.ViewHolder viewHolder;
+
+            viewHolder = mActionsRow.findViewHolderForPosition(mNumItems - 1);
+            boolean showRight = (viewHolder == null ||
+                    viewHolder.itemView.getRight() > mActionsRow.getWidth());
+
+            viewHolder = mActionsRow.findViewHolderForPosition(0);
+            boolean showLeft = (viewHolder == null || viewHolder.itemView.getLeft() < 0);
+
+            if (DEBUG) Log.v(TAG, "checkFirstAndLast fromScroll " + fromScroll +
+                    " showRight " + showRight + " showLeft " + showLeft);
+
+            showMoreRight(showRight);
+            showMoreLeft(showLeft);
+        }
+
+        private void showMoreLeft(boolean show) {
+            if (show != mShowMoreLeft) {
+                mActionsRow.setFadingLeftEdge(show);
+                mShowMoreLeft = show;
+            }
+        }
+
+        private void showMoreRight(boolean show) {
+            if (show != mShowMoreRight) {
+                mActionsRow.setFadingRightEdge(show);
+                mShowMoreRight = show;
+            }
+        }
+
+        /**
+         * Constructor for the ViewHolder.
+         *
+         * @param rootView The root View that this view holder will be attached
+         *        to.
+         */
+        public ViewHolder(View rootView, Presenter detailsPresenter) {
+            super(rootView);
+            mOverviewView = (ViewGroup) rootView.findViewById(R.id.details_overview);
+            mImageView = (ImageView) rootView.findViewById(R.id.details_overview_image);
+            mRightPanel = (ViewGroup) rootView.findViewById(R.id.details_overview_right_panel);
+            mDetailsDescriptionFrame =
+                    (FrameLayout) mRightPanel.findViewById(R.id.details_overview_description);
+            mActionsRow =
+                    (HorizontalGridView) mRightPanel.findViewById(R.id.details_overview_actions);
+            mActionsRow.setOnScrollListener(mScrollListener);
+
+            final int fadeLength = rootView.getResources().getDimensionPixelSize(
+                    R.dimen.lb_details_overview_actions_fade_size);
+            mActionsRow.setFadingRightEdgeLength(fadeLength);
+            mActionsRow.setFadingLeftEdgeLength(fadeLength);
+            mDetailsDescriptionViewHolder =
+                    detailsPresenter.onCreateViewHolder(mDetailsDescriptionFrame);
+            mDetailsDescriptionFrame.addView(mDetailsDescriptionViewHolder.view);
+        }
+    }
+
+    private final Presenter mDetailsPresenter;
+    private final ActionPresenterSelector mActionPresenterSelector;
+    private final ItemBridgeAdapter mActionBridgeAdapter;
+    private int mBackgroundColor = Color.TRANSPARENT;
+    private boolean mBackgroundColorSet;
+    private boolean mIsStyleLarge = true;
+
+    private DetailsOverviewSharedElementHelper mSharedElementHelper;
+
+    /**
+     * Constructor for a DetailsOverviewRowPresenter.
+     *
+     * @param detailsPresenter The {@link Presenter} used to render the detailed
+     *        description of the row.
+     */
+    public DetailsOverviewRowPresenter(Presenter detailsPresenter) {
+        setHeaderPresenter(null);
+        setSelectEffectEnabled(false);
+        mDetailsPresenter = detailsPresenter;
+        mActionPresenterSelector = new ActionPresenterSelector();
+        mActionBridgeAdapter = new ItemBridgeAdapter();
+    }
+
+    /**
+     * Sets the listener for Action click events.
+     */
+    public void setOnActionClickedListener(OnActionClickedListener listener) {
+        mActionPresenterSelector.setOnActionClickedListener(listener);
+    }
+
+    /**
+     * Gets the listener for Action click events.
+     */
+    public OnActionClickedListener getOnActionClickedListener() {
+        return mActionPresenterSelector.getOnActionClickedListener();
+    }
+
+    private void applyBackground(View view) {
+        view.setBackgroundColor(mBackgroundColorSet ?
+                mBackgroundColor : getDefaultBackgroundColor(view.getContext()));
+    }
+
+    /**
+     * Sets the background color.  If not set, a default from the theme will be used.
+     */
+    public void setBackgroundColor(int color) {
+        mBackgroundColor = color;
+        mBackgroundColorSet = true;
+    }
+
+    /**
+     * Returns the background color.  If no background color was set, transparent
+     * is returned.
+     */
+    public int getBackgroundColor() {
+        return mBackgroundColor;
+    }
+
+    /**
+     * Sets the layout style to be large or small. This affects the height of
+     * the overview, including the text description. The default is large.
+     */
+    public void setStyleLarge(boolean large) {
+        mIsStyleLarge = large;
+    }
+
+    /**
+     * Returns true if the layout style is large.
+     */
+    public boolean isStyleLarge() {
+        return mIsStyleLarge;
+    }
+
+    /**
+     * Set enter transition of target activity (typically a DetailActivity) to be
+     * transiting into overview row created by this presenter.
+     * <p>
+     * It assumes shared element passed from calling activity is an ImageView;
+     * the shared element transits to overview image on the left of detail
+     * overview row, while bounds of overview row grows and reveals text
+     * and buttons on the right.
+     * <p>
+     * The method must be invoked in target Activity's onCreate().
+     */
+    public final void setSharedElementEnterTransition(Activity activity,
+            String sharedElementName) {
+        if (mSharedElementHelper == null) {
+            mSharedElementHelper = new DetailsOverviewSharedElementHelper();
+        }
+        mSharedElementHelper.setSharedElementEnterTransition(activity, sharedElementName);
+    }
+
+    private int getDefaultBackgroundColor(Context context) {
+        TypedValue outValue = new TypedValue();
+        context.getTheme().resolveAttribute(R.attr.defaultBrandColor, outValue, true);
+        return context.getResources().getColor(outValue.resourceId);
+    }
+
+    @Override
+    protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
+        View v = LayoutInflater.from(parent.getContext())
+            .inflate(R.layout.lb_details_overview, parent, false);
+        ViewHolder vh = new ViewHolder(v, mDetailsPresenter);
+
+        initDetailsOverview(vh);
+
+        return vh;
+    }
+
+    private int getCardHeight(Context context) {
+        int resId = mIsStyleLarge ? R.dimen.lb_details_overview_height_large :
+            R.dimen.lb_details_overview_height_small;
+        return context.getResources().getDimensionPixelSize(resId);
+    }
+
+    private void initDetailsOverview(ViewHolder vh) {
+        View overview = vh.view.findViewById(R.id.details_overview);
+        ViewGroup.LayoutParams lp = overview.getLayoutParams();
+        lp.height = getCardHeight(overview.getContext());
+        overview.setLayoutParams(lp);
+
+        ShadowHelper.getInstance().setZ(overview, 0f);
+    }
+
+    private static int getNonNegativeWidth(Drawable drawable) {
+        final int width = (drawable == null) ? 0 : drawable.getIntrinsicWidth();
+        return (width > 0 ? width : 0);
+    }
+
+    private static int getNonNegativeHeight(Drawable drawable) {
+        final int height = (drawable == null) ? 0 : drawable.getIntrinsicHeight();
+        return (height > 0 ? height : 0);
+    }
+
+    @Override
+    protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
+        super.onBindRowViewHolder(holder, item);
+
+        DetailsOverviewRow row = (DetailsOverviewRow) item;
+        ViewHolder vh = (ViewHolder) holder;
+
+        ViewGroup.MarginLayoutParams layoutParams =
+                (ViewGroup.MarginLayoutParams) vh.mImageView.getLayoutParams();
+        final int cardHeight = getCardHeight(vh.mImageView.getContext());
+        final int verticalMargin = vh.mImageView.getResources().getDimensionPixelSize(
+                R.dimen.lb_details_overview_image_margin_vertical);
+        final int horizontalMargin = vh.mImageView.getResources().getDimensionPixelSize(
+                R.dimen.lb_details_overview_image_margin_horizontal);
+        final int drawableWidth = getNonNegativeWidth(row.getImageDrawable());
+        final int drawableHeight = getNonNegativeHeight(row.getImageDrawable());
+
+        boolean scaleImage = row.isImageScaleUpAllowed();
+        boolean useMargin = false;
+
+        if (row.getImageDrawable() != null) {
+            boolean landscape = false;
+
+            // If large style and landscape image we always use margin.
+            if (drawableWidth > drawableHeight) {
+                landscape = true;
+                if (mIsStyleLarge) {
+                    useMargin = true;
+                }
+            }
+            // If long dimension bigger than the card height we scale down.
+            if ((landscape && drawableWidth > cardHeight) ||
+                    (!landscape && drawableHeight > cardHeight)) {
+                scaleImage = true;
+            }
+            // If we're not scaling to fit the card height then we always use margin.
+            if (!scaleImage) {
+                useMargin = true;
+            }
+            // If using margin than may need to scale down.
+            if (useMargin && !scaleImage) {
+                if (landscape && drawableWidth > cardHeight - horizontalMargin) {
+                    scaleImage = true;
+                } else if (!landscape && drawableHeight > cardHeight - 2 * verticalMargin) {
+                    scaleImage = true;
+                }
+            }
+        }
+
+        if (useMargin) {
+            layoutParams.leftMargin = horizontalMargin;
+            layoutParams.topMargin = layoutParams.bottomMargin = verticalMargin;
+            applyBackground(vh.mOverviewView);
+            vh.mRightPanel.setBackground(null);
+            vh.mImageView.setBackground(null);
+        } else {
+            layoutParams.leftMargin = layoutParams.topMargin = layoutParams.bottomMargin = 0;
+            applyBackground(vh.mRightPanel);
+            applyBackground(vh.mImageView);
+            vh.mOverviewView.setBackground(null);
+        }
+        if (scaleImage) {
+            vh.mImageView.setScaleType(ImageView.ScaleType.FIT_START);
+            vh.mImageView.setAdjustViewBounds(true);
+            vh.mImageView.setMaxWidth(cardHeight);
+            layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
+            layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+        } else {
+            vh.mImageView.setScaleType(ImageView.ScaleType.CENTER);
+            vh.mImageView.setAdjustViewBounds(false);
+            layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+            // Limit width to the card height
+            layoutParams.width = Math.min(cardHeight, drawableWidth);
+        }
+        vh.mImageView.setLayoutParams(layoutParams);
+        vh.mImageView.setImageDrawable(row.getImageDrawable());
+
+        mDetailsPresenter.onBindViewHolder(vh.mDetailsDescriptionViewHolder, row.getItem());
+
+        mActionBridgeAdapter.clear();
+        ArrayObjectAdapter aoa = new ArrayObjectAdapter(mActionPresenterSelector);
+        aoa.addAll(0, (Collection)row.getActions());
+
+        mActionBridgeAdapter.setAdapter(aoa);
+        vh.mActionsRow.setAdapter(mActionBridgeAdapter);
+
+        vh.bind(mActionBridgeAdapter);
+
+        if (row.getImageDrawable() != null && mSharedElementHelper != null) {
+            mSharedElementHelper.onBindToDrawable(vh);
+        }
+    }
+
+    @Override
+    protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
+        super.onUnbindRowViewHolder(holder);
+
+        ViewHolder vh = (ViewHolder) holder;
+        if (vh.mDetailsDescriptionViewHolder != null) {
+            mDetailsPresenter.onUnbindViewHolder(vh.mDetailsDescriptionViewHolder);
+        }
+
+        vh.mActionsRow.setAdapter(null);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java
new file mode 100644
index 0000000..b98aac2
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.os.Handler;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.SharedElementListener;
+import android.support.v4.view.ViewCompat;
+import android.support.v17.leanback.transition.TransitionListener;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.widget.DetailsOverviewRowPresenter.ViewHolder;
+import android.app.Activity;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.MeasureSpec;
+
+import java.util.List;
+
+final class DetailsOverviewSharedElementHelper extends SharedElementListener {
+
+    private ViewHolder mViewHolder;
+    private Activity mActivityToRunTransition;
+    private String mSharedElementName;
+    private int mRightPanelWidth;
+    private int mRightPanelHeight;
+
+    @Override
+    public void setSharedElementStart(List<String> sharedElementNames,
+            List<View> sharedElements, List<View> sharedElementSnapshots) {
+        if (sharedElements.size() < 1) {
+            return;
+        }
+        View overviewView = sharedElements.get(0);
+        if (mViewHolder == null || mViewHolder.mOverviewView != overviewView) {
+            return;
+        }
+        View imageView = mViewHolder.mImageView;
+        final int width = overviewView.getWidth();
+        final int height = overviewView.getHeight();
+        imageView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+        imageView.layout(0, 0, width, height);
+        final View rightPanel = mViewHolder.mRightPanel;
+        if (mRightPanelWidth != 0 && mRightPanelHeight != 0) {
+            rightPanel.measure(MeasureSpec.makeMeasureSpec(mRightPanelWidth, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(mRightPanelHeight, MeasureSpec.EXACTLY));
+            rightPanel.layout(width, rightPanel.getTop(), width + mRightPanelWidth,
+                    rightPanel.getTop() + mRightPanelHeight);
+        } else {
+            rightPanel.offsetLeftAndRight(width - rightPanel.getLeft());
+        }
+        mViewHolder.mActionsRow.setVisibility(View.INVISIBLE);
+        mViewHolder.mDetailsDescriptionFrame.setVisibility(View.INVISIBLE);
+    }
+
+    @Override
+    public void setSharedElementEnd(List<String> sharedElementNames,
+            List<View> sharedElements, List<View> sharedElementSnapshots) {
+        if (sharedElements.size() < 1) {
+            return;
+        }
+        View overviewView = sharedElements.get(0);
+        if (mViewHolder == null || mViewHolder.mOverviewView != overviewView) {
+            return;
+        }
+        // temporary let action row take focus so we defer button background animation
+        mViewHolder.mActionsRow.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+        mViewHolder.mActionsRow.setVisibility(View.VISIBLE);
+        mViewHolder.mActionsRow.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+        mViewHolder.mDetailsDescriptionFrame.setVisibility(View.VISIBLE);
+    }
+
+    void setSharedElementEnterTransition(Activity activity, String sharedElementName) {
+        if (activity == null && !TextUtils.isEmpty(sharedElementName) ||
+                activity != null && TextUtils.isEmpty(sharedElementName)) {
+            throw new IllegalArgumentException();
+        }
+        if (activity == mActivityToRunTransition &&
+                TextUtils.equals(sharedElementName, mSharedElementName)) {
+            return;
+        }
+        if (mActivityToRunTransition != null) {
+            ActivityCompat.setEnterSharedElementListener(mActivityToRunTransition, null);
+        }
+        mActivityToRunTransition = activity;
+        mSharedElementName = sharedElementName;
+        if (mActivityToRunTransition != null) {
+            ActivityCompat.setEnterSharedElementListener(mActivityToRunTransition, this);
+            ActivityCompat.postponeEnterTransition(mActivityToRunTransition);
+            TransitionHelper transitionHelper = TransitionHelper.getInstance();
+            Object transition = transitionHelper.getSharedElementEnterTransition(
+                    mActivityToRunTransition.getWindow());
+            if (transition != null) {
+                transitionHelper.setTransitionListener(transition, new TransitionListener() {
+                    public void onTransitionEnd(Object transition) {
+                        // after transition if the action row still focused, transfer
+                        // focus to its children
+                        if (mViewHolder.mActionsRow.isFocused()) {
+                            mViewHolder.mActionsRow.requestFocus();
+                        }
+                    }
+                });
+            }
+        }
+    }
+
+    void onBindToDrawable(ViewHolder vh) {
+        // After we got a image drawable,  we can determine size of right panel.
+        // We want right panel to have fixed size so that the right panel don't change size
+        // when the overview is layout as a small bounds in transition.
+        mViewHolder = vh;
+        mViewHolder.mRightPanel.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+            @Override
+            public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                mViewHolder.mRightPanel.removeOnLayoutChangeListener(this);
+                mRightPanelWidth = mViewHolder.mRightPanel.getWidth();
+                mRightPanelHeight = mViewHolder.mRightPanel.getHeight();
+            }
+        });
+        if (mActivityToRunTransition != null) {
+            mViewHolder.mRightPanel.postOnAnimation(new Runnable() {
+                @Override
+                public void run() {
+                    if (mActivityToRunTransition == null) {
+                        return;
+                    }
+                    ViewCompat.setTransitionName(mViewHolder.mOverviewView, mSharedElementName);
+                    ActivityCompat.startPostponedEnterTransition(mActivityToRunTransition);
+                    mActivityToRunTransition = null;
+                    mSharedElementName = null;
+                }
+            });
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlight.java b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlight.java
new file mode 100644
index 0000000..2171992
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlight.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.view.View;
+
+/**
+ * Interface for highlighting the item that has focus.
+ *
+ */
+public interface FocusHighlight {
+    /**
+     * No zoom factor.
+     */
+    public static final int ZOOM_FACTOR_NONE = 0;
+
+    /**
+     * A small zoom factor, recommended for large item views.
+     */
+    public static final int ZOOM_FACTOR_SMALL = 1;
+
+    /**
+     * A medium zoom factor, recommended for medium sized item views.
+     */
+    public static final int ZOOM_FACTOR_MEDIUM = 2;
+
+    /**
+     * A large zoom factor, recommended for small item views.
+     */
+    public static final int ZOOM_FACTOR_LARGE = 3;
+
+    /**
+     * Called when an item gains or loses focus.
+     * @hide
+     *
+     * @param view The view whose focus is changing.
+     * @param hasFocus True if focus is gained; false otherwise.
+     */
+    void onItemFocused(View view, boolean hasFocus);
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
new file mode 100644
index 0000000..4afb3e9
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/FocusHighlightHelper.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.support.v17.leanback.R;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.animation.TimeAnimator;
+import android.content.res.Resources;
+
+/**
+ * Setup the behavior how to highlight when a item gains focus.
+ */
+public class FocusHighlightHelper {
+
+    static class FocusAnimator implements TimeAnimator.TimeListener {
+        private final View mView;
+        private final int mDuration;
+        private final ShadowOverlayContainer mWrapper;
+        private final float mScaleDiff;
+        private float mFocusLevel = 0f;
+        private float mFocusLevelStart;
+        private float mFocusLevelDelta;
+        private final TimeAnimator mAnimator = new TimeAnimator();
+        private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
+
+        void animateFocus(boolean select, boolean immediate) {
+            endAnimation();
+            final float end = select ? 1 : 0;
+            if (immediate) {
+                setFocusLevel(end);
+            } else if (mFocusLevel != end) {
+                mFocusLevelStart = mFocusLevel;
+                mFocusLevelDelta = end - mFocusLevelStart;
+                mAnimator.start();
+            }
+        }
+
+        FocusAnimator(View view, float scale, int duration) {
+            mView = view;
+            mDuration = duration;
+            mScaleDiff = scale - 1f;
+            if (view instanceof ShadowOverlayContainer) {
+                mWrapper = (ShadowOverlayContainer) view;
+            } else {
+                mWrapper = null;
+            }
+            mAnimator.setTimeListener(this);
+        }
+
+        void setFocusLevel(float level) {
+            mFocusLevel = level;
+            float scale = 1f + mScaleDiff * level;
+            mView.setScaleX(scale);
+            mView.setScaleY(scale);
+            if (mWrapper != null) {
+                mWrapper.setShadowFocusLevel(level);
+            }
+        }
+
+        float getFocusLevel() {
+            return mFocusLevel;
+        }
+
+        void endAnimation() {
+            mAnimator.end();
+        }
+
+        @Override
+        public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
+            float fraction;
+            if (totalTime >= mDuration) {
+                fraction = 1;
+                mAnimator.end();
+            } else {
+                fraction = (float) (totalTime / (double) mDuration);
+            }
+            if (mInterpolator != null) {
+                fraction = mInterpolator.getInterpolation(fraction);
+            }
+            setFocusLevel(mFocusLevelStart + fraction * mFocusLevelDelta);
+        }
+    }
+
+    static class BrowseItemFocusHighlight implements FocusHighlight {
+        private static final int DURATION_MS = 150;
+
+        private static float[] sScaleFactor = new float[4];
+
+        private int mScaleIndex;
+
+        BrowseItemFocusHighlight(int zoomIndex) {
+            mScaleIndex = (zoomIndex >= 0 && zoomIndex < sScaleFactor.length) ?
+                    zoomIndex : ZOOM_FACTOR_MEDIUM;
+        }
+
+        private static void lazyInit(Resources resources) {
+            if (sScaleFactor[ZOOM_FACTOR_NONE] == 0f) {
+                sScaleFactor[ZOOM_FACTOR_NONE] = 1f;
+                sScaleFactor[ZOOM_FACTOR_SMALL] =
+                        resources.getFraction(R.fraction.lb_focus_zoom_factor_small, 1, 1);
+                sScaleFactor[ZOOM_FACTOR_MEDIUM] =
+                        resources.getFraction(R.fraction.lb_focus_zoom_factor_medium, 1, 1);
+                sScaleFactor[ZOOM_FACTOR_LARGE] =
+                        resources.getFraction(R.fraction.lb_focus_zoom_factor_large, 1, 1);
+            }
+        }
+
+        private float getScale(View view) {
+            lazyInit(view.getResources());
+            return sScaleFactor[mScaleIndex];
+        }
+
+        private void viewFocused(View view, boolean hasFocus) {
+            view.setSelected(hasFocus);
+            FocusAnimator animator = (FocusAnimator) view.getTag(R.id.lb_focus_animator);
+            if (animator == null) {
+                animator = new FocusAnimator(view, getScale(view), DURATION_MS);
+                view.setTag(R.id.lb_focus_animator, animator);
+            }
+            animator.animateFocus(hasFocus, false);
+        }
+
+        @Override
+        public void onItemFocused(View view, boolean hasFocus) {
+            viewFocused(view, hasFocus);
+        }
+    }
+
+    /**
+     * Setup the focus highlight behavior of a focused item in browse list row.
+     * @param adapter  adapter of the list row.
+     */
+    public static void setupBrowseItemFocusHighlight(ItemBridgeAdapter adapter, int zoomIndex) {
+        adapter.setFocusHighlight(new BrowseItemFocusHighlight(zoomIndex));
+    }
+
+    /**
+     * Setup the focus highlight behavior of a focused item in header list.
+     * @param gridView  the header list.
+     */
+    public static void setupHeaderItemFocusHighlight(VerticalGridView gridView) {
+        if (gridView.getAdapter() instanceof ItemBridgeAdapter) {
+            ((ItemBridgeAdapter) gridView.getAdapter())
+                    .setFocusHighlight(new HeaderItemFocusHighlight(gridView));
+        }
+    }
+
+    static class HeaderItemFocusHighlight implements FocusHighlight {
+        private static boolean sInitialized;
+        private static float sSelectScale;
+        private static int sDuration;
+        private BaseGridView mGridView;
+
+        HeaderItemFocusHighlight(BaseGridView gridView) {
+            mGridView = gridView;
+            lazyInit(gridView.getContext().getResources());
+        }
+
+        private static void lazyInit(Resources res) {
+            if (!sInitialized) {
+                sSelectScale =
+                        Float.parseFloat(res.getString(R.dimen.lb_browse_header_select_scale));
+                sDuration =
+                        Integer.parseInt(res.getString(R.dimen.lb_browse_header_select_duration));
+                sInitialized = true;
+            }
+        }
+
+        class HeaderFocusAnimator extends FocusAnimator {
+
+            ItemBridgeAdapter.ViewHolder mViewHolder;
+            HeaderFocusAnimator(View view, float scale, int duration) {
+                super(view, scale, duration);
+                mViewHolder = (ItemBridgeAdapter.ViewHolder) mGridView.getChildViewHolder(view);
+            }
+
+            @Override
+            void setFocusLevel(float level) {
+                Presenter presenter = mViewHolder.getPresenter();
+                if (presenter instanceof RowHeaderPresenter) {
+                    ((RowHeaderPresenter) presenter).setSelectLevel(
+                            ((RowHeaderPresenter.ViewHolder) mViewHolder.getViewHolder()), level);
+                }
+                super.setFocusLevel(level);
+            }
+
+        }
+
+        private void viewFocused(View view, boolean hasFocus) {
+            view.setSelected(hasFocus);
+            FocusAnimator animator = (FocusAnimator) view.getTag(R.id.lb_focus_animator);
+            if (animator == null) {
+                animator = new HeaderFocusAnimator(view, sSelectScale, sDuration);
+                view.setTag(R.id.lb_focus_animator, animator);
+            }
+            animator.animateFocus(hasFocus, false);
+        }
+
+        @Override
+        public void onItemFocused(View view, boolean hasFocus) {
+            viewFocused(view, hasFocus);
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
new file mode 100644
index 0000000..d4223f4
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -0,0 +1,2458 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.LinearSmoothScroller;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.Recycler;
+import android.support.v7.widget.RecyclerView.State;
+
+import static android.support.v7.widget.RecyclerView.NO_ID;
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+import static android.support.v7.widget.RecyclerView.HORIZONTAL;
+import static android.support.v7.widget.RecyclerView.VERTICAL;
+
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.FocusFinder;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.ViewGroup;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+final class GridLayoutManager extends RecyclerView.LayoutManager {
+
+     /*
+      * LayoutParams for {@link HorizontalGridView} and {@link VerticalGridView}.
+      * The class currently does two internal jobs:
+      * - Saves optical bounds insets.
+      * - Caches focus align view center.
+      */
+    static class LayoutParams extends RecyclerView.LayoutParams {
+
+        // The view is saved only during animation.
+        private View mView;
+
+        // For placement
+        private int mLeftInset;
+        private int mTopInset;
+        private int mRighInset;
+        private int mBottomInset;
+
+        // For alignment
+        private int mAlignX;
+        private int mAlignY;
+
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+        }
+
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        public LayoutParams(MarginLayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(ViewGroup.LayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(RecyclerView.LayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(LayoutParams source) {
+            super(source);
+        }
+
+        int getAlignX() {
+            return mAlignX;
+        }
+
+        int getAlignY() {
+            return mAlignY;
+        }
+
+        int getOpticalLeft(View view) {
+            return view.getLeft() + mLeftInset;
+        }
+
+        int getOpticalTop(View view) {
+            return view.getTop() + mTopInset;
+        }
+
+        int getOpticalRight(View view) {
+            return view.getRight() - mRighInset;
+        }
+
+        int getOpticalBottom(View view) {
+            return view.getBottom() - mBottomInset;
+        }
+
+        int getOpticalWidth(View view) {
+            return view.getWidth() - mLeftInset - mRighInset;
+        }
+
+        int getOpticalHeight(View view) {
+            return view.getHeight() - mTopInset - mBottomInset;
+        }
+
+        int getOpticalLeftInset() {
+            return mLeftInset;
+        }
+
+        int getOpticalRightInset() {
+            return mRighInset;
+        }
+
+        int getOpticalTopInset() {
+            return mTopInset;
+        }
+
+        int getOpticalBottomInset() {
+            return mBottomInset;
+        }
+
+        void setAlignX(int alignX) {
+            mAlignX = alignX;
+        }
+
+        void setAlignY(int alignY) {
+            mAlignY = alignY;
+        }
+
+        void setOpticalInsets(int leftInset, int topInset, int rightInset, int bottomInset) {
+            mLeftInset = leftInset;
+            mTopInset = topInset;
+            mRighInset = rightInset;
+            mBottomInset = bottomInset;
+        }
+
+        private void invalidateItemDecoration() {
+            ViewParent parent = mView.getParent();
+            if (parent instanceof RecyclerView) {
+                // TODO: we only need invalidate parent if it has ItemDecoration
+                ((RecyclerView) parent).invalidate();
+            }
+        }
+    }
+
+    private static final String TAG = "GridLayoutManager";
+    private static final boolean DEBUG = false;
+
+    private String getTag() {
+        return TAG + ":" + mBaseGridView.getId();
+    }
+
+    private final BaseGridView mBaseGridView;
+
+    /**
+     * The orientation of a "row".
+     */
+    private int mOrientation = HORIZONTAL;
+
+    private RecyclerView.State mState;
+    private RecyclerView.Recycler mRecycler;
+
+    private boolean mInLayout = false;
+
+    private OnChildSelectedListener mChildSelectedListener = null;
+
+    /**
+     * The focused position, it's not the currently visually aligned position
+     * but it is the final position that we intend to focus on. If there are
+     * multiple setSelection() called, mFocusPosition saves last value.
+     */
+    private int mFocusPosition = NO_POSITION;
+
+    /**
+     * Force a full layout under certain situations.
+     */
+    private boolean mForceFullLayout;
+
+    /**
+     * True if layout is enabled.
+     */
+    private boolean mLayoutEnabled = true;
+
+    /**
+     * The scroll offsets of the viewport relative to the entire view.
+     */
+    private int mScrollOffsetPrimary;
+    private int mScrollOffsetSecondary;
+
+    /**
+     * User-specified row height/column width.  Can be WRAP_CONTENT.
+     */
+    private int mRowSizeSecondaryRequested;
+
+    /**
+     * The fixed size of each grid item in the secondary direction. This corresponds to
+     * the row height, equal for all rows. Grid items may have variable length
+     * in the primary direction.
+     */
+    private int mFixedRowSizeSecondary;
+
+    /**
+     * Tracks the secondary size of each row.
+     */
+    private int[] mRowSizeSecondary;
+
+    /**
+     * Flag controlling whether the current/next layout should
+     * be updating the secondary size of rows.
+     */
+    private boolean mRowSecondarySizeRefresh;
+
+    /**
+     * The maximum measured size of the view.
+     */
+    private int mMaxSizeSecondary;
+
+    /**
+     * Margin between items.
+     */
+    private int mHorizontalMargin;
+    /**
+     * Margin between items vertically.
+     */
+    private int mVerticalMargin;
+    /**
+     * Margin in main direction.
+     */
+    private int mMarginPrimary;
+    /**
+     * Margin in second direction.
+     */
+    private int mMarginSecondary;
+    /**
+     * How to position child in secondary direction.
+     */
+    private int mGravity = Gravity.LEFT | Gravity.TOP;
+    /**
+     * The number of rows in the grid.
+     */
+    private int mNumRows;
+    /**
+     * Number of rows requested, can be 0 to be determined by parent size and
+     * rowHeight.
+     */
+    private int mNumRowsRequested = 1;
+
+    /**
+     * Tracking start/end position of each row for visible items.
+     */
+    private StaggeredGrid.Row[] mRows;
+
+    /**
+     * Saves grid information of each view.
+     */
+    private StaggeredGrid mGrid;
+    /**
+     * Position of first item (included) that has attached views.
+     */
+    private int mFirstVisiblePos;
+    /**
+     * Position of last item (included) that has attached views.
+     */
+    private int mLastVisiblePos;
+
+    /**
+     * Focus Scroll strategy.
+     */
+    private int mFocusScrollStrategy = BaseGridView.FOCUS_SCROLL_ALIGNED;
+    /**
+     * Defines how item view is aligned in the window.
+     */
+    private final WindowAlignment mWindowAlignment = new WindowAlignment();
+
+    /**
+     * Defines how item view is aligned.
+     */
+    private final ItemAlignment mItemAlignment = new ItemAlignment();
+
+    /**
+     * Dimensions of the view, width or height depending on orientation.
+     */
+    private int mSizePrimary;
+
+    /**
+     *  Allow DPAD key to navigate out at the front of the View (where position = 0),
+     *  default is false.
+     */
+    private boolean mFocusOutFront;
+
+    /**
+     * Allow DPAD key to navigate out at the end of the view, default is false.
+     */
+    private boolean mFocusOutEnd;
+
+    /**
+     * True if focus search is disabled.
+     */
+    private boolean mFocusSearchDisabled;
+
+    /**
+     * True if prune child,  might be disabled during transition.
+     */
+    private boolean mPruneChild = true;
+
+    /**
+     * True if scroll content,  might be disabled during transition.
+     */
+    private boolean mScrollEnabled = true;
+
+    private int[] mTempDeltas = new int[2];
+
+    private boolean mUseDeltaInPreLayout;
+
+    private int mDeltaInPreLayout, mDeltaSecondaryInPreLayout;
+
+    /**
+     * Temporaries used for measuring.
+     */
+    private int[] mMeasuredDimension = new int[2];
+
+    public GridLayoutManager(BaseGridView baseGridView) {
+        mBaseGridView = baseGridView;
+    }
+
+    public void setOrientation(int orientation) {
+        if (orientation != HORIZONTAL && orientation != VERTICAL) {
+            if (DEBUG) Log.v(getTag(), "invalid orientation: " + orientation);
+            return;
+        }
+
+        mOrientation = orientation;
+        mWindowAlignment.setOrientation(orientation);
+        mItemAlignment.setOrientation(orientation);
+        mForceFullLayout = true;
+    }
+
+    public int getFocusScrollStrategy() {
+        return mFocusScrollStrategy;
+    }
+
+    public void setFocusScrollStrategy(int focusScrollStrategy) {
+        mFocusScrollStrategy = focusScrollStrategy;
+    }
+
+    public void setWindowAlignment(int windowAlignment) {
+        mWindowAlignment.mainAxis().setWindowAlignment(windowAlignment);
+    }
+
+    public int getWindowAlignment() {
+        return mWindowAlignment.mainAxis().getWindowAlignment();
+    }
+
+    public void setWindowAlignmentOffset(int alignmentOffset) {
+        mWindowAlignment.mainAxis().setWindowAlignmentOffset(alignmentOffset);
+    }
+
+    public int getWindowAlignmentOffset() {
+        return mWindowAlignment.mainAxis().getWindowAlignmentOffset();
+    }
+
+    public void setWindowAlignmentOffsetPercent(float offsetPercent) {
+        mWindowAlignment.mainAxis().setWindowAlignmentOffsetPercent(offsetPercent);
+    }
+
+    public float getWindowAlignmentOffsetPercent() {
+        return mWindowAlignment.mainAxis().getWindowAlignmentOffsetPercent();
+    }
+
+    public void setItemAlignmentOffset(int alignmentOffset) {
+        mItemAlignment.mainAxis().setItemAlignmentOffset(alignmentOffset);
+        updateChildAlignments();
+    }
+
+    public int getItemAlignmentOffset() {
+        return mItemAlignment.mainAxis().getItemAlignmentOffset();
+    }
+
+    public void setItemAlignmentOffsetWithPadding(boolean withPadding) {
+        mItemAlignment.mainAxis().setItemAlignmentOffsetWithPadding(withPadding);
+        updateChildAlignments();
+    }
+
+    public boolean isItemAlignmentOffsetWithPadding() {
+        return mItemAlignment.mainAxis().isItemAlignmentOffsetWithPadding();
+    }
+
+    public void setItemAlignmentOffsetPercent(float offsetPercent) {
+        mItemAlignment.mainAxis().setItemAlignmentOffsetPercent(offsetPercent);
+        updateChildAlignments();
+    }
+
+    public float getItemAlignmentOffsetPercent() {
+        return mItemAlignment.mainAxis().getItemAlignmentOffsetPercent();
+    }
+
+    public void setItemAlignmentViewId(int viewId) {
+        mItemAlignment.mainAxis().setItemAlignmentViewId(viewId);
+        updateChildAlignments();
+    }
+
+    public int getItemAlignmentViewId() {
+        return mItemAlignment.mainAxis().getItemAlignmentViewId();
+    }
+
+    public void setFocusOutAllowed(boolean throughFront, boolean throughEnd) {
+        mFocusOutFront = throughFront;
+        mFocusOutEnd = throughEnd;
+    }
+
+    public void setNumRows(int numRows) {
+        if (numRows < 0) throw new IllegalArgumentException();
+        mNumRowsRequested = numRows;
+        mForceFullLayout = true;
+    }
+
+    /**
+     * Set the row height. May be WRAP_CONTENT, or a size in pixels.
+     */
+    public void setRowHeight(int height) {
+        if (height >= 0 || height == ViewGroup.LayoutParams.WRAP_CONTENT) {
+            mRowSizeSecondaryRequested = height;
+        } else {
+            throw new IllegalArgumentException("Invalid row height: " + height);
+        }
+    }
+
+    public void setItemMargin(int margin) {
+        mVerticalMargin = mHorizontalMargin = margin;
+        mMarginPrimary = mMarginSecondary = margin;
+    }
+
+    public void setVerticalMargin(int margin) {
+        if (mOrientation == HORIZONTAL) {
+            mMarginSecondary = mVerticalMargin = margin;
+        } else {
+            mMarginPrimary = mVerticalMargin = margin;
+        }
+    }
+
+    public void setHorizontalMargin(int margin) {
+        if (mOrientation == HORIZONTAL) {
+            mMarginPrimary = mHorizontalMargin = margin;
+        } else {
+            mMarginSecondary = mHorizontalMargin = margin;
+        }
+    }
+
+    public int getVerticalMargin() {
+        return mVerticalMargin;
+    }
+
+    public int getHorizontalMargin() {
+        return mHorizontalMargin;
+    }
+
+    public void setGravity(int gravity) {
+        mGravity = gravity;
+    }
+
+    protected boolean hasDoneFirstLayout() {
+        return mGrid != null;
+    }
+
+    public void setOnChildSelectedListener(OnChildSelectedListener listener) {
+        mChildSelectedListener = listener;
+    }
+
+    private int getPositionByView(View view) {
+        if (view == null) {
+            return NO_POSITION;
+        }
+        LayoutParams params = (LayoutParams) view.getLayoutParams();
+        if (params == null || params.isItemRemoved()) {
+            // when item is removed, the position value can be any value.
+            return NO_POSITION;
+        }
+        return params.getViewPosition();
+    }
+
+    private int getPositionByIndex(int index) {
+        return getPositionByView(getChildAt(index));
+    }
+
+    private void dispatchChildSelected() {
+        if (mChildSelectedListener == null) {
+            return;
+        }
+        if (mFocusPosition != NO_POSITION) {
+            View view = findViewByPosition(mFocusPosition);
+            if (view != null) {
+                RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(view);
+                mChildSelectedListener.onChildSelected(mBaseGridView, view, mFocusPosition,
+                        vh == null? NO_ID: vh.getItemId());
+                return;
+            }
+        }
+        mChildSelectedListener.onChildSelected(mBaseGridView, null, NO_POSITION, NO_ID);
+    }
+
+    @Override
+    public boolean canScrollHorizontally() {
+        // We can scroll horizontally if we have horizontal orientation, or if
+        // we are vertical and have more than one column.
+        return mOrientation == HORIZONTAL || mNumRows > 1;
+    }
+
+    @Override
+    public boolean canScrollVertically() {
+        // We can scroll vertically if we have vertical orientation, or if we
+        // are horizontal and have more than one row.
+        return mOrientation == VERTICAL || mNumRows > 1;
+    }
+
+    @Override
+    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    public RecyclerView.LayoutParams generateLayoutParams(Context context, AttributeSet attrs) {
+        return new LayoutParams(context, attrs);
+    }
+
+    @Override
+    public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+        if (lp instanceof LayoutParams) {
+            return new LayoutParams((LayoutParams) lp);
+        } else if (lp instanceof RecyclerView.LayoutParams) {
+            return new LayoutParams((RecyclerView.LayoutParams) lp);
+        } else if (lp instanceof MarginLayoutParams) {
+            return new LayoutParams((MarginLayoutParams) lp);
+        } else {
+            return new LayoutParams(lp);
+        }
+    }
+
+    protected View getViewForPosition(int position) {
+        return mRecycler.getViewForPosition(position);
+    }
+
+    final int getOpticalLeft(View v) {
+        return ((LayoutParams) v.getLayoutParams()).getOpticalLeft(v);
+    }
+
+    final int getOpticalRight(View v) {
+        return ((LayoutParams) v.getLayoutParams()).getOpticalRight(v);
+    }
+
+    final int getOpticalTop(View v) {
+        return ((LayoutParams) v.getLayoutParams()).getOpticalTop(v);
+    }
+
+    final int getOpticalBottom(View v) {
+        return ((LayoutParams) v.getLayoutParams()).getOpticalBottom(v);
+    }
+
+    private int getViewMin(View v) {
+        return (mOrientation == HORIZONTAL) ? getOpticalLeft(v) : getOpticalTop(v);
+    }
+
+    private int getViewMax(View v) {
+        return (mOrientation == HORIZONTAL) ? getOpticalRight(v) : getOpticalBottom(v);
+    }
+
+    private int getViewCenter(View view) {
+        return (mOrientation == HORIZONTAL) ? getViewCenterX(view) : getViewCenterY(view);
+    }
+
+    private int getViewCenterSecondary(View view) {
+        return (mOrientation == HORIZONTAL) ? getViewCenterY(view) : getViewCenterX(view);
+    }
+
+    private int getViewCenterX(View v) {
+        LayoutParams p = (LayoutParams) v.getLayoutParams();
+        return p.getOpticalLeft(v) + p.getAlignX();
+    }
+
+    private int getViewCenterY(View v) {
+        LayoutParams p = (LayoutParams) v.getLayoutParams();
+        return p.getOpticalTop(v) + p.getAlignY();
+    }
+
+    /**
+     * Save Recycler and State for convenience.  Must be paired with leaveContext().
+     */
+    private void saveContext(Recycler recycler, State state) {
+        if (mRecycler != null || mState != null) {
+            Log.e(TAG, "Recycler information was not released, bug!");
+        }
+        mRecycler = recycler;
+        mState = state;
+    }
+
+    /**
+     * Discard saved Recycler and State.
+     */
+    private void leaveContext() {
+        mRecycler = null;
+        mState = null;
+    }
+
+    /**
+     * Re-initialize data structures for a data change or handling invisible
+     * selection. The method tries its best to preserve position information so
+     * that staggered grid looks same before and after re-initialize.
+     * @param focusPosition The initial focusPosition that we would like to
+     *        focus on.
+     * @return Actual position that can be focused on.
+     */
+    private int init(int focusPosition) {
+
+        final int newItemCount = mState.getItemCount();
+
+        if (focusPosition == NO_POSITION && newItemCount > 0) {
+            // if focus position is never set before,  initialize it to 0
+            focusPosition = 0;
+        }
+        // If adapter has changed then caches are invalid; otherwise,
+        // we try to maintain each row's position if number of rows keeps the same
+        // and existing mGrid contains the focusPosition.
+        if (mRows != null && mNumRows == mRows.length &&
+                mGrid != null && mGrid.getSize() > 0 && focusPosition >= 0 &&
+                focusPosition >= mGrid.getFirstIndex() &&
+                focusPosition <= mGrid.getLastIndex()) {
+            // strip mGrid to a subset (like a column) that contains focusPosition
+            mGrid.stripDownTo(focusPosition);
+            // make sure that remaining items do not exceed new adapter size
+            int firstIndex = mGrid.getFirstIndex();
+            int lastIndex = mGrid.getLastIndex();
+            if (DEBUG) {
+                Log .v(getTag(), "mGrid firstIndex " + firstIndex + " lastIndex " + lastIndex);
+            }
+            for (int i = lastIndex; i >=firstIndex; i--) {
+                if (i >= newItemCount) {
+                    mGrid.removeLast();
+                }
+            }
+            if (mGrid.getSize() == 0) {
+                focusPosition = newItemCount - 1;
+                // initialize row start locations
+                for (int i = 0; i < mNumRows; i++) {
+                    mRows[i].low = 0;
+                    mRows[i].high = 0;
+                }
+                if (DEBUG) Log.v(getTag(), "mGrid zero size");
+            } else {
+                // initialize row start locations
+                for (int i = 0; i < mNumRows; i++) {
+                    mRows[i].low = Integer.MAX_VALUE;
+                    mRows[i].high = Integer.MIN_VALUE;
+                }
+                firstIndex = mGrid.getFirstIndex();
+                lastIndex = mGrid.getLastIndex();
+                if (focusPosition > lastIndex) {
+                    focusPosition = mGrid.getLastIndex();
+                }
+                if (DEBUG) {
+                    Log.v(getTag(), "mGrid firstIndex " + firstIndex + " lastIndex "
+                        + lastIndex + " focusPosition " + focusPosition);
+                }
+                // fill rows with minimal view positions of the subset
+                for (int i = firstIndex; i <= lastIndex; i++) {
+                    View v = findViewByPosition(i);
+                    if (v == null) {
+                        continue;
+                    }
+                    int row = mGrid.getLocation(i).row;
+                    int low = getViewMin(v) + mScrollOffsetPrimary;
+                    if (low < mRows[row].low) {
+                        mRows[row].low = mRows[row].high = low;
+                    }
+                }
+                int firstItemRowPosition = mRows[mGrid.getLocation(firstIndex).row].low;
+                if (firstItemRowPosition == Integer.MAX_VALUE) {
+                    firstItemRowPosition = 0;
+                }
+                if (mState.didStructureChange()) {
+                    // if there is structure change, the removed item might be in the
+                    // subset,  so it is meaningless to maintain the low locations.
+                    for (int i = 0; i < mNumRows; i++) {
+                        mRows[i].low = firstItemRowPosition;
+                        mRows[i].high = firstItemRowPosition;
+                    }
+                } else {
+                    // fill other rows that does not include the subset using first item
+                    for (int i = 0; i < mNumRows; i++) {
+                        if (mRows[i].low == Integer.MAX_VALUE) {
+                            mRows[i].low = mRows[i].high = firstItemRowPosition;
+                        }
+                    }
+                }
+            }
+
+            // Same adapter, we can reuse any attached views
+            detachAndScrapAttachedViews(mRecycler);
+
+        } else {
+            // otherwise recreate data structure
+            mRows = new StaggeredGrid.Row[mNumRows];
+
+            for (int i = 0; i < mNumRows; i++) {
+                mRows[i] = new StaggeredGrid.Row();
+            }
+            mGrid = new StaggeredGridDefault();
+            if (newItemCount == 0) {
+                focusPosition = NO_POSITION;
+            } else if (focusPosition >= newItemCount) {
+                focusPosition = newItemCount - 1;
+            }
+
+            // Adapter may have changed so remove all attached views permanently
+            removeAndRecycleAllViews(mRecycler);
+
+            mScrollOffsetPrimary = 0;
+            mScrollOffsetSecondary = 0;
+            mWindowAlignment.reset();
+        }
+
+        mGrid.setProvider(mGridProvider);
+        // mGrid share the same Row array information
+        mGrid.setRows(mRows);
+        mFirstVisiblePos = mLastVisiblePos = NO_POSITION;
+
+        initScrollController();
+        updateScrollSecondAxis();
+
+        return focusPosition;
+    }
+
+    private int getRowSizeSecondary(int rowIndex) {
+        if (mFixedRowSizeSecondary != 0) {
+            return mFixedRowSizeSecondary;
+        }
+        if (mRowSizeSecondary == null) {
+            return 0;
+        }
+        return mRowSizeSecondary[rowIndex];
+    }
+
+    private int getRowStartSecondary(int rowIndex) {
+        int start = 0;
+        for (int i = 0; i < rowIndex; i++) {
+            start += getRowSizeSecondary(i) + mMarginSecondary;
+        }
+        return start;
+    }
+
+    private int getSizeSecondary() {
+        return getRowStartSecondary(mNumRows - 1) + getRowSizeSecondary(mNumRows - 1);
+    }
+
+    private void measureScrapChild(int position, int widthSpec, int heightSpec,
+            int[] measuredDimension) {
+        View view = mRecycler.getViewForPosition(position);
+        if (view != null) {
+            LayoutParams p = (LayoutParams) view.getLayoutParams();
+            int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
+                    getPaddingLeft() + getPaddingRight(), p.width);
+            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
+                    getPaddingTop() + getPaddingBottom(), p.height);
+            view.measure(childWidthSpec, childHeightSpec);
+            measuredDimension[0] = view.getMeasuredWidth();
+            measuredDimension[1] = view.getMeasuredHeight();
+            mRecycler.recycleView(view);
+        }
+    }
+
+    private boolean processRowSizeSecondary(boolean measure) {
+        if (mFixedRowSizeSecondary != 0) {
+            return false;
+        }
+
+        List<Integer>[] rows = mGrid == null ? null :
+            mGrid.getItemPositionsInRows(mFirstVisiblePos, mLastVisiblePos);
+        boolean changed = false;
+        int scrapChildWidth = -1;
+        int scrapChildHeight = -1;
+
+        for (int rowIndex = 0; rowIndex < mNumRows; rowIndex++) {
+            final int rowItemCount = rows == null ? 0 : rows[rowIndex].size();
+            if (DEBUG) Log.v(getTag(), "processRowSizeSecondary row " + rowIndex +
+                    " rowItemCount " + rowItemCount);
+
+            int rowSize = -1;
+            for (int i = 0; i < rowItemCount; i++) {
+                final View view = findViewByPosition(rows[rowIndex].get(i));
+                if (view == null) {
+                    continue;
+                }
+                if (measure && view.isLayoutRequested()) {
+                    measureChild(view);
+                }
+                final int secondarySize = mOrientation == HORIZONTAL ?
+                        view.getMeasuredHeight() : view.getMeasuredWidth();
+                if (secondarySize > rowSize) {
+                    rowSize = secondarySize;
+                }
+            }
+
+            if (measure && rowSize < 0 && mState.getItemCount() > 0) {
+                if (scrapChildWidth < 0 && scrapChildHeight < 0) {
+                    measureScrapChild(mFocusPosition == NO_POSITION ? 0 : mFocusPosition,
+                            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                            mMeasuredDimension);
+                    scrapChildWidth = mMeasuredDimension[0];
+                    scrapChildHeight = mMeasuredDimension[1];
+                    if (DEBUG) Log.v(TAG, "measured scrap child: " + scrapChildWidth +
+                            " " + scrapChildHeight);
+                }
+                rowSize = mOrientation == HORIZONTAL ? scrapChildHeight : scrapChildWidth;
+            }
+
+            if (rowSize < 0) {
+                rowSize = 0;
+            }
+
+            if (DEBUG) Log.v(getTag(), "row " + rowIndex + " rowItemCount " + rowItemCount +
+                    " rowSize " + rowSize);
+
+            if (mRowSizeSecondary[rowIndex] != rowSize) {
+                if (DEBUG) Log.v(getTag(), "row size secondary changed: " + mRowSizeSecondary[rowIndex] +
+                        ", " + rowSize);
+
+                mRowSizeSecondary[rowIndex] = rowSize;
+                changed = true;
+            }
+        }
+
+        return changed;
+    }
+
+    /**
+     * Checks if we need to update row secondary sizes.
+     */
+    private void updateRowSecondarySizeRefresh() {
+        mRowSecondarySizeRefresh = processRowSizeSecondary(false);
+        if (mRowSecondarySizeRefresh) {
+            if (DEBUG) Log.v(getTag(), "mRowSecondarySizeRefresh now set");
+            forceRequestLayout();
+        }
+    }
+
+    private void forceRequestLayout() {
+        if (DEBUG) Log.v(getTag(), "forceRequestLayout");
+        // RecyclerView prevents us from requesting layout in many cases
+        // (during layout, during scroll, etc.)
+        // For secondary row size wrap_content support we currently need a
+        // second layout pass to update the measured size after having measured
+        // and added child views in layoutChildren.
+        // Force the second layout by posting a delayed runnable.
+        // TODO: investigate allowing a second layout pass,
+        // or move child add/measure logic to the measure phase.
+        ViewCompat.postOnAnimation(mBaseGridView, mRequestLayoutRunnable);
+    }
+
+    private final Runnable mRequestLayoutRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (DEBUG) Log.v(getTag(), "request Layout from runnable");
+            requestLayout();
+        }
+     };
+
+    @Override
+    public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
+        saveContext(recycler, state);
+
+        int sizePrimary, sizeSecondary, modeSecondary, paddingSecondary;
+        int measuredSizeSecondary;
+        if (mOrientation == HORIZONTAL) {
+            sizePrimary = MeasureSpec.getSize(widthSpec);
+            sizeSecondary = MeasureSpec.getSize(heightSpec);
+            modeSecondary = MeasureSpec.getMode(heightSpec);
+            paddingSecondary = getPaddingTop() + getPaddingBottom();
+        } else {
+            sizeSecondary = MeasureSpec.getSize(widthSpec);
+            sizePrimary = MeasureSpec.getSize(heightSpec);
+            modeSecondary = MeasureSpec.getMode(widthSpec);
+            paddingSecondary = getPaddingLeft() + getPaddingRight();
+        }
+        if (DEBUG) Log.v(getTag(), "onMeasure widthSpec " + Integer.toHexString(widthSpec) +
+                " heightSpec " + Integer.toHexString(heightSpec) +
+                " modeSecondary " + Integer.toHexString(modeSecondary) +
+                " sizeSecondary " + sizeSecondary + " " + this);
+
+        mMaxSizeSecondary = sizeSecondary;
+
+        if (mRowSizeSecondaryRequested == ViewGroup.LayoutParams.WRAP_CONTENT) {
+            mNumRows = mNumRowsRequested == 0 ? 1 : mNumRowsRequested;
+            mFixedRowSizeSecondary = 0;
+
+            if (mRowSizeSecondary == null || mRowSizeSecondary.length != mNumRows) {
+                mRowSizeSecondary = new int[mNumRows];
+            }
+
+            // Measure all current children and update cached row heights
+            processRowSizeSecondary(true);
+
+            switch (modeSecondary) {
+            case MeasureSpec.UNSPECIFIED:
+                measuredSizeSecondary = getSizeSecondary() + paddingSecondary;
+                break;
+            case MeasureSpec.AT_MOST:
+                measuredSizeSecondary = Math.min(getSizeSecondary() + paddingSecondary,
+                        mMaxSizeSecondary);
+                break;
+            case MeasureSpec.EXACTLY:
+                measuredSizeSecondary = mMaxSizeSecondary;
+                break;
+            default:
+                throw new IllegalStateException("wrong spec");
+            }
+
+        } else {
+            switch (modeSecondary) {
+            case MeasureSpec.UNSPECIFIED:
+                if (mRowSizeSecondaryRequested == 0) {
+                    if (mOrientation == HORIZONTAL) {
+                        throw new IllegalStateException("Must specify rowHeight or view height");
+                    } else {
+                        throw new IllegalStateException("Must specify columnWidth or view width");
+                    }
+                }
+                mFixedRowSizeSecondary = mRowSizeSecondaryRequested;
+                mNumRows = mNumRowsRequested == 0 ? 1 : mNumRowsRequested;
+                measuredSizeSecondary = mFixedRowSizeSecondary * mNumRows + mMarginSecondary
+                    * (mNumRows - 1) + paddingSecondary;
+                break;
+            case MeasureSpec.AT_MOST:
+            case MeasureSpec.EXACTLY:
+                if (mNumRowsRequested == 0 && mRowSizeSecondaryRequested == 0) {
+                    mNumRows = 1;
+                    mFixedRowSizeSecondary = sizeSecondary - paddingSecondary;
+                } else if (mNumRowsRequested == 0) {
+                    mFixedRowSizeSecondary = mRowSizeSecondaryRequested;
+                    mNumRows = (sizeSecondary + mMarginSecondary)
+                        / (mRowSizeSecondaryRequested + mMarginSecondary);
+                } else if (mRowSizeSecondaryRequested == 0) {
+                    mNumRows = mNumRowsRequested;
+                    mFixedRowSizeSecondary = (sizeSecondary - paddingSecondary - mMarginSecondary
+                            * (mNumRows - 1)) / mNumRows;
+                } else {
+                    mNumRows = mNumRowsRequested;
+                    mFixedRowSizeSecondary = mRowSizeSecondaryRequested;
+                }
+                measuredSizeSecondary = sizeSecondary;
+                if (modeSecondary == MeasureSpec.AT_MOST) {
+                    int childrenSize = mFixedRowSizeSecondary * mNumRows + mMarginSecondary
+                        * (mNumRows - 1) + paddingSecondary;
+                    if (childrenSize < measuredSizeSecondary) {
+                        measuredSizeSecondary = childrenSize;
+                    }
+                }
+                break;
+            default:
+                throw new IllegalStateException("wrong spec");
+            }
+        }
+        if (mOrientation == HORIZONTAL) {
+            setMeasuredDimension(sizePrimary, measuredSizeSecondary);
+        } else {
+            setMeasuredDimension(measuredSizeSecondary, sizePrimary);
+        }
+        if (DEBUG) {
+            Log.v(getTag(), "onMeasure sizePrimary " + sizePrimary +
+                    " measuredSizeSecondary " + measuredSizeSecondary +
+                    " mFixedRowSizeSecondary " + mFixedRowSizeSecondary +
+                    " mNumRows " + mNumRows);
+        }
+
+        leaveContext();
+    }
+
+    private void measureChild(View child) {
+        final ViewGroup.LayoutParams lp = child.getLayoutParams();
+        final int secondarySpec = (mRowSizeSecondaryRequested == ViewGroup.LayoutParams.WRAP_CONTENT) ?
+                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) :
+                MeasureSpec.makeMeasureSpec(mFixedRowSizeSecondary, MeasureSpec.EXACTLY);
+        int widthSpec, heightSpec;
+
+        if (mOrientation == HORIZONTAL) {
+            widthSpec = ViewGroup.getChildMeasureSpec(
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                    0, lp.width);
+            heightSpec = ViewGroup.getChildMeasureSpec(secondarySpec, 0, lp.height);
+        } else {
+            heightSpec = ViewGroup.getChildMeasureSpec(
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                    0, lp.height);
+            widthSpec = ViewGroup.getChildMeasureSpec(secondarySpec, 0, lp.width);
+        }
+
+        child.measure(widthSpec, heightSpec);
+
+        if (DEBUG) Log.v(getTag(), "measureChild secondarySpec " + Integer.toHexString(secondarySpec) +
+                " widthSpec " + Integer.toHexString(widthSpec) +
+                " heightSpec " + Integer.toHexString(heightSpec) +
+                " measuredWidth " + child.getMeasuredWidth() +
+                " measuredHeight " + child.getMeasuredHeight());
+        if (DEBUG) Log.v(getTag(), "child lp width " + lp.width + " height " + lp.height);
+    }
+
+    private StaggeredGrid.Provider mGridProvider = new StaggeredGrid.Provider() {
+
+        @Override
+        public int getCount() {
+            return mState.getItemCount();
+        }
+
+        @Override
+        public void createItem(int index, int rowIndex, boolean append) {
+            View v = getViewForPosition(index);
+            if (mFirstVisiblePos >= 0) {
+                // when StaggeredGrid append or prepend item, we must guarantee
+                // that sibling item has created views already.
+                if (append && index != mLastVisiblePos + 1) {
+                    throw new RuntimeException();
+                } else if (!append && index != mFirstVisiblePos - 1) {
+                    throw new RuntimeException();
+                }
+            }
+
+            // See recyclerView docs:  we don't need re-add scraped view if it was removed.
+            if (!((RecyclerView.LayoutParams) v.getLayoutParams()).isItemRemoved()) {
+                if (append) {
+                    addView(v);
+                } else {
+                    addView(v, 0);
+                }
+                measureChild(v);
+            }
+
+            int length = mOrientation == HORIZONTAL ? v.getMeasuredWidth() : v.getMeasuredHeight();
+            int start, end;
+            if (append) {
+                start = mRows[rowIndex].high;
+                if (start != mRows[rowIndex].low) {
+                    // if there are existing item in the row,  add margin between
+                    start += mMarginPrimary;
+                } else {
+                    final int lastRow = mRows.length - 1;
+                    if (lastRow != rowIndex && mRows[lastRow].high != mRows[lastRow].low) {
+                        // if there are existing item in the last row, insert
+                        // the new item after the last item of last row.
+                        start = mRows[lastRow].high + mMarginPrimary;
+                    }
+                }
+                end = start + length;
+                mRows[rowIndex].high = end;
+            } else {
+                end = mRows[rowIndex].low;
+                if (end != mRows[rowIndex].high) {
+                    end -= mMarginPrimary;
+                } else if (0 != rowIndex && mRows[0].high != mRows[0].low) {
+                    // if there are existing item in the first row, insert
+                    // the new item before the first item of first row.
+                    end = mRows[0].low - mMarginPrimary;
+                }
+                start = end - length;
+                mRows[rowIndex].low = start;
+            }
+            if (mFirstVisiblePos < 0) {
+                mFirstVisiblePos = mLastVisiblePos = index;
+            } else {
+                if (append) {
+                    mLastVisiblePos++;
+                } else {
+                    mFirstVisiblePos--;
+                }
+            }
+            if (DEBUG) Log.v(getTag(), "start " + start + " end " + end);
+            int startSecondary = getRowStartSecondary(rowIndex) - mScrollOffsetSecondary;
+            layoutChild(rowIndex, v, start - mScrollOffsetPrimary, end - mScrollOffsetPrimary,
+                    startSecondary);
+            if (DEBUG) {
+                Log.d(getTag(), "addView " + index + " " + v);
+            }
+            if (index == mFirstVisiblePos) {
+                updateScrollMin();
+            }
+            if (index == mLastVisiblePos) {
+                updateScrollMax();
+            }
+        }
+    };
+
+    private void layoutChild(int rowIndex, View v, int start, int end, int startSecondary) {
+        int sizeSecondary = mOrientation == HORIZONTAL ? v.getMeasuredHeight()
+                : v.getMeasuredWidth();
+        if (mFixedRowSizeSecondary > 0) {
+            sizeSecondary = Math.min(sizeSecondary, mFixedRowSizeSecondary);
+        }
+        final int verticalGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+        final int horizontalGravity = mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+        if (mOrientation == HORIZONTAL && verticalGravity == Gravity.TOP
+                || mOrientation == VERTICAL && horizontalGravity == Gravity.LEFT) {
+            // do nothing
+        } else if (mOrientation == HORIZONTAL && verticalGravity == Gravity.BOTTOM
+                || mOrientation == VERTICAL && horizontalGravity == Gravity.RIGHT) {
+            startSecondary += getRowSizeSecondary(rowIndex) - sizeSecondary;
+        } else if (mOrientation == HORIZONTAL && verticalGravity == Gravity.CENTER_VERTICAL
+                || mOrientation == VERTICAL && horizontalGravity == Gravity.CENTER_HORIZONTAL) {
+            startSecondary += (getRowSizeSecondary(rowIndex) - sizeSecondary) / 2;
+        }
+        int left, top, right, bottom;
+        if (mOrientation == HORIZONTAL) {
+            left = start;
+            top = startSecondary;
+            right = end;
+            bottom = startSecondary + sizeSecondary;
+        } else {
+            top = start;
+            left = startSecondary;
+            bottom = end;
+            right = startSecondary + sizeSecondary;
+        }
+        v.layout(left, top, right, bottom);
+        updateChildOpticalInsets(v, left, top, right, bottom);
+        updateChildAlignments(v);
+    }
+
+    private void updateChildOpticalInsets(View v, int left, int top, int right, int bottom) {
+        LayoutParams p = (LayoutParams) v.getLayoutParams();
+        p.setOpticalInsets(left - v.getLeft(), top - v.getTop(),
+                v.getRight() - right, v.getBottom() - bottom);
+    }
+
+    private void updateChildAlignments(View v) {
+        LayoutParams p = (LayoutParams) v.getLayoutParams();
+        p.setAlignX(mItemAlignment.horizontal.getAlignmentPosition(v));
+        p.setAlignY(mItemAlignment.vertical.getAlignmentPosition(v));
+    }
+
+    private void updateChildAlignments() {
+        for (int i = 0, c = getChildCount(); i < c; i++) {
+            updateChildAlignments(getChildAt(i));
+        }
+    }
+
+    private boolean needsAppendVisibleItem() {
+        if (mLastVisiblePos < mFocusPosition) {
+            return true;
+        }
+        int right = mScrollOffsetPrimary + mSizePrimary;
+        for (int i = 0; i < mNumRows; i++) {
+            if (mRows[i].low == mRows[i].high) {
+                if (mRows[i].high < right) {
+                    return true;
+                }
+            } else if (mRows[i].high < right - mMarginPrimary) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean needsPrependVisibleItem() {
+        if (mFirstVisiblePos > mFocusPosition) {
+            return true;
+        }
+        for (int i = 0; i < mNumRows; i++) {
+            if (mRows[i].low == mRows[i].high) {
+                if (mRows[i].low > mScrollOffsetPrimary) {
+                    return true;
+                }
+            } else if (mRows[i].low - mMarginPrimary > mScrollOffsetPrimary) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // Append one column if possible and return true if reach end.
+    private boolean appendOneVisibleItem() {
+        while (true) {
+            if (mLastVisiblePos != NO_POSITION && mLastVisiblePos < mState.getItemCount() -1 &&
+                    mLastVisiblePos < mGrid.getLastIndex()) {
+                // append invisible view of saved location till last row
+                final int index = mLastVisiblePos + 1;
+                final int row = mGrid.getLocation(index).row;
+                mGridProvider.createItem(index, row, true);
+                if (row == mNumRows - 1) {
+                    return false;
+                }
+            } else if ((mLastVisiblePos == NO_POSITION && mState.getItemCount() > 0) ||
+                    (mLastVisiblePos != NO_POSITION &&
+                            mLastVisiblePos < mState.getItemCount() - 1)) {
+                mGrid.appendItems(mScrollOffsetPrimary + mSizePrimary);
+                return false;
+            } else {
+                return true;
+            }
+        }
+    }
+
+    private void appendVisibleItems() {
+        while (needsAppendVisibleItem()) {
+            if (appendOneVisibleItem()) {
+                break;
+            }
+        }
+    }
+
+    // Prepend one column if possible and return true if reach end.
+    private boolean prependOneVisibleItem() {
+        while (true) {
+            if (mFirstVisiblePos > 0) {
+                if (mFirstVisiblePos > mGrid.getFirstIndex()) {
+                    // prepend invisible view of saved location till first row
+                    final int index = mFirstVisiblePos - 1;
+                    final int row = mGrid.getLocation(index).row;
+                    mGridProvider.createItem(index, row, false);
+                    if (row == 0) {
+                        return false;
+                    }
+                } else {
+                    mGrid.prependItems(mScrollOffsetPrimary);
+                    return false;
+                }
+            } else {
+                return true;
+            }
+        }
+    }
+
+    private void prependVisibleItems() {
+        while (needsPrependVisibleItem()) {
+            if (prependOneVisibleItem()) {
+                break;
+            }
+        }
+    }
+
+    private void removeChildAt(int position) {
+        View v = findViewByPosition(position);
+        if (v != null) {
+            if (DEBUG) {
+                Log.d(getTag(), "removeAndRecycleViewAt " + position);
+            }
+            removeAndRecycleView(v, mRecycler);
+        }
+    }
+
+    private void removeInvisibleViewsAtEnd() {
+        if (!mPruneChild) {
+            return;
+        }
+        boolean update = false;
+        while(mLastVisiblePos > mFirstVisiblePos && mLastVisiblePos > mFocusPosition) {
+            View view = findViewByPosition(mLastVisiblePos);
+            if (getViewMin(view) > mSizePrimary) {
+                removeChildAt(mLastVisiblePos);
+                mLastVisiblePos--;
+                update = true;
+            } else {
+                break;
+            }
+        }
+        if (update) {
+            updateRowsMinMax();
+        }
+    }
+
+    private void removeInvisibleViewsAtFront() {
+        if (!mPruneChild) {
+            return;
+        }
+        boolean update = false;
+        while(mLastVisiblePos > mFirstVisiblePos && mFirstVisiblePos < mFocusPosition) {
+            View view = findViewByPosition(mFirstVisiblePos);
+            if (getViewMax(view) < 0) {
+                removeChildAt(mFirstVisiblePos);
+                mFirstVisiblePos++;
+                update = true;
+            } else {
+                break;
+            }
+        }
+        if (update) {
+            updateRowsMinMax();
+        }
+    }
+
+    private void updateRowsMinMax() {
+        if (mFirstVisiblePos < 0) {
+            return;
+        }
+        for (int i = 0; i < mNumRows; i++) {
+            mRows[i].low = Integer.MAX_VALUE;
+            mRows[i].high = Integer.MIN_VALUE;
+        }
+        for (int i = mFirstVisiblePos; i <= mLastVisiblePos; i++) {
+            View view = findViewByPosition(i);
+            int row = mGrid.getLocation(i).row;
+            int low = getViewMin(view) + mScrollOffsetPrimary;
+            if (low < mRows[row].low) {
+                mRows[row].low = low;
+            }
+            int high = getViewMax(view) + mScrollOffsetPrimary;
+            if (high > mRows[row].high) {
+                mRows[row].high = high;
+            }
+        }
+    }
+
+    // Fast layout when there is no structure change, adapter change, etc.
+    protected void fastRelayout() {
+        initScrollController();
+
+        List<Integer>[] rows = mGrid.getItemPositionsInRows(mFirstVisiblePos, mLastVisiblePos);
+
+        // relayout and repositioning views on each row
+        for (int i = 0; i < mNumRows; i++) {
+            List<Integer> row = rows[i];
+            final int startSecondary = getRowStartSecondary(i) - mScrollOffsetSecondary;
+            for (int j = 0, size = row.size(); j < size; j++) {
+                final int position = row.get(j);
+                final View view = findViewByPosition(position);
+                int primaryDelta, start, end;
+
+                if (mOrientation == HORIZONTAL) {
+                    final int primarySize = view.getMeasuredWidth();
+                    if (view.isLayoutRequested()) {
+                        measureChild(view);
+                    }
+                    start = getViewMin(view);
+                    end = start + view.getMeasuredWidth();
+                    primaryDelta = view.getMeasuredWidth() - primarySize;
+                    if (primaryDelta != 0) {
+                        for (int k = j + 1; k < size; k++) {
+                            findViewByPosition(row.get(k)).offsetLeftAndRight(primaryDelta);
+                        }
+                    }
+                } else {
+                    final int primarySize = view.getMeasuredHeight();
+                    if (view.isLayoutRequested()) {
+                        measureChild(view);
+                    }
+                    start = getViewMin(view);
+                    end = start + view.getMeasuredHeight();
+                    primaryDelta = view.getMeasuredHeight() - primarySize;
+                    if (primaryDelta != 0) {
+                        for (int k = j + 1; k < size; k++) {
+                            findViewByPosition(row.get(k)).offsetTopAndBottom(primaryDelta);
+                        }
+                    }
+                }
+                layoutChild(i, view, start, end, startSecondary);
+            }
+        }
+
+        updateRowsMinMax();
+        appendVisibleItems();
+        prependVisibleItems();
+
+        updateRowsMinMax();
+        updateScrollMin();
+        updateScrollMax();
+        updateScrollSecondAxis();
+
+        if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED) {
+            View focusView = findViewByPosition(mFocusPosition == NO_POSITION ? 0 : mFocusPosition);
+            scrollToView(focusView, false);
+        }
+    }
+
+    public void removeAndRecycleAllViews(RecyclerView.Recycler recycler) {
+        if (DEBUG) Log.v(TAG, "removeAndRecycleAllViews " + getChildCount());
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            removeAndRecycleViewAt(i, recycler);
+        }
+    }
+
+    // Lays out items based on the current scroll position
+    @Override
+    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+        if (DEBUG) {
+            Log.v(getTag(), "layoutChildren start numRows " + mNumRows + " mScrollOffsetSecondary "
+                    + mScrollOffsetSecondary + " mScrollOffsetPrimary " + mScrollOffsetPrimary
+                    + " inPreLayout " + state.isPreLayout()
+                    + " didStructureChange " + state.didStructureChange()
+                    + " mForceFullLayout " + mForceFullLayout);
+            Log.v(getTag(), "width " + getWidth() + " height " + getHeight());
+        }
+
+        if (mNumRows == 0) {
+            // haven't done measure yet
+            return;
+        }
+        final int itemCount = state.getItemCount();
+        if (itemCount < 0) {
+            return;
+        }
+
+        if (!mLayoutEnabled) {
+            discardLayoutInfo();
+            removeAndRecycleAllViews(recycler);
+            return;
+        }
+        mInLayout = true;
+
+        saveContext(recycler, state);
+        // Track the old focus view so we can adjust our system scroll position
+        // so that any scroll animations happening now will remain valid.
+        // We must use same delta in Pre Layout (if prelayout exists) and second layout.
+        // So we cache the deltas in PreLayout and use it in second layout.
+        int delta = 0, deltaSecondary = 0;
+        if (!state.isPreLayout() && mUseDeltaInPreLayout) {
+            delta = mDeltaInPreLayout;
+            deltaSecondary = mDeltaSecondaryInPreLayout;
+        } else {
+            if (mFocusPosition != NO_POSITION
+                    && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED) {
+                // FIXME: we should get the remaining scroll animation offset from RecyclerView
+                View focusView = findViewByPosition(mFocusPosition);
+                if (focusView != null) {
+                    delta = mWindowAlignment.mainAxis().getSystemScrollPos(mScrollOffsetPrimary
+                            + getViewCenter(focusView), false, false) - mScrollOffsetPrimary;
+                    deltaSecondary = mWindowAlignment.secondAxis().getSystemScrollPos(
+                            mScrollOffsetSecondary + getViewCenterSecondary(focusView),
+                            false, false) - mScrollOffsetSecondary;
+                    if (mUseDeltaInPreLayout = state.isPreLayout()) {
+                        mDeltaInPreLayout = delta;
+                        mDeltaSecondaryInPreLayout = deltaSecondary;
+                    }
+                }
+            }
+        }
+
+        final boolean hasDoneFirstLayout = hasDoneFirstLayout();
+        int savedFocusPos = mFocusPosition;
+        boolean fastRelayout = false;
+        if (!mState.didStructureChange() && !mForceFullLayout && hasDoneFirstLayout) {
+            fastRelayout = true;
+            fastRelayout();
+        } else {
+            boolean hadFocus = mBaseGridView.hasFocus();
+
+            int newFocusPosition = init(mFocusPosition);
+            if (DEBUG) {
+                Log.v(getTag(), "mFocusPosition " + mFocusPosition + " newFocusPosition "
+                    + newFocusPosition);
+            }
+
+            // depending on result of init(), either recreating everything
+            // or try to reuse the row start positions near mFocusPosition
+            if (mGrid.getSize() == 0) {
+                // this is a fresh creating all items, starting from
+                // mFocusPosition with a estimated row index.
+                mGrid.setStart(newFocusPosition, StaggeredGrid.START_DEFAULT);
+
+                // Can't track the old focus view
+                delta = deltaSecondary = 0;
+
+            } else {
+                // mGrid remembers Locations for the column that
+                // contains mFocusePosition and also mRows remembers start
+                // positions of each row.
+                // Manually re-create child views for that column
+                int firstIndex = mGrid.getFirstIndex();
+                int lastIndex = mGrid.getLastIndex();
+                for (int i = firstIndex; i <= lastIndex; i++) {
+                    mGridProvider.createItem(i, mGrid.getLocation(i).row, true);
+                }
+            }
+            // add visible views at end until reach the end of window
+            appendVisibleItems();
+            // add visible views at front until reach the start of window
+            prependVisibleItems();
+            // multiple rounds: scrollToView of first round may drag first/last child into
+            // "visible window" and we update scrollMin/scrollMax then run second scrollToView
+            int oldFirstVisible;
+            int oldLastVisible;
+            do {
+                oldFirstVisible = mFirstVisiblePos;
+                oldLastVisible = mLastVisiblePos;
+                View focusView = findViewByPosition(newFocusPosition);
+                // we need force to initialize the child view's position
+                scrollToView(focusView, false);
+                if (focusView != null && hadFocus) {
+                    focusView.requestFocus();
+                }
+                appendVisibleItems();
+                prependVisibleItems();
+                removeInvisibleViewsAtFront();
+                removeInvisibleViewsAtEnd();
+            } while (mFirstVisiblePos != oldFirstVisible || mLastVisiblePos != oldLastVisible);
+        }
+        mForceFullLayout = false;
+
+        if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED) {
+            scrollDirectionPrimary(-delta);
+            scrollDirectionSecondary(-deltaSecondary);
+        }
+        appendVisibleItems();
+        prependVisibleItems();
+        removeInvisibleViewsAtFront();
+        removeInvisibleViewsAtEnd();
+
+        if (DEBUG) {
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw);
+            mGrid.debugPrint(pw);
+            Log.d(getTag(), sw.toString());
+        }
+
+        if (mRowSecondarySizeRefresh) {
+            mRowSecondarySizeRefresh = false;
+        } else {
+            updateRowSecondarySizeRefresh();
+        }
+
+        if (!state.isPreLayout()) {
+            mUseDeltaInPreLayout = false;
+            if (!fastRelayout || mFocusPosition != savedFocusPos) {
+                dispatchChildSelected();
+            }
+        }
+        mInLayout = false;
+        leaveContext();
+        if (DEBUG) Log.v(getTag(), "layoutChildren end");
+    }
+
+    private void offsetChildrenSecondary(int increment) {
+        final int childCount = getChildCount();
+        if (mOrientation == HORIZONTAL) {
+            for (int i = 0; i < childCount; i++) {
+                getChildAt(i).offsetTopAndBottom(increment);
+            }
+        } else {
+            for (int i = 0; i < childCount; i++) {
+                getChildAt(i).offsetLeftAndRight(increment);
+            }
+        }
+    }
+
+    private void offsetChildrenPrimary(int increment) {
+        final int childCount = getChildCount();
+        if (mOrientation == VERTICAL) {
+            for (int i = 0; i < childCount; i++) {
+                getChildAt(i).offsetTopAndBottom(increment);
+            }
+        } else {
+            for (int i = 0; i < childCount; i++) {
+                getChildAt(i).offsetLeftAndRight(increment);
+            }
+        }
+    }
+
+    @Override
+    public int scrollHorizontallyBy(int dx, Recycler recycler, RecyclerView.State state) {
+        if (DEBUG) Log.v(getTag(), "scrollHorizontallyBy " + dx);
+        if (!mLayoutEnabled || !hasDoneFirstLayout()) {
+            return 0;
+        }
+        saveContext(recycler, state);
+        int result;
+        if (mOrientation == HORIZONTAL) {
+            result = scrollDirectionPrimary(dx);
+        } else {
+            result = scrollDirectionSecondary(dx);
+        }
+        leaveContext();
+        return result;
+    }
+
+    @Override
+    public int scrollVerticallyBy(int dy, Recycler recycler, RecyclerView.State state) {
+        if (DEBUG) Log.v(getTag(), "scrollVerticallyBy " + dy);
+        if (!mLayoutEnabled || !hasDoneFirstLayout()) {
+            return 0;
+        }
+        saveContext(recycler, state);
+        int result;
+        if (mOrientation == VERTICAL) {
+            result = scrollDirectionPrimary(dy);
+        } else {
+            result = scrollDirectionSecondary(dy);
+        }
+        leaveContext();
+        return result;
+    }
+
+    // scroll in main direction may add/prune views
+    private int scrollDirectionPrimary(int da) {
+        if (da > 0) {
+            if (!mWindowAlignment.mainAxis().isMaxUnknown()) {
+                int maxScroll = mWindowAlignment.mainAxis().getMaxScroll();
+                if (mScrollOffsetPrimary + da > maxScroll) {
+                    da = maxScroll - mScrollOffsetPrimary;
+                }
+            }
+        } else if (da < 0) {
+            if (!mWindowAlignment.mainAxis().isMinUnknown()) {
+                int minScroll = mWindowAlignment.mainAxis().getMinScroll();
+                if (mScrollOffsetPrimary + da < minScroll) {
+                    da = minScroll - mScrollOffsetPrimary;
+                }
+            }
+        }
+        if (da == 0) {
+            return 0;
+        }
+        offsetChildrenPrimary(-da);
+        mScrollOffsetPrimary += da;
+        if (mInLayout) {
+            return da;
+        }
+
+        int childCount = getChildCount();
+        boolean updated;
+
+        if (da > 0) {
+            appendVisibleItems();
+        } else if (da < 0) {
+            prependVisibleItems();
+        }
+        updated = getChildCount() > childCount;
+        childCount = getChildCount();
+
+        if (da > 0) {
+            removeInvisibleViewsAtFront();
+        } else if (da < 0) {
+            removeInvisibleViewsAtEnd();
+        }
+        updated |= getChildCount() < childCount;
+
+        if (updated) {
+            updateRowSecondarySizeRefresh();
+        }
+
+        mBaseGridView.invalidate();
+        return da;
+    }
+
+    // scroll in second direction will not add/prune views
+    private int scrollDirectionSecondary(int dy) {
+        if (dy == 0) {
+            return 0;
+        }
+        offsetChildrenSecondary(-dy);
+        mScrollOffsetSecondary += dy;
+        mBaseGridView.invalidate();
+        return dy;
+    }
+
+    private void updateScrollMax() {
+        if (mLastVisiblePos < 0) {
+            return;
+        }
+        final boolean lastAvailable = mLastVisiblePos == mState.getItemCount() - 1;
+        final boolean maxUnknown = mWindowAlignment.mainAxis().isMaxUnknown();
+        if (!lastAvailable && maxUnknown) {
+            return;
+        }
+        int maxEdge = Integer.MIN_VALUE;
+        int rowIndex = -1;
+        for (int i = 0; i < mRows.length; i++) {
+            if (mRows[i].high > maxEdge) {
+                maxEdge = mRows[i].high;
+                rowIndex = i;
+            }
+        }
+        int maxScroll = Integer.MAX_VALUE;
+        for (int i = mLastVisiblePos; i >= mFirstVisiblePos; i--) {
+            StaggeredGrid.Location location = mGrid.getLocation(i);
+            if (location != null && location.row == rowIndex) {
+                int savedMaxEdge = mWindowAlignment.mainAxis().getMaxEdge();
+                mWindowAlignment.mainAxis().setMaxEdge(maxEdge);
+                maxScroll = getPrimarySystemScrollPosition(findViewByPosition(i));
+                mWindowAlignment.mainAxis().setMaxEdge(savedMaxEdge);
+                break;
+            }
+        }
+        if (lastAvailable) {
+            mWindowAlignment.mainAxis().setMaxEdge(maxEdge);
+            mWindowAlignment.mainAxis().setMaxScroll(maxScroll);
+            if (DEBUG) Log.v(getTag(), "updating scroll maxEdge to " + maxEdge +
+                    " scrollMax to " + maxScroll);
+        } else {
+            // the maxScroll for currently last visible item is larger,
+            // so we must invalidate the max scroll value.
+            if (maxScroll > mWindowAlignment.mainAxis().getMaxScroll()) {
+                mWindowAlignment.mainAxis().invalidateScrollMax();
+                if (DEBUG) Log.v(getTag(), "Invalidate scrollMax since it should be "
+                        + "greater than " + maxScroll);
+            }
+        }
+    }
+
+    private void updateScrollMin() {
+        if (mFirstVisiblePos < 0) {
+            return;
+        }
+        final boolean firstAvailable = mFirstVisiblePos == 0;
+        final boolean minUnknown = mWindowAlignment.mainAxis().isMinUnknown();
+        if (!firstAvailable && minUnknown) {
+            return;
+        }
+        int minEdge = Integer.MAX_VALUE;
+        int rowIndex = -1;
+        for (int i = 0; i < mRows.length; i++) {
+            if (mRows[i].low < minEdge) {
+                minEdge = mRows[i].low;
+                rowIndex = i;
+            }
+        }
+        int minScroll = Integer.MIN_VALUE;
+        for (int i = mFirstVisiblePos; i <= mLastVisiblePos; i++) {
+            StaggeredGrid.Location location = mGrid.getLocation(i);
+            if (location != null && location.row == rowIndex) {
+                int savedMinEdge = mWindowAlignment.mainAxis().getMinEdge();
+                mWindowAlignment.mainAxis().setMinEdge(minEdge);
+                minScroll = getPrimarySystemScrollPosition(findViewByPosition(i));
+                mWindowAlignment.mainAxis().setMinEdge(savedMinEdge);
+                break;
+            }
+        }
+        if (firstAvailable) {
+            mWindowAlignment.mainAxis().setMinEdge(minEdge);
+            mWindowAlignment.mainAxis().setMinScroll(minScroll);
+            if (DEBUG) Log.v(getTag(), "updating scroll minEdge to " + minEdge +
+                    " scrollMin to " + minScroll);
+        } else {
+            // the minScroll for currently first visible item is smaller,
+            // so we must invalidate the min scroll value.
+            if (minScroll < mWindowAlignment.mainAxis().getMinScroll()) {
+                mWindowAlignment.mainAxis().invalidateScrollMin();
+                if (DEBUG) Log.v(getTag(), "Invalidate scrollMin, since it should be "
+                        + "less than " + minScroll);
+            }
+        }
+    }
+
+    private void updateScrollSecondAxis() {
+        mWindowAlignment.secondAxis().setMinEdge(0);
+        mWindowAlignment.secondAxis().setMaxEdge(getSizeSecondary());
+    }
+
+    private void initScrollController() {
+        mWindowAlignment.horizontal.setSize(getWidth());
+        mWindowAlignment.horizontal.setPadding(getPaddingLeft(), getPaddingRight());
+        mWindowAlignment.vertical.setSize(getHeight());
+        mWindowAlignment.vertical.setPadding(getPaddingTop(), getPaddingBottom());
+        mSizePrimary = mWindowAlignment.mainAxis().getSize();
+
+        if (DEBUG) {
+            Log.v(getTag(), "initScrollController mSizePrimary " + mSizePrimary
+                    + " mWindowAlignment " + mWindowAlignment);
+        }
+    }
+
+    public void setSelection(RecyclerView parent, int position) {
+        setSelection(parent, position, false);
+    }
+
+    public void setSelectionSmooth(RecyclerView parent, int position) {
+        setSelection(parent, position, true);
+    }
+
+    public int getSelection() {
+        return mFocusPosition;
+    }
+
+    public void setSelection(RecyclerView parent, int position, boolean smooth) {
+        if (mFocusPosition == position) {
+            return;
+        }
+        View view = findViewByPosition(position);
+        if (view != null) {
+            scrollToView(view, smooth);
+        } else {
+            mFocusPosition = position;
+            if (!mLayoutEnabled) {
+                return;
+            }
+            if (smooth) {
+                if (!hasDoneFirstLayout()) {
+                    Log.w(getTag(), "setSelectionSmooth should " +
+                            "not be called before first layout pass");
+                    return;
+                }
+                LinearSmoothScroller linearSmoothScroller =
+                        new LinearSmoothScroller(parent.getContext()) {
+                    @Override
+                    public PointF computeScrollVectorForPosition(int targetPosition) {
+                        if (getChildCount() == 0) {
+                            return null;
+                        }
+                        final int firstChildPos = getPosition(getChildAt(0));
+                        final int direction = targetPosition < firstChildPos ? -1 : 1;
+                        if (mOrientation == HORIZONTAL) {
+                            return new PointF(direction, 0);
+                        } else {
+                            return new PointF(0, direction);
+                        }
+                    }
+                    @Override
+                    protected void onTargetFound(View targetView,
+                            RecyclerView.State state, Action action) {
+                        if (hasFocus()) {
+                            targetView.requestFocus();
+                        }
+                        dispatchChildSelected();
+                        if (getScrollPosition(targetView, mTempDeltas)) {
+                            int dx, dy;
+                            if (mOrientation == HORIZONTAL) {
+                                dx = mTempDeltas[0];
+                                dy = mTempDeltas[1];
+                            } else {
+                                dx = mTempDeltas[1];
+                                dy = mTempDeltas[0];
+                            }
+                            final int distance = (int) Math.sqrt(dx * dx + dy * dy);
+                            final int time = calculateTimeForDeceleration(distance);
+                            action.update(dx, dy, time, mDecelerateInterpolator);
+                        }
+                    }
+                };
+                linearSmoothScroller.setTargetPosition(position);
+                startSmoothScroll(linearSmoothScroller);
+            } else {
+                mForceFullLayout = true;
+                parent.requestLayout();
+            }
+        }
+    }
+
+    @Override
+    public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
+        boolean needsLayout = false;
+        if (itemCount != 0) {
+            if (mFirstVisiblePos < 0) {
+                needsLayout = true;
+            } else if (!(positionStart > mLastVisiblePos + 1 ||
+                    positionStart + itemCount < mFirstVisiblePos - 1)) {
+                needsLayout = true;
+            }
+        }
+        if (needsLayout) {
+            recyclerView.requestLayout();
+        }
+    }
+
+    @Override
+    public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) {
+        if (mFocusSearchDisabled) {
+            return true;
+        }
+        if (!mInLayout) {
+            scrollToView(child, true);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean requestChildRectangleOnScreen(RecyclerView parent, View view, Rect rect,
+            boolean immediate) {
+        if (DEBUG) Log.v(getTag(), "requestChildRectangleOnScreen " + view + " " + rect);
+        return false;
+    }
+
+    int getScrollOffsetX() {
+        return mOrientation == HORIZONTAL ? mScrollOffsetPrimary : mScrollOffsetSecondary;
+    }
+
+    int getScrollOffsetY() {
+        return mOrientation == HORIZONTAL ? mScrollOffsetSecondary : mScrollOffsetPrimary;
+    }
+
+    public void getViewSelectedOffsets(View view, int[] offsets) {
+        if (mOrientation == HORIZONTAL) {
+            offsets[0] = getPrimarySystemScrollPosition(view) - mScrollOffsetPrimary;
+            offsets[1] = getSecondarySystemScrollPosition(view) - mScrollOffsetSecondary;
+        } else {
+            offsets[1] = getPrimarySystemScrollPosition(view) - mScrollOffsetPrimary;
+            offsets[0] = getSecondarySystemScrollPosition(view) - mScrollOffsetSecondary;
+        }
+    }
+
+    private int getPrimarySystemScrollPosition(View view) {
+        int viewCenterPrimary = mScrollOffsetPrimary + getViewCenter(view);
+        int pos = getPositionByView(view);
+        StaggeredGrid.Location location = mGrid.getLocation(pos);
+        final int row = location.row;
+        boolean isFirst = mFirstVisiblePos == 0;
+        // TODO: change to use State object in onRequestChildFocus()
+        boolean isLast = mLastVisiblePos == (mState == null ?
+                getItemCount() : mState.getItemCount()) - 1;
+        if (isFirst || isLast) {
+            for (int i = getChildCount() - 1; i >= 0; i--) {
+                int position = getPositionByIndex(i);
+                StaggeredGrid.Location loc = mGrid.getLocation(position);
+                if (loc != null && loc.row == row) {
+                    if (position < pos) {
+                        isFirst = false;
+                    } else if (position > pos) {
+                        isLast = false;
+                    }
+                }
+            }
+        }
+        return mWindowAlignment.mainAxis().getSystemScrollPos(viewCenterPrimary, isFirst, isLast);
+    }
+
+    private int getSecondarySystemScrollPosition(View view) {
+        int viewCenterSecondary = mScrollOffsetSecondary + getViewCenterSecondary(view);
+        int pos = getPositionByView(view);
+        StaggeredGrid.Location location = mGrid.getLocation(pos);
+        final int row = location.row;
+        boolean isFirst = row == 0;
+        boolean isLast = row == mGrid.getNumRows() - 1;
+        return mWindowAlignment.secondAxis().getSystemScrollPos(viewCenterSecondary,
+                isFirst, isLast);
+    }
+
+    /**
+     * Scroll to a given child view and change mFocusPosition.
+     */
+    private void scrollToView(View view, boolean smooth) {
+        int newFocusPosition = getPositionByView(view);
+        if (newFocusPosition != mFocusPosition) {
+            mFocusPosition = newFocusPosition;
+            if (!mInLayout) {
+                dispatchChildSelected();
+            }
+        }
+        if (mBaseGridView.isChildrenDrawingOrderEnabledInternal()) {
+            mBaseGridView.invalidate();
+        }
+        if (view == null) {
+            return;
+        }
+        if (!view.hasFocus() && mBaseGridView.hasFocus()) {
+            // transfer focus to the child if it does not have focus yet (e.g. triggered
+            // by setSelection())
+            view.requestFocus();
+        }
+        if (!mScrollEnabled) {
+            return;
+        }
+        if (getScrollPosition(view, mTempDeltas)) {
+            scrollGrid(mTempDeltas[0], mTempDeltas[1], smooth);
+        }
+    }
+
+    private boolean getScrollPosition(View view, int[] deltas) {
+        switch (mFocusScrollStrategy) {
+        case BaseGridView.FOCUS_SCROLL_ALIGNED:
+        default:
+            return getAlignedPosition(view, deltas);
+        case BaseGridView.FOCUS_SCROLL_ITEM:
+        case BaseGridView.FOCUS_SCROLL_PAGE:
+            return getNoneAlignedPosition(view, deltas);
+        }
+    }
+
+    private boolean getNoneAlignedPosition(View view, int[] deltas) {
+        int pos = getPositionByView(view);
+        int viewMin = getViewMin(view);
+        int viewMax = getViewMax(view);
+        // we either align "firstView" to left/top padding edge
+        // or align "lastView" to right/bottom padding edge
+        View firstView = null;
+        View lastView = null;
+        int paddingLow = mWindowAlignment.mainAxis().getPaddingLow();
+        int clientSize = mWindowAlignment.mainAxis().getClientSize();
+        final int row = mGrid.getLocation(pos).row;
+        if (viewMin < paddingLow) {
+            // view enters low padding area:
+            firstView = view;
+            if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_PAGE) {
+                // scroll one "page" left/top,
+                // align first visible item of the "page" at the low padding edge.
+                while (!prependOneVisibleItem()) {
+                    List<Integer> positions =
+                            mGrid.getItemPositionsInRows(mFirstVisiblePos, pos)[row];
+                    firstView = findViewByPosition(positions.get(0));
+                    if (viewMax - getViewMin(firstView) > clientSize) {
+                        if (positions.size() > 1) {
+                            firstView = findViewByPosition(positions.get(1));
+                        }
+                        break;
+                    }
+                }
+            }
+        } else if (viewMax > clientSize + paddingLow) {
+            // view enters high padding area:
+            if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_PAGE) {
+                // scroll whole one page right/bottom, align view at the low padding edge.
+                firstView = view;
+                do {
+                    List<Integer> positions =
+                            mGrid.getItemPositionsInRows(pos, mLastVisiblePos)[row];
+                    lastView = findViewByPosition(positions.get(positions.size() - 1));
+                    if (getViewMax(lastView) - viewMin > clientSize) {
+                        lastView = null;
+                        break;
+                    }
+                } while (!appendOneVisibleItem());
+                if (lastView != null) {
+                    // however if we reached end,  we should align last view.
+                    firstView = null;
+                }
+            } else {
+                lastView = view;
+            }
+        }
+        int scrollPrimary = 0;
+        int scrollSecondary = 0;
+        if (firstView != null) {
+            scrollPrimary = getViewMin(firstView) - paddingLow;
+        } else if (lastView != null) {
+            scrollPrimary = getViewMax(lastView) - (paddingLow + clientSize);
+        }
+        View secondaryAlignedView;
+        if (firstView != null) {
+            secondaryAlignedView = firstView;
+        } else if (lastView != null) {
+            secondaryAlignedView = lastView;
+        } else {
+            secondaryAlignedView = view;
+        }
+        scrollSecondary = getSecondarySystemScrollPosition(secondaryAlignedView);
+        scrollSecondary -= mScrollOffsetSecondary;
+        if (scrollPrimary != 0 || scrollSecondary != 0) {
+            deltas[0] = scrollPrimary;
+            deltas[1] = scrollSecondary;
+            return true;
+        }
+        return false;
+    }
+
+    private boolean getAlignedPosition(View view, int[] deltas) {
+        int scrollPrimary = getPrimarySystemScrollPosition(view);
+        int scrollSecondary = getSecondarySystemScrollPosition(view);
+        if (DEBUG) {
+            Log.v(getTag(), "getAlignedPosition " + scrollPrimary + " " + scrollSecondary
+                    +" " + mWindowAlignment);
+        }
+        scrollPrimary -= mScrollOffsetPrimary;
+        scrollSecondary -= mScrollOffsetSecondary;
+        if (scrollPrimary != 0 || scrollSecondary != 0) {
+            deltas[0] = scrollPrimary;
+            deltas[1] = scrollSecondary;
+            return true;
+        }
+        return false;
+    }
+
+    private void scrollGrid(int scrollPrimary, int scrollSecondary, boolean smooth) {
+        if (mInLayout) {
+            scrollDirectionPrimary(scrollPrimary);
+            scrollDirectionSecondary(scrollSecondary);
+        } else {
+            int scrollX;
+            int scrollY;
+            if (mOrientation == HORIZONTAL) {
+                scrollX = scrollPrimary;
+                scrollY = scrollSecondary;
+            } else {
+                scrollX = scrollSecondary;
+                scrollY = scrollPrimary;
+            }
+            if (smooth) {
+                mBaseGridView.smoothScrollBy(scrollX, scrollY);
+            } else {
+                mBaseGridView.scrollBy(scrollX, scrollY);
+            }
+        }
+    }
+
+    public void setPruneChild(boolean pruneChild) {
+        if (mPruneChild != pruneChild) {
+            mPruneChild = pruneChild;
+            if (mPruneChild) {
+                requestLayout();
+            }
+        }
+    }
+
+    public boolean getPruneChild() {
+        return mPruneChild;
+    }
+
+    public void setScrollEnabled(boolean scrollEnabled) {
+        if (mScrollEnabled != scrollEnabled) {
+            mScrollEnabled = scrollEnabled;
+            if (mScrollEnabled && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED) {
+                View focusView = findViewByPosition(mFocusPosition == NO_POSITION ? 0 :
+                    mFocusPosition);
+                if (focusView != null) {
+                    scrollToView(focusView, true);
+                }
+            }
+        }
+    }
+
+    public boolean isScrollEnabled() {
+        return mScrollEnabled;
+    }
+
+    private int findImmediateChildIndex(View view) {
+        while (view != null && view != mBaseGridView) {
+            int index = mBaseGridView.indexOfChild(view);
+            if (index >= 0) {
+                return index;
+            }
+            view = (View) view.getParent();
+        }
+        return NO_POSITION;
+    }
+
+    void setFocusSearchDisabled(boolean disabled) {
+        mFocusSearchDisabled = disabled;
+    }
+
+    boolean isFocusSearchDisabled() {
+        return mFocusSearchDisabled;
+    }
+
+    @Override
+    public View onInterceptFocusSearch(View focused, int direction) {
+        if (mFocusSearchDisabled) {
+            return focused;
+        }
+        return null;
+    }
+
+    boolean hasPreviousViewInSameRow(int pos) {
+        if (mGrid == null || pos == NO_POSITION) {
+            return false;
+        }
+        if (mFirstVisiblePos > 0) {
+            return true;
+        }
+        final int focusedRow = mGrid.getLocation(pos).row;
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            int position = getPositionByIndex(i);
+            StaggeredGrid.Location loc = mGrid.getLocation(position);
+            if (loc != null && loc.row == focusedRow) {
+                if (position < pos) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onAddFocusables(RecyclerView recyclerView,
+            ArrayList<View> views, int direction, int focusableMode) {
+        if (mFocusSearchDisabled) {
+            return true;
+        }
+        // If this viewgroup or one of its children currently has focus then we
+        // consider our children for focus searching in main direction on the same row.
+        // If this viewgroup has no focus and using focus align, we want the system
+        // to ignore our children and pass focus to the viewgroup, which will pass
+        // focus on to its children appropriately.
+        // If this viewgroup has no focus and not using focus align, we want to
+        // consider the child that does not overlap with padding area.
+        if (recyclerView.hasFocus()) {
+            final int movement = getMovement(direction);
+            if (movement != PREV_ITEM && movement != NEXT_ITEM) {
+                // Move on secondary direction uses default addFocusables().
+                return false;
+            }
+            final View focused = recyclerView.findFocus();
+            final int focusedPos = getPositionByIndex(findImmediateChildIndex(focused));
+            // Add focusables of focused item.
+            if (focusedPos != NO_POSITION) {
+                findViewByPosition(focusedPos).addFocusables(views,  direction, focusableMode);
+            }
+            final int focusedRow = mGrid != null && focusedPos != NO_POSITION ?
+                    mGrid.getLocation(focusedPos).row : NO_POSITION;
+            // Add focusables of next neighbor of same row on the focus search direction.
+            if (mGrid != null) {
+                final int focusableCount = views.size();
+                for (int i = 0, count = getChildCount(); i < count; i++) {
+                    int index = movement == NEXT_ITEM ? i : count - 1 - i;
+                    final View child = getChildAt(index);
+                    if (child.getVisibility() != View.VISIBLE) {
+                        continue;
+                    }
+                    int position = getPositionByIndex(index);
+                    StaggeredGrid.Location loc = mGrid.getLocation(position);
+                    if (focusedRow == NO_POSITION || (loc != null && loc.row == focusedRow)) {
+                        if (focusedPos == NO_POSITION || 
+                                (movement == NEXT_ITEM && position > focusedPos)
+                                || (movement == PREV_ITEM && position < focusedPos)) {
+                            child.addFocusables(views,  direction, focusableMode);
+                            if (views.size() > focusableCount) {
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+        } else {
+            if (mFocusScrollStrategy != BaseGridView.FOCUS_SCROLL_ALIGNED) {
+                // adding views not overlapping padding area to avoid scrolling in gaining focus
+                int left = mWindowAlignment.mainAxis().getPaddingLow();
+                int right = mWindowAlignment.mainAxis().getClientSize() + left;
+                int focusableCount = views.size();
+                for (int i = 0, count = getChildCount(); i < count; i++) {
+                    View child = getChildAt(i);
+                    if (child.getVisibility() == View.VISIBLE) {
+                        if (getViewMin(child) >= left && getViewMax(child) <= right) {
+                            child.addFocusables(views, direction, focusableMode);
+                        }
+                    }
+                }
+                // if we cannot find any, then just add all children.
+                if (views.size() == focusableCount) {
+                    for (int i = 0, count = getChildCount(); i < count; i++) {
+                        View child = getChildAt(i);
+                        if (child.getVisibility() == View.VISIBLE) {
+                            child.addFocusables(views, direction, focusableMode);
+                        }
+                    }
+                    if (views.size() != focusableCount) {
+                        return true;
+                    }
+                } else {
+                    return true;
+                }
+                // if still cannot find any, fall through and add itself
+            }
+            if (recyclerView.isFocusable()) {
+                views.add(recyclerView);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public View onFocusSearchFailed(View focused, int direction, Recycler recycler,
+            RecyclerView.State state) {
+        if (DEBUG) Log.v(getTag(), "onFocusSearchFailed direction " + direction);
+
+        View view = null;
+        int movement = getMovement(direction);
+        if (mNumRows == 1) {
+            // for simple row, use LinearSmoothScroller to smooth animation.
+            // It will stay at a fixed cap speed in continuous scroll.
+            if (movement == NEXT_ITEM) {
+                int newPos = mFocusPosition + mNumRows;
+                if (newPos < getItemCount()) {
+                    setSelectionSmooth(mBaseGridView, newPos);
+                    view = focused;
+                } else {
+                    if (!mFocusOutEnd) {
+                        view = focused;
+                    }
+                }
+            } else if (movement == PREV_ITEM){
+                int newPos = mFocusPosition - mNumRows;
+                if (newPos >= 0) {
+                    setSelectionSmooth(mBaseGridView, newPos);
+                    view = focused;
+                } else {
+                    if (!mFocusOutFront) {
+                        view = focused;
+                    }
+                }
+            }
+        } else if (mNumRows > 1) {
+            // for possible staggered grid,  we need guarantee focus to same row/column.
+            // TODO: we may also use LinearSmoothScroller.
+            saveContext(recycler, state);
+            final FocusFinder ff = FocusFinder.getInstance();
+            if (movement == NEXT_ITEM) {
+                while (view == null && !appendOneVisibleItem()) {
+                    view = ff.findNextFocus(mBaseGridView, focused, direction);
+                }
+            } else if (movement == PREV_ITEM){
+                while (view == null && !prependOneVisibleItem()) {
+                    view = ff.findNextFocus(mBaseGridView, focused, direction);
+                }
+            }
+            if (view == null) {
+                // returning the same view to prevent focus lost when scrolling past the end of the list
+                if (movement == PREV_ITEM) {
+                    view = mFocusOutFront ? null : focused;
+                } else if (movement == NEXT_ITEM){
+                    view = mFocusOutEnd ? null : focused;
+                }
+            }
+            leaveContext();
+        }
+        if (DEBUG) Log.v(getTag(), "returning view " + view);
+        return view;
+    }
+
+    boolean gridOnRequestFocusInDescendants(RecyclerView recyclerView, int direction,
+            Rect previouslyFocusedRect) {
+        switch (mFocusScrollStrategy) {
+        case BaseGridView.FOCUS_SCROLL_ALIGNED:
+        default:
+            return gridOnRequestFocusInDescendantsAligned(recyclerView,
+                    direction, previouslyFocusedRect);
+        case BaseGridView.FOCUS_SCROLL_PAGE:
+        case BaseGridView.FOCUS_SCROLL_ITEM:
+            return gridOnRequestFocusInDescendantsUnaligned(recyclerView,
+                    direction, previouslyFocusedRect);
+        }
+    }
+
+    private boolean gridOnRequestFocusInDescendantsAligned(RecyclerView recyclerView,
+            int direction, Rect previouslyFocusedRect) {
+        View view = findViewByPosition(mFocusPosition);
+        if (view != null) {
+            boolean result = view.requestFocus(direction, previouslyFocusedRect);
+            if (!result && DEBUG) {
+                Log.w(getTag(), "failed to request focus on " + view);
+            }
+            return result;
+        }
+        return false;
+    }
+
+    private boolean gridOnRequestFocusInDescendantsUnaligned(RecyclerView recyclerView,
+            int direction, Rect previouslyFocusedRect) {
+        // focus to view not overlapping padding area to avoid scrolling in gaining focus
+        int index;
+        int increment;
+        int end;
+        int count = getChildCount();
+        if ((direction & View.FOCUS_FORWARD) != 0) {
+            index = 0;
+            increment = 1;
+            end = count;
+        } else {
+            index = count - 1;
+            increment = -1;
+            end = -1;
+        }
+        int left = mWindowAlignment.mainAxis().getPaddingLow();
+        int right = mWindowAlignment.mainAxis().getClientSize() + left;
+        for (int i = index; i != end; i += increment) {
+            View child = getChildAt(i);
+            if (child.getVisibility() == View.VISIBLE) {
+                if (getViewMin(child) >= left && getViewMax(child) <= right) {
+                    if (child.requestFocus(direction, previouslyFocusedRect)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    private final static int PREV_ITEM = 0;
+    private final static int NEXT_ITEM = 1;
+    private final static int PREV_ROW = 2;
+    private final static int NEXT_ROW = 3;
+
+    private int getMovement(int direction) {
+        int movement = View.FOCUS_LEFT;
+
+        if (mOrientation == HORIZONTAL) {
+            switch(direction) {
+                case View.FOCUS_LEFT:
+                    movement = PREV_ITEM;
+                    break;
+                case View.FOCUS_RIGHT:
+                    movement = NEXT_ITEM;
+                    break;
+                case View.FOCUS_UP:
+                    movement = PREV_ROW;
+                    break;
+                case View.FOCUS_DOWN:
+                    movement = NEXT_ROW;
+                    break;
+            }
+         } else if (mOrientation == VERTICAL) {
+             switch(direction) {
+                 case View.FOCUS_LEFT:
+                     movement = PREV_ROW;
+                     break;
+                 case View.FOCUS_RIGHT:
+                     movement = NEXT_ROW;
+                     break;
+                 case View.FOCUS_UP:
+                     movement = PREV_ITEM;
+                     break;
+                 case View.FOCUS_DOWN:
+                     movement = NEXT_ITEM;
+                     break;
+             }
+         }
+
+        return movement;
+    }
+
+    int getChildDrawingOrder(RecyclerView recyclerView, int childCount, int i) {
+        View view = findViewByPosition(mFocusPosition);
+        if (view == null) {
+            return i;
+        }
+        int focusIndex = recyclerView.indexOfChild(view);
+        // supposely 0 1 2 3 4 5 6 7 8 9, 4 is the center item
+        // drawing order is 0 1 2 3 9 8 7 6 5 4
+        if (i < focusIndex) {
+            return i;
+        } else if (i < childCount - 1) {
+            return focusIndex + childCount - 1 - i;
+        } else {
+            return focusIndex;
+        }
+    }
+
+    @Override
+    public void onAdapterChanged(RecyclerView.Adapter oldAdapter,
+            RecyclerView.Adapter newAdapter) {
+        discardLayoutInfo();
+        mFocusPosition = NO_POSITION;
+        super.onAdapterChanged(oldAdapter, newAdapter);
+    }
+
+    private void discardLayoutInfo() {
+        mGrid = null;
+        mRows = null;
+        mRowSizeSecondary = null;
+        mFirstVisiblePos = -1;
+        mLastVisiblePos = -1;
+        mRowSecondarySizeRefresh = false;
+    }
+
+    public void setLayoutEnabled(boolean layoutEnabled) {
+        if (mLayoutEnabled != layoutEnabled) {
+            mLayoutEnabled = layoutEnabled;
+            requestLayout();
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java b/v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java
new file mode 100644
index 0000000..a280f4f
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import static android.support.v17.leanback.widget.ObjectAdapter.NO_ID;
+
+/**
+ * A header item is an item that describes metadata of {@link Row}, such as a category
+ * of media items.  Developer may override this class to add more information.
+ */
+public class HeaderItem {
+
+    private final long mId;
+    private final String mImageUri;
+    private final String mName;
+
+    /**
+     * Create a header item.  All fields are optional.
+     */
+    public HeaderItem(long id, String name, String imageUri) {
+        mId = id;
+        mName = name;
+        mImageUri = imageUri;
+    }
+
+    /**
+     * Create a header item.  All fields are optional.
+     */
+    public HeaderItem(String name, String imageUri) {
+        this(NO_ID, name, imageUri);
+    }
+
+    /**
+     * Returns a unique identifier for this item.
+     */
+    public final long getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the name of this header item.
+     */
+    public final String getName() {
+        return mName;
+    }
+
+    /**
+     * Returns the icon for this header item.
+     */
+    public final String getImageUri() {
+        return mImageUri;
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/HorizontalGridView.java b/v17/leanback/src/android/support/v17/leanback/widget/HorizontalGridView.java
new file mode 100644
index 0000000..1150805
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/HorizontalGridView.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.support.v17.leanback.R;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.View;
+
+/**
+ * A view that shows items in a horizontal scrolling list. The items come from
+ * the {@link RecyclerView.Adapter} associated with this view.
+ */
+public class HorizontalGridView extends BaseGridView {
+
+    private boolean mFadingLowEdge;
+    private boolean mFadingHighEdge;
+
+    private Paint mTempPaint = new Paint();
+    private Bitmap mTempBitmapLow;
+    private LinearGradient mLowFadeShader;
+    private int mLowFadeShaderLength;
+    private int mLowFadeShaderOffset;
+    private Bitmap mTempBitmapHigh;
+    private LinearGradient mHighFadeShader;
+    private int mHighFadeShaderLength;
+    private int mHighFadeShaderOffset;
+    private Rect mTempRect = new Rect();
+
+    public HorizontalGridView(Context context) {
+        this(context, null);
+    }
+
+    public HorizontalGridView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public HorizontalGridView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mLayoutManager.setOrientation(RecyclerView.HORIZONTAL);
+        initAttributes(context, attrs);
+    }
+
+    protected void initAttributes(Context context, AttributeSet attrs) {
+        initBaseGridViewAttributes(context, attrs);
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbHorizontalGridView);
+        setRowHeight(a);
+        setNumRows(a.getInt(R.styleable.lbHorizontalGridView_numberOfRows, 1));
+        a.recycle();
+        updateLayerType();
+        mTempPaint = new Paint();
+        mTempPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
+    }
+
+    void setRowHeight(TypedArray array) {
+        TypedValue typedValue = array.peekValue(R.styleable.lbHorizontalGridView_rowHeight);
+        int size;
+        if (typedValue != null && typedValue.type == TypedValue.TYPE_DIMENSION) {
+            size = array.getDimensionPixelSize(R.styleable.lbHorizontalGridView_rowHeight, 0);
+        } else {
+            size = array.getInt(R.styleable.lbHorizontalGridView_rowHeight, 0);
+        }
+        setRowHeight(size);
+    }
+
+    /**
+     * Set the number of rows.  Defaults to one.
+     */
+    public void setNumRows(int numRows) {
+        mLayoutManager.setNumRows(numRows);
+        requestLayout();
+    }
+
+    /**
+     * Set the row height.
+     *
+     * @param height May be WRAP_CONTENT, or a size in pixels. If zero,
+     * row height will be fixed based on number of rows and view height.
+     */
+    public void setRowHeight(int height) {
+        mLayoutManager.setRowHeight(height);
+        requestLayout();
+    }
+
+    /**
+     * Set fade out left edge to transparent.   Note turn on fading edge is very expensive
+     * that you should turn off when HorizontalGridView is scrolling.
+     */
+    public final void setFadingLeftEdge(boolean fading) {
+        if (mFadingLowEdge != fading) {
+            mFadingLowEdge = fading;
+            if (!mFadingLowEdge) {
+                mTempBitmapLow = null;
+            }
+            invalidate();
+            updateLayerType();
+        }
+    }
+
+    /**
+     * Return true if fading left edge.
+     */
+    public final boolean getFadingLeftEdge() {
+        return mFadingLowEdge;
+    }
+
+    /**
+     * Set left edge fading length in pixels.
+     */
+    public final void setFadingLeftEdgeLength(int fadeLength) {
+        if (mLowFadeShaderLength != fadeLength) {
+            mLowFadeShaderLength = fadeLength;
+            if (mLowFadeShaderLength != 0) {
+                mLowFadeShader = new LinearGradient(0, 0, mLowFadeShaderLength, 0,
+                        Color.TRANSPARENT, Color.BLACK, Shader.TileMode.CLAMP);
+            } else {
+                mLowFadeShader = null;
+            }
+            invalidate();
+        }
+    }
+
+    /**
+     * Get left edge fading length in pixels.
+     */
+    public final int getFadingLeftEdgeLength() {
+        return mLowFadeShaderLength;
+    }
+
+    /**
+     * Set distance in pixels between fading start position and left padding edge.
+     * The fading start position is positive when start position is inside left padding
+     * area.  Default value is 0, means that the fading starts from left padding edge.
+     */
+    public final void setFadingLeftEdgeOffset(int fadeOffset) {
+        if (mLowFadeShaderOffset != fadeOffset) {
+            mLowFadeShaderOffset = fadeOffset;
+            invalidate();
+        }
+    }
+
+    /**
+     * Get distance in pixels between fading start position and left padding edge.
+     * The fading start position is positive when start position is inside left padding
+     * area.  Default value is 0, means that the fading starts from left padding edge.
+     */
+    public final int getFadingLeftEdgeOffset() {
+        return mLowFadeShaderOffset;
+    }
+
+    /**
+     * Set fade out right edge to transparent.   Note turn on fading edge is very expensive
+     * that you should turn off when HorizontalGridView is scrolling.
+     */
+    public final void setFadingRightEdge(boolean fading) {
+        if (mFadingHighEdge != fading) {
+            mFadingHighEdge = fading;
+            if (!mFadingHighEdge) {
+                mTempBitmapHigh = null;
+            }
+            invalidate();
+            updateLayerType();
+        }
+    }
+
+    /**
+     * Return true if fading right edge.
+     */
+    public final boolean getFadingRightEdge() {
+        return mFadingHighEdge;
+    }
+
+    /**
+     * Set right edge fading length in pixels.
+     */
+    public final void setFadingRightEdgeLength(int fadeLength) {
+        if (mHighFadeShaderLength != fadeLength) {
+            mHighFadeShaderLength = fadeLength;
+            if (mHighFadeShaderLength != 0) {
+                mHighFadeShader = new LinearGradient(0, 0, mHighFadeShaderLength, 0,
+                        Color.BLACK, Color.TRANSPARENT, Shader.TileMode.CLAMP);
+            } else {
+                mHighFadeShader = null;
+            }
+            invalidate();
+        }
+    }
+
+    /**
+     * Get right edge fading length in pixels.
+     */
+    public final int getFadingRightEdgeLength() {
+        return mHighFadeShaderLength;
+    }
+
+    /**
+     * Get distance in pixels between fading start position and right padding edge.
+     * The fading start position is positive when start position is inside right padding
+     * area.  Default value is 0, means that the fading starts from right padding edge.
+     */
+    public final void setFadingRightEdgeOffset(int fadeOffset) {
+        if (mHighFadeShaderOffset != fadeOffset) {
+            mHighFadeShaderOffset = fadeOffset;
+            invalidate();
+        }
+    }
+
+    /**
+     * Set distance in pixels between fading start position and right padding edge.
+     * The fading start position is positive when start position is inside right padding
+     * area.  Default value is 0, means that the fading starts from right padding edge.
+     */
+    public final int getFadingRightEdgeOffset() {
+        return mHighFadeShaderOffset;
+    }
+
+    private boolean needsFadingLowEdge() {
+        if (!mFadingLowEdge) {
+            return false;
+        }
+        final int c = getChildCount();
+        for (int i = 0; i < c; i++) {
+            View view = getChildAt(i);
+            if (mLayoutManager.getOpticalLeft(view) <
+                    getPaddingLeft() - mLowFadeShaderOffset) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean needsFadingHighEdge() {
+        if (!mFadingHighEdge) {
+            return false;
+        }
+        final int c = getChildCount();
+        for (int i = c - 1; i >= 0; i--) {
+            View view = getChildAt(i);
+            if (mLayoutManager.getOpticalRight(view) > getWidth()
+                    - getPaddingRight() + mHighFadeShaderOffset) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private Bitmap getTempBitmapLow() {
+        if (mTempBitmapLow == null
+                || mTempBitmapLow.getWidth() != mLowFadeShaderLength
+                || mTempBitmapLow.getHeight() != getHeight()) {
+            mTempBitmapLow = Bitmap.createBitmap(mLowFadeShaderLength, getHeight(),
+                    Bitmap.Config.ARGB_8888);
+        }
+        return mTempBitmapLow;
+    }
+
+    private Bitmap getTempBitmapHigh() {
+        if (mTempBitmapHigh == null
+                || mTempBitmapHigh.getWidth() != mHighFadeShaderLength
+                || mTempBitmapHigh.getHeight() != getHeight()) {
+            // TODO: fix logic for sharing mTempBitmapLow
+            if (false && mTempBitmapLow != null
+                    && mTempBitmapLow.getWidth() == mHighFadeShaderLength
+                    && mTempBitmapLow.getHeight() == getHeight()) {
+                // share same bitmap for low edge fading and high edge fading.
+                mTempBitmapHigh = mTempBitmapLow;
+            } else {
+                mTempBitmapHigh = Bitmap.createBitmap(mHighFadeShaderLength, getHeight(),
+                        Bitmap.Config.ARGB_8888);
+            }
+        }
+        return mTempBitmapHigh;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        final boolean needsFadingLow = needsFadingLowEdge();
+        final boolean needsFadingHigh = needsFadingHighEdge();
+        if (!needsFadingLow) {
+            mTempBitmapLow = null;
+        }
+        if (!needsFadingHigh) {
+            mTempBitmapHigh = null;
+        }
+        if (!needsFadingLow && !needsFadingHigh) {
+            super.draw(canvas);
+            return;
+        }
+
+        int lowEdge = mFadingLowEdge? getPaddingLeft() - mLowFadeShaderOffset - mLowFadeShaderLength : 0;
+        int highEdge = mFadingHighEdge ? getWidth() - getPaddingRight()
+                + mHighFadeShaderOffset + mHighFadeShaderLength : getWidth();
+
+        // draw not-fade content
+        int save = canvas.save();
+        canvas.clipRect(lowEdge + (mFadingLowEdge ? mLowFadeShaderLength : 0), 0,
+                highEdge - (mFadingHighEdge ? mHighFadeShaderLength : 0), getHeight());
+        super.draw(canvas);
+        canvas.restoreToCount(save);
+
+        Canvas tmpCanvas = new Canvas();
+        mTempRect.top = 0;
+        mTempRect.bottom = getHeight();
+        if (needsFadingLow && mLowFadeShaderLength > 0) {
+            Bitmap tempBitmap = getTempBitmapLow();
+            tempBitmap.eraseColor(Color.TRANSPARENT);
+            tmpCanvas.setBitmap(tempBitmap);
+            // draw original content
+            int tmpSave = tmpCanvas.save();
+            tmpCanvas.clipRect(0, 0, mLowFadeShaderLength, getHeight());
+            tmpCanvas.translate(-lowEdge, 0);
+            super.draw(tmpCanvas);
+            tmpCanvas.restoreToCount(tmpSave);
+            // draw fading out
+            mTempPaint.setShader(mLowFadeShader);
+            tmpCanvas.drawRect(0, 0, mLowFadeShaderLength, getHeight(), mTempPaint);
+            // copy back to canvas
+            mTempRect.left = 0;
+            mTempRect.right = mLowFadeShaderLength;
+            canvas.translate(lowEdge, 0);
+            canvas.drawBitmap(tempBitmap, mTempRect, mTempRect, null);
+            canvas.translate(-lowEdge, 0);
+        }
+        if (needsFadingHigh && mHighFadeShaderLength > 0) {
+            Bitmap tempBitmap = getTempBitmapHigh();
+            tempBitmap.eraseColor(Color.TRANSPARENT);
+            tmpCanvas.setBitmap(tempBitmap);
+            // draw original content
+            int tmpSave = tmpCanvas.save();
+            tmpCanvas.clipRect(0, 0, mHighFadeShaderLength, getHeight());
+            tmpCanvas.translate(-(highEdge - mHighFadeShaderLength), 0);
+            super.draw(tmpCanvas);
+            tmpCanvas.restoreToCount(tmpSave);
+            // draw fading out
+            mTempPaint.setShader(mHighFadeShader);
+            tmpCanvas.drawRect(0, 0, mHighFadeShaderLength, getHeight(), mTempPaint);
+            // copy back to canvas
+            mTempRect.left = 0;
+            mTempRect.right = mHighFadeShaderLength;
+            canvas.translate(highEdge - mHighFadeShaderLength, 0);
+            canvas.drawBitmap(tempBitmap, mTempRect, mTempRect, null);
+            canvas.translate(-(highEdge - mHighFadeShaderLength), 0);
+        }
+    }
+
+    /**
+     * Updates the layer type for this view.
+     * If fading edges are needed, use a hardware layer.  This works around the problem
+     * that when a child invalidates itself (for example has an animated background),
+     * the parent view must also be invalidated to refresh the display list which
+     * updates the the caching bitmaps used to draw the fading edges.
+     */
+    private void updateLayerType() {
+        if (mFadingLowEdge || mFadingHighEdge) {
+            setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            setWillNotDraw(false);
+        } else {
+            setLayerType(View.LAYER_TYPE_NONE, null);
+            setWillNotDraw(true);
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java b/v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java
new file mode 100644
index 0000000..0700995
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/HorizontalHoverCardSwitcher.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup.MarginLayoutParams;
+
+/**
+ * Helper class that stay bellow a HorizontalGridView and shows a hover card and align
+ * the hover card left to left of selected child view.  If there is no space when scroll
+ * to the end, right edge hover card will be aligned to right of parent view excluding
+ * right padding.
+ */
+public final class HorizontalHoverCardSwitcher extends PresenterSwitcher {
+    // left and right of selected card view
+    int mCardLeft, mCardRight;
+
+    private int[] mTmpOffsets = new int[2];
+    private Rect mTmpRect = new Rect();
+
+    @Override
+    protected void insertView(View view) {
+        // append hovercard to the end of container
+        getParentViewGroup().addView(view);
+    }
+
+    @Override
+    protected void onViewSelected(View view) {
+        int rightLimit = getParentViewGroup().getWidth() -
+                getParentViewGroup().getPaddingRight();
+        // measure the hover card width, if it's too large,  align hover card
+        // right edge with row view's right edge
+        view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+        MarginLayoutParams params = (MarginLayoutParams) view.getLayoutParams();
+        if (mCardLeft + view.getMeasuredWidth() > rightLimit) {
+            params.leftMargin = rightLimit  - view.getMeasuredWidth();
+        } else {
+            params.leftMargin = mCardLeft;
+        }
+        view.requestLayout();
+    }
+
+    /**
+     * Select a childView inside a grid view and create/bind a corresponding hover card view
+     * for the object.
+     */
+    public void select(HorizontalGridView gridView, View childView, Object object) {
+        ViewGroup parent = getParentViewGroup();
+        gridView.getViewSelectedOffsets(childView, mTmpOffsets);
+        mTmpRect.set(0, 0, childView.getWidth(), childView.getHeight());
+        parent.offsetDescendantRectToMyCoords(childView, mTmpRect);
+        mCardLeft = mTmpRect.left - mTmpOffsets[0];
+        mCardRight = mTmpRect.right - mTmpOffsets[0];
+        select(object);
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java b/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
new file mode 100644
index 0000000..fb131e0
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.support.v17.leanback.R;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.TextView;
+
+/**
+ * A card view with an {@link ImageView} as its main region.
+ */
+public class ImageCardView extends BaseCardView {
+
+    private ImageView mImageView;
+    private View mInfoArea;
+    private TextView mTitleView;
+    private TextView mContentView;
+    private ImageView mBadgeImage;
+    private ImageView mBadgeFadeMask;
+
+    public ImageCardView(Context context) {
+        this(context, null);
+    }
+
+    public ImageCardView(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.imageCardViewStyle);
+    }
+
+    public ImageCardView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        LayoutInflater inflater = LayoutInflater.from(context);
+        View v = inflater.inflate(R.layout.lb_image_card_view, this);
+
+        mImageView = (ImageView) v.findViewById(R.id.main_image);
+        mImageView.setVisibility(View.INVISIBLE);
+        mInfoArea = v.findViewById(R.id.info_field);
+        mTitleView = (TextView) v.findViewById(R.id.title_text);
+        mContentView = (TextView) v.findViewById(R.id.content_text);
+        mBadgeImage = (ImageView) v.findViewById(R.id.extra_badge);
+        mBadgeFadeMask = (ImageView) v.findViewById(R.id.fade_mask);
+
+        if (mInfoArea != null) {
+            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbImageCardView,
+                    defStyle, 0);
+            try {
+                setInfoAreaBackground(
+                        a.getDrawable(R.styleable.lbImageCardView_infoAreaBackground));
+            } finally {
+                a.recycle();
+            }
+        }
+    }
+
+    public final ImageView getMainImageView() {
+        return mImageView;
+    }
+
+    public void setMainImageAdjustViewBounds(boolean adjustViewBounds) {
+        if (mImageView != null) {
+            mImageView.setAdjustViewBounds(adjustViewBounds);
+        }
+    }
+
+    public void setMainImageScaleType(ScaleType scaleType) {
+        if (mImageView != null) {
+            mImageView.setScaleType(scaleType);
+        }
+    }
+
+    /**
+     * Set drawable with fade-in animation.
+     */
+    public void setMainImage(Drawable drawable) {
+        setMainImage(drawable, true);
+    }
+
+    /**
+     * Set drawable with optional fade-in animation.
+     */
+    public void setMainImage(Drawable drawable, boolean fade) {
+        if (mImageView == null) {
+            return;
+        }
+
+        mImageView.setImageDrawable(drawable);
+        if (drawable == null) {
+            mImageView.animate().cancel();
+            mImageView.setAlpha(1f);
+            mImageView.setVisibility(View.INVISIBLE);
+        } else {
+            mImageView.setVisibility(View.VISIBLE);
+            if (fade) {
+                fadeIn(mImageView);
+            } else {
+                mImageView.animate().cancel();
+                mImageView.setAlpha(1f);
+            }
+        }
+    }
+
+    public void setMainImageDimensions(int width, int height) {
+        ViewGroup.LayoutParams lp = mImageView.getLayoutParams();
+        lp.width = width;
+        lp.height = height;
+        mImageView.setLayoutParams(lp);
+    }
+
+    public Drawable getMainImage() {
+        if (mImageView == null) {
+            return null;
+        }
+
+        return mImageView.getDrawable();
+    }
+
+    public Drawable getInfoAreaBackground() {
+        if (mInfoArea != null) {
+            return mInfoArea.getBackground();
+        }
+        return null;
+    }
+
+    public void setInfoAreaBackground(Drawable drawable) {
+        if (mInfoArea != null) {
+            mInfoArea.setBackground(drawable);
+            if (mBadgeImage != null) {
+                mBadgeImage.setBackground(drawable);
+            }
+        }
+    }
+
+    public void setInfoAreaBackgroundColor(int color) {
+        if (mInfoArea != null) {
+            mInfoArea.setBackgroundColor(color);
+            if (mBadgeImage != null) {
+                mBadgeImage.setBackgroundColor(color);
+            }
+        }
+    }
+
+    public void setTitleText(CharSequence text) {
+        if (mTitleView == null) {
+            return;
+        }
+
+        mTitleView.setText(text);
+        setTextMaxLines();
+    }
+
+    public CharSequence getTitleText() {
+        if (mTitleView == null) {
+            return null;
+        }
+
+        return mTitleView.getText();
+    }
+
+    public void setContentText(CharSequence text) {
+        if (mContentView == null) {
+            return;
+        }
+
+        mContentView.setText(text);
+        setTextMaxLines();
+    }
+
+    public CharSequence getContentText() {
+        if (mContentView == null) {
+            return null;
+        }
+
+        return mContentView.getText();
+    }
+
+    public void setBadgeImage(Drawable drawable) {
+        if (mBadgeImage == null) {
+            return;
+        }
+
+        if (drawable != null) {
+            mBadgeImage.setImageDrawable(drawable);
+            mBadgeImage.setVisibility(View.VISIBLE);
+            mBadgeFadeMask.setVisibility(View.VISIBLE);
+        } else {
+            mBadgeImage.setVisibility(View.GONE);
+            mBadgeFadeMask.setVisibility(View.GONE);
+        }
+    }
+
+    public Drawable getBadgeImage() {
+        if (mBadgeImage == null) {
+            return null;
+        }
+
+        return mBadgeImage.getDrawable();
+    }
+
+    private void fadeIn(View v) {
+        v.setAlpha(0f);
+        v.animate().alpha(1f).setDuration(v.getContext().getResources().getInteger(
+                android.R.integer.config_shortAnimTime)).start();
+    }
+
+    private void setTextMaxLines() {
+        if (TextUtils.isEmpty(getTitleText())) {
+            mContentView.setMaxLines(2);
+        } else {
+            mContentView.setMaxLines(1);
+        }
+        if (TextUtils.isEmpty(getContentText())) {
+            mTitleView.setMaxLines(2);
+        } else {
+            mTitleView.setMaxLines(1);
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignment.java b/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignment.java
new file mode 100644
index 0000000..3cc3ddf
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignment.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.support.v17.leanback.widget;
+
+import static android.support.v7.widget.RecyclerView.HORIZONTAL;
+import static android.support.v7.widget.RecyclerView.VERTICAL;
+import static android.support.v17.leanback.widget.BaseGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED;
+
+import android.graphics.Rect;
+import android.support.v17.leanback.widget.GridLayoutManager.LayoutParams;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Defines alignment position on two directions of an item view. Typically item
+ * view alignment is at the center of the view. The class allows defining
+ * alignment at left/right or fixed offset/percentage position; it also allows
+ * using descendant view by id match.
+ */
+class ItemAlignment {
+
+    final static class Axis {
+        private int mOrientation;
+        private int mOffset = 0;
+        private float mOffsetPercent = 50;
+        private int mViewId = 0;
+        private boolean mOffsetWithPadding = false;
+        private Rect mRect = new Rect();
+
+        Axis(int orientation) {
+            mOrientation = orientation;
+        }
+
+        public void setItemAlignmentOffset(int offset) {
+            mOffset = offset;
+        }
+
+        public int getItemAlignmentOffset() {
+            return mOffset;
+        }
+
+        public void setItemAlignmentOffsetWithPadding(boolean withPadding) {
+            mOffsetWithPadding = withPadding;
+        }
+
+        public boolean isItemAlignmentOffsetWithPadding() {
+            return mOffsetWithPadding;
+        }
+
+        public void setItemAlignmentOffsetPercent(float percent) {
+            if ( (percent < 0 || percent > 100) &&
+                    percent != ITEM_ALIGN_OFFSET_PERCENT_DISABLED) {
+                throw new IllegalArgumentException();
+            }
+            mOffsetPercent = percent;
+        }
+
+        public float getItemAlignmentOffsetPercent() {
+            return mOffsetPercent;
+        }
+
+        public void setItemAlignmentViewId(int viewId) {
+            mViewId = viewId;
+        }
+
+        public int getItemAlignmentViewId() {
+            return mViewId;
+        }
+
+        /**
+         * get alignment position relative to optical left/top of itemView.
+         */
+        public int getAlignmentPosition(View itemView) {
+            LayoutParams p = (LayoutParams) itemView.getLayoutParams();
+            View view = itemView;
+            if (mViewId != 0) {
+                view = itemView.findViewById(mViewId);
+                if (view == null) {
+                    view = itemView;
+                }
+            }
+            int alignPos;
+            if (mOrientation == HORIZONTAL) {
+                if (mOffset >= 0) {
+                    alignPos = mOffset;
+                    if (mOffsetWithPadding) {
+                        alignPos += view.getPaddingLeft();
+                    }
+                } else {
+                    alignPos = view == itemView ? p.getOpticalWidth(view) : view.getWidth()
+                            + mOffset;
+                    if (mOffsetWithPadding) {
+                        alignPos -= view.getPaddingRight();
+                    }
+                }
+                if (mOffsetPercent != ITEM_ALIGN_OFFSET_PERCENT_DISABLED) {
+                    alignPos += ((view == itemView ? p.getOpticalWidth(view) : view.getWidth())
+                            * mOffsetPercent) / 100f;
+                }
+                if (itemView != view) {
+                    mRect.left = alignPos;
+                    ((ViewGroup) itemView).offsetDescendantRectToMyCoords(view, mRect);
+                    alignPos = mRect.left - p.getOpticalLeftInset();
+                }
+            } else {
+                if (mOffset >= 0) {
+                    alignPos = mOffset;
+                    if (mOffsetWithPadding) {
+                        alignPos += view.getPaddingTop();
+                    }
+                } else {
+                    alignPos = view == itemView ? p.getOpticalHeight(view) : view.getHeight()
+                            + mOffset;
+                    if (mOffsetWithPadding) {
+                        alignPos += view.getPaddingBottom();
+                    }
+                }
+                if (mOffsetPercent != ITEM_ALIGN_OFFSET_PERCENT_DISABLED) {
+                    alignPos += ((view == itemView ? p.getOpticalHeight(view) : view.getHeight())
+                            * mOffsetPercent) / 100f;
+                }
+                if (itemView != view) {
+                    mRect.top = alignPos;
+                    ((ViewGroup) itemView).offsetDescendantRectToMyCoords(view, mRect);
+                    alignPos = mRect.top - p.getOpticalTopInset();
+                }
+            }
+            return alignPos;
+        }
+    }
+
+    private int mOrientation = HORIZONTAL;
+
+    final public Axis vertical = new Axis(VERTICAL);
+
+    final public Axis horizontal = new Axis(HORIZONTAL);
+
+    private Axis mMainAxis = horizontal;
+
+    private Axis mSecondAxis = vertical;
+
+    final public Axis mainAxis() {
+        return mMainAxis;
+    }
+
+    final public Axis secondAxis() {
+        return mSecondAxis;
+    }
+
+    final public void setOrientation(int orientation) {
+        mOrientation = orientation;
+        if (mOrientation == HORIZONTAL) {
+            mMainAxis = horizontal;
+            mSecondAxis = vertical;
+        } else {
+            mMainAxis = vertical;
+            mSecondAxis = horizontal;
+        }
+    }
+
+    final public int getOrientation() {
+        return mOrientation;
+    }
+
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapter.java
new file mode 100644
index 0000000..e970171
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ItemBridgeAdapter.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * Bridge from Presenter to RecyclerView.Adapter. Public to allow use by third
+ * party presenters.
+ */
+public class ItemBridgeAdapter extends RecyclerView.Adapter {
+    private static final String TAG = "ItemBridgeAdapter";
+    private static final boolean DEBUG = false;
+
+    /**
+     * Interface for listening to view holder operations.
+     */
+    public static class AdapterListener {
+        public void onAddPresenter(Presenter presenter, int type) {
+        }
+        public void onCreate(ViewHolder viewHolder) {
+        }
+        public void onBind(ViewHolder viewHolder) {
+        }
+        public void onUnbind(ViewHolder viewHolder) {
+        }
+        public void onAttachedToWindow(ViewHolder viewHolder) {
+        }
+        public void onDetachedFromWindow(ViewHolder viewHolder) {
+        }
+    }
+
+    /**
+     * Interface for wrapping a view created by presenter into another view.
+     * The wrapper must be immediate parent of the wrapped view.
+     */
+    public static abstract class Wrapper {
+        public abstract View createWrapper(View root);
+        public abstract void wrap(View wrapper, View wrapped);
+    }
+
+    private ObjectAdapter mAdapter;
+    private Wrapper mWrapper;
+    private PresenterSelector mPresenterSelector;
+    private FocusHighlight mFocusHighlight;
+    private AdapterListener mAdapterListener;
+    private ArrayList<Presenter> mPresenters = new ArrayList<Presenter>();
+
+    final class OnFocusChangeListener implements View.OnFocusChangeListener {
+        View.OnFocusChangeListener mChainedListener;
+
+        @Override
+        public void onFocusChange(View view, boolean hasFocus) {
+            if (DEBUG) Log.v(TAG, "onFocusChange " + hasFocus + " " + view
+                    + " mFocusHighlight" + mFocusHighlight);
+            if (mWrapper != null) {
+                view = (View) view.getParent();
+            }
+            if (mFocusHighlight != null) {
+                mFocusHighlight.onItemFocused(view, hasFocus);
+            }
+            if (mChainedListener != null) {
+                mChainedListener.onFocusChange(view, hasFocus);
+            }
+        }
+    }
+
+    public class ViewHolder extends RecyclerView.ViewHolder {
+        final Presenter mPresenter;
+        final Presenter.ViewHolder mHolder;
+        final OnFocusChangeListener mFocusChangeListener = new OnFocusChangeListener();
+        Object mItem;
+        Object mExtraObject;
+
+        /**
+         * Get {@link Presenter}.
+         */
+        public final Presenter getPresenter() {
+            return mPresenter;
+        }
+
+        /**
+         * Get {@link Presenter.ViewHolder}.
+         */
+        public final Presenter.ViewHolder getViewHolder() {
+            return mHolder;
+        }
+
+        /**
+         * Get currently bound object.
+         */
+        public final Object getItem() {
+            return mItem;
+        }
+
+        /**
+         * Get extra object associated with the view.  Developer can attach
+         * any customized UI object in addition to {@link Presenter.ViewHolder}.
+         * A typical use case is attaching an animator object.
+         */
+        public final Object getExtraObject() {
+            return mExtraObject;
+        }
+
+        /**
+         * Set extra object associated with the view.  Developer can attach
+         * any customized UI object in addition to {@link Presenter.ViewHolder}.
+         * A typical use case is attaching an animator object.
+         */
+        public void setExtraObject(Object object) {
+            mExtraObject = object;
+        }
+
+        ViewHolder(Presenter presenter, View view, Presenter.ViewHolder holder) {
+            super(view);
+            mPresenter = presenter;
+            mHolder = holder;
+        }
+    }
+
+    private ObjectAdapter.DataObserver mDataObserver = new ObjectAdapter.DataObserver() {
+        @Override
+        public void onChanged() {
+            ItemBridgeAdapter.this.notifyDataSetChanged();
+        }
+        @Override
+        public void onItemRangeChanged(int positionStart, int itemCount) {
+            ItemBridgeAdapter.this.notifyItemRangeChanged(positionStart, itemCount);
+        }
+        @Override
+        public void onItemRangeInserted(int positionStart, int itemCount) {
+            ItemBridgeAdapter.this.notifyItemRangeInserted(positionStart, itemCount);
+        }
+        @Override
+        public void onItemRangeRemoved(int positionStart, int itemCount) {
+            ItemBridgeAdapter.this.notifyItemRangeRemoved(positionStart, itemCount);
+        }
+    };
+
+    public ItemBridgeAdapter(ObjectAdapter adapter, PresenterSelector presenterSelector) {
+        setAdapter(adapter);
+        mPresenterSelector = presenterSelector;
+    }
+
+    public ItemBridgeAdapter(ObjectAdapter adapter) {
+        this(adapter, null);
+    }
+
+    public ItemBridgeAdapter() {
+    }
+
+    public void setAdapter(ObjectAdapter adapter) {
+        if (mAdapter != null) {
+            mAdapter.unregisterObserver(mDataObserver);
+        }
+        mAdapter = adapter;
+        if (mAdapter == null) {
+            return;
+        }
+
+        mAdapter.registerObserver(mDataObserver);
+        if (hasStableIds() != mAdapter.hasStableIds()) {
+            setHasStableIds(mAdapter.hasStableIds());
+        }
+    }
+
+    public void setWrapper(Wrapper wrapper) {
+        mWrapper = wrapper;
+    }
+
+    public Wrapper getWrapper() {
+        return mWrapper;
+    }
+
+    void setFocusHighlight(FocusHighlight listener) {
+        mFocusHighlight = listener;
+        if (DEBUG) Log.v(TAG, "setFocusHighlight " + mFocusHighlight);
+    }
+
+    public void clear() {
+        setAdapter(null);
+    }
+
+    public void setPresenterMapper(ArrayList<Presenter> presenters) {
+        mPresenters = presenters;
+    }
+
+    public ArrayList<Presenter> getPresenterMapper() {
+        return mPresenters;
+    }
+
+    @Override
+    public int getItemCount() {
+        return mAdapter.size();
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        PresenterSelector presenterSelector = mPresenterSelector != null ?
+                mPresenterSelector : mAdapter.getPresenterSelector();
+        Object item = mAdapter.get(position);
+        Presenter presenter = presenterSelector.getPresenter(item);
+        int type = mPresenters.indexOf(presenter);
+        if (type < 0) {
+            mPresenters.add(presenter);
+            type = mPresenters.indexOf(presenter);
+            if (DEBUG) Log.v(TAG, "getItemViewType added presenter " + presenter + " type " + type);
+            if (mAdapterListener != null) {
+                mAdapterListener.onAddPresenter(presenter, type);
+            }
+        }
+        return type;
+    }
+
+    /**
+     * {@link View.OnFocusChangeListener} that assigned in
+     * {@link Presenter#onCreateViewHolder(ViewGroup)} may be chained, user should never change
+     * {@link View.OnFocusChangeListener} after that.
+     */
+    @Override
+    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        if (DEBUG) Log.v(TAG, "onCreateViewHolder viewType " + viewType);
+        Presenter presenter = mPresenters.get(viewType);
+        Presenter.ViewHolder presenterVh;
+        View view;
+        if (mWrapper != null) {
+            view = mWrapper.createWrapper(parent);
+            presenterVh = presenter.onCreateViewHolder(parent);
+            mWrapper.wrap(view, presenterVh.view);
+        } else {
+            presenterVh = presenter.onCreateViewHolder(parent);
+            view = presenterVh.view;
+        }
+        ViewHolder viewHolder = new ViewHolder(presenter, view, presenterVh);
+        if (mAdapterListener != null) {
+            mAdapterListener.onCreate(viewHolder);
+        }
+        View presenterView = viewHolder.mHolder.view;
+        if (presenterView != null) {
+            viewHolder.mFocusChangeListener.mChainedListener = presenterView.getOnFocusChangeListener();
+            presenterView.setOnFocusChangeListener(viewHolder.mFocusChangeListener);
+        }
+        return viewHolder;
+    }
+
+    public void setAdapterListener(AdapterListener listener) {
+        mAdapterListener = listener;
+    }
+
+    @Override
+    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+        if (DEBUG) Log.v(TAG, "onBindViewHolder position " + position);
+        ViewHolder viewHolder = (ViewHolder) holder;
+        viewHolder.mItem = mAdapter.get(position);
+
+        viewHolder.mPresenter.onBindViewHolder(viewHolder.mHolder, viewHolder.mItem);
+
+        if (mAdapterListener != null) {
+            mAdapterListener.onBind(viewHolder);
+        }
+    }
+
+    @Override
+    public void onViewRecycled(RecyclerView.ViewHolder holder) {
+        ViewHolder viewHolder = (ViewHolder) holder;
+        viewHolder.mPresenter.onUnbindViewHolder(viewHolder.mHolder);
+
+        viewHolder.mItem = null;
+
+        if (mAdapterListener != null) {
+            mAdapterListener.onUnbind(viewHolder);
+        }
+    }
+
+    @Override
+    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
+        ViewHolder viewHolder = (ViewHolder) holder;
+        if (mAdapterListener != null) {
+            mAdapterListener.onAttachedToWindow(viewHolder);
+        }
+        viewHolder.mPresenter.onViewAttachedToWindow(viewHolder.mHolder);
+    }
+
+    @Override
+    public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
+        ViewHolder viewHolder = (ViewHolder) holder;
+        viewHolder.mPresenter.onViewDetachedFromWindow(viewHolder.mHolder);
+        if (mAdapterListener != null) {
+            mAdapterListener.onDetachedFromWindow(viewHolder);
+        }
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return mAdapter.getId(position);
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRow.java b/v17/leanback/src/android/support/v17/leanback/widget/ListRow.java
new file mode 100644
index 0000000..3ce18f7
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRow.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+/**
+ * A row composed of a optional {@link HeaderItem}, and an {@link ObjectAdapter}
+ * describing children.
+ */
+public class ListRow extends Row {
+    private final ObjectAdapter mAdapter;
+
+    /**
+     * Get the {@link ObjectAdapter} that represents a list of objects.
+     */
+    public final ObjectAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    public ListRow(HeaderItem header, ObjectAdapter adapter) {
+        super(header);
+        mAdapter = adapter;
+        verify();
+    }
+
+    public ListRow(long id, HeaderItem header, ObjectAdapter adapter) {
+        super(id, header);
+        mAdapter = adapter;
+        verify();
+    }
+
+    public ListRow(ObjectAdapter adapter) {
+        super();
+        mAdapter = adapter;
+        verify();
+    }
+
+    private void verify() {
+        if (mAdapter == null) {
+            throw new IllegalArgumentException("ObjectAdapter cannot be null");
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRowHoverCardView.java b/v17/leanback/src/android/support/v17/leanback/widget/ListRowHoverCardView.java
new file mode 100644
index 0000000..c5b2f49
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRowHoverCardView.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.support.v17.leanback.R;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * ListRowHoverCardView contains a title and description.
+ */
+public final class ListRowHoverCardView extends LinearLayout {
+
+    private final TextView mTitleView;
+    private final TextView mDescriptionView;
+
+    public ListRowHoverCardView(Context context) {
+       this(context, null);
+    }
+
+    public ListRowHoverCardView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ListRowHoverCardView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        LayoutInflater inflater = LayoutInflater.from(context);
+        inflater.inflate(R.layout.lb_list_row_hovercard, this);
+        mTitleView = (TextView) findViewById(R.id.title);
+        mDescriptionView = (TextView) findViewById(R.id.description);
+    }
+
+    public final CharSequence getTitle() {
+        return mTitleView.getText();
+    }
+
+    public final void setTitle(CharSequence text) {
+        if (!TextUtils.isEmpty(text)) {
+            mTitleView.setText(text);
+            mTitleView.setVisibility(View.VISIBLE);
+        } else {
+            mTitleView.setVisibility(View.GONE);
+        }
+    }
+
+    public final CharSequence getDescription() {
+        return mDescriptionView.getText();
+    }
+
+    public final void setDescription(CharSequence text) {
+        if (!TextUtils.isEmpty(text)) {
+            mDescriptionView.setText(text);
+            mDescriptionView.setVisibility(View.VISIBLE);
+        } else {
+            mDescriptionView.setVisibility(View.GONE);
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
new file mode 100644
index 0000000..f6d3ccf
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
@@ -0,0 +1,515 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.graphics.ColorOverlayDimmer;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+
+/**
+ * ListRowPresenter renders {@link ListRow} using a
+ * {@link HorizontalGridView} hosted in a {@link ListRowView}.
+ *
+ * <h3>Hover card</h3>
+ * Optionally, {@link #setHoverCardPresenterSelector(PresenterSelector)} can be used to
+ * display a view for the currently focused list item below the rendered
+ * list. This view is known as a hover card.
+ *
+ * <h3>Selection animation</h3>
+ * ListRowPresenter disables {@link RowPresenter}'s default dimming effect and draw
+ * a dim overlay on top of each individual child items.  Subclass may override and disable
+ * {@link #isUsingDefaultListSelectEffect()} and write its own dim effect in
+ * {@link #onSelectLevelChanged(RowPresenter.ViewHolder)}.
+ *
+ * <h3>Shadow</h3>
+ * ListRowPresenter applies a default shadow to child of each view.  Call
+ * {@link #setShadowEnabled(boolean)} to disable shadow.  Subclass may override and return
+ * false in {@link #isUsingDefaultShadow()} and replace with its own shadow implementation.
+ */
+public class ListRowPresenter extends RowPresenter {
+
+    private static final String TAG = "ListRowPresenter";
+    private static final boolean DEBUG = false;
+
+    public static class ViewHolder extends RowPresenter.ViewHolder {
+        final ListRowPresenter mListRowPresenter;
+        final HorizontalGridView mGridView;
+        final ItemBridgeAdapter mItemBridgeAdapter = new ItemBridgeAdapter();
+        final HorizontalHoverCardSwitcher mHoverCardViewSwitcher = new HorizontalHoverCardSwitcher();
+        final ColorOverlayDimmer mColorDimmer;
+        final int mPaddingTop;
+        final int mPaddingBottom;
+        final int mPaddingLeft;
+        final int mPaddingRight;
+
+        public ViewHolder(View rootView, HorizontalGridView gridView, ListRowPresenter p) {
+            super(rootView);
+            mGridView = gridView;
+            mListRowPresenter = p;
+            mColorDimmer = ColorOverlayDimmer.createDefault(rootView.getContext());
+            mPaddingTop = mGridView.getPaddingTop();
+            mPaddingBottom = mGridView.getPaddingBottom();
+            mPaddingLeft = mGridView.getPaddingLeft();
+            mPaddingRight = mGridView.getPaddingRight();
+        }
+
+        public final ListRowPresenter getListRowPresenter() {
+            return mListRowPresenter;
+        }
+
+        public final HorizontalGridView getGridView() {
+            return mGridView;
+        }
+
+        public final ItemBridgeAdapter getBridgeAdapter() {
+            return mItemBridgeAdapter;
+        }
+    }
+
+    private int mRowHeight;
+    private int mExpandedRowHeight;
+    private PresenterSelector mHoverCardPresenterSelector;
+    private int mZoomFactor;
+    private boolean mShadowEnabled = true;
+    private int mBrowseRowsFadingEdgeLength = -1;
+
+    private static int sSelectedRowTopPadding;
+    private static int sExpandedSelectedRowTopPadding;
+    private static int sExpandedRowNoHovercardBottomPadding;
+
+    /**
+     * Constructs a ListRowPresenter with defaults.
+     * Uses {@link FocusHighlight#ZOOM_FACTOR_MEDIUM} for focus zooming.
+     */
+    public ListRowPresenter() {
+        this(FocusHighlight.ZOOM_FACTOR_MEDIUM);
+    }
+
+    /**
+     * Constructs a ListRowPresenter with the given parameters.
+     *
+     * @param zoomFactor Controls the zoom factor used when an item view is focused. One of
+     *         {@link FocusHighlight#ZOOM_FACTOR_NONE},
+     *         {@link FocusHighlight#ZOOM_FACTOR_SMALL},
+     *         {@link FocusHighlight#ZOOM_FACTOR_MEDIUM},
+     *         {@link FocusHighlight#ZOOM_FACTOR_LARGE}
+     */
+    public ListRowPresenter(int zoomFactor) {
+        mZoomFactor = zoomFactor;
+    }
+
+    /**
+     * Sets the row height for rows created by this Presenter. Rows
+     * created before calling this method will not be updated.
+     *
+     * @param rowHeight Row height in pixels, or WRAP_CONTENT, or 0
+     * to use the default height.
+     */
+    public void setRowHeight(int rowHeight) {
+        mRowHeight = rowHeight;
+    }
+
+    /**
+     * Returns the row height for list rows created by this Presenter.
+     */
+    public int getRowHeight() {
+        return mRowHeight;
+    }
+
+    /**
+     * Sets the expanded row height for rows created by this Presenter.
+     * If not set, expanded rows have the same height as unexpanded
+     * rows.
+     *
+     * @param rowHeight The row height in to use when the row is expanded,
+     *        in pixels, or WRAP_CONTENT, or 0 to use the default.
+     */
+    public void setExpandedRowHeight(int rowHeight) {
+        mExpandedRowHeight = rowHeight;
+    }
+
+    /**
+     * Returns the expanded row height for rows created by this Presenter.
+     */
+    public int getExpandedRowHeight() {
+        return mExpandedRowHeight != 0 ? mExpandedRowHeight : mRowHeight;
+    }
+
+    /**
+     * Returns the zoom factor used for focus highlighting.
+     */
+    public final int getZoomFactor() {
+        return mZoomFactor;
+    }
+
+    private ItemBridgeAdapter.Wrapper mCardWrapper = new ItemBridgeAdapter.Wrapper() {
+        @Override
+        public View createWrapper(View root) {
+            ShadowOverlayContainer wrapper = new ShadowOverlayContainer(root.getContext());
+            wrapper.setLayoutParams(
+                    new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+            wrapper.initialize(needsDefaultShadow(), needsDefaultListSelectEffect());
+            return wrapper;
+        }
+        @Override
+        public void wrap(View wrapper, View wrapped) {
+            ((ShadowOverlayContainer) wrapper).wrap(wrapped);
+        }
+    };
+
+    @Override
+    protected void initializeRowViewHolder(RowPresenter.ViewHolder holder) {
+        super.initializeRowViewHolder(holder);
+        final ViewHolder rowViewHolder = (ViewHolder) holder;
+        if (needsDefaultListSelectEffect() || needsDefaultShadow()) {
+            rowViewHolder.mItemBridgeAdapter.setWrapper(mCardWrapper);
+        }
+        if (needsDefaultListSelectEffect()) {
+            ShadowOverlayContainer.prepareParentForShadow(rowViewHolder.mGridView);
+            ((ViewGroup) rowViewHolder.view).setClipChildren(false);
+            if (rowViewHolder.mContainerViewHolder != null) {
+                ((ViewGroup) rowViewHolder.mContainerViewHolder.view).setClipChildren(false);
+            }
+        }
+        FocusHighlightHelper.setupBrowseItemFocusHighlight(rowViewHolder.mItemBridgeAdapter, mZoomFactor);
+        rowViewHolder.mGridView.setFocusDrawingOrderEnabled(!isUsingZOrder());
+        rowViewHolder.mGridView.setOnChildSelectedListener(
+                new OnChildSelectedListener() {
+            @Override
+            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
+                selectChildView(rowViewHolder, view);
+            }
+        });
+        rowViewHolder.mItemBridgeAdapter.setAdapterListener(
+                new ItemBridgeAdapter.AdapterListener() {
+            @Override
+            public void onCreate(final ItemBridgeAdapter.ViewHolder viewHolder) {
+                // Only when having an OnItemClickListner, we will attach the OnClickListener.
+                if (getOnItemClickedListener() != null || getOnItemViewClickedListener() != null) {
+                    viewHolder.mHolder.view.setOnClickListener(new View.OnClickListener() {
+                        @Override
+                        public void onClick(View v) {
+                            ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
+                                    rowViewHolder.mGridView
+                                            .getChildViewHolder(viewHolder.itemView);
+                            if (getOnItemClickedListener() != null) {
+                                getOnItemClickedListener().onItemClicked(ibh.mItem,
+                                        (ListRow) rowViewHolder.mRow);
+                            }
+                            if (getOnItemViewClickedListener() != null) {
+                                getOnItemViewClickedListener().onItemClicked(viewHolder.mHolder,
+                                        ibh.mItem, rowViewHolder, (ListRow) rowViewHolder.mRow);
+                            }
+                        }
+                    });
+                }
+            }
+
+            @Override
+            public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
+                if (viewHolder.itemView instanceof ShadowOverlayContainer) {
+                    int dimmedColor = rowViewHolder.mColorDimmer.getPaint().getColor();
+                    ((ShadowOverlayContainer) viewHolder.itemView).setOverlayColor(dimmedColor);
+                }
+                viewHolder.itemView.setActivated(rowViewHolder.mExpanded);
+            }
+
+            @Override
+            public void onAddPresenter(Presenter presenter, int type) {
+                rowViewHolder.getGridView().getRecycledViewPool().setMaxRecycledViews(type, 24);
+            }
+        });
+    }
+
+    final boolean needsDefaultListSelectEffect() {
+        return isUsingDefaultListSelectEffect() && getSelectEffectEnabled();
+    }
+
+    /**
+     * Set {@link PresenterSelector} used for showing a select object in a hover card.
+     */
+    public final void setHoverCardPresenterSelector(PresenterSelector selector) {
+        mHoverCardPresenterSelector = selector;
+    }
+
+    /**
+     * Get {@link PresenterSelector} used for showing a select object in a hover card.
+     */
+    public final PresenterSelector getHoverCardPresenterSelector() {
+        return mHoverCardPresenterSelector;
+    }
+
+    /*
+     * Perform operations when a child of horizontal grid view is selected.
+     */
+    private void selectChildView(ViewHolder rowViewHolder, View view) {
+        ItemBridgeAdapter.ViewHolder ibh = null;
+        if (view != null) {
+            ibh = (ItemBridgeAdapter.ViewHolder)
+                    rowViewHolder.mGridView.getChildViewHolder(view);
+        }
+        if (view == null) {
+            if (mHoverCardPresenterSelector != null) {
+                rowViewHolder.mHoverCardViewSwitcher.unselect();
+            }
+            if (getOnItemViewSelectedListener() != null) {
+                getOnItemViewSelectedListener().onItemSelected(null, null,
+                        rowViewHolder, rowViewHolder.mRow);
+            }
+            if (getOnItemSelectedListener() != null) {
+                getOnItemSelectedListener().onItemSelected(null, rowViewHolder.mRow);
+            }
+        } else if (rowViewHolder.mExpanded && rowViewHolder.mSelected) {
+            if (mHoverCardPresenterSelector != null) {
+                rowViewHolder.mHoverCardViewSwitcher.select(rowViewHolder.mGridView, view,
+                        ibh.mItem);
+            }
+            if (getOnItemViewSelectedListener() != null) {
+                getOnItemViewSelectedListener().onItemSelected(ibh.mHolder, ibh.mItem,
+                        rowViewHolder, rowViewHolder.mRow);
+            }
+            if (getOnItemSelectedListener() != null) {
+                getOnItemSelectedListener().onItemSelected(ibh.mItem, rowViewHolder.mRow);
+            }
+        }
+    }
+
+    private static void initStatics(Context context) {
+        if (sSelectedRowTopPadding == 0) {
+            sSelectedRowTopPadding = context.getResources().getDimensionPixelSize(
+                    R.dimen.lb_browse_selected_row_top_padding);
+            sExpandedSelectedRowTopPadding = context.getResources().getDimensionPixelSize(
+                    R.dimen.lb_browse_expanded_selected_row_top_padding);
+            sExpandedRowNoHovercardBottomPadding = context.getResources().getDimensionPixelSize(
+                    R.dimen.lb_browse_expanded_row_no_hovercard_bottom_padding);
+        }
+    }
+
+    private int getSpaceUnderBaseline(ListRowPresenter.ViewHolder vh) {
+        RowHeaderPresenter.ViewHolder headerViewHolder = vh.getHeaderViewHolder();
+        if (headerViewHolder != null) {
+            if (getHeaderPresenter() != null) {
+                return getHeaderPresenter().getSpaceUnderBaseline(headerViewHolder);
+            }
+            return headerViewHolder.view.getPaddingBottom();
+        }
+        return 0;
+    }
+
+    private void setVerticalPadding(ListRowPresenter.ViewHolder vh) {
+        int paddingTop, paddingBottom;
+        if (vh.isExpanded()) {
+            int headerSpaceUnderBaseline = getSpaceUnderBaseline(vh);
+            if (DEBUG) Log.v(TAG, "headerSpaceUnderBaseline " + headerSpaceUnderBaseline);
+            paddingTop = (vh.isSelected() ? sExpandedSelectedRowTopPadding : vh.mPaddingTop) -
+                    headerSpaceUnderBaseline;
+            paddingBottom = mHoverCardPresenterSelector == null ?
+                    sExpandedRowNoHovercardBottomPadding : vh.mPaddingBottom;
+        } else if (vh.isSelected()) {
+            paddingTop = sSelectedRowTopPadding;
+            paddingBottom = sSelectedRowTopPadding - vh.mPaddingTop;
+        } else {
+            paddingTop = vh.mPaddingTop;
+            paddingBottom = 0;
+        }
+        vh.getGridView().setPadding(vh.mPaddingLeft, paddingTop, vh.mPaddingRight,
+                paddingBottom);
+    }
+
+    @Override
+    protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
+        initStatics(parent.getContext());
+        ListRowView rowView = new ListRowView(parent.getContext());
+        setupFadingEffect(rowView);
+        if (mRowHeight != 0) {
+            rowView.getGridView().setRowHeight(mRowHeight);
+        }
+        return new ViewHolder(rowView, rowView.getGridView(), this);
+    }
+
+    @Override
+    protected void onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected) {
+        super.onRowViewSelected(holder, selected);
+        ViewHolder vh = (ViewHolder) holder;
+        setVerticalPadding(vh);
+        updateFooterViewSwitcher(vh);
+    }
+
+    /*
+     * Show or hide hover card when row selection or expanded state is changed.
+     */
+    private void updateFooterViewSwitcher(ViewHolder vh) {
+        if (vh.mExpanded && vh.mSelected) {
+            if (mHoverCardPresenterSelector != null) {
+                vh.mHoverCardViewSwitcher.init((ViewGroup) vh.view,
+                        mHoverCardPresenterSelector);
+            }
+            ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
+                    vh.mGridView.findViewHolderForPosition(
+                            vh.mGridView.getSelectedPosition());
+            selectChildView(vh, ibh == null ? null : ibh.itemView);
+        } else {
+            if (mHoverCardPresenterSelector != null) {
+                vh.mHoverCardViewSwitcher.unselect();
+            }
+        }
+    }
+
+    private void setupFadingEffect(ListRowView rowView) {
+        // content is completely faded at 1/2 padding of left, fading length is 1/2 of padding.
+        HorizontalGridView gridView = rowView.getGridView();
+        if (mBrowseRowsFadingEdgeLength < 0) {
+            TypedArray ta = gridView.getContext()
+                    .obtainStyledAttributes(R.styleable.LeanbackTheme);
+            mBrowseRowsFadingEdgeLength = (int) ta.getDimension(
+                    R.styleable.LeanbackTheme_browseRowsFadingEdgeLength, 0);
+            ta.recycle();
+        }
+        gridView.setFadingLeftEdgeLength(mBrowseRowsFadingEdgeLength);
+    }
+
+    @Override
+    protected void onRowViewExpanded(RowPresenter.ViewHolder holder, boolean expanded) {
+        super.onRowViewExpanded(holder, expanded);
+        ViewHolder vh = (ViewHolder) holder;
+        if (getRowHeight() != getExpandedRowHeight()) {
+            int newHeight = expanded ? getExpandedRowHeight() : getRowHeight();
+            vh.getGridView().setRowHeight(newHeight);
+        }
+        setVerticalPadding(vh);
+        vh.getGridView().setFadingLeftEdge(!expanded);
+        updateFooterViewSwitcher(vh);
+    }
+
+    @Override
+    protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
+        super.onBindRowViewHolder(holder, item);
+        ViewHolder vh = (ViewHolder) holder;
+        ListRow rowItem = (ListRow) item;
+        vh.mItemBridgeAdapter.clear();
+        vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter());
+        vh.mGridView.setAdapter(vh.mItemBridgeAdapter);
+    }
+
+    @Override
+    protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
+        ((ViewHolder) holder).mGridView.setAdapter(null);
+        super.onUnbindRowViewHolder(holder);
+    }
+
+    /**
+     * ListRowPresenter overrides the default select effect of {@link RowPresenter}
+     * and return false.
+     */
+    @Override
+    public final boolean isUsingDefaultSelectEffect() {
+        return false;
+    }
+
+    /**
+     * Returns true so that default select effect is applied to each individual
+     * child of {@link HorizontalGridView}.  Subclass may return false to disable
+     * the default implementation.
+     * @see #onSelectLevelChanged(RowPresenter.ViewHolder)
+     */
+    public boolean isUsingDefaultListSelectEffect() {
+        return true;
+    }
+
+    /**
+     * Returns true if SDK >= 18, where default shadow
+     * is applied to each individual child of {@link HorizontalGridView}.
+     * Subclass may return false to disable.
+     */
+    public boolean isUsingDefaultShadow() {
+        return ShadowOverlayContainer.supportsShadow();
+    }
+
+    /**
+     * Returns true if SDK >= L, where Z shadow is enabled so that Z order is enabled
+     * on each child of horizontal list.   If subclass returns false in isUsingDefaultShadow()
+     * and does not use Z-shadow on SDK >= L, it should override isUsingZOrder() return false.
+     */
+    public boolean isUsingZOrder() {
+        return ShadowHelper.getInstance().usesZShadow();
+    }
+
+    /**
+     * Enable or disable child shadow.
+     * This is not only for enable/disable default shadow implementation but also subclass must
+     * respect this flag.
+     */
+    public final void setShadowEnabled(boolean enabled) {
+        mShadowEnabled = enabled;
+    }
+
+    /**
+     * Returns true if child shadow is enabled.
+     * This is not only for enable/disable default shadow implementation but also subclass must
+     * respect this flag.
+     */
+    public final boolean getShadowEnabled() {
+        return mShadowEnabled;
+    }
+
+    final boolean needsDefaultShadow() {
+        return isUsingDefaultShadow() && getShadowEnabled();
+    }
+
+    @Override
+    public boolean canDrawOutOfBounds() {
+        return needsDefaultShadow();
+    }
+
+    /**
+     * Applies select level to header and draw a default color dim over each child
+     * of {@link HorizontalGridView}.
+     * <p>
+     * Subclass may override this method.  A subclass
+     * needs to call super.onSelectLevelChanged() for applying header select level
+     * and optionally applying a default select level to each child view of
+     * {@link HorizontalGridView} if {@link #isUsingDefaultListSelectEffect()}
+     * is true.  Subclass may override {@link #isUsingDefaultListSelectEffect()} to return
+     * false and deal with the individual item select level by itself.
+     * </p>
+     */
+    @Override
+    protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) {
+        super.onSelectLevelChanged(holder);
+        if (needsDefaultListSelectEffect()) {
+            ViewHolder vh = (ViewHolder) holder;
+            vh.mColorDimmer.setActiveLevel(holder.mSelectLevel);
+            int dimmedColor = vh.mColorDimmer.getPaint().getColor();
+            for (int i = 0, count = vh.mGridView.getChildCount(); i < count; i++) {
+                ShadowOverlayContainer wrapper = (ShadowOverlayContainer) vh.mGridView.getChildAt(i);
+                wrapper.setOverlayColor(dimmedColor);
+            }
+            if (vh.mGridView.getFadingLeftEdge()) {
+                vh.mGridView.invalidate();
+            }
+        }
+    }
+
+    @Override
+    public void freeze(RowPresenter.ViewHolder holder, boolean freeze) {
+        ViewHolder vh = (ViewHolder) holder;
+        vh.mGridView.setScrollEnabled(!freeze);
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRowView.java b/v17/leanback/src/android/support/v17/leanback/widget/ListRowView.java
new file mode 100644
index 0000000..ab5729b
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRowView.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.support.v17.leanback.R;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+/**
+ * ListRowView contains a horizontal grid view.
+ */
+public final class ListRowView extends LinearLayout {
+
+    private HorizontalGridView mGridView;
+
+    public ListRowView(Context context) {
+        this(context, null);
+    }
+
+    public ListRowView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ListRowView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        LayoutInflater inflater = LayoutInflater.from(context);
+        inflater.inflate(R.layout.lb_list_row, this);
+
+        mGridView = (HorizontalGridView) findViewById(R.id.row_content);
+        // Uncomment this to experiment with page-based scrolling.
+        // mGridView.setFocusScrollStrategy(HorizontalGridView.FOCUS_SCROLL_PAGE);
+
+        setOrientation(LinearLayout.VERTICAL);
+        setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+    }
+
+    public HorizontalGridView getGridView() {
+        return mGridView;
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ObjectAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/ObjectAdapter.java
new file mode 100644
index 0000000..6a2f610
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ObjectAdapter.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.database.Observable;
+
+/**
+ * Adapter for leanback activities.  Provides access to a data model and is
+ * decoupled from the presentation of the items via {@link PresenterSelector}.
+ */
+public abstract class ObjectAdapter {
+
+    /** Indicates that an id has not been set. */
+    public static final int NO_ID = -1;
+
+    /**
+     * A DataObserver can be notified when an ObjectAdapter's underlying data
+     * changes. Separate methods provide notifications about different types of
+     * changes.
+     */
+    public static abstract class DataObserver {
+        /**
+         * Called whenever the ObjectAdapter's data has changed in some manner
+         * outside of the set of changes covered by the other range-based change
+         * notification methods.
+         */
+        public void onChanged() {
+        }
+
+        /**
+         * Called when a range of items in the ObjectAdapter has changed. The
+         * basic ordering and structure of the ObjectAdapter has not changed.
+         *
+         * @param positionStart The position of the first item that changed.
+         * @param itemCount The number of items changed.
+         */
+        public void onItemRangeChanged(int positionStart, int itemCount) {
+            onChanged();
+        }
+
+        /**
+         * Called when a range of items is inserted into the ObjectAdapter.
+         *
+         * @param positionStart The position of the first inserted item.
+         * @param itemCount The number of items inserted.
+         */
+        public void onItemRangeInserted(int positionStart, int itemCount) {
+            onChanged();
+        }
+
+        /**
+         * Called when a range of items is removed from the ObjectAdapter.
+         *
+         * @param positionStart The position of the first removed item.
+         * @param itemCount The number of items removed.
+         */
+        public void onItemRangeRemoved(int positionStart, int itemCount) {
+            onChanged();
+        }
+    }
+
+    private static final class DataObservable extends Observable<DataObserver> {
+
+        public void notifyChanged() {
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onChanged();
+            }
+        }
+
+        public void notifyItemRangeChanged(int positionStart, int itemCount) {
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onItemRangeChanged(positionStart, itemCount);
+            }
+        }
+
+        public void notifyItemRangeInserted(int positionStart, int itemCount) {
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
+            }
+        }
+
+        public void notifyItemRangeRemoved(int positionStart, int itemCount) {
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
+            }
+        }
+    }
+
+    private final DataObservable mObservable = new DataObservable();
+    private boolean mHasStableIds;
+    private PresenterSelector mPresenterSelector;
+
+    /**
+     * Construct an adapter with the given {@link PresenterSelector}.
+     */
+    public ObjectAdapter(PresenterSelector presenterSelector) {
+        setPresenterSelector(presenterSelector);
+    }
+
+    /**
+     * Construct an adapter that uses the given {@link Presenter} for all items.
+     */
+    public ObjectAdapter(Presenter presenter) {
+        setPresenterSelector(new SinglePresenterSelector(presenter));
+    }
+
+    /**
+     * Construct an adapter.
+     */
+    public ObjectAdapter() {
+    }
+
+    /**
+     * Set the presenter selector.  May not be null.
+     */
+    public final void setPresenterSelector(PresenterSelector presenterSelector) {
+        if (presenterSelector == null) {
+            throw new IllegalArgumentException("Presenter selector must not be null");
+        }
+        final boolean update = (mPresenterSelector != null);
+        final boolean selectorChanged = update && mPresenterSelector != presenterSelector;
+
+        mPresenterSelector = presenterSelector;
+
+        if (selectorChanged) {
+            onPresenterSelectorChanged();
+        }
+        if (update) {
+            notifyChanged();
+        }
+    }
+
+    /**
+     * Called when {@link #setPresenterSelector(PresenterSelector)} is called
+     * and the PresenterSelector differs from the previous one.
+     */
+    protected void onPresenterSelectorChanged() {
+    }
+
+    /**
+     * Returns the presenter selector for this ObjectAdapter.
+     */
+    public final PresenterSelector getPresenterSelector() {
+        return mPresenterSelector;
+    }
+
+    /**
+     * Register a DataObserver for data change notifications.
+     */
+    public final void registerObserver(DataObserver observer) {
+        mObservable.registerObserver(observer);
+    }
+
+    /**
+     * Unregister a DataObserver for data change notifications.
+     */
+    public final void unregisterObserver(DataObserver observer) {
+        mObservable.unregisterObserver(observer);
+    }
+
+    /**
+     * Unregister all DataObservers for this ObjectAdapter.
+     */
+    public final void unregisterAllObservers() {
+        mObservable.unregisterAll();
+    }
+
+    final protected void notifyItemRangeChanged(int positionStart, int itemCount) {
+        mObservable.notifyItemRangeChanged(positionStart, itemCount);
+    }
+
+    final protected void notifyItemRangeInserted(int positionStart, int itemCount) {
+        mObservable.notifyItemRangeInserted(positionStart, itemCount);
+    }
+
+    final protected void notifyItemRangeRemoved(int positionStart, int itemCount) {
+        mObservable.notifyItemRangeRemoved(positionStart, itemCount);
+    }
+
+    final protected void notifyChanged() {
+        mObservable.notifyChanged();
+    }
+
+    /**
+     * Indicates whether the item ids are stable across changes to the
+     * underlying data.  When this is true, clients of the ObjectAdapter can use
+     * {@link #getId(int)} to correlate Objects across changes.
+     */
+    public final boolean hasStableIds() {
+        return mHasStableIds;
+    }
+
+    /**
+     * Sets whether the item ids are stable across changes to the underlying
+     * data.
+     */
+    public final void setHasStableIds(boolean hasStableIds) {
+        boolean changed = mHasStableIds != hasStableIds;
+        mHasStableIds = hasStableIds;
+
+        if (changed) {
+            onHasStableIdsChanged();
+        }
+    }
+
+    /**
+     * Called when {@link #setHasStableIds(boolean)} is called and the status
+     * of stable ids has changed.
+     */
+    protected void onHasStableIdsChanged() {
+    }
+
+    /**
+     * Returns the {@link Presenter} for the given item from the adapter.
+     */
+    public final Presenter getPresenter(Object item) {
+        if (mPresenterSelector == null) {
+            throw new IllegalStateException("Presenter selector must not be null");
+        }
+        return mPresenterSelector.getPresenter(item);
+    }
+
+    /**
+     * Returns the number of items in the adapter.
+     */
+    public abstract int size();
+
+    /**
+     * Returns the item for the given position.
+     */
+    public abstract Object get(int position);
+
+    /**
+     * Returns id for the given position.
+     */
+    public long getId(int position) {
+        return NO_ID;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnActionClickedListener.java b/v17/leanback/src/android/support/v17/leanback/widget/OnActionClickedListener.java
new file mode 100644
index 0000000..5dd45e1
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/OnActionClickedListener.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+/**
+ * Interface for receiving notification when an action is clicked.
+ */
+public interface OnActionClickedListener {
+
+    public void onActionClicked(Action action);
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnChildSelectedListener.java b/v17/leanback/src/android/support/v17/leanback/widget/OnChildSelectedListener.java
new file mode 100644
index 0000000..f5f18f8
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/OnChildSelectedListener.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Interface definition for a callback to be invoked when a child of this
+ * viewgroup has been selected.
+ */
+public interface OnChildSelectedListener {
+    /**
+     * Callback method to be invoked when a child of this viewgroup has been
+     * selected.
+     *
+     * <p>This method may be called during layout, so implementations of this
+     * interface need to be careful not to ... (todo).
+     *
+     * @param parent The ViewGroup where the selection happened.
+     * @param view The view within the ViewGroup that is selected, or null if no
+     *        view is selected.
+     * @param position The position of the view in the adapter, or NO_POSITION
+     *        if no view is selected.
+     * @param id The id of the child that is selected, or NO_ID if no view is
+     *        selected.
+     */
+    void onChildSelected(ViewGroup parent, View view, int position, long id);
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnItemClickedListener.java b/v17/leanback/src/android/support/v17/leanback/widget/OnItemClickedListener.java
new file mode 100644
index 0000000..c6b33b5
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/OnItemClickedListener.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.view.View;
+
+/**
+ * Interface for receiving notification when a item is clicked.
+ *
+ * @deprecated Uses {@link OnItemViewClickedListener}
+ */
+public interface OnItemClickedListener {
+
+    public void onItemClicked(Object item, Row row);
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnItemSelectedListener.java b/v17/leanback/src/android/support/v17/leanback/widget/OnItemSelectedListener.java
new file mode 100644
index 0000000..7d46e8f
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/OnItemSelectedListener.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+/**
+ * Interface for receiving notification when a row or item becomes selected.
+ *
+ * @deprecated Use {@link OnItemViewSelectedListener}
+ */
+public interface OnItemSelectedListener {
+    /**
+     * Called when the a row or a new item becomes selected.  The concept of current selection
+     * is different than focus.  Row or item can be selected even they don't have focus.
+     * Having the concept of selection will allow developer to switch background to selected
+     * item or selected row when user selects rows outside row UI (e.g. headers left of
+     * rows).
+     * <p>
+     * For a none {@link ListRow} case,  parameter item is always null.  Event is fired when
+     * selection changes between rows, regardless if row view has focus or not.
+     * <p>
+     * For a {@link ListRow} case, parameter item can be null if the list row is empty.
+     * </p>
+     * <p>
+     * In the case of a grid, the row parameter is always null.
+     * </p>
+     * <li>
+     * Row has focus: event is fired when focus changes between child of the row.
+     * </li>
+     * <li>
+     * None of the row has focus: the event is fired with the current selected row and last
+     * focused item in the row.
+     * </li>
+     *
+     * @param item The item that is currently selected.
+     * @param row The row that is currently selected.
+     */
+    public void onItemSelected(Object item, Row row);
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewClickedListener.java b/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewClickedListener.java
new file mode 100644
index 0000000..c5dc25c
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewClickedListener.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+/**
+ * Interface for receiving notification when a item view holder is clicked.
+ */
+public interface OnItemViewClickedListener {
+
+    public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+            RowPresenter.ViewHolder rowViewHolder, Row row);
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewSelectedListener.java b/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewSelectedListener.java
new file mode 100644
index 0000000..9ecfffb
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewSelectedListener.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+/**
+ * Interface for receiving notification when a row or item becomes selected.
+ */
+public interface OnItemViewSelectedListener {
+    /**
+     * Called when the a row or a new item becomes selected.  The concept of current selection
+     * is different than focus.  Row or item can be selected even they don't have focus.
+     * Having the concept of selection will allow developer to switch background to selected
+     * item or selected row when user selects rows outside row UI (e.g. headers left of
+     * rows).
+     * <p>
+     * For a none {@link ListRow} case,  parameter item is always null.  Event is fired when
+     * selection changes between rows, regardless if row view has focus or not.
+     * <p>
+     * For a {@link ListRow} case, parameter item can be null if the list row is empty.
+     * </p>
+     * <p>
+     * In the case of a grid, the row parameter is always null.
+     * </p>
+     * <li>
+     * Row has focus: event is fired when focus changes between child of the row.
+     * </li>
+     * <li>
+     * None of the row has focus: the event is fired with the current selected row and last
+     * focused item in the row.
+     * </li>
+     *
+     * @param itemViewHolder The view holder of item that is currently selected.
+     * @param item The item that is currently selected.
+     * @param rowViewHolder The view holder of row that is currently selected.
+     * @param row The row that is currently selected.
+     */
+    public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+            RowPresenter.ViewHolder rowViewHolder, Row row);
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsPresenter.java
new file mode 100644
index 0000000..9027e6e
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsPresenter.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.ClipDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.support.v17.leanback.R;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.view.animation.LinearInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+/**
+ * A presenter for a control bar that supports "more actions",
+ * and toggling the set of controls between primary and secondary
+ * sets of {@link Actions}.
+ */
+class PlaybackControlsPresenter extends ControlBarPresenter {
+
+    /**
+     * The data type expected by this presenter.
+     */
+    static class BoundData extends ControlBarPresenter.BoundData {
+        /**
+         * The adapter containing secondary actions.
+         */
+        ObjectAdapter secondaryActionsAdapter;
+    }
+
+    class ViewHolder extends ControlBarPresenter.ViewHolder {
+        ObjectAdapter mMoreActionsAdapter;
+        ObjectAdapter.DataObserver mMoreActionsObserver;
+        final FrameLayout mMoreActionsDock;
+        Presenter.ViewHolder mMoreActionsViewHolder;
+        boolean mMoreActionsShowing;
+        final TextView mCurrentTime;
+        final TextView mTotalTime;
+        final ProgressBar mProgressBar;
+        int mCurrentTimeInSeconds;
+        StringBuilder mTotalTimeStringBuilder = new StringBuilder();
+        StringBuilder mCurrentTimeStringBuilder = new StringBuilder();
+        int mCurrentTimeMarginStart;
+        int mTotalTimeMarginEnd;
+
+        ViewHolder(View rootView) {
+            super(rootView);
+            mMoreActionsDock = (FrameLayout) rootView.findViewById(R.id.more_actions_dock);
+            mCurrentTime = (TextView) rootView.findViewById(R.id.current_time);
+            mTotalTime = (TextView) rootView.findViewById(R.id.total_time);
+            mProgressBar = (ProgressBar) rootView.findViewById(R.id.playback_progress);
+            mMoreActionsObserver = new ObjectAdapter.DataObserver() {
+                @Override
+                public void onChanged() {
+                    if (mMoreActionsShowing) {
+                        showControls(mPresenter);
+                    }
+                }
+                @Override
+                public void onItemRangeChanged(int positionStart, int itemCount) {
+                    if (mMoreActionsShowing) {
+                        for (int i = 0; i < itemCount; i++) {
+                            bindControlToAction(positionStart + i, mPresenter);
+                        }
+                    }
+                }
+            };
+            mCurrentTimeMarginStart =
+                    ((MarginLayoutParams) mCurrentTime.getLayoutParams()).getMarginStart();
+            mTotalTimeMarginEnd =
+                    ((MarginLayoutParams) mTotalTime.getLayoutParams()).getMarginEnd();
+        }
+
+        void showMoreActions(boolean show) {
+            if (show) {
+                if (mMoreActionsViewHolder == null) {
+                    Action action = new PlaybackControlsRow.MoreActions(mMoreActionsDock.getContext());
+                    mMoreActionsViewHolder = mPresenter.onCreateViewHolder(mMoreActionsDock);
+                    mPresenter.onBindViewHolder(mMoreActionsViewHolder, action);
+                    mPresenter.setOnClickListener(mMoreActionsViewHolder, new View.OnClickListener() {
+                        @Override
+                        public void onClick(View v) {
+                            toggleMoreActions();
+                        }
+                    });
+                }
+                if (mMoreActionsViewHolder.view.getParent() == null) {
+                    mMoreActionsDock.addView(mMoreActionsViewHolder.view);
+                }
+            } else if (mMoreActionsViewHolder != null &&
+                    mMoreActionsViewHolder.view.getParent() != null) {
+                mMoreActionsDock.removeView(mMoreActionsViewHolder.view);
+            }
+        }
+
+        void toggleMoreActions() {
+            mMoreActionsShowing = !mMoreActionsShowing;
+            showControls(mPresenter);
+        }
+
+        @Override
+        ObjectAdapter getDisplayedAdapter() {
+            return mMoreActionsShowing ? mMoreActionsAdapter : mAdapter;
+        }
+
+        @Override
+        int getChildMarginFromCenter(Context context, int numControls) {
+            int margin = getControlIconWidth(context);
+            if (numControls < 4) {
+                margin += getChildMarginBiggest(context);
+            } else if (numControls < 6) {
+                margin += getChildMarginBigger(context);
+            } else {
+                margin += getChildMarginDefault(context);
+            }
+            return margin;
+        }
+
+        void setTotalTime(int totalTimeMs) {
+            if (totalTimeMs <= 0) {
+                mTotalTime.setVisibility(View.GONE);
+                mProgressBar.setVisibility(View.GONE);
+            } else {
+                mTotalTime.setVisibility(View.VISIBLE);
+                mProgressBar.setVisibility(View.VISIBLE);
+                formatTime(totalTimeMs / 1000, mTotalTimeStringBuilder);
+                mTotalTime.setText(mTotalTimeStringBuilder.toString());
+                mProgressBar.setMax(totalTimeMs);
+            }
+        }
+
+        int getTotalTime() {
+            return mProgressBar.getMax();
+        }
+
+        void setCurrentTime(int currentTimeMs) {
+            int seconds = currentTimeMs / 1000;
+            if (seconds != mCurrentTimeInSeconds) {
+                mCurrentTimeInSeconds = seconds;
+                formatTime(mCurrentTimeInSeconds, mCurrentTimeStringBuilder);
+                mCurrentTime.setText(mCurrentTimeStringBuilder.toString());
+            }
+            mProgressBar.setProgress(currentTimeMs);
+        }
+
+        int getCurrentTime() {
+            return mProgressBar.getProgress();
+        }
+
+        void setSecondaryProgress(int progressMs) {
+            mProgressBar.setSecondaryProgress(progressMs);
+        }
+
+        int getSecondaryProgress() {
+            return mProgressBar.getSecondaryProgress();
+        }
+    }
+
+    private static void formatTime(int seconds, StringBuilder sb) {
+        int minutes = seconds / 60;
+        int hours = minutes / 60;
+        seconds -= minutes * 60;
+        minutes -= hours * 60;
+
+        sb.setLength(0);
+        if (hours > 0) {
+            sb.append(hours).append(':');
+            if (minutes < 10) {
+                sb.append('0');
+            }
+        }
+        sb.append(minutes).append(':');
+        if (seconds < 10) {
+            sb.append('0');
+        }
+        sb.append(seconds);
+    }
+
+    private boolean mMoreActionsEnabled = true;
+    private static int sChildMarginBigger;
+    private static int sChildMarginBiggest;
+
+    /**
+     * Constructor for a PlaybackControlsRowPresenter.
+     *
+     * @param layoutResourceId The resource id of the layout for this presenter.
+     */
+    public PlaybackControlsPresenter(int layoutResourceId) {
+        super(layoutResourceId);
+    }
+
+    /**
+     * Enables the display of secondary actions.
+     * A "more actions" button will be displayed.  When "more actions" is selected,
+     * the primary actions are replaced with the secondary actions.
+     */
+    public void enableSecondaryActions(boolean enable) {
+        mMoreActionsEnabled = enable;
+    }
+
+    /**
+     * Returns true if secondary actions are enabled.
+     */
+    public boolean areMoreActionsEnabled() {
+        return mMoreActionsEnabled;
+    }
+
+    public void setProgressColor(ViewHolder vh, int color) {
+        Drawable drawable = new ClipDrawable(new ColorDrawable(color),
+                Gravity.LEFT, ClipDrawable.HORIZONTAL);
+        ((LayerDrawable) vh.mProgressBar.getProgressDrawable())
+                .setDrawableByLayerId(android.R.id.progress, drawable);
+    }
+
+    public void setTotalTime(ViewHolder vh, int ms) {
+        vh.setTotalTime(ms);
+    }
+
+    public int getTotalTime(ViewHolder vh) {
+        return vh.getTotalTime();
+    }
+
+    public void setCurrentTime(ViewHolder vh, int ms) {
+        vh.setCurrentTime(ms);
+    }
+
+    public int getCurrentTime(ViewHolder vh) {
+        return vh.getCurrentTime();
+    }
+
+    public void setSecondaryProgress(ViewHolder vh, int progressMs) {
+        vh.setSecondaryProgress(progressMs);
+    }
+
+    public int getSecondaryProgress(ViewHolder vh) {
+        return vh.getSecondaryProgress();
+    }
+
+    public void showPrimaryActions(ViewHolder vh) {
+        if (vh.mMoreActionsShowing) {
+            vh.toggleMoreActions();
+        }
+    }
+
+    public void enableTimeMargins(ViewHolder vh, boolean enable) {
+        MarginLayoutParams lp;
+        lp = (MarginLayoutParams) vh.mCurrentTime.getLayoutParams();
+        lp.setMarginStart(enable ? vh.mCurrentTimeMarginStart : 0);
+        vh.mCurrentTime.setLayoutParams(lp);
+
+        lp = (MarginLayoutParams) vh.mTotalTime.getLayoutParams();
+        lp.setMarginEnd(enable ? vh.mTotalTimeMarginEnd : 0);
+        vh.mTotalTime.setLayoutParams(lp);
+    }
+
+    @Override
+    public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
+        View v = LayoutInflater.from(parent.getContext())
+            .inflate(getLayoutResourceId(), parent, false);
+        return new ViewHolder(v);
+    }
+
+    @Override
+    public void onBindViewHolder(Presenter.ViewHolder holder, Object item) {
+        ViewHolder vh = (ViewHolder) holder;
+        BoundData data = (BoundData) item;
+
+        // If binding to a new adapter, display primary actions.
+        if (vh.mMoreActionsAdapter != data.secondaryActionsAdapter) {
+            vh.mMoreActionsAdapter = data.secondaryActionsAdapter;
+            vh.mMoreActionsAdapter.registerObserver(vh.mMoreActionsObserver);
+            vh.mMoreActionsShowing = false;
+        }
+
+        super.onBindViewHolder(holder, item);
+        vh.showMoreActions(mMoreActionsEnabled);
+    }
+
+    @Override
+    public void onUnbindViewHolder(Presenter.ViewHolder holder) {
+        super.onUnbindViewHolder(holder);
+        ViewHolder vh = (ViewHolder) holder;
+        vh.mMoreActionsAdapter.unregisterObserver(vh.mMoreActionsObserver);
+        vh.mMoreActionsAdapter = null;
+    }
+
+    int getChildMarginBigger(Context context) {
+        if (sChildMarginBigger == 0) {
+            sChildMarginBigger = context.getResources().getDimensionPixelSize(
+                    R.dimen.lb_playback_controls_child_margin_bigger);
+        }
+        return sChildMarginBigger;
+    }
+
+    int getChildMarginBiggest(Context context) {
+        if (sChildMarginBiggest == 0) {
+            sChildMarginBiggest = context.getResources().getDimensionPixelSize(
+                    R.dimen.lb_playback_controls_child_margin_biggest);
+        }
+        return sChildMarginBiggest;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
new file mode 100644
index 0000000..e7f15ee
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
@@ -0,0 +1,635 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.support.v17.leanback.R;
+import android.util.TypedValue;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+/**
+ * A row of playback controls to be displayed by a {@link PlaybackControlsRowPresenter}.
+ *
+ * This row consists of some optional item detail, a series of primary actions,
+ * and an optional series of secondary actions.
+ *
+ * Controls are specified via an {@link ObjectAdapter} containing one or more
+ * {@link Action}s.
+ *
+ * Adapters should have their {@link PresenterSelector} set to an instance of
+ * {@link ControlButtonPresenterSelector}.
+ *
+ */
+public class PlaybackControlsRow extends Row {
+
+    /**
+     * Base class for an action comprised of a series of icons.
+     */
+    public static abstract class MultiAction extends Action {
+        private int mIndex;
+        private Drawable[] mDrawables;
+
+        /**
+         * Constructor
+         * @param id The id of the Action.
+         */
+        public MultiAction(int id) {
+            super(id);
+        }
+
+        /**
+         * Sets the array of drawables.  The size of the array defines the range
+         * of valid indices for this action.
+         */
+        public void setDrawables(Drawable[] drawables) {
+            mDrawables = drawables;
+            setIndex(0);
+        }
+
+        /**
+         * Returns the number of drawables.
+         */
+        public int getNumberOfDrawables() {
+            return mDrawables.length;
+        }
+
+        /**
+         * Returns the drawable at the given index.
+         */
+        public Drawable getDrawable(int index) {
+            return mDrawables[index];
+        }
+
+        /**
+         * Increments the index, wrapping to zero once the end is reached.
+         */
+        public void nextIndex() {
+            setIndex(mIndex < mDrawables.length - 1 ? mIndex + 1 : 0);
+        }
+
+        /**
+         * @deprecated Use nextIndex instead
+         */
+        @Deprecated
+        public void toggle() {
+            nextIndex();
+        }
+
+        /**
+         * Sets the current index.
+         */
+        public void setIndex(int index) {
+            mIndex = index;
+            setIcon(mDrawables[mIndex]);
+        }
+
+        /**
+         * Gets the current index.
+         */
+        public int getIndex() {
+            return mIndex;
+        }
+    }
+
+    /**
+     * An action displaying icons for play and pause.
+     */
+    public static class PlayPauseAction extends MultiAction {
+        /**
+         * Action index for the play icon.
+         */
+        public static int PLAY = 0;
+
+        /**
+         * Action index for the pause icon.
+         */
+        public static int PAUSE = 1;
+
+        /**
+         * Constructor
+         * @param context Context used for loading resources.
+         */
+        public PlayPauseAction(Context context) {
+            super(R.id.lb_control_play_pause);
+            Drawable[] drawables = new Drawable[2];
+            drawables[PLAY] = getStyledDrawable(context,
+                    R.styleable.lbPlaybackControlsActionIcons_play);
+            drawables[PAUSE] = getStyledDrawable(context,
+                    R.styleable.lbPlaybackControlsActionIcons_pause);
+            setDrawables(drawables);
+        }
+
+        /**
+         * Returns true if the current icon is play.
+         * @deprecated Use getIndex instead
+         */
+        @Deprecated
+        public boolean isPlayIconShown() {
+            return getIndex() == PLAY;
+        }
+    }
+
+    /**
+     * An action displaying an icon for fast forward.
+     */
+    public static class FastForwardAction extends Action {
+        /**
+         * Constructor
+         * @param context Context used for loading resources.
+         */
+        public FastForwardAction(Context context) {
+            super(R.id.lb_control_fast_forward);
+            setIcon(getStyledDrawable(context,
+                    R.styleable.lbPlaybackControlsActionIcons_fast_forward));
+        }
+    }
+
+    /**
+     * An action displaying an icon for rewind.
+     */
+    public static class RewindAction extends Action {
+        /**
+         * Constructor
+         * @param context Context used for loading resources.
+         */
+        public RewindAction(Context context) {
+            super(R.id.lb_control_fast_rewind);
+            setIcon(getStyledDrawable(context,
+                    R.styleable.lbPlaybackControlsActionIcons_rewind));
+        }
+    }
+
+    /**
+     * An action displaying an icon for skip next.
+     */
+    public static class SkipNextAction extends Action {
+        /**
+         * Constructor
+         * @param context Context used for loading resources.
+         */
+        public SkipNextAction(Context context) {
+            super(R.id.lb_control_skip_next);
+            setIcon(getStyledDrawable(context,
+                    R.styleable.lbPlaybackControlsActionIcons_skip_next));
+        }
+    }
+
+    /**
+     * An action displaying an icon for skip previous.
+     */
+    public static class SkipPreviousAction extends Action {
+        /**
+         * Constructor
+         * @param context Context used for loading resources.
+         */
+        public SkipPreviousAction(Context context) {
+            super(R.id.lb_control_skip_previous);
+            setIcon(getStyledDrawable(context,
+                    R.styleable.lbPlaybackControlsActionIcons_skip_previous));
+        }
+    }
+
+    /**
+     * An action displaying an icon for "more actions".
+     */
+    public static class MoreActions extends Action {
+        /**
+         * Constructor
+         * @param context Context used for loading resources.
+         */
+        public MoreActions(Context context) {
+            super(R.id.lb_control_more_actions);
+            setIcon(context.getResources().getDrawable(R.drawable.lb_ic_more));
+        }
+    }
+
+    /**
+     * A base class for displaying a thumbs action.
+     */
+    public static abstract class ThumbsAction extends MultiAction {
+        /**
+         * Action index for the solid thumb icon.
+         */
+        public static int SOLID = 0;
+
+        /**
+         * Action index for the outline thumb icon.
+         */
+        public static int OUTLINE = 1;
+
+        /**
+         * Constructor
+         * @param context Context used for loading resources.
+         */
+        public ThumbsAction(int id, Context context, int solidIconIndex, int outlineIconIndex) {
+            super(id);
+            Drawable[] drawables = new Drawable[2];
+            drawables[SOLID] = getStyledDrawable(context, solidIconIndex);
+            drawables[OUTLINE] = getStyledDrawable(context, outlineIconIndex);
+            setDrawables(drawables);
+        }
+    }
+
+    /**
+     * An action displaying an icon for thumbs up.
+     */
+    public static class ThumbsUpAction extends ThumbsAction {
+        public ThumbsUpAction(Context context) {
+            super(R.id.lb_control_thumbs_up, context,
+                    R.styleable.lbPlaybackControlsActionIcons_thumb_up,
+                    R.styleable.lbPlaybackControlsActionIcons_thumb_up_outline);
+        }
+    }
+
+    /**
+     * An action displaying an icon for thumbs down.
+     */
+    public static class ThumbsDownAction extends ThumbsAction {
+        public ThumbsDownAction(Context context) {
+            super(R.id.lb_control_thumbs_down, context,
+                    R.styleable.lbPlaybackControlsActionIcons_thumb_down,
+                    R.styleable.lbPlaybackControlsActionIcons_thumb_down_outline);
+        }
+    }
+
+     /**
+     * An action for displaying three repeat states: none, one, or all.
+     */
+    public static class RepeatAction extends MultiAction {
+        /**
+         * Action index for the repeat-none icon.
+         */
+        public static int NONE = 0;
+
+        /**
+         * Action index for the repeat-all icon.
+         */
+        public static int ALL = 1;
+
+        /**
+         * Action index for the repeat-one icon.
+         */
+        public static int ONE = 2;
+
+        /**
+         * Constructor
+         * @param context Context used for loading resources.
+         */
+        public RepeatAction(Context context) {
+            this(context, getColorFromTheme(context,
+                    R.attr.playbackControlsIconHighlightColor));
+        }
+
+        /**
+         * Constructor
+         * @param context Context used for loading resources
+         * @param highlightColor Color to display the repeat-all and repeat0one icons.
+         */
+        public RepeatAction(Context context, int highlightColor) {
+            this(context, highlightColor, highlightColor);
+        }
+
+        /**
+         * Constructor
+         * @param context Context used for loading resources
+         * @param repeatAllColor Color to display the repeat-all icon.
+         * @param repeatOneColor Color to display the repeat-one icon.
+         */
+        public RepeatAction(Context context, int repeatAllColor, int repeatOneColor) {
+            super(R.id.lb_control_repeat);
+            Drawable[] drawables = new Drawable[3];
+            BitmapDrawable repeatDrawable = (BitmapDrawable) getStyledDrawable(context,
+                    R.styleable.lbPlaybackControlsActionIcons_repeat);
+            BitmapDrawable repeatOneDrawable = (BitmapDrawable) getStyledDrawable(context,
+                    R.styleable.lbPlaybackControlsActionIcons_repeat_one);
+            drawables[NONE] = repeatDrawable;
+            drawables[ALL] = new BitmapDrawable(context.getResources(),
+                    createBitmap(repeatDrawable.getBitmap(), repeatAllColor));
+            drawables[ONE] = new BitmapDrawable(context.getResources(),
+                    createBitmap(repeatOneDrawable.getBitmap(), repeatOneColor));
+            setDrawables(drawables);
+        }
+
+        /**
+         * Display the next icon in the series.
+         * @deprecated Use nextIndex instead
+         */
+        @Deprecated
+        public void next() {
+            nextIndex();
+        }
+    }
+
+    /**
+     * An action for displaying a shuffle icon.
+     */
+    public static class ShuffleAction extends MultiAction {
+        public static int OFF = 0;
+        public static int ON = 1;
+
+        /**
+         * Constructor
+         * @param context Context used for loading resources.
+         */
+        public ShuffleAction(Context context) {
+            this(context, getColorFromTheme(context,
+                    R.attr.playbackControlsIconHighlightColor));
+        }
+
+        /**
+         * Constructor
+         * @param context Context used for loading resources.
+         * @param highlightColor Color for the highlighted icon state.
+         */
+        public ShuffleAction(Context context, int highlightColor) {
+            super(R.id.lb_control_shuffle);
+            BitmapDrawable uncoloredDrawable = (BitmapDrawable) getStyledDrawable(context,
+                    R.styleable.lbPlaybackControlsActionIcons_shuffle);
+            Drawable[] drawables = new Drawable[2];
+            drawables[OFF] = uncoloredDrawable;
+            drawables[ON] = new BitmapDrawable(context.getResources(),
+                    createBitmap(uncoloredDrawable.getBitmap(), highlightColor));
+            setDrawables(drawables);
+        }
+    }
+
+    /**
+     * An action for displaying a HQ (High Quality) icon.
+     */
+    public static class HighQualityAction extends MultiAction {
+        public static int OFF = 0;
+        public static int ON = 1;
+
+        /**
+         * Constructor
+         * @param context Context used for loading resources.
+         */
+        public HighQualityAction(Context context) {
+            this(context, getColorFromTheme(context,
+                    R.attr.playbackControlsIconHighlightColor));
+        }
+
+        /**
+         * Constructor
+         * @param context Context used for loading resources.
+         * @param highlightColor Color for the highlighted icon state.
+         */
+        public HighQualityAction(Context context, int highlightColor) {
+            super(R.id.lb_control_high_quality);
+            BitmapDrawable uncoloredDrawable = (BitmapDrawable) getStyledDrawable(context,
+                    R.styleable.lbPlaybackControlsActionIcons_high_quality);
+            Drawable[] drawables = new Drawable[2];
+            drawables[OFF] = uncoloredDrawable;
+            drawables[ON] = new BitmapDrawable(context.getResources(),
+                    createBitmap(uncoloredDrawable.getBitmap(), highlightColor));
+            setDrawables(drawables);
+        }
+    }
+
+    /**
+     * An action for displaying a CC (Closed Captioning) icon.
+     */
+    public static class ClosedCaptioningAction extends MultiAction {
+        public static int OFF = 0;
+        public static int ON = 1;
+
+        /**
+         * Constructor
+         * @param context Context used for loading resources.
+         */
+        public ClosedCaptioningAction(Context context) {
+            this(context, getColorFromTheme(context,
+                    R.attr.playbackControlsIconHighlightColor));
+        }
+
+        /**
+         * Constructor
+         * @param context Context used for loading resources.
+         * @param highlightColor Color for the highlighted icon state.
+         */
+        public ClosedCaptioningAction(Context context, int highlightColor) {
+            super(R.id.lb_control_high_quality);
+            BitmapDrawable uncoloredDrawable = (BitmapDrawable) getStyledDrawable(context,
+                    R.styleable.lbPlaybackControlsActionIcons_closed_captioning);
+            Drawable[] drawables = new Drawable[2];
+            drawables[OFF] = uncoloredDrawable;
+            drawables[ON] = new BitmapDrawable(context.getResources(),
+                    createBitmap(uncoloredDrawable.getBitmap(), highlightColor));
+            setDrawables(drawables);
+        }
+    }
+
+    private static Bitmap createBitmap(Bitmap bitmap, int color) {
+        Bitmap dst = bitmap.copy(bitmap.getConfig(), true);
+        Canvas canvas = new Canvas(dst);
+        Paint paint = new Paint();
+        paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP));
+        canvas.drawBitmap(bitmap, 0, 0, paint);
+        return dst;
+    }
+
+    private static int getColorFromTheme(Context context, int attributeResId) {
+        TypedValue outValue = new TypedValue();
+        context.getTheme().resolveAttribute(attributeResId, outValue, true);
+        return outValue.data;
+    }
+
+    private static Drawable getStyledDrawable(Context context, int index) {
+        TypedValue outValue = new TypedValue();
+        context.getTheme().resolveAttribute(
+                R.attr.playbackControlsActionIcons, outValue, false);
+        TypedArray array = context.getTheme().obtainStyledAttributes(outValue.data,
+                R.styleable.lbPlaybackControlsActionIcons);
+        Drawable drawable = array.getDrawable(index);
+        array.recycle();
+        return drawable;
+    }
+
+    private Object mItem;
+    private Drawable mImageDrawable;
+    private ObjectAdapter mPrimaryActionsAdapter;
+    private ObjectAdapter mSecondaryActionsAdapter;
+    private int mTotalTimeMs;
+    private int mCurrentTimeMs;
+    private int mBufferedProgressMs;
+    private OnPlaybackStateChangedListener mListener;
+
+    /**
+     * Constructor for a PlaybackControlsRow that displays some details from
+     * the given item.
+     *
+     * @param item The main item for the row.
+     */
+    public PlaybackControlsRow(Object item) {
+        mItem = item;
+    }
+
+    /**
+     * Constructor for a PlaybackControlsRow that has no item details.
+     */
+    public PlaybackControlsRow() {
+    }
+
+    /**
+     * Gets the main item for the details page.
+     */
+    public final Object getItem() {
+        return mItem;
+    }
+
+    /**
+     * Sets a {link @Drawable} image for this row.
+     *
+     * @param drawable The drawable to set.
+     */
+    public final void setImageDrawable(Drawable drawable) {
+        mImageDrawable = drawable;
+    }
+
+    /**
+     * Sets a {@link Bitmap} for this row.
+     *
+     * @param context The context to retrieve display metrics from.
+     * @param bm The bitmap to set.
+     */
+    public final void setImageBitmap(Context context, Bitmap bm) {
+        mImageDrawable = new BitmapDrawable(context.getResources(), bm);
+    }
+
+    /**
+     * Gets the image {@link Drawable} of this row.
+     *
+     * @return The overview's image drawable, or null if no drawable has been
+     *         assigned.
+     */
+    public final Drawable getImageDrawable() {
+        return mImageDrawable;
+    }
+
+    /**
+     * Sets the primary actions {@link ObjectAdapter}.
+     */
+    public final void setPrimaryActionsAdapter(ObjectAdapter adapter) {
+        mPrimaryActionsAdapter = adapter;
+    }
+
+    /**
+     * Sets the secondary actions {@link ObjectAdapter}.
+     */
+    public final void setSecondaryActionsAdapter(ObjectAdapter adapter) {
+        mSecondaryActionsAdapter = adapter;
+    }
+
+    /**
+     * Returns the primary actions {@link ObjectAdapter}.
+     */
+    public final ObjectAdapter getPrimaryActionsAdapter() {
+        return mPrimaryActionsAdapter;
+    }
+
+    /**
+     * Returns the secondary actions {@link ObjectAdapter}.
+     */
+    public final ObjectAdapter getSecondaryActionsAdapter() {
+        return mSecondaryActionsAdapter;
+    }
+
+    /**
+     * Sets the total time in milliseconds for the playback controls row.
+     */
+    public void setTotalTime(int ms) {
+        mTotalTimeMs = ms;
+    }
+
+    /**
+     * Returns the total time in milliseconds for the playback controls row.
+     */
+    public int getTotalTime() {
+        return mTotalTimeMs;
+    }
+
+    /**
+     * Sets the current time in milliseconds for the playback controls row.
+     */
+    public void setCurrentTime(int ms) {
+        if (mCurrentTimeMs != ms) {
+            mCurrentTimeMs = ms;
+            currentTimeChanged();
+        }
+    }
+
+    /**
+     * Returns the current time in milliseconds for the playback controls row.
+     */
+    public int getCurrentTime() {
+        return mCurrentTimeMs;
+    }
+
+    /**
+     * Sets the buffered progress for the playback controls row.
+     */
+    public void setBufferedProgress(int ms) {
+        if (mBufferedProgressMs != ms) {
+            mBufferedProgressMs = ms;
+            bufferedProgressChanged();
+        }
+    }
+
+    /**
+     * Returns the buffered progress for the playback controls row.
+     */
+    public int getBufferedProgress() {
+        return mBufferedProgressMs;
+    }
+
+    interface OnPlaybackStateChangedListener {
+        public void onCurrentTimeChanged(int currentTimeMs);
+        public void onBufferedProgressChanged(int bufferedProgressMs);
+    }
+
+    /**
+     * Sets a listener to be called when the playback state changes.
+     */
+    public void setOnPlaybackStateChangedListener(OnPlaybackStateChangedListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Returns the playback state listener.
+     */
+    public OnPlaybackStateChangedListener getOnPlaybackStateChangedListener() {
+        return mListener;
+    }
+
+    private void currentTimeChanged() {
+        if (mListener != null) {
+            mListener.onCurrentTimeChanged(mCurrentTimeMs);
+        }
+    }
+
+    private void bufferedProgressChanged() {
+        if (mListener != null) {
+            mListener.onBufferedProgressChanged(mBufferedProgressMs);
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java
new file mode 100644
index 0000000..d235b98
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowPresenter.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.support.v17.leanback.R;
+import android.content.Context;
+import android.graphics.Color;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewGroup.MarginLayoutParams;
+import android.widget.ImageView;
+
+/**
+ * A PlaybackControlsRowPresenter renders a {@link PlaybackControlsRow} to display a
+ * series of playback control buttons. Typically this row will be the first row in a fragment
+ * such as the {@link android.support.v17.leanback.app.PlaybackOverlayFragment
+ * PlaybackControlsFragment}.
+ */
+public class PlaybackControlsRowPresenter extends RowPresenter {
+
+    /**
+     * A ViewHolder for the PlaybackControlsRow.
+     */
+    public class ViewHolder extends RowPresenter.ViewHolder {
+        public final Presenter.ViewHolder mDescriptionViewHolder;
+        final ViewGroup mCard;
+        final ImageView mImageView;
+        final ViewGroup mDescriptionDock;
+        final ViewGroup mControlsDock;
+        final ViewGroup mSecondaryControlsDock;
+        final View mSpacer;
+        final View mBottomSpacer;
+        View mBgView;
+        int mCardHeight;
+        int mControlsDockMarginStart;
+        int mControlsDockMarginEnd;
+        PlaybackControlsPresenter.ViewHolder mControlsVh;
+        Presenter.ViewHolder mSecondaryControlsVh;
+        PlaybackControlsPresenter.BoundData mControlsBoundData =
+                new PlaybackControlsPresenter.BoundData();
+        ControlBarPresenter.BoundData mSecondaryBoundData = new ControlBarPresenter.BoundData();
+        final PlaybackControlsRow.OnPlaybackStateChangedListener mListener =
+                new PlaybackControlsRow.OnPlaybackStateChangedListener() {
+            @Override
+            public void onCurrentTimeChanged(int ms) {
+                mPlaybackControlsPresenter.setCurrentTime(mControlsVh, ms);
+            }
+            @Override
+            public void onBufferedProgressChanged(int ms) {
+                mPlaybackControlsPresenter.setSecondaryProgress(mControlsVh, ms);
+            }
+        };
+        final OnItemViewSelectedListener mOnItemViewSelectedListener =
+                new OnItemViewSelectedListener() {
+            @Override
+            public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+                    RowPresenter.ViewHolder rowViewHolder, Row row) {
+                if (getOnItemViewSelectedListener() != null) {
+                    getOnItemViewSelectedListener().onItemSelected(itemViewHolder, item,
+                            ViewHolder.this, getRow());
+                }
+            }
+        };
+
+        ViewHolder(View rootView, Presenter descriptionPresenter) {
+            super(rootView);
+            mCard = (ViewGroup) rootView.findViewById(R.id.controls_card);
+            mImageView = (ImageView) rootView.findViewById(R.id.image);
+            mDescriptionDock = (ViewGroup) rootView.findViewById(R.id.description_dock);
+            mControlsDock = (ViewGroup) rootView.findViewById(R.id.controls_dock);
+            mSecondaryControlsDock =
+                    (ViewGroup) rootView.findViewById(R.id.secondary_controls_dock);
+            mSpacer = rootView.findViewById(R.id.spacer);
+            mBottomSpacer = rootView.findViewById(R.id.bottom_spacer);
+            mDescriptionViewHolder = descriptionPresenter == null ? null :
+                    descriptionPresenter.onCreateViewHolder(mDescriptionDock);
+            if (mDescriptionViewHolder != null) {
+                mDescriptionDock.addView(mDescriptionViewHolder.view);
+            }
+        }
+
+        Presenter getPresenter(Object item, boolean primary) {
+            ObjectAdapter adapter = primary ?
+                    ((PlaybackControlsRow) getRow()).getPrimaryActionsAdapter() :
+                            ((PlaybackControlsRow) getRow()).getSecondaryActionsAdapter();
+            if (adapter.getPresenterSelector() instanceof ControlButtonPresenterSelector) {
+                ControlButtonPresenterSelector selector =
+                        (ControlButtonPresenterSelector) adapter.getPresenterSelector();
+                return primary ? selector.getPrimaryPresenter() :
+                    selector.getSecondaryPresenter();
+            }
+            return adapter.getPresenter(item);
+        }
+
+        void setBackground(View view) {
+            if (mBgView != null) {
+                mBgView.setBackgroundColor(Color.TRANSPARENT);
+                ShadowHelper.getInstance().clearZ(mBgView);
+            }
+            mBgView = view;
+            view.setBackgroundColor(mBackgroundColorSet ?
+                    mBackgroundColor : getDefaultBackgroundColor(view.getContext()));
+            ShadowHelper.getInstance().setZ(view, 0f);
+        }
+    }
+
+    private int mBackgroundColor = Color.TRANSPARENT;
+    private boolean mBackgroundColorSet;
+    private int mProgressColor = Color.TRANSPARENT;
+    private boolean mProgressColorSet;
+    private boolean mSecondaryActionsHidden;
+    private Presenter mDescriptionPresenter;
+    private PlaybackControlsPresenter mPlaybackControlsPresenter;
+    private ControlBarPresenter mSecondaryControlsPresenter;
+
+    /**
+     * Constructor for a PlaybackControlsRowPresenter.
+     *
+     * @param descriptionPresenter Presenter for displaying item details.
+     */
+    public PlaybackControlsRowPresenter(Presenter descriptionPresenter) {
+        setHeaderPresenter(null);
+        setSelectEffectEnabled(false);
+
+        mDescriptionPresenter = descriptionPresenter;
+        mPlaybackControlsPresenter = new PlaybackControlsPresenter(R.layout.lb_playback_controls);
+        mSecondaryControlsPresenter = new ControlBarPresenter(R.layout.lb_control_bar);
+    }
+
+    /**
+     * Constructor for a PlaybackControlsRowPresenter.
+     */
+    public PlaybackControlsRowPresenter() {
+        this(null);
+    }
+
+    /**
+     * Sets the listener for {@link Action} click events.
+     */
+    public void setOnActionClickedListener(OnActionClickedListener listener) {
+        mPlaybackControlsPresenter.setOnActionClickedListener(listener);
+        mSecondaryControlsPresenter.setOnActionClickedListener(listener);
+    }
+
+    /**
+     * Gets the listener for {@link Action} click events.
+     */
+    public OnActionClickedListener getOnActionClickedListener() {
+        return mPlaybackControlsPresenter.getOnActionClickedListener();
+    }
+
+    /**
+     * Sets the background color.  If not set, a default from the theme will be used.
+     */
+    public void setBackgroundColor(int color) {
+        mBackgroundColor = color;
+        mBackgroundColorSet = true;
+    }
+
+    /**
+     * Returns the background color.  If no background color was set, transparent
+     * is returned.
+     */
+    public int getBackgroundColor() {
+        return mBackgroundColor;
+    }
+
+    /**
+     * Sets the primary color for the progress bar.  If not set, a default from
+     * the theme will be used.
+     */
+    public void setProgressColor(int color) {
+        mProgressColor = color;
+        mProgressColorSet = true;
+    }
+
+    /**
+     * Returns the primary color for the progress bar.  If no color was set, transparent
+     * is returned.
+     */
+    public int getProgressColor() {
+        return mProgressColor;
+    }
+
+    /**
+     * Sets the secondary actions to be hidden behind a "more actions" button.
+     * When "more actions" is selected, the primary actions are replaced with
+     * the secondary actions.
+     */
+    public void setSecondaryActionsHidden(boolean hidden) {
+        mSecondaryActionsHidden = hidden;
+    }
+
+    /**
+     * Returns true if secondary actions are hidden.
+     */
+    public boolean areSecondaryActionsHidden() {
+        return mSecondaryActionsHidden;
+    }
+
+    /**
+     * Shows or hides space at the bottom of the playback controls row.
+     * This allows the row to hug the bottom of the display when no
+     * other rows are present.
+     */
+    public void showBottomSpace(ViewHolder vh, boolean show) {
+        vh.mBottomSpacer.setVisibility(show ? View.VISIBLE : View.GONE);
+    }
+
+    /**
+     * Display the primary actions.
+     */
+    public void showPrimaryActions(ViewHolder vh) {
+        mPlaybackControlsPresenter.showPrimaryActions(vh.mControlsVh);
+    }
+
+    private int getDefaultBackgroundColor(Context context) {
+        TypedValue outValue = new TypedValue();
+        context.getTheme().resolveAttribute(R.attr.defaultBrandColor, outValue, true);
+        return context.getResources().getColor(outValue.resourceId);
+    }
+
+    private int getDefaultProgressColor(Context context) {
+        TypedValue outValue = new TypedValue();
+        context.getTheme().resolveAttribute(R.attr.playbackProgressPrimaryColor, outValue, true);
+        return context.getResources().getColor(outValue.resourceId);
+    }
+
+    @Override
+    protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
+        View v = LayoutInflater.from(parent.getContext())
+            .inflate(R.layout.lb_playback_controls_row, parent, false);
+        ViewHolder vh = new ViewHolder(v, mDescriptionPresenter);
+        initRow(vh);
+        return vh;
+    }
+
+    private void initRow(ViewHolder vh) {
+        vh.mCardHeight = vh.mCard.getLayoutParams().height;
+
+        MarginLayoutParams lp = (MarginLayoutParams) vh.mControlsDock.getLayoutParams();
+        vh.mControlsDockMarginStart = lp.getMarginStart();
+        vh.mControlsDockMarginEnd = lp.getMarginEnd();
+
+        vh.mControlsVh = (PlaybackControlsPresenter.ViewHolder)
+                mPlaybackControlsPresenter.onCreateViewHolder(vh.mControlsDock);
+        mPlaybackControlsPresenter.setProgressColor(vh.mControlsVh,
+                mProgressColorSet ? mProgressColor :
+                        getDefaultProgressColor(vh.mControlsDock.getContext()));
+        mPlaybackControlsPresenter.setOnItemViewSelectedListener(vh.mOnItemViewSelectedListener);
+        vh.mControlsDock.addView(vh.mControlsVh.view);
+
+        vh.mSecondaryControlsVh =
+                mSecondaryControlsPresenter.onCreateViewHolder(vh.mSecondaryControlsDock);
+        if (!mSecondaryActionsHidden) {
+            vh.mSecondaryControlsDock.addView(vh.mSecondaryControlsVh.view);
+        }
+        mSecondaryControlsPresenter.setOnItemViewSelectedListener(vh.mOnItemViewSelectedListener);
+    }
+
+    @Override
+    protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
+        super.onBindRowViewHolder(holder, item);
+
+        ViewHolder vh = (ViewHolder) holder;
+        PlaybackControlsRow row = (PlaybackControlsRow) vh.getRow();
+
+        mPlaybackControlsPresenter.enableSecondaryActions(mSecondaryActionsHidden);
+
+        if (row.getItem() == null) {
+            LayoutParams lp = vh.mCard.getLayoutParams();
+            lp.height = LayoutParams.WRAP_CONTENT;
+            vh.mCard.setLayoutParams(lp);
+            vh.mDescriptionDock.setVisibility(View.GONE);
+            vh.mSpacer.setVisibility(View.GONE);
+        } else {
+            LayoutParams lp = vh.mCard.getLayoutParams();
+            lp.height = vh.mCardHeight;
+            vh.mCard.setLayoutParams(lp);
+            vh.mDescriptionDock.setVisibility(View.VISIBLE);
+            if (vh.mDescriptionViewHolder != null) {
+                mDescriptionPresenter.onBindViewHolder(vh.mDescriptionViewHolder, row.getItem());
+            }
+            vh.mSpacer.setVisibility(View.VISIBLE);
+        }
+
+        MarginLayoutParams lp = (MarginLayoutParams) vh.mControlsDock.getLayoutParams();
+        if (row.getImageDrawable() == null || row.getItem() == null) {
+            vh.setBackground(vh.mControlsDock);
+            lp.setMarginStart(0);
+            lp.setMarginEnd(0);
+            mPlaybackControlsPresenter.enableTimeMargins(vh.mControlsVh, true);
+        } else {
+            vh.mImageView.setImageDrawable(row.getImageDrawable());
+            vh.setBackground(vh.mCard);
+            lp.setMarginStart(vh.mControlsDockMarginStart);
+            lp.setMarginEnd(vh.mControlsDockMarginEnd);
+            mPlaybackControlsPresenter.enableTimeMargins(vh.mControlsVh, false);
+        }
+        vh.mControlsDock.setLayoutParams(lp);
+
+        vh.mControlsBoundData.adapter = row.getPrimaryActionsAdapter();
+        vh.mControlsBoundData.secondaryActionsAdapter = row.getSecondaryActionsAdapter();
+        vh.mControlsBoundData.presenter = vh.getPresenter(
+                row.getPrimaryActionsAdapter().get(0), true);
+        mPlaybackControlsPresenter.onBindViewHolder(vh.mControlsVh, vh.mControlsBoundData);
+
+        vh.mSecondaryBoundData.adapter = row.getSecondaryActionsAdapter();
+        vh.mSecondaryBoundData.presenter = vh.getPresenter(
+                row.getSecondaryActionsAdapter().get(0), false);
+        mSecondaryControlsPresenter.onBindViewHolder(vh.mSecondaryControlsVh,
+                vh.mSecondaryBoundData);
+
+        mPlaybackControlsPresenter.setTotalTime(vh.mControlsVh, row.getTotalTime());
+        mPlaybackControlsPresenter.setCurrentTime(vh.mControlsVh, row.getCurrentTime());
+        mPlaybackControlsPresenter.setSecondaryProgress(vh.mControlsVh, row.getBufferedProgress());
+        row.setOnPlaybackStateChangedListener(vh.mListener);
+    }
+
+    @Override
+    protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
+        ViewHolder vh = (ViewHolder) holder;
+        PlaybackControlsRow row = (PlaybackControlsRow) vh.getRow();
+
+        if (vh.mDescriptionViewHolder != null) {
+            mDescriptionPresenter.onUnbindViewHolder(vh.mDescriptionViewHolder);
+        }
+        mPlaybackControlsPresenter.onUnbindViewHolder(vh.mControlsVh);
+        mSecondaryControlsPresenter.onUnbindViewHolder(vh.mSecondaryControlsVh);
+        row.setOnPlaybackStateChangedListener(null);
+
+        super.onUnbindRowViewHolder(holder);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java b/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java
new file mode 100644
index 0000000..ed7714d
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A Presenter is used to generate {@link View}s and bind Objects to them on
+ * demand. It is closely related to concept of an {@link
+ * android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, but is
+ * not position-based.
+ *
+ * <p>
+ * A trivial Presenter that takes a string and renders it into a {@link
+ * android.widget.TextView TextView}:
+ *
+ * <pre class="prettyprint">
+ * public class StringTextViewPresenter extends Presenter {
+ *     // This class does not need a custom ViewHolder, since it does not use
+ *     // a complex layout.
+ *
+ *     {@literal @}Override
+ *     public ViewHolder onCreateViewHolder(ViewGroup parent) {
+ *         return new ViewHolder(new TextView(parent.getContext()));
+ *     }
+ *
+ *     {@literal @}Override
+ *     public void onBindViewHolder(ViewHolder viewHolder, Object item) {
+ *         String str = (String) item;
+ *         TextView textView = (TextView) viewHolder.mView;
+ *
+ *         textView.setText(item);
+ *     }
+ *
+ *     {@literal @}Override
+ *     public void onUnbindViewHolder(ViewHolder viewHolder) {
+ *         // Nothing to unbind for TextView, but if this viewHolder had
+ *         // allocated bitmaps, they can be released here.
+ *     }
+ * }
+ * </pre>
+ */
+public abstract class Presenter {
+    /**
+     * ViewHolder can be subclassed and used to cache any view accessors needed
+     * to improve binding performance (for example, results of findViewById)
+     * without needing to subclass a View.
+     */
+    public static class ViewHolder {
+        public final View view;
+
+        public ViewHolder(View view) {
+            this.view = view;
+        }
+    }
+
+    /**
+     * Creates a new {@link View}.
+     */
+    public abstract ViewHolder onCreateViewHolder(ViewGroup parent);
+
+    /**
+     * Binds a {@link View} to an item.
+     */
+    public abstract void onBindViewHolder(ViewHolder viewHolder, Object item);
+
+    /**
+     * Unbinds a {@link View} from an item. Any expensive references may be
+     * released here, and any fields that are not bound for every item should be
+     * cleared here.
+     */
+    public abstract void onUnbindViewHolder(ViewHolder viewHolder);
+
+    /**
+     * Called when a view created by this presenter has been attached to a window.
+     *
+     * <p>This can be used as a reasonable signal that the view is about to be seen
+     * by the user. If the adapter previously freed any resources in
+     * {@link #onViewDetachedFromWindow(ViewHolder)}
+     * those resources should be restored here.</p>
+     *
+     * @param holder Holder of the view being attached
+     */
+    public void onViewAttachedToWindow(ViewHolder holder) {
+    }
+
+    /**
+     * Called when a view created by this presenter has been detached from its window.
+     *
+     * <p>Becoming detached from the window is not necessarily a permanent condition;
+     * the consumer of an presenter's views may choose to cache views offscreen while they
+     * are not visible, attaching an detaching them as appropriate.</p>
+     *
+     * @param holder Holder of the view being detached
+     */
+    public void onViewDetachedFromWindow(ViewHolder holder) {
+    }
+
+    /**
+     * Called to set a click listener for the given view holder.
+     *
+     * The default implementation sets the click listener on the root view in the view holder.
+     * If the root view isn't focusable this method should be overridden to set the listener
+     * on the appropriate focusable child view(s).
+     *
+     * @param holder The view holder containing the view(s) on which the listener should be set.
+     * @param listener The click listener to be set.
+     */
+    public void setOnClickListener(ViewHolder holder, View.OnClickListener listener) {
+        holder.view.setOnClickListener(listener);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PresenterSelector.java b/v17/leanback/src/android/support/v17/leanback/widget/PresenterSelector.java
new file mode 100644
index 0000000..c38957d
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PresenterSelector.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+/**
+ * A PresenterSelector is used to obtain a {@link Presenter} for a given Object.
+ */
+public abstract class PresenterSelector {
+    /**
+     * Returns a presenter for the given item.
+     */
+    public abstract Presenter getPresenter(Object item);
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PresenterSwitcher.java b/v17/leanback/src/android/support/v17/leanback/widget/PresenterSwitcher.java
new file mode 100644
index 0000000..8a9c726
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PresenterSwitcher.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * An abstract helper class that switches view in parent view using {@link PresenterSelector}
+ * subclass should define {@link #insertView(View)} of how to add the view
+ * in parent and optionally override {@link #onViewSelected(View)}.
+ */
+public abstract class PresenterSwitcher {
+
+    private ViewGroup mParent;
+    private PresenterSelector mPresenterSelector;
+    private Presenter mCurrentPresenter;
+    private Presenter.ViewHolder mCurrentViewHolder;
+
+    /**
+     * Initialize switcher with a parent view to insert view into and a
+     * {@link PresenterSelector} for choose {@link Presenter} for object.
+     * This will destroy any existing views.
+     */
+    public void init(ViewGroup parent, PresenterSelector presenterSelector) {
+        clear();
+        mParent = parent;
+        mPresenterSelector = presenterSelector;
+    }
+
+    public void select(Object object) {
+        switchView(object);
+        showView(true);
+    }
+
+    public void unselect() {
+        showView(false);
+    }
+
+    public final ViewGroup getParentViewGroup() {
+        return mParent;
+    }
+
+    private void showView(boolean show) {
+        if (mCurrentViewHolder != null) {
+            showView(mCurrentViewHolder.view, show);
+        }
+    }
+
+    private void switchView(Object object) {
+        Presenter presenter = mPresenterSelector.getPresenter(object);
+        if (presenter != mCurrentPresenter) {
+            showView(false);
+            clear();
+            mCurrentPresenter = presenter;
+            if (mCurrentPresenter == null) {
+                return;
+            }
+            mCurrentViewHolder = mCurrentPresenter.onCreateViewHolder(mParent);
+            insertView(mCurrentViewHolder.view);
+        } else {
+            if (mCurrentPresenter == null) {
+                return;
+            }
+            mCurrentPresenter.onUnbindViewHolder(mCurrentViewHolder);
+        }
+        mCurrentPresenter.onBindViewHolder(mCurrentViewHolder, object);
+        onViewSelected(mCurrentViewHolder.view);
+    }
+
+    protected abstract void insertView(View view);
+
+    /**
+     * Called when a view is bound to the object of {@link #select(Object)}.
+     */
+    protected void onViewSelected(View view) {
+    }
+
+    protected void showView(View view, boolean visible) {
+        view.setVisibility(visible ? View.VISIBLE : View.GONE);
+    }
+
+    /**
+     * Destroy created views.
+     */
+    public void clear() {
+        if (mCurrentPresenter != null) {
+            mCurrentPresenter.onUnbindViewHolder(mCurrentViewHolder);
+            mParent.removeView(mCurrentViewHolder.view);
+            mCurrentViewHolder = null;
+            mCurrentPresenter = null;
+        }
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Row.java b/v17/leanback/src/android/support/v17/leanback/widget/Row.java
new file mode 100644
index 0000000..237fcf7
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/Row.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import static android.support.v17.leanback.widget.ObjectAdapter.NO_ID;
+
+/**
+ * A row in a RowsFragment.  This is the base class for all rows.
+ * You will typically use a {@link ListRow}, but you may override this class
+ * for non-list Rows.
+ */
+public class Row {
+
+    private static final int FLAG_ID_USE_MASK = 1;
+    private static final int FLAG_ID_USE_HEADER = 1;
+    private static final int FLAG_ID_USE_ID = 0;
+
+    private int mFlags = FLAG_ID_USE_HEADER;
+    private HeaderItem mHeaderItem;
+    private long mId = NO_ID;
+
+    /**
+     * Constructor for a Row.
+     *
+     * @param id The id of the row.
+     * @param headerItem The {@link HeaderItem} for this Row, or null if there
+     *        is no header.
+     */
+    public Row(long id, HeaderItem headerItem) {
+        setId(id);
+        setHeaderItem(headerItem);
+    }
+
+    /**
+     * Constructor for a Row.
+     *
+     * @param headerItem The {@link HeaderItem} for this Row, or null if there
+     *        is no header.
+     */
+    public Row(HeaderItem headerItem) {
+        setHeaderItem(headerItem);
+    }
+
+    /**
+     * Constructor for a Row.
+     */
+    public Row() {
+    }
+
+    /**
+     * Get the {@link HeaderItem} that represents metadata for the row.
+     *
+     * @return The HeaderItem for this row, or null if unset.
+     */
+    public final HeaderItem getHeaderItem() {
+        return mHeaderItem;
+    }
+
+    /**
+     * Set the {@link HeaderItem} that represents metadata for the row.
+     *
+     * @param headerItem The HeaderItem for this Row, or null if there is no
+     *        header.
+     */
+    public final void setHeaderItem(HeaderItem headerItem) {
+        mHeaderItem = headerItem;
+    }
+
+    /**
+     * Set the id for this row.
+     *
+     * @param id The id of the row.
+     */
+    public final void setId(long id) {
+        mId = id;
+        setFlags(FLAG_ID_USE_ID, FLAG_ID_USE_MASK);
+    }
+
+    /**
+     * Returns a unique identifier for this row. This id can come from one of
+     * three places:
+     * <ul>
+     *   <li>If {@link #setId(long)} is ever called on this row, it will return
+     *   this id.
+     *   <li>If {@link #setId(long)} has not been called but the header item is
+     *   not null, the result of {@link HeaderItem#getId()} is returned.
+     *   <li>Otherwise {@link ObjectAdapter#NO_ID NO_ID} is returned.
+     * </ul>
+     */
+    public final long getId() {
+        if ( (mFlags & FLAG_ID_USE_MASK) == FLAG_ID_USE_HEADER) {
+            HeaderItem header = getHeaderItem();
+            if (header != null) {
+                return header.getId();
+            }
+            return NO_ID;
+        } else {
+            return mId;
+        }
+    }
+
+    final void setFlags(int flags, int mask) {
+        mFlags = (mFlags & ~mask) | (flags & mask);
+    }
+
+    final int getFlags() {
+        return mFlags;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowContainerView.java b/v17/leanback/src/android/support/v17/leanback/widget/RowContainerView.java
new file mode 100644
index 0000000..4fbcb6f
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowContainerView.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.support.v17.leanback.R;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+/**
+ * RowContainerView wraps header and user defined row view
+ */
+final class RowContainerView extends LinearLayout {
+
+    private ViewGroup mHeaderDock;
+
+    public RowContainerView(Context context) {
+        this(context, null, 0);
+    }
+
+    public RowContainerView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public RowContainerView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        setOrientation(VERTICAL);
+        LayoutInflater inflater = LayoutInflater.from(context);
+        inflater.inflate(R.layout.lb_row_container, this);
+
+        mHeaderDock = (ViewGroup) findViewById(R.id.lb_row_container_header_dock);
+        setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+    }
+
+    public void addHeaderView(View headerView) {
+        if (mHeaderDock.indexOfChild(headerView) < 0) {
+            mHeaderDock.addView(headerView, 0);
+        }
+    }
+
+    public void removeHeaderView(View headerView) {
+        if (mHeaderDock.indexOfChild(headerView) >= 0) {
+            mHeaderDock.removeView(headerView);
+        }
+    }
+
+    public void addRowView(View view) {
+        addView(view);
+    }
+
+    public void showHeader(boolean show) {
+        mHeaderDock.setVisibility(show ? View.VISIBLE : View.GONE);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
new file mode 100644
index 0000000..886bf89
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.graphics.Paint;
+import android.support.v17.leanback.R;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/**
+ * RowHeaderPresenter provides a default implementation for header using TextView.
+ * If subclass override and creates its own view, subclass must also override
+ * {@link #onSelectLevelChanged(ViewHolder)}.
+ */
+public class RowHeaderPresenter extends Presenter {
+
+    private final int mLayoutResourceId;
+    private final Paint mFontMeasurePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+    public RowHeaderPresenter() {
+        this(R.layout.lb_row_header);
+    }
+
+    /**
+     * @hide
+     */
+    public RowHeaderPresenter(int layoutResourceId) {
+        mLayoutResourceId = layoutResourceId;
+    }
+
+    public static class ViewHolder extends Presenter.ViewHolder {
+        float mSelectLevel;
+        int mOriginalTextColor;
+        float mUnselectAlpha;
+
+        public ViewHolder(View view) {
+            super(view);
+        }
+        public final float getSelectLevel() {
+            return mSelectLevel;
+        }
+    }
+
+    @Override
+    public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
+        RowHeaderView headerView = (RowHeaderView) LayoutInflater.from(parent.getContext())
+                .inflate(mLayoutResourceId, parent, false);
+
+        ViewHolder viewHolder = new ViewHolder(headerView);
+        viewHolder.mOriginalTextColor = headerView.getCurrentTextColor();
+        viewHolder.mUnselectAlpha = parent.getResources().getFraction(
+                R.fraction.lb_browse_header_unselect_alpha, 1, 1);
+        return viewHolder;
+    }
+
+    @Override
+    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+        setSelectLevel((ViewHolder) viewHolder, 0);
+        Row rowItem = (Row) item;
+        if (rowItem != null) {
+            HeaderItem headerItem = rowItem.getHeaderItem();
+            if (headerItem != null) {
+                String text = headerItem.getName();
+                ((RowHeaderView) viewHolder.view).setText(text);
+            }
+        }
+    }
+
+    @Override
+    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+    }
+
+    public final void setSelectLevel(ViewHolder holder, float selectLevel) {
+        holder.mSelectLevel = selectLevel;
+        onSelectLevelChanged(holder);
+    }
+
+    protected void onSelectLevelChanged(ViewHolder holder) {
+        holder.view.setAlpha(holder.mUnselectAlpha + holder.mSelectLevel *
+                (1f - holder.mUnselectAlpha));
+    }
+
+    /**
+     * Returns the space (distance in pixels) below the baseline of the
+     * text view, if one exists; otherwise, returns 0.
+     */
+    public int getSpaceUnderBaseline(ViewHolder holder) {
+        int space = holder.view.getPaddingBottom();
+        if (holder.view instanceof TextView) {
+            space += (int) getFontDescent((TextView) holder.view, mFontMeasurePaint);
+        }
+        return space;
+    }
+
+    protected static float getFontDescent(TextView textView, Paint fontMeasurePaint) {
+        if (fontMeasurePaint.getTextSize() != textView.getTextSize()) {
+            fontMeasurePaint.setTextSize(textView.getTextSize());
+        }
+        if (fontMeasurePaint.getTypeface() != textView.getTypeface()) {
+            fontMeasurePaint.setTypeface(textView.getTypeface());
+        }
+        return fontMeasurePaint.descent();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderView.java b/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderView.java
new file mode 100644
index 0000000..0a8f98e
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderView.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.support.v17.leanback.R;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+/**
+ * RowHeaderView is a header text view.
+ */
+public final class RowHeaderView extends TextView {
+
+    public RowHeaderView(Context context) {
+        this(context, null);
+    }
+
+    public RowHeaderView(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.rowHeaderStyle);
+    }
+
+    public RowHeaderView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
new file mode 100644
index 0000000..70f8623
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
@@ -0,0 +1,486 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.support.v17.leanback.app.HeadersFragment;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * An abstract {@link Presenter} that renders a {@link Row}.
+ *
+ * <h3>Customize UI widgets</h3>
+ * When a subclass of RowPresenter adds UI widgets, it should subclass
+ * {@link RowPresenter.ViewHolder} and override {@link #createRowViewHolder(ViewGroup)}
+ * and {@link #initializeRowViewHolder(ViewHolder)}. The subclass must use layout id
+ * "row_content" for the widget that will be aligned to the title of any {@link HeadersFragment}
+ * that may exist in the parent fragment. RowPresenter contains an optional and
+ * replaceable {@link RowHeaderPresenter} that renders the header. You can disable
+ * the default rendering or replace the Presenter with a new header presenter
+ * by calling {@link #setHeaderPresenter(RowHeaderPresenter)}.
+ *
+ * <h3>UI events from fragments</h3>
+ * RowPresenter receives calls from its parent (typically a Fragment) when:
+ * <ul>
+ * <li>
+ * A Row is selected via {@link #setRowViewSelected(Presenter.ViewHolder, boolean)}.  The event
+ * is triggered immediately when there is a row selection change before the selection
+ * animation is started.
+ * Subclasses of RowPresenter may override {@link #onRowViewSelected(ViewHolder, boolean)}.
+ * </li>
+ * <li>
+ * A Row is expanded to full width via {@link #setRowViewExpanded(Presenter.ViewHolder, boolean)}.
+ * The event is triggered immediately before the expand animation is started.
+ * Subclasses of RowPresenter may override {@link #onRowViewExpanded(ViewHolder, boolean)}.
+ * </li>
+ * </ul>
+ *
+ * <h3>User events</h3>
+ * RowPresenter provides {@link OnItemSelectedListener} and {@link OnItemClickedListener}.
+ * If a subclass wants to add its own {@link View.OnFocusChangeListener} or
+ * {@link View.OnClickListener}, it must do that in {@link #createRowViewHolder(ViewGroup)}
+ * to be properly chained by the library.  Adding View listeners after
+ * {@link #createRowViewHolder(ViewGroup)} is undefined and may result in
+ * incorrect behavior by the library's listeners.
+ *
+ * <h3>Selection animation</h3>
+ * <p>
+ * When a user scrolls through rows, a fragment will initiate animation and call
+ * {@link #setSelectLevel(Presenter.ViewHolder, float)} with float value between
+ * 0 and 1.  By default, the RowPresenter draws a dim overlay on top of the row
+ * view for views that are not selected. Subclasses may override this default effect
+ * by having {@link #isUsingDefaultSelectEffect()} return false and overriding
+ * {@link #onSelectLevelChanged(ViewHolder)} to apply a different selection effect.
+ * </p>
+ * <p>
+ * Call {@link #setSelectEffectEnabled(boolean)} to enable/disable the select effect,
+ * This will not only enable/disable the default dim effect but also subclasses must
+ * respect this flag as well.
+ * </p>
+ */
+public abstract class RowPresenter extends Presenter {
+
+    static class ContainerViewHolder extends Presenter.ViewHolder {
+        /**
+         * wrapped row view holder
+         */
+        final ViewHolder mRowViewHolder;
+
+        public ContainerViewHolder(RowContainerView containerView, ViewHolder rowViewHolder) {
+            super(containerView);
+            containerView.addRowView(rowViewHolder.view);
+            if (rowViewHolder.mHeaderViewHolder != null) {
+                containerView.addHeaderView(rowViewHolder.mHeaderViewHolder.view);
+            }
+            mRowViewHolder = rowViewHolder;
+            mRowViewHolder.mContainerViewHolder = this;
+        }
+    }
+
+    /**
+     * A view holder for a {@link Row}.
+     */
+    public static class ViewHolder extends Presenter.ViewHolder {
+        ContainerViewHolder mContainerViewHolder;
+        RowHeaderPresenter.ViewHolder mHeaderViewHolder;
+        Row mRow;
+        boolean mSelected;
+        boolean mExpanded;
+        boolean mInitialzed;
+        float mSelectLevel = 0f; // initially unselected
+
+        /**
+         * Constructor for ViewHolder.
+         *
+         * @param view The View bound to the Row.
+         */
+        public ViewHolder(View view) {
+            super(view);
+        }
+
+        /**
+         * Returns the Row bound to the View in this ViewHolder.
+         */
+        public final Row getRow() {
+            return mRow;
+        }
+
+        /**
+         * Returns whether the Row is in its expanded state.
+         *
+         * @return true if the Row is expanded, false otherwise.
+         */
+        public final boolean isExpanded() {
+            return mExpanded;
+        }
+
+        /**
+         * Returns whether the Row is selected.
+         *
+         * @return true if the Row is selected, false otherwise.
+         */
+        public final boolean isSelected() {
+            return mSelected;
+        }
+
+        /**
+         * Returns the current selection level of the Row.
+         */
+        public final float getSelectLevel() {
+            return mSelectLevel;
+        }
+
+        /**
+         * Returns the view holder for the Row header for this Row.
+         */
+        public final RowHeaderPresenter.ViewHolder getHeaderViewHolder() {
+            return mHeaderViewHolder;
+        }
+    }
+
+    private RowHeaderPresenter mHeaderPresenter = new RowHeaderPresenter();
+    private OnItemSelectedListener mOnItemSelectedListener;
+    private OnItemClickedListener mOnItemClickedListener;
+    private OnItemViewSelectedListener mOnItemViewSelectedListener;
+    private OnItemViewClickedListener mOnItemViewClickedListener;
+
+    boolean mSelectEffectEnabled = true;
+
+    @Override
+    public final Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
+        ViewHolder vh = createRowViewHolder(parent);
+        vh.mInitialzed = false;
+        Presenter.ViewHolder result;
+        if (needsRowContainerView()) {
+            RowContainerView containerView = new RowContainerView(parent.getContext());
+            if (mHeaderPresenter != null) {
+                vh.mHeaderViewHolder = (RowHeaderPresenter.ViewHolder)
+                        mHeaderPresenter.onCreateViewHolder((ViewGroup) vh.view);
+            }
+            result = new ContainerViewHolder(containerView, vh);
+        } else {
+            result = vh;
+        }
+        initializeRowViewHolder(vh);
+        if (!vh.mInitialzed) {
+            throw new RuntimeException("super.initializeRowViewHolder() must be called");
+        }
+        return result;
+    }
+
+    /**
+     * Called to create a ViewHolder object for a Row. Subclasses will override
+     * this method to return a different concrete ViewHolder object. 
+     *
+     * @param parent The parent View for the Row's view holder.
+     * @return A ViewHolder for the Row's View.
+     */
+    protected abstract ViewHolder createRowViewHolder(ViewGroup parent);
+
+    /**
+     * Called after a {@link RowPresenter.ViewHolder} is created for a Row.
+     * Subclasses may override this method and start by calling
+     * super.initializeRowViewHolder(ViewHolder).
+     *
+     * @param vh The ViewHolder to initialize for the Row.
+     */
+    protected void initializeRowViewHolder(ViewHolder vh) {
+        vh.mInitialzed = true;
+    }
+
+    /**
+     * Set the Presenter used for rendering the header. Can be null to disable
+     * header rendering. The method must be called before creating any Row Views.
+     */
+    public final void setHeaderPresenter(RowHeaderPresenter headerPresenter) {
+        mHeaderPresenter = headerPresenter;
+    }
+
+    /**
+     * Get the Presenter used for rendering the header, or null if none has been
+     * set.
+     */
+    public final RowHeaderPresenter getHeaderPresenter() {
+        return mHeaderPresenter;
+    }
+
+    /**
+     * Get the {@link RowPresenter.ViewHolder} from the given Presenter
+     * ViewHolder.
+     */
+    public final ViewHolder getRowViewHolder(Presenter.ViewHolder holder) {
+        if (holder instanceof ContainerViewHolder) {
+            return ((ContainerViewHolder) holder).mRowViewHolder;
+        } else {
+            return (ViewHolder) holder;
+        }
+    }
+
+    /**
+     * Set the expanded state of a Row view.
+     *
+     * @param holder The Row ViewHolder to set expanded state on.
+     * @param expanded True if the Row is expanded, false otherwise.
+     */
+    public final void setRowViewExpanded(Presenter.ViewHolder holder, boolean expanded) {
+        ViewHolder rowViewHolder = getRowViewHolder(holder);
+        rowViewHolder.mExpanded = expanded;
+        onRowViewExpanded(rowViewHolder, expanded);
+    }
+
+    /**
+     * Set the selected state of a Row view.
+     *
+     * @param holder The Row ViewHolder to set expanded state on.
+     * @param selected True if the Row is expanded, false otherwise.
+     */
+    public final void setRowViewSelected(Presenter.ViewHolder holder, boolean selected) {
+        ViewHolder rowViewHolder = getRowViewHolder(holder);
+        rowViewHolder.mSelected = selected;
+        onRowViewSelected(rowViewHolder, selected);
+    }
+
+    /**
+     * Subclass may override this to respond to expanded state changes of a Row.
+     * The default implementation will hide/show the header view. Subclasses may
+     * make visual changes to the Row View but must not create animation on the
+     * Row view.
+     */
+    protected void onRowViewExpanded(ViewHolder vh, boolean expanded) {
+        updateHeaderViewVisibility(vh);
+        vh.view.setActivated(expanded);
+    }
+
+    /**
+     * Subclass may override this to respond to selected state changes of a Row.
+     * Subclass may make visual changes to Row view but must not create
+     * animation on the Row view.
+     */
+    protected void onRowViewSelected(ViewHolder vh, boolean selected) {
+        if (selected) {
+            if (mOnItemViewSelectedListener != null) {
+                mOnItemViewSelectedListener.onItemSelected(null, null, vh, vh.getRow());
+            }
+            if (mOnItemSelectedListener != null) {
+                mOnItemSelectedListener.onItemSelected(null, vh.getRow());
+            }
+        }
+        updateHeaderViewVisibility(vh);
+    }
+
+    private void updateHeaderViewVisibility(ViewHolder vh) {
+        if (mHeaderPresenter != null && vh.mHeaderViewHolder != null) {
+            RowContainerView containerView = ((RowContainerView) vh.mContainerViewHolder.view);
+            containerView.showHeader(vh.isExpanded());
+        }
+    }
+
+    /**
+     * Set the current select level to a value between 0 (unselected) and 1 (selected).
+     * Subclasses may override {@link #onSelectLevelChanged(ViewHolder)} to
+     * respond to changes in the selected level.
+     */
+    public final void setSelectLevel(Presenter.ViewHolder vh, float level) {
+        ViewHolder rowViewHolder = getRowViewHolder(vh);
+        rowViewHolder.mSelectLevel = level;
+        onSelectLevelChanged(rowViewHolder);
+    }
+
+    /**
+     * Get the current select level. The value will be between 0 (unselected) 
+     * and 1 (selected).
+     */
+    public final float getSelectLevel(Presenter.ViewHolder vh) {
+        return getRowViewHolder(vh).mSelectLevel;
+    }
+
+    /**
+     * Callback when select level is changed. The default implementation applies
+     * the select level to {@link RowHeaderPresenter#setSelectLevel(RowHeaderPresenter.ViewHolder, float)}
+     * when {@link #getSelectEffectEnabled()} is true. Subclasses may override
+     * this function and implement a different select effect. In this case, you
+     * should also override {@link #isUsingDefaultSelectEffect()} to disable
+     * the default dimming effect applied by the library.
+     */
+    protected void onSelectLevelChanged(ViewHolder vh) {
+        if (getSelectEffectEnabled() && vh.mHeaderViewHolder != null) {
+            mHeaderPresenter.setSelectLevel(vh.mHeaderViewHolder, vh.mSelectLevel);
+        }
+    }
+
+    /**
+     * Enables or disables the row selection effect.
+     * This will not only affect the default dim effect, but subclasses must
+     * respect this flag as well.
+     */
+    public final void setSelectEffectEnabled(boolean applyDimOnSelect) {
+        mSelectEffectEnabled = applyDimOnSelect;
+    }
+
+    /**
+     * Returns true if the row selection effect is enabled.
+     * This value not only determines whether the default dim implementation is
+     * used, but subclasses must also respect this flag.
+     */
+    public final boolean getSelectEffectEnabled() {
+        return mSelectEffectEnabled;
+    }
+
+    /**
+     * Return whether this RowPresenter is using the default dimming effect
+     * provided by the library.  Subclasses may(most likely) return false and
+     * override {@link #onSelectLevelChanged(ViewHolder)}.
+     */
+    public boolean isUsingDefaultSelectEffect() {
+        return true;
+    }
+
+    final boolean needsDefaultSelectEffect() {
+        return isUsingDefaultSelectEffect() && getSelectEffectEnabled();
+    }
+
+    final boolean needsRowContainerView() {
+        return mHeaderPresenter != null;
+    }
+
+    /**
+     * Return true if the Row view can draw outside its bounds.
+     */
+    public boolean canDrawOutOfBounds() {
+        return false;
+    }
+
+    @Override
+    public final void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+        onBindRowViewHolder(getRowViewHolder(viewHolder), item);
+    }
+
+    protected void onBindRowViewHolder(ViewHolder vh, Object item) {
+        vh.mRow = (Row) item;
+        if (vh.mHeaderViewHolder != null) {
+            mHeaderPresenter.onBindViewHolder(vh.mHeaderViewHolder, item);
+        }
+    }
+
+    @Override
+    public final void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+        onUnbindRowViewHolder(getRowViewHolder(viewHolder));
+    }
+
+    protected void onUnbindRowViewHolder(ViewHolder vh) {
+        if (vh.mHeaderViewHolder != null) {
+            mHeaderPresenter.onUnbindViewHolder(vh.mHeaderViewHolder);
+        }
+        vh.mRow = null;
+    }
+
+    @Override
+    public final void onViewAttachedToWindow(Presenter.ViewHolder holder) {
+        onRowViewAttachedToWindow(getRowViewHolder(holder));
+    }
+
+    protected void onRowViewAttachedToWindow(ViewHolder vh) {
+        if (vh.mHeaderViewHolder != null) {
+            mHeaderPresenter.onViewAttachedToWindow(vh.mHeaderViewHolder);
+        }
+    }
+
+    @Override
+    public final void onViewDetachedFromWindow(Presenter.ViewHolder holder) {
+        onRowViewDetachedFromWindow(getRowViewHolder(holder));
+    }
+
+    protected void onRowViewDetachedFromWindow(ViewHolder vh) {
+        if (vh.mHeaderViewHolder != null) {
+            mHeaderPresenter.onViewDetachedFromWindow(vh.mHeaderViewHolder);
+        }
+    }
+
+    /**
+     * Set the listener for item or row selection. A RowPresenter fires a row
+     * selection event with a null item. Subclasses (e.g. {@link ListRowPresenter})
+     * can fire a selection event with the selected item.
+     */
+    public final void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mOnItemSelectedListener = listener;
+    }
+
+    /**
+     * Get the listener for item or row selection.
+     */
+    public final OnItemSelectedListener getOnItemSelectedListener() {
+        return mOnItemSelectedListener;
+    }
+
+    /**
+     * Set the listener for item click events. A RowPresenter does not use this
+     * listener, but a subclass may fire an item click event if it has the concept
+     * of an item. The {@link OnItemClickedListener} will override any
+     * {@link View.OnClickListener} that an item's Presenter sets during
+     * {@link Presenter#onCreateViewHolder(ViewGroup)}. So in general, you
+     * should choose to use an OnItemClickedListener or a {@link
+     * View.OnClickListener}, but not both.
+     */
+    public final void setOnItemClickedListener(OnItemClickedListener listener) {
+        mOnItemClickedListener = listener;
+    }
+
+    /**
+     * Get the listener for item click events.
+     */
+    public final OnItemClickedListener getOnItemClickedListener() {
+        return mOnItemClickedListener;
+    }
+
+    /**
+     * Set listener for item or row selection.  RowPresenter fires row selection
+     * event with null item, subclass of RowPresenter e.g. {@link ListRowPresenter} can
+     * fire a selection event with selected item.
+     */
+    public final void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        mOnItemViewSelectedListener = listener;
+    }
+
+    /**
+     * Get listener for item or row selection.
+     */
+    public final OnItemViewSelectedListener getOnItemViewSelectedListener() {
+        return mOnItemViewSelectedListener;
+    }
+
+    /**
+     * Set listener for item click event.  RowPresenter does nothing but subclass of
+     * RowPresenter may fire item click event if it does have a concept of item.
+     * OnItemViewClickedListener will override {@link View.OnClickListener} that
+     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
+     * So in general,  developer should choose one of the listeners but not both.
+     */
+    public final void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        mOnItemViewClickedListener = listener;
+    }
+
+    /**
+     * Set listener for item click event.
+     */
+    public final OnItemViewClickedListener getOnItemViewClickedListener() {
+        return mOnItemViewClickedListener;
+    }
+
+    /**
+     * Freeze/Unfreeze the row, typically used when transition starts/ends.
+     */
+    public void freeze(ViewHolder holder, boolean freeze) {
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java b/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
new file mode 100644
index 0000000..694f077
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
@@ -0,0 +1,646 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.media.SoundPool;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.speech.RecognitionListener;
+import android.speech.RecognizerIntent;
+import android.speech.SpeechRecognizer;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseIntArray;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ImageView;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.RelativeLayout;
+import android.support.v17.leanback.R;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>SearchBar is a search widget.</p>
+ *
+ * <p>Note: Your application will need to request android.permission.RECORD_AUDIO</p>
+ */
+public class SearchBar extends RelativeLayout {
+    private static final String TAG = SearchBar.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final float FULL_LEFT_VOLUME = 1.0f;
+    private static final float FULL_RIGHT_VOLUME = 1.0f;
+    private static final int DEFAULT_PRIORITY = 1;
+    private static final int DO_NOT_LOOP = 0;
+    private static final float DEFAULT_RATE = 1.0f;
+
+    /**
+     * Listener for search query changes
+     */
+    public interface SearchBarListener {
+
+        /**
+         * Method invoked when the search bar detects a change in the query.
+         *
+         * @param query The current full query.
+         */
+        public void onSearchQueryChange(String query);
+
+        /**
+         * <p>Method invoked when the search query is submitted.</p>
+         *
+         * <p>This method can be called without a preceeding onSearchQueryChange,
+         * in particular in the case of a voice input.</p>
+         *
+         * @param query The query being submitted.
+         */
+        public void onSearchQuerySubmit(String query);
+
+        /**
+         * Method invoked when the IME is being dismissed.
+         *
+         * @param query The query set in the search bar at the time the IME is being dismissed.
+         */
+        public void onKeyboardDismiss(String query);
+    }
+
+    private SearchBarListener mSearchBarListener;
+    private SearchEditText mSearchTextEditor;
+    private SpeechOrbView mSpeechOrbView;
+    private ImageView mBadgeView;
+    private String mSearchQuery;
+    private String mTitle;
+    private Drawable mBadgeDrawable;
+    private final Handler mHandler = new Handler();
+    private final InputMethodManager mInputMethodManager;
+    private boolean mAutoStartRecognition = false;
+    private Drawable mBarBackground;
+
+    private int mTextColor;
+    private int mTextSpeechColor;
+    private int mBackgroundAlpha;
+    private int mBackgroundSpeechAlpha;
+    private int mBarHeight;
+    private SpeechRecognizer mSpeechRecognizer;
+    private boolean mListening;
+    private SoundPool mSoundPool;
+    private SparseIntArray mSoundMap = new SparseIntArray();
+    private boolean mRecognizing = false;
+    private final Context mContext;
+
+    public SearchBar(Context context) {
+        this(context, null);
+    }
+
+    public SearchBar(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SearchBar(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mContext = context;
+        enforceAudioRecordPermission();
+
+        Resources r = getResources();
+
+        LayoutInflater inflater = LayoutInflater.from(getContext());
+        inflater.inflate(R.layout.lb_search_bar, this, true);
+
+        mBarHeight = getResources().getDimensionPixelSize(R.dimen.lb_search_bar_height);
+        RelativeLayout.LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                mBarHeight);
+        params.addRule(ALIGN_PARENT_TOP, RelativeLayout.TRUE);
+        setLayoutParams(params);
+        setBackgroundColor(Color.TRANSPARENT);
+        setClipChildren(false);
+
+        mSearchQuery = "";
+        mInputMethodManager =
+                (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
+
+        mTextSpeechColor = r.getColor(R.color.lb_search_bar_text_speech_color);
+        mBackgroundSpeechAlpha = r.getInteger(R.integer.lb_search_bar_speech_mode_background_alpha);
+
+        mTextColor = r.getColor(R.color.lb_search_bar_text_color);
+        mBackgroundAlpha = r.getInteger(R.integer.lb_search_bar_text_mode_background_alpha);
+
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        RelativeLayout items = (RelativeLayout)findViewById(R.id.lb_search_bar_items);
+        mBarBackground = items.getBackground();
+
+        mSearchTextEditor = (SearchEditText)findViewById(R.id.lb_search_text_editor);
+        mBadgeView = (ImageView)findViewById(R.id.lb_search_bar_badge);
+        if (null != mBadgeDrawable) {
+            mBadgeView.setImageDrawable(mBadgeDrawable);
+        }
+
+        mSearchTextEditor.setOnFocusChangeListener(new OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(View view, boolean hasFocus) {
+                if (DEBUG) Log.v(TAG, "EditText.onFocusChange " + hasFocus);
+                if (hasFocus) {
+                    showNativeKeyboard();
+                }
+                updateUi();
+            }
+        });
+        mSearchTextEditor.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
+
+            }
+
+            @Override
+            public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
+                if (mSearchTextEditor.hasFocus()) {
+                    setSearchQuery(charSequence.toString());
+                }
+            }
+
+            @Override
+            public void afterTextChanged(Editable editable) {
+
+            }
+        });
+        mSearchTextEditor.setOnKeyboardDismissListener(
+                new SearchEditText.OnKeyboardDismissListener() {
+                    @Override
+                    public void onKeyboardDismiss() {
+                        if (null != mSearchBarListener) {
+                            mSearchBarListener.onKeyboardDismiss(mSearchQuery);
+                        }
+                    }
+                });
+
+        mSearchTextEditor.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+            @Override
+            public boolean onEditorAction(TextView textView, int action, KeyEvent keyEvent) {
+                if (DEBUG) Log.v(TAG, "onEditorAction: " + action + " event: " + keyEvent);
+                boolean handled = true;
+                if (EditorInfo.IME_ACTION_SEARCH == action && null != mSearchBarListener) {
+                    if (DEBUG) Log.v(TAG, "Action Pressed");
+                    hideNativeKeyboard();
+                    mHandler.postDelayed(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (DEBUG) Log.v(TAG, "Delayed action handling (search)");
+                            submitQuery();
+                        }
+                    }, 500);
+
+                } else if (EditorInfo.IME_ACTION_NONE == action && null != mSearchBarListener) {
+                    if (DEBUG) Log.v(TAG, "Escaped North");
+                    hideNativeKeyboard();
+                    mHandler.postDelayed(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (DEBUG) Log.v(TAG, "Delayed action handling (escape_north)");
+                            mSearchBarListener.onKeyboardDismiss(mSearchQuery);
+                        }
+                    }, 500);
+                } else if (EditorInfo.IME_ACTION_GO == action) {
+                    if (DEBUG) Log.v(TAG, "Voice Clicked");
+                        hideNativeKeyboard();
+                        mHandler.postDelayed(new Runnable() {
+                            @Override
+                            public void run() {
+                                if (DEBUG) Log.v(TAG, "Delayed action handling (voice_mode)");
+                                mAutoStartRecognition = true;
+                                mSpeechOrbView.requestFocus();
+                            }
+                        }, 500);
+                } else {
+                    handled = false;
+                }
+
+                return handled;
+            }
+        });
+
+        mSearchTextEditor.setPrivateImeOptions("EscapeNorth=1;VoiceDismiss=1;");
+
+        mSpeechOrbView = (SpeechOrbView)findViewById(R.id.lb_search_bar_speech_orb);
+        mSpeechOrbView.setOnOrbClickedListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                toggleRecognition();
+            }
+        });
+        mSpeechOrbView.setOnFocusChangeListener(new OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(View view, boolean hasFocus) {
+                if (DEBUG) Log.v(TAG, "SpeechOrb.onFocusChange " + hasFocus);
+                if (hasFocus) {
+                    hideNativeKeyboard();
+                    if (mAutoStartRecognition) {
+                        startRecognition();
+                        mAutoStartRecognition = false;
+                    }
+                } else {
+                    stopRecognition();
+                }
+                updateUi();
+            }
+        });
+
+        updateHint();
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (DEBUG) Log.v(TAG, "Loading soundPool");
+        mSoundPool = new SoundPool(2, AudioManager.STREAM_SYSTEM, 0);
+        loadSounds(mContext);
+
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                startRecognition();
+            }
+        }, 300);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        if (DEBUG) Log.v(TAG, "Releasing SoundPool");
+        mSoundPool.release();
+
+        super.onDetachedFromWindow();
+    }
+
+    /**
+     * Set a listener for when the term search changes
+     * @param listener
+     */
+    public void setSearchBarListener(SearchBarListener listener) {
+        mSearchBarListener = listener;
+    }
+
+    /**
+     * Set the search query
+     * @param query the search query to use
+     */
+    public void setSearchQuery(String query) {
+        if (query.equals(mSearchQuery)) {
+            return;
+        }
+        mSearchQuery = query;
+        if (null != mSearchBarListener) {
+            mSearchBarListener.onSearchQueryChange(mSearchQuery);
+        }
+    }
+
+    /**
+     * Set the title text used in the hint shown in the search bar.
+     * @param title The hint to use.
+     */
+    public void setTitle(String title) {
+        mTitle = title;
+        updateHint();
+    }
+
+    /**
+     * Returns the current title
+     */
+    public String getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Set the badge drawable showing inside the search bar.
+     * @param drawable The drawable to be used in the search bar.
+     */
+    public void setBadgeDrawable(Drawable drawable) {
+        mBadgeDrawable = drawable;
+        if (null != mBadgeView) {
+            mBadgeView.setImageDrawable(drawable);
+            if (null != drawable) {
+                mBadgeView.setVisibility(View.VISIBLE);
+            } else {
+                mBadgeView.setVisibility(View.GONE);
+            }
+        }
+    }
+
+    /**
+     * Returns the badge drawable
+     */
+    public Drawable getBadgeDrawable() {
+        return mBadgeDrawable;
+    }
+
+    /**
+     * Update the completion list shown by the IME
+     *
+     * @param completions list of completions shown in the IME, can be null or empty to clear them
+     */
+    public void displayCompletions(List<String> completions) {
+        List<CompletionInfo> infos = new ArrayList<CompletionInfo>();
+        if (null != completions) {
+            for (String completion : completions) {
+                infos.add(new CompletionInfo(infos.size(), infos.size(), completion));
+            }
+        }
+
+        mInputMethodManager.displayCompletions(mSearchTextEditor,
+                infos.toArray(new CompletionInfo[] {}));
+    }
+
+    /**
+     * Set the speech recognizer to be used when doing voice search. The Activity/Fragment is in
+     * charge of creating and destroying the recognizer with its own lifecycle.
+     *
+     * @param recognizer a SpeechRecognizer
+     */
+    public void setSpeechRecognizer(SpeechRecognizer recognizer) {
+        if (null != mSpeechRecognizer) {
+            mSpeechRecognizer.setRecognitionListener(null);
+            if (mListening) {
+                mSpeechRecognizer.stopListening();
+                mListening = false;
+            }
+        }
+        mSpeechRecognizer = recognizer;
+    }
+
+    private void hideNativeKeyboard() {
+        mInputMethodManager.hideSoftInputFromWindow(mSearchTextEditor.getWindowToken(),
+                InputMethodManager.RESULT_UNCHANGED_SHOWN);
+    }
+
+    private void showNativeKeyboard() {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mSearchTextEditor.requestFocusFromTouch();
+                mSearchTextEditor.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
+                        SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN,
+                        mSearchTextEditor.getWidth(), mSearchTextEditor.getHeight(), 0));
+                mSearchTextEditor.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
+                        SystemClock.uptimeMillis(), MotionEvent.ACTION_UP,
+                        mSearchTextEditor.getWidth(), mSearchTextEditor.getHeight(), 0));
+            }
+        });
+    }
+
+    /**
+     * This will update the hint for the search bar properly depending on state and provided title
+     */
+    private void updateHint() {
+        if (null == mSearchTextEditor) return;
+
+        String title = getResources().getString(R.string.lb_search_bar_hint);
+        if (!TextUtils.isEmpty(mTitle)) {
+            if (isVoiceMode()) {
+                title = getResources().getString(R.string.lb_search_bar_hint_with_title_speech, mTitle);
+            } else {
+                title = getResources().getString(R.string.lb_search_bar_hint_with_title, mTitle);
+            }
+        } else if (isVoiceMode()) {
+            title = getResources().getString(R.string.lb_search_bar_hint_speech);
+        }
+        mSearchTextEditor.setHint(title);
+    }
+
+    private void toggleRecognition() {
+        if (mRecognizing) {
+            stopRecognition();
+        } else {
+            startRecognition();
+        }
+    }
+
+    private void stopRecognition() {
+        if (null == mSpeechRecognizer) return;
+        if (!mRecognizing) return;
+        mRecognizing = false;
+
+        if (DEBUG) Log.v(TAG, "stopRecognition " + mListening);
+        mSpeechOrbView.showNotListening();
+
+        if (mListening) {
+            mSpeechRecognizer.cancel();
+        }
+    }
+
+    private void startRecognition() {
+        if (null == mSpeechRecognizer) return;
+        if (mRecognizing) return;
+        mRecognizing = true;
+
+        if (DEBUG) Log.v(TAG, "startRecognition " + mListening);
+
+        mSearchTextEditor.setText("");
+
+        Intent recognizerIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+
+        recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
+                RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
+        recognizerIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true);
+
+        mSpeechRecognizer.setRecognitionListener(new RecognitionListener() {
+            @Override
+            public void onReadyForSpeech(Bundle bundle) {
+                if (DEBUG) Log.v(TAG, "onReadyForSpeech");
+            }
+
+            @Override
+            public void onBeginningOfSpeech() {
+                if (DEBUG) Log.v(TAG, "onBeginningOfSpeech");
+                mListening = true;
+            }
+
+            @Override
+            public void onRmsChanged(float rmsdB) {
+                if (DEBUG) Log.v(TAG, "onRmsChanged " + rmsdB);
+                int level = rmsdB < 0 ? 0 : (int)(10 * rmsdB);
+                mSpeechOrbView.setSoundLevel(level);
+            }
+
+            @Override
+            public void onBufferReceived(byte[] bytes) {
+                if (DEBUG) Log.v(TAG, "onBufferReceived " + bytes.length);
+            }
+
+            @Override
+            public void onEndOfSpeech() {
+                if (DEBUG) Log.v(TAG, "onEndOfSpeech");
+                mListening = false;
+            }
+
+            @Override
+            public void onError(int error) {
+                if (DEBUG) Log.v(TAG, "onError " + error);
+                switch (error) {
+                    case SpeechRecognizer.ERROR_NO_MATCH:
+                        Log.d(TAG, "recognizer error no match");
+                        break;
+                    case SpeechRecognizer.ERROR_SERVER:
+                        Log.d(TAG, "recognizer error server error");
+                        break;
+                    case SpeechRecognizer.ERROR_SPEECH_TIMEOUT:
+                        Log.d(TAG, "recognizer error speech timeout");
+                        break;
+                    case SpeechRecognizer.ERROR_CLIENT:
+                        Log.d(TAG, "recognizer error client error");
+                        break;
+                    default:
+                        Log.d(TAG, "recognizer other error");
+                        break;
+                }
+
+                mSpeechRecognizer.stopListening();
+                mListening = false;
+                mSpeechRecognizer.setRecognitionListener(null);
+                mSpeechOrbView.showNotListening();
+                playSearchFailure();
+            }
+
+            @Override
+            public void onResults(Bundle bundle) {
+                if (DEBUG) Log.v(TAG, "onResults");
+                final ArrayList<String> matches =
+                        bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
+                if (matches != null) {
+                    Log.v(TAG, "Got results" + matches);
+
+                    mSearchQuery = matches.get(0);
+                    mSearchTextEditor.setText(mSearchQuery);
+                    submitQuery();
+
+                    if (mListening) {
+                        mSpeechRecognizer.stopListening();
+                    }
+                }
+                mSpeechRecognizer.setRecognitionListener(null);
+                mSpeechOrbView.showNotListening();
+                playSearchSuccess();
+            }
+
+            @Override
+            public void onPartialResults(Bundle bundle) {
+
+            }
+
+            @Override
+            public void onEvent(int i, Bundle bundle) {
+
+            }
+        });
+
+        mSpeechOrbView.showListening();
+        playSearchOpen();
+        mSpeechRecognizer.startListening(recognizerIntent);
+        mListening = true;
+    }
+
+    private void updateUi() {
+        if (DEBUG) Log.v(TAG, String.format("Update UI %s %s",
+                isVoiceMode() ? "Voice" : "Text",
+                hasFocus() ? "Focused" : "Unfocused"));
+        if (isVoiceMode()) {
+            mBarBackground.setAlpha(mBackgroundSpeechAlpha);
+            mSearchTextEditor.setTextColor(mTextSpeechColor);
+        } else {
+            mBarBackground.setAlpha(mBackgroundAlpha);
+            mSearchTextEditor.setTextColor(mTextColor);
+        }
+
+        updateHint();
+    }
+
+    private boolean isVoiceMode() {
+        return mSpeechOrbView.isFocused();
+    }
+
+    private void submitQuery() {
+        if (!TextUtils.isEmpty(mSearchQuery) && null != mSearchBarListener) {
+            mSearchBarListener.onSearchQuerySubmit(mSearchQuery);
+        }
+    }
+
+    private void enforceAudioRecordPermission() {
+        String permission = "android.permission.RECORD_AUDIO";
+        int res = getContext().checkCallingOrSelfPermission(permission);
+        if (PackageManager.PERMISSION_GRANTED != res) {
+            throw new IllegalStateException("android.permission.RECORD_AUDIO required for search");
+        }
+    }
+
+    private void loadSounds(Context context) {
+        int[] sounds = {
+                R.raw.lb_voice_failure,
+                R.raw.lb_voice_open,
+                R.raw.lb_voice_no_input,
+                R.raw.lb_voice_success,
+        };
+        for (int sound : sounds) {
+            mSoundMap.put(sound, mSoundPool.load(context, sound, 1));
+        }
+    }
+
+    private void play(final int resId) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                int sound = mSoundMap.get(resId);
+                mSoundPool.play(sound, FULL_LEFT_VOLUME, FULL_RIGHT_VOLUME, DEFAULT_PRIORITY,
+                        DO_NOT_LOOP, DEFAULT_RATE);
+            }
+        });
+    }
+
+    private void playSearchOpen() {
+        play(R.raw.lb_voice_open);
+    }
+
+    private void playSearchFailure() {
+        play(R.raw.lb_voice_failure);
+    }
+
+    private void playSearchNoInput() {
+        play(R.raw.lb_voice_no_input);
+    }
+
+    private void playSearchSuccess() {
+        play(R.raw.lb_voice_success);
+    }
+
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SearchEditText.java b/v17/leanback/src/android/support/v17/leanback/widget/SearchEditText.java
new file mode 100644
index 0000000..c421b6a
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SearchEditText.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.support.v17.leanback.R;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.widget.EditText;
+
+/**
+ * EditText widget that monitors keyboard changes.
+ */
+public class SearchEditText extends EditText {
+    private static final String TAG = SearchEditText.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    public interface OnKeyboardDismissListener {
+        public void onKeyboardDismiss();
+    }
+
+    private OnKeyboardDismissListener mKeyboardDismissListener;
+
+    public SearchEditText(Context context) {
+        this(context, null);
+    }
+
+    public SearchEditText(Context context, AttributeSet attrs) {
+        this(context, attrs, R.style.TextAppearance_Leanback_SearchTextEdit);
+    }
+
+    public SearchEditText(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+            if (DEBUG) Log.v(TAG, "Keyboard being dismissed");
+            mKeyboardDismissListener.onKeyboardDismiss();
+            return false;
+        }
+        return super.onKeyPreIme(keyCode, event);
+    }
+
+    /**
+     * Set a keyboard dismissed listener.
+     *
+     * @param listener The listener.
+     */
+    public void setOnKeyboardDismissListener(OnKeyboardDismissListener listener) {
+        mKeyboardDismissListener = listener;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SearchOrbView.java b/v17/leanback/src/android/support/v17/leanback/widget/SearchOrbView.java
new file mode 100644
index 0000000..1af3740
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SearchOrbView.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v17.leanback.widget;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.support.v17.leanback.R;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+/**
+ * <p>A widget that draws a search affordance, represented by a round background and an icon.</p>
+ *
+ * Background color and icon can be customized
+ */
+public class SearchOrbView extends FrameLayout implements View.OnClickListener {
+    private OnClickListener mListener;
+    private View mRootView;
+    private View mSearchOrbView;
+    private ImageView mIcon;
+    private Drawable mIconDrawable;
+    private Colors mColors;
+    private final float mFocusedZoom;
+    private final int mPulseDurationMs;
+    private final int mScaleDurationMs;
+    private ValueAnimator mColorAnimator;
+
+    /**
+     * A set of colors used to display the search orb.
+     */
+    public static class Colors {
+        private static final float sBrightnessAlpha = 0.15f;
+
+        /**
+         * Constructs a color set using the given color for the search orb.
+         * Other colors are provided by the framework.
+         * 
+         * @param color The main search orb color.
+         */
+        public Colors(int color) {
+            this(color, color);
+        }
+
+        /**
+         * Constructs a color set using the given colors for the search orb.
+         * Other colors are provided by the framework.
+         * 
+         * @param color The main search orb color.
+         * @param brightColor A brighter version of the search orb used for animation.
+         */
+        public Colors(int color, int brightColor) {
+            this(color, brightColor, Color.TRANSPARENT);
+        }
+
+        /**
+         * Constructs a color set using the given colors.
+         *
+         * @param color The main search orb color.
+         * @param brightColor A brighter version of the search orb used for animation.
+         * @param iconColor A color used to tint the search orb icon.
+         */
+        public Colors(int color, int brightColor, int iconColor) {
+            this.color = color;
+            this.brightColor = brightColor == color ? getBrightColor(color) : brightColor;
+            this.iconColor = iconColor;
+        }
+
+        /**
+         * The main color of the search orb.
+         */
+        public int color;
+
+        /**
+         * A brighter version of the search orb used for animation.
+         */
+        public int brightColor;
+
+        /**
+         * A color used to tint the search orb icon.
+         */
+        public int iconColor;
+
+        /**
+         * Computes a default brighter version of the given color.
+         */
+        public static int getBrightColor(int color) {
+            final float brightnessValue = 0xff * sBrightnessAlpha;
+            int red = (int)(Color.red(color) * (1 - sBrightnessAlpha) + brightnessValue);
+            int green = (int)(Color.green(color) * (1 - sBrightnessAlpha) + brightnessValue);
+            int blue = (int)(Color.blue(color) * (1 - sBrightnessAlpha) + brightnessValue);
+            int alpha = (int)(Color.alpha(color) * (1 - sBrightnessAlpha) + brightnessValue);
+            return Color.argb(alpha, red, green, blue);
+        }
+    }
+
+    private final ArgbEvaluator mColorEvaluator = new ArgbEvaluator();
+
+    private final ValueAnimator.AnimatorUpdateListener mUpdateListener =
+            new ValueAnimator.AnimatorUpdateListener() {
+        @Override
+        public void onAnimationUpdate(ValueAnimator animator) {
+            Integer color = (Integer) animator.getAnimatedValue();
+            setOrbViewColor(color.intValue());
+        }
+    };
+
+    private ValueAnimator mShadowFocusAnimator;
+
+    private final ValueAnimator.AnimatorUpdateListener mFocusUpdateListener =
+            new ValueAnimator.AnimatorUpdateListener() {
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            ShadowHelper.getInstance().setZ(mSearchOrbView, animation.getAnimatedFraction());
+        }
+    };
+
+    public SearchOrbView(Context context) {
+        this(context, null);
+    }
+
+    public SearchOrbView(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.searchOrbViewStyle);
+    }
+
+    public SearchOrbView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        final Resources res = context.getResources();
+
+        LayoutInflater inflater = (LayoutInflater) context
+                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mRootView = inflater.inflate(R.layout.lb_search_orb, this, true);
+        mSearchOrbView = mRootView.findViewById(R.id.search_orb);
+        mIcon = (ImageView) mRootView.findViewById(R.id.icon);
+
+        mFocusedZoom = context.getResources().getFraction(
+                R.fraction.lb_search_orb_focused_zoom, 1, 1);
+        mPulseDurationMs = context.getResources().getInteger(
+                R.integer.lb_search_orb_pulse_duration_ms);
+        mScaleDurationMs = context.getResources().getInteger(
+                R.integer.lb_search_orb_scale_duration_ms);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbSearchOrbView,
+                defStyleAttr, 0);
+
+        Drawable img = a.getDrawable(R.styleable.lbSearchOrbView_searchOrbIcon);
+        if (img == null) {
+            img = res.getDrawable(R.drawable.lb_ic_in_app_search);
+        }
+        setOrbIcon(img);
+
+        int defColor = res.getColor(R.color.lb_default_search_color);
+        int color = a.getColor(R.styleable.lbSearchOrbView_searchOrbColor, defColor);
+        int brightColor = a.getColor(
+                R.styleable.lbSearchOrbView_searchOrbBrightColor, color);
+        int iconColor = a.getColor(R.styleable.lbSearchOrbView_searchOrbIconColor, Color.TRANSPARENT);
+        setOrbColors(new Colors(color, brightColor, iconColor));
+        a.recycle();
+
+        setFocusable(true);
+        setClipChildren(false);
+        setOnClickListener(this);
+
+        ShadowHelper.getInstance().setZ(mSearchOrbView, 0f);
+        // Icon has no background, but must be on top of the search orb view
+        ShadowHelper.getInstance().setZ(mIcon, 1f);
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (null != mListener) {
+            mListener.onClick(view);
+        }
+    }
+
+    private void startShadowFocusAnimation(boolean gainFocus, int duration) {
+        if (mShadowFocusAnimator == null) {
+            mShadowFocusAnimator = ValueAnimator.ofFloat(0f, 1f);
+            mShadowFocusAnimator.addUpdateListener(mFocusUpdateListener);
+        }
+        if (gainFocus) {
+            mShadowFocusAnimator.start();
+        } else {
+            mShadowFocusAnimator.reverse();
+        }
+        mShadowFocusAnimator.setDuration(duration);
+    }
+
+    @Override
+    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+        final float zoom = gainFocus ? mFocusedZoom : 1f;
+        mRootView.animate().scaleX(zoom).scaleY(zoom).setDuration(mScaleDurationMs).start();
+        startShadowFocusAnimation(gainFocus, mScaleDurationMs);
+        enableOrbColorAnimation(gainFocus);
+    }
+
+    /**
+     * Set the orb icon
+     * @param icon the drawable to be used as the icon
+     */
+    public void setOrbIcon(Drawable icon) {
+        mIconDrawable = icon;
+        mIcon.setImageDrawable(mIconDrawable);
+    }
+
+    /**
+     * Returns the orb icon
+     * @return the drawable used as the icon
+     */
+    public Drawable getOrbIcon() {
+        return mIconDrawable;
+    }
+
+    /**
+     * Set the on click listener for the orb
+     * @param listener The listener.
+     */
+    public void setOnOrbClickedListener(OnClickListener listener) {
+        mListener = listener;
+        if (null != listener) {
+            setVisibility(View.VISIBLE);
+        } else {
+            setVisibility(View.INVISIBLE);
+        }
+    }
+
+    /**
+     * Sets the background color of the search orb.
+     * Other colors will be provided by the framework.
+     * 
+     * @param color the RGBA color
+     */
+    public void setOrbColor(int color) {
+        setOrbColors(new Colors(color, color, Color.TRANSPARENT));
+    }
+
+    /**
+     * Sets the search orb colors.
+     * Other colors are provided by the framework.
+     * @deprecated Use {@link #setOrbColors(Colors)} instead.
+     */
+    @Deprecated
+    public void setOrbColor(int color, int brightColor) {
+        setOrbColors(new Colors(color, brightColor, Color.TRANSPARENT));
+    }
+
+    /**
+     * Returns the orb color
+     * @return the RGBA color
+     */
+    public int getOrbColor() {
+        return mColors.color;
+    }
+
+    /**
+     * Set the {@link Colors} used to display the search orb.
+     */
+    public void setOrbColors(Colors colors) {
+        mColors = colors;
+        mIcon.setColorFilter(mColors.iconColor);
+
+        if (mColorAnimator == null) {
+            setOrbViewColor(mColors.color);
+        } else {
+            enableOrbColorAnimation(true);
+        }
+    }
+
+    /**
+     * Returns the {@link Colors} used to display the search orb.
+     */
+    public Colors getOrbColors() {
+        return mColors;
+    }
+
+    private void enableOrbColorAnimation(boolean enable) {
+        if (mColorAnimator != null) {
+            mColorAnimator.end();
+            mColorAnimator = null;
+        }
+        if (enable) {
+            // TODO: set interpolator (material if available)
+            mColorAnimator = ValueAnimator.ofObject(mColorEvaluator,
+                    mColors.color, mColors.brightColor, mColors.color);
+            mColorAnimator.setRepeatCount(ValueAnimator.INFINITE);
+            mColorAnimator.setDuration(mPulseDurationMs * 2);
+            mColorAnimator.addUpdateListener(mUpdateListener);
+            mColorAnimator.start();
+        }
+    }
+
+    private void setOrbViewColor(int color) {
+        if (mSearchOrbView.getBackground() instanceof GradientDrawable) {
+            ((GradientDrawable) mSearchOrbView.getBackground()).setColor(color);
+        }
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java
new file mode 100644
index 0000000..91da59f
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ShadowHelper.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.os.Build;
+import android.view.ViewGroup;
+import android.view.View;
+
+
+/**
+ * Helper for shadow.
+ */
+final class ShadowHelper {
+
+    final static ShadowHelper sInstance = new ShadowHelper();
+    boolean mSupportsShadow;
+    boolean mUsesZShadow;
+    ShadowHelperVersionImpl mImpl;
+
+    /**
+     * Interface implemented by classes that support Shadow.
+     */
+    static interface ShadowHelperVersionImpl {
+
+        public void prepareParent(ViewGroup parent);
+
+        public Object addShadow(ViewGroup shadowContainer);
+
+        public void setZ(View view, float focusLevel);
+
+        public void clearZ(View view);
+
+        public void setShadowFocusLevel(Object impl, float level);
+
+    }
+
+    /**
+     * Interface used when we do not support Shadow animations.
+     */
+    private static final class ShadowHelperStubImpl implements ShadowHelperVersionImpl {
+
+        @Override
+        public void prepareParent(ViewGroup parent) {
+            // do nothing
+        }
+
+        @Override
+        public Object addShadow(ViewGroup shadowContainer) {
+            // do nothing
+            return null;
+        }
+
+        @Override
+        public void setShadowFocusLevel(Object impl, float level) {
+            // do nothing
+        }
+
+        @Override
+        public void setZ(View view, float focusLevel) {
+            // do nothing
+        }
+
+        @Override
+        public void clearZ(View view) {
+            // do nothing
+        }
+
+    }
+
+    /**
+     * Implementation used on JBMR2 (and above).
+     */
+    private static final class ShadowHelperJbmr2Impl implements ShadowHelperVersionImpl {
+
+        @Override
+        public void prepareParent(ViewGroup parent) {
+            ShadowHelperJbmr2.prepareParent(parent);
+        }
+
+        @Override
+        public Object addShadow(ViewGroup shadowContainer) {
+            return ShadowHelperJbmr2.addShadow(shadowContainer);
+        }
+
+        @Override
+        public void setShadowFocusLevel(Object impl, float level) {
+            ShadowHelperJbmr2.setShadowFocusLevel(impl, level);
+        }
+
+        @Override
+        public void setZ(View view, float focusLevel) {
+            // Not supported
+        }
+
+        @Override
+        public void clearZ(View view) {
+            // Not supported
+        }
+
+    }
+
+    /**
+     * Implementation used on api 21 (and above).
+     */
+    private static final class ShadowHelperApi21Impl implements ShadowHelperVersionImpl {
+
+        @Override
+        public void prepareParent(ViewGroup parent) {
+            // do nothing
+        }
+
+        @Override
+        public Object addShadow(ViewGroup shadowContainer) {
+            return ShadowHelperApi21.addShadow(shadowContainer);
+        }
+
+        @Override
+        public void setShadowFocusLevel(Object impl, float level) {
+            ShadowHelperApi21.setShadowFocusLevel(impl, level);
+        }
+
+        @Override
+        public void setZ(View view, float focusLevel) {
+            ShadowHelperApi21.setZ(view, focusLevel);
+        }
+
+        @Override
+        public void clearZ(View view) {
+            ShadowHelperApi21.clearZ(view);
+        }
+
+    }
+
+    /**
+     * Returns the ShadowHelper.
+     */
+    private ShadowHelper() {
+     // TODO: we should use version number once "L" is published
+        if ("L".equals(Build.VERSION.RELEASE)) {
+            mSupportsShadow = true;
+            mUsesZShadow = true;
+            mImpl = new ShadowHelperApi21Impl();
+        } else if (Build.VERSION.SDK_INT >= 18) {
+            mSupportsShadow = true;
+            mImpl = new ShadowHelperJbmr2Impl();
+        } else {
+            mSupportsShadow = false;
+            mImpl = new ShadowHelperStubImpl();
+        }
+    }
+
+    public static ShadowHelper getInstance() {
+        return sInstance;
+    }
+
+    public boolean supportsShadow() {
+        return mSupportsShadow;
+    }
+
+    public boolean usesZShadow() {
+        return mUsesZShadow;
+    }
+
+    public void prepareParent(ViewGroup parent) {
+        mImpl.prepareParent(parent);
+    }
+
+    public Object addShadow(ViewGroup shadowContainer) {
+        return mImpl.addShadow(shadowContainer);
+    }
+
+    public void setShadowFocusLevel(Object impl, float level) {
+        mImpl.setShadowFocusLevel(impl, level);
+    }
+
+    /**
+     * Set the view z coordinate with the given focus level from 0..1.
+     */
+    public void setZ(View view, float focusLevel) {
+        mImpl.setZ(view, focusLevel);
+    }
+
+    public void clearZ(View view) {
+        mImpl.clearZ(view);
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java b/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java
new file mode 100644
index 0000000..4ba0cc6
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ShadowOverlayContainer.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.support.v17.leanback.R;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.graphics.Rect;
+
+/**
+ * ShadowOverlayContainer Provides a SDK version independent wrapper container
+ * to take care of shadow and/or color overlay.
+ * <p>
+ * Shadow and color dimmer overlay are both optional.  When shadow is used,  it's
+ * user's responsibility to properly call setClipChildren(false) on parent views if
+ * the shadow can appear outside bounds of parent views.
+ * {@link #prepareParentForShadow(ViewGroup)} must be called on parent of container
+ * before using shadow.  Depending on sdk version, optical bounds might be applied
+ * to parent.
+ * </p>
+ * <p>
+ * {@link #initialize(boolean, boolean)} must be first called on the container to initialize
+ * shadows and/or color overlay.  Then call {@link #wrap(View)} to insert wrapped view
+ * into container.
+ * </p>
+ * <p>
+ * Call {@link #setShadowFocusLevel(float)} to control shadow alpha.
+ * </p>
+ * <p>
+ * Call {@link #setOverlayColor(int)} to control overlay color.
+ * </p>
+ */
+public class ShadowOverlayContainer extends ViewGroup {
+
+    private boolean mInitialized;
+    private View mColorDimOverlay;
+    private Object mShadowImpl;
+    private View mWrappedView;
+    private static final Rect sTempRect = new Rect();
+
+    public ShadowOverlayContainer(Context context) {
+        this(context, null, 0);
+    }
+
+    public ShadowOverlayContainer(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ShadowOverlayContainer(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    /**
+     * Return true if the platform sdk supports shadow.
+     */
+    public static boolean supportsShadow() {
+        return ShadowHelper.getInstance().supportsShadow();
+    }
+
+    /**
+     * {@link #prepareParentForShadow(ViewGroup)} must be called on parent of container
+     * before using shadow.  Depending on sdk version, optical bounds might be applied
+     * to parent.
+     */
+    public static void prepareParentForShadow(ViewGroup parent) {
+        ShadowHelper.getInstance().prepareParent(parent);
+    }
+
+    /**
+     * Initialize shadows and/or color overlay.  Both are optional.
+     */
+    public void initialize(boolean hasShadow, boolean hasColorDimOverlay) {
+        if (mInitialized) {
+            throw new IllegalStateException();
+        }
+        mInitialized = true;
+        if (hasShadow) {
+            mShadowImpl = ShadowHelper.getInstance().addShadow(this);
+        }
+        if (hasColorDimOverlay) {
+            mColorDimOverlay = LayoutInflater.from(getContext())
+                    .inflate(R.layout.lb_card_color_overlay, this, false);
+            addView(mColorDimOverlay);
+        }
+    }
+
+    /**
+     * Set shadow focus level (0 to 1). 0 for unfocused, 1f for fully focused.
+     */
+    public void setShadowFocusLevel(float level) {
+        if (mShadowImpl != null) {
+            if (level < 0f) {
+                level = 0f;
+            } else if (level > 1f) {
+                level = 1f;
+            }
+            ShadowHelper.getInstance().setShadowFocusLevel(mShadowImpl, level);
+        }
+    }
+
+    /**
+     * Set color (with alpha) of the overlay.
+     */
+    public void setOverlayColor(int overlayColor) {
+        if (mColorDimOverlay != null) {
+            mColorDimOverlay.setBackgroundColor(overlayColor);
+        }
+    }
+
+    /**
+     * Inserts view into the wrapper.
+     */
+    public void wrap(View view) {
+        if (!mInitialized || mWrappedView != null) {
+            throw new IllegalStateException();
+        }
+        if (mColorDimOverlay != null) {
+            addView(view, indexOfChild(mColorDimOverlay));
+        } else {
+            addView(view);
+        }
+        mWrappedView = view;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (mWrappedView == null) {
+            throw new IllegalStateException();
+        }
+        // padding and child margin are not supported.
+        // first measure the wrapped view, then measure the shadow view and/or overlay view.
+        int childWidthMeasureSpec, childHeightMeasureSpec;
+        LayoutParams lp = mWrappedView.getLayoutParams();
+        if (lp.width == LayoutParams.MATCH_PARENT) {
+            childWidthMeasureSpec = MeasureSpec.makeMeasureSpec
+                    (MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY);
+        } else {
+            childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
+        }
+        if (lp.height == LayoutParams.MATCH_PARENT) {
+            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec
+                    (MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY);
+        } else {
+            childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
+        }
+        mWrappedView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+
+        int measuredWidth = mWrappedView.getMeasuredWidth();
+        int measuredHeight = mWrappedView.getMeasuredHeight();
+
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            if (child == mWrappedView) {
+                continue;
+            }
+            lp = child.getLayoutParams();
+            if (lp.width == LayoutParams.MATCH_PARENT) {
+                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec
+                        (measuredWidth, MeasureSpec.EXACTLY);
+            } else {
+                childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
+            }
+
+            if (lp.height == LayoutParams.MATCH_PARENT) {
+                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec
+                        (measuredHeight, MeasureSpec.EXACTLY);
+            } else {
+                childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
+            }
+            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+        }
+        setMeasuredDimension(measuredWidth, measuredHeight);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                final int width = child.getMeasuredWidth();
+                final int height = child.getMeasuredHeight();
+                child.layout(0, 0, width, height);
+            }
+        }
+        if (mWrappedView != null) {
+            sTempRect.left = (int) mWrappedView.getPivotX();
+            sTempRect.top = (int) mWrappedView.getPivotY();
+            offsetDescendantRectToMyCoords(mWrappedView, sTempRect);
+            setPivotX(sTempRect.left);
+            setPivotY(sTempRect.top);
+        }
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SinglePresenterSelector.java b/v17/leanback/src/android/support/v17/leanback/widget/SinglePresenterSelector.java
new file mode 100644
index 0000000..261b638
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SinglePresenterSelector.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+/**
+ * A {@link PresenterSelector} that always returns the same {@link Presenter}.
+ * Useful for rows of items of the same type that are all rendered the same way.
+ */
+public final class SinglePresenterSelector extends PresenterSelector {
+
+    private final Presenter mPresenter;
+
+    /**
+     * @param presenter The Presenter to return for every item.
+     */
+    public SinglePresenterSelector(Presenter presenter) {
+        mPresenter = presenter;
+    }
+
+    @Override
+    public Presenter getPresenter(Object item) {
+        return mPresenter;
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SpeechOrbView.java b/v17/leanback/src/android/support/v17/leanback/widget/SpeechOrbView.java
new file mode 100644
index 0000000..46165d5
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SpeechOrbView.java
@@ -0,0 +1,121 @@
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.graphics.drawable.GradientDrawable;
+import android.support.v17.leanback.R;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+public class SpeechOrbView extends FrameLayout implements View.OnClickListener {
+    private OnClickListener mListener;
+    private View mSpeechOrbView;
+    private final float mFocusedZoom;
+    private final float mSoundLevelMaxZoom;
+    private final int mNotRecordingColor;
+    private final int mRecordingColor;
+    private ImageView mIcon;
+
+    private int mCurrentLevel = 0;
+    private boolean mListening = false;
+
+    public SpeechOrbView(Context context) {
+        this(context, null);
+    }
+
+    public SpeechOrbView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SpeechOrbView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        LayoutInflater inflater = (LayoutInflater) context
+                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        View root = inflater.inflate(R.layout.lb_speech_orb, this, true);
+        mSpeechOrbView = root.findViewById(R.id.lb_speech_orb);
+        mIcon = (ImageView)root.findViewById(R.id.lb_speech_icon);
+
+        setFocusable(true);
+        setClipChildren(false);
+
+        Resources resources = context.getResources();
+        mFocusedZoom =
+                resources.getFraction(R.fraction.lb_search_bar_speech_orb_focused_zoom, 1, 1);
+        mSoundLevelMaxZoom =
+                resources.getFraction(R.fraction.lb_search_bar_speech_orb_max_level_zoom, 1, 1);
+        mNotRecordingColor = resources.getColor(R.color.lb_speech_orb_not_recording);
+        mRecordingColor = resources.getColor(R.color.lb_speech_orb_recording);
+
+        setOnClickListener(this);
+        showNotListening();
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (null != mListener) {
+            mListener.onClick(view);
+        }
+    }
+
+    @Override
+    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+        final float zoom = gainFocus ? mFocusedZoom : 1f;
+        mSpeechOrbView.animate().scaleX(zoom).scaleY(zoom).setDuration(200).start();
+        if (gainFocus) {
+            mIcon.setImageResource(R.drawable.lb_ic_search_mic);
+        } else {
+            mIcon.setImageResource(R.drawable.lb_ic_search_mic_out);
+        }
+    }
+
+    /**
+     * Set the on click listener for the orb
+     * @param listener The listener.
+     */
+    public void setOnOrbClickedListener(OnClickListener listener) {
+        mListener = listener;
+    }
+
+    public void showListening() {
+        setOrbColor(mRecordingColor);
+        mSpeechOrbView.setScaleX(1f);
+        mSpeechOrbView.setScaleY(1f);
+        mListening = true;
+    }
+
+    public void showNotListening() {
+        setOrbColor(mNotRecordingColor);
+        mSpeechOrbView.setScaleX(1f);
+        mSpeechOrbView.setScaleY(1f);
+        mListening = false;
+    }
+
+    public void setSoundLevel(int level) {
+        if (!mListening) return;
+
+        // Either ease towards the target level, or decay away from it depending on whether
+        // its higher or lower than the current.
+        if (level > mCurrentLevel) {
+            mCurrentLevel = mCurrentLevel + ((level - mCurrentLevel) / 4);
+        } else {
+            mCurrentLevel = (int) (mCurrentLevel * 0.95f);
+        }
+
+        float zoom = mFocusedZoom + ((mSoundLevelMaxZoom - mFocusedZoom) * mCurrentLevel) / 100;
+        mSpeechOrbView.setScaleX(zoom);
+        mSpeechOrbView.setScaleY(zoom);
+    }
+
+    public void setOrbColor(int color) {
+        if (mSpeechOrbView.getBackground() instanceof GradientDrawable) {
+            ((GradientDrawable) mSpeechOrbView.getBackground()).setColor(color);
+        }
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
new file mode 100644
index 0000000..5404418
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.support.v4.util.CircularArray;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A dynamic data structure that maintains staggered grid position information
+ * for each individual child. The algorithm ensures that each row will be kept
+ * as balanced as possible when prepending and appending a child.
+ *
+ * <p>
+ * You may keep view {@link StaggeredGrid.Location} inside StaggeredGrid as much
+ * as possible since prepending and appending views is not symmetric: layout
+ * going from 0 to N will likely produce a different result than layout going
+ * from N to 0 for the staggered cases. If a user scrolls from 0 to N then
+ * scrolls back to 0 and we don't keep history location information, edges of
+ * the very beginning of rows will not be aligned. It is recommended to keep a
+ * list of tens of thousands of {@link StaggeredGrid.Location}s which will be
+ * big enough to remember a typical user's scroll history. There are situations
+ * where StaggeredGrid falls back to the simple case where we do not need save a
+ * huge list of locations inside StaggeredGrid:
+ * <ul>
+ *   <li>Only one row (e.g., a single row listview)</li>
+ *   <li> Each item has the same length (not staggered at all)</li>
+ * </ul>
+ *
+ * <p>
+ * This class is abstract and can be replaced with different implementations.
+ */
+abstract class StaggeredGrid {
+
+    /**
+     * TODO: document this
+     */
+    public static interface Provider {
+        /**
+         * Return how many items are in the adapter.
+         */
+        public abstract int getCount();
+
+        /**
+         * Create the object at a given row.
+         */
+        public abstract void createItem(int index, int row, boolean append);
+    }
+
+    /**
+     * Location of an item in the grid. For now it only saves row index but
+     * more information may be added in the future.
+     */
+    public final static class Location {
+        /**
+         * The index of the row for this Location.
+         */
+        public final int row;
+
+        /**
+         * Create a Location with the given row index.
+         */
+        public Location(int row) {
+            this.row = row;
+        }
+    }
+
+    /**
+     * TODO: document this
+     */
+    public final static class Row {
+        /**
+         * first view start location
+         */
+        public int low;
+        /**
+         * last view end location
+         */
+        public int high;
+    }
+
+    protected Provider mProvider;
+    protected int mNumRows = 1; // mRows.length
+    protected Row[] mRows;
+    protected CircularArray<Location> mLocations = new CircularArray<Location>(64);
+    private ArrayList<Integer>[] mTmpItemPositionsInRows;
+
+    /**
+     * A constant representing a default starting index, indicating that the
+     * developer did not provide a start index.
+     */
+    public static final int START_DEFAULT = -1;
+
+    // the first index that grid will layout
+    protected int mStartIndex = START_DEFAULT;
+    // the row to layout the first index
+    protected int mStartRow = START_DEFAULT;
+
+    protected int mFirstIndex = -1;
+
+    /**
+     * Sets the {@link Provider} for this staggered grid.
+     *
+     * @param provider The provider for this staggered grid.
+     */
+    public void setProvider(Provider provider) {
+        mProvider = provider;
+    }
+
+    /**
+     * Sets the array of {@link Row}s to fill into. For views that represent a
+     * horizontal list, this will be the rows of the view. For views that
+     * represent a vertical list, this will be the columns.
+     *
+     * @param row The array of {@link Row}s to be filled.
+     */
+    public final void setRows(Row[] row) {
+        if (row == null || row.length == 0) {
+            throw new IllegalArgumentException();
+        }
+        mNumRows = row.length;
+        mRows = row;
+        mTmpItemPositionsInRows = new ArrayList[mNumRows];
+        for (int i = 0; i < mNumRows; i++) {
+            mTmpItemPositionsInRows[i] = new ArrayList(32);
+        }
+    }
+
+    /**
+     * Returns the number of rows in the staggered grid.
+     */
+    public final int getNumRows() {
+        return mNumRows;
+    }
+
+    /**
+     * Set the first item index and the row index to load when there are no
+     * items.
+     *
+     * @param startIndex the index of the first item
+     * @param startRow the index of the row
+     */
+    public final void setStart(int startIndex, int startRow) {
+        mStartIndex = startIndex;
+        mStartRow = startRow;
+    }
+
+    /**
+     * Returns the first index in the staggered grid.
+     */
+    public final int getFirstIndex() {
+        return mFirstIndex;
+    }
+
+    /**
+     * Returns the last index in the staggered grid.
+     */
+    public final int getLastIndex() {
+        return mFirstIndex + mLocations.size() - 1;
+    }
+
+    /**
+     * Returns the size of the saved {@link Location}s.
+     */
+    public final int getSize() {
+        return mLocations.size();
+    }
+
+    /**
+     * Returns the {@link Location} at the given index.
+     */
+    public final Location getLocation(int index) {
+        if (mLocations.size() == 0) {
+            return null;
+        }
+        return mLocations.get(index - mFirstIndex);
+    }
+
+    /**
+     * Removes the first element.
+     */
+    public final void removeFirst() {
+        mFirstIndex++;
+        mLocations.popFirst();
+    }
+
+    /**
+     * Removes the last element.
+     */
+    public final void removeLast() {
+        mLocations.popLast();
+    }
+
+    public final void debugPrint(PrintWriter pw) {
+        for (int i = 0, size = mLocations.size(); i < size; i++) {
+            Location loc = mLocations.get(i);
+            pw.print("<" + (mFirstIndex + i) + "," + loc.row + ">");
+            pw.print(" ");
+            pw.println();
+        }
+    }
+
+    protected final int getMaxHighRowIndex() {
+        int maxHighRowIndex = 0;
+        for (int i = 1; i < mNumRows; i++) {
+            if (mRows[i].high > mRows[maxHighRowIndex].high) {
+                maxHighRowIndex = i;
+            }
+        }
+        return maxHighRowIndex;
+    }
+
+    protected final int getMinHighRowIndex() {
+        int minHighRowIndex = 0;
+        for (int i = 1; i < mNumRows; i++) {
+            if (mRows[i].high < mRows[minHighRowIndex].high) {
+                minHighRowIndex = i;
+            }
+        }
+        return minHighRowIndex;
+    }
+
+    protected final Location appendItemToRow(int itemIndex, int rowIndex) {
+        Location loc = new Location(rowIndex);
+        if (mLocations.size() == 0) {
+            mFirstIndex = itemIndex;
+        }
+        mLocations.addLast(loc);
+        mProvider.createItem(itemIndex, rowIndex, true);
+        return loc;
+    }
+
+    /**
+     * Append items until the high edge reaches upTo.
+     */
+    public abstract void appendItems(int upTo);
+
+    protected final int getMaxLowRowIndex() {
+        int maxLowRowIndex = 0;
+        for (int i = 1; i < mNumRows; i++) {
+            if (mRows[i].low > mRows[maxLowRowIndex].low) {
+                maxLowRowIndex = i;
+            }
+        }
+        return maxLowRowIndex;
+    }
+
+    protected final int getMinLowRowIndex() {
+        int minLowRowIndex = 0;
+        for (int i = 1; i < mNumRows; i++) {
+            if (mRows[i].low < mRows[minLowRowIndex].low) {
+                minLowRowIndex = i;
+            }
+        }
+        return minLowRowIndex;
+    }
+
+    protected final Location prependItemToRow(int itemIndex, int rowIndex) {
+        Location loc = new Location(rowIndex);
+        mFirstIndex = itemIndex;
+        mLocations.addFirst(loc);
+        mProvider.createItem(itemIndex, rowIndex, false);
+        return loc;
+    }
+
+    /**
+     * Return array of Lists for all rows, each List contains item positions
+     * on that row between startPos(included) and endPositions(included).
+     * Returned value is read only, do not change it.
+     */
+    public final List<Integer>[] getItemPositionsInRows(int startPos, int endPos) {
+        for (int i = 0; i < mNumRows; i++) {
+            mTmpItemPositionsInRows[i].clear();
+        }
+        if (startPos >= 0) {
+            for (int i = startPos; i <= endPos; i++) {
+                mTmpItemPositionsInRows[getLocation(i).row].add(i);
+            }
+        }
+        return mTmpItemPositionsInRows;
+    }
+
+    /**
+     * Prepend items until the low edge reaches downTo.
+     */
+    public abstract void prependItems(int downTo);
+
+    /**
+     * Strip items, keep a contiguous subset of items; the subset should include
+     * at least one item on every row that currently has at least one item.
+     *
+     * <p>
+     * TODO: document this better
+     */
+    public abstract void stripDownTo(int itemIndex);
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java
new file mode 100644
index 0000000..9f2a06c
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGridDefault.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+/**
+ * A default implementation of {@link StaggeredGrid}.
+ *
+ * This implementation tries to fill items in consecutive row order. The next
+ * item is always in same row or in the next row.
+ */
+final class StaggeredGridDefault extends StaggeredGrid {
+
+    @Override
+    public void appendItems(int upTo) {
+        int count = mProvider.getCount();
+        int itemIndex;
+        int rowIndex;
+        if (mLocations.size() > 0) {
+            itemIndex = getLastIndex() + 1;
+            rowIndex = (mLocations.getLast().row + 1) % mNumRows;
+        } else {
+            itemIndex = mStartIndex != START_DEFAULT ? mStartIndex : 0;
+            rowIndex = mStartRow != START_DEFAULT ? mStartRow : itemIndex % mNumRows;
+        }
+
+    top_loop:
+        while (true) {
+            // find highest row (.high is biggest)
+            int maxHighRowIndex = mLocations.size() > 0 ? getMaxHighRowIndex() : -1;
+            int maxHigh = maxHighRowIndex != -1 ? mRows[maxHighRowIndex].high : Integer.MIN_VALUE;
+            // fill from current row till last row so that each row will grow longer than
+            // the previous highest row.
+            for (; rowIndex < mNumRows; rowIndex++) {
+                // fill one item to a row
+                if (itemIndex == count) {
+                    break top_loop;
+                }
+                appendItemToRow(itemIndex++, rowIndex);
+                // fill more item to the row to make sure this row is longer than
+                // the previous highest row.
+                if (maxHighRowIndex == -1) {
+                    maxHighRowIndex = getMaxHighRowIndex();
+                    maxHigh = mRows[maxHighRowIndex].high;
+                } else  if (rowIndex != maxHighRowIndex) {
+                    while (mRows[rowIndex].high < maxHigh) {
+                        if (itemIndex == count) {
+                            break top_loop;
+                        }
+                        appendItemToRow(itemIndex++, rowIndex);
+                    }
+                }
+            }
+            if (mRows[getMinHighRowIndex()].high >= upTo) {
+                break;
+            }
+            // start fill from row 0 again
+            rowIndex = 0;
+        }
+    }
+
+    @Override
+    public void prependItems(int downTo) {
+        if (mProvider.getCount() <= 0) return;
+        int itemIndex;
+        int rowIndex;
+        if (mLocations.size() > 0) {
+            itemIndex = getFirstIndex() - 1;
+            rowIndex = mLocations.getFirst().row;
+            if (rowIndex == 0) {
+                rowIndex = mNumRows - 1;
+            } else {
+                rowIndex--;
+            }
+        } else {
+            itemIndex = mStartIndex != START_DEFAULT ? mStartIndex : 0;
+            rowIndex = mStartRow != START_DEFAULT ? mStartRow : itemIndex % mNumRows;
+        }
+
+    top_loop:
+        while (true) {
+            int minLowRowIndex = mLocations.size() > 0 ? getMinLowRowIndex() : -1;
+            int minLow = minLowRowIndex != -1 ? mRows[minLowRowIndex].low : Integer.MAX_VALUE;
+            for (; rowIndex >=0 ; rowIndex--) {
+                if (itemIndex < 0) {
+                    break top_loop;
+                }
+                prependItemToRow(itemIndex--, rowIndex);
+                if (minLowRowIndex == -1) {
+                    minLowRowIndex = getMinLowRowIndex();
+                    minLow = mRows[minLowRowIndex].low;
+                } else if (rowIndex != minLowRowIndex) {
+                    while (mRows[rowIndex].low > minLow) {
+                        if (itemIndex < 0) {
+                            break top_loop;
+                        }
+                        prependItemToRow(itemIndex--, rowIndex);
+                    }
+                }
+            }
+            if (mRows[getMaxLowRowIndex()].low <= downTo) {
+                break;
+            }
+            rowIndex = mNumRows - 1;
+        }
+    }
+
+    @Override
+    public final void stripDownTo(int itemIndex) {
+        // because we layout the items in the order that next item is either same row
+        // or next row,  so we can easily find the row range by searching items forward and
+        // backward until we see the row is 0 or mNumRow - 1
+        Location loc = getLocation(itemIndex);
+        if (loc == null) {
+            return;
+        }
+        int firstIndex = getFirstIndex();
+        int lastIndex = getLastIndex();
+        int row = loc.row;
+
+        int endIndex = itemIndex;
+        int endRow = row;
+        while (endRow < mNumRows - 1 && endIndex < lastIndex) {
+            endIndex++;
+            endRow = getLocation(endIndex).row;
+        }
+
+        int startIndex = itemIndex;
+        int startRow = row;
+        while (startRow > 0 && startIndex > firstIndex) {
+            startIndex--;
+            startRow = getLocation(startIndex).row;
+        }
+        // trim information
+        for (int i = firstIndex; i < startIndex; i++) {
+            removeFirst();
+        }
+        for (int i = endIndex; i < lastIndex; i++) {
+            removeLast();
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/TitleView.java b/v17/leanback/src/android/support/v17/leanback/widget/TitleView.java
new file mode 100644
index 0000000..1c61fa0
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/TitleView.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.v17.leanback.R;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * Title view for a leanback fragment.
+ * @hide
+ */
+public class TitleView extends FrameLayout {
+
+    private ImageView mBadgeView;
+    private TextView mTextView;
+    private SearchOrbView mSearchOrbView;
+
+    public TitleView(Context context) {
+        this(context, null);
+    }
+
+    public TitleView(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.browseTitleViewStyle);
+    }
+
+    public TitleView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        LayoutInflater inflater = LayoutInflater.from(context);
+        View rootView = inflater.inflate(R.layout.lb_title_view, this);
+
+        mBadgeView = (ImageView) rootView.findViewById(R.id.browse_badge);
+        mTextView = (TextView) rootView.findViewById(R.id.browse_title);
+        mSearchOrbView = (SearchOrbView) rootView.findViewById(R.id.browse_orb);
+
+        setClipToPadding(false);
+        setClipChildren(false);
+    }
+
+    /**
+     * Sets the title text.
+     */
+    public void setTitle(String titleText) {
+        mTextView.setText(titleText);
+    }
+
+    /**
+     * Returns the title text.
+     */
+    public CharSequence getTitle() {
+        return mTextView.getText();
+    }
+
+    /**
+     * Sets the badge drawable.
+     * If non-null, the drawable is displayed instead of the title text.
+     */
+    public void setBadgeDrawable(Drawable drawable) {
+        mBadgeView.setImageDrawable(drawable);
+        if (drawable != null) {
+            mBadgeView.setVisibility(View.VISIBLE);
+            mTextView.setVisibility(View.GONE);
+        } else {
+            mBadgeView.setVisibility(View.GONE);
+            mTextView.setVisibility(View.VISIBLE);
+        }
+    }
+
+    /**
+     * Returns the badge drawable.
+     */
+    public Drawable getBadgeDrawable() {
+        return mBadgeView.getDrawable();
+    }
+
+    /**
+     * Sets the listener to be called when the search affordance is clicked.
+     */
+    public void setOnSearchClickedListener(View.OnClickListener listener) {
+        mSearchOrbView.setOnOrbClickedListener(listener);
+    }
+
+    /**
+     *  Returns the view for the search affordance.
+     */
+    public View getSearchAffordanceView() {
+        return mSearchOrbView;
+    }
+
+    /**
+     * Sets the {@link SearchOrbView.Colors} used to draw the search affordance.
+     */
+    public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
+        mSearchOrbView.setOrbColors(colors);
+    }
+
+    /**
+     * Returns the {@link SearchOrbView.Colors} used to draw the search affordance.
+     */
+    public SearchOrbView.Colors getSearchAffordanceColors() {
+        return mSearchOrbView.getOrbColors();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java
new file mode 100644
index 0000000..b421d19
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridPresenter.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.support.v17.leanback.R;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.util.Log;
+
+/**
+ * A presenter that renders objects in a vertical grid.
+ *
+ */
+public class VerticalGridPresenter extends Presenter {
+    private static final String TAG = "GridPresenter";
+    private static final boolean DEBUG = false;
+
+    public static class ViewHolder extends Presenter.ViewHolder {
+        final ItemBridgeAdapter mItemBridgeAdapter = new ItemBridgeAdapter();
+        final VerticalGridView mGridView;
+        boolean mInitialized;
+
+        public ViewHolder(VerticalGridView view) {
+            super(view);
+            mGridView = view;
+        }
+
+        public VerticalGridView getGridView() {
+            return mGridView;
+        }
+    }
+
+    private int mNumColumns = -1;
+    private int mZoomFactor;
+    private boolean mShadowEnabled = true;
+    private OnItemClickedListener mOnItemClickedListener;
+    private OnItemSelectedListener mOnItemSelectedListener;
+    private OnItemViewSelectedListener mOnItemViewSelectedListener;
+    private OnItemViewClickedListener mOnItemViewClickedListener;
+
+    public VerticalGridPresenter() {
+        this(FocusHighlight.ZOOM_FACTOR_MEDIUM);
+    }
+
+    public VerticalGridPresenter(int zoomFactor) {
+        mZoomFactor = zoomFactor;
+    }
+
+    /**
+     * Sets the number of columns in the vertical grid.
+     */
+    public void setNumberOfColumns(int numColumns) {
+        if (numColumns < 0) {
+            throw new IllegalArgumentException("Invalid number of columns");
+        }
+        if (mNumColumns != numColumns) {
+            mNumColumns = numColumns;
+        }
+    }
+
+    /**
+     * Returns the number of columns in the vertical grid.
+     */
+    public int getNumberOfColumns() {
+        return mNumColumns;
+    }
+
+    /**
+     * Enable or disable child shadow.
+     * This is not only for enable/disable default shadow implementation but also subclass must
+     * respect this flag.
+     */
+    public final void setShadowEnabled(boolean enabled) {
+        mShadowEnabled = enabled;
+    }
+
+    /**
+     * Returns true if child shadow is enabled.
+     * This is not only for enable/disable default shadow implementation but also subclass must
+     * respect this flag.
+     */
+    public final boolean getShadowEnabled() {
+        return mShadowEnabled;
+    }
+
+    /**
+     * Returns true if opticalBounds is supported (SDK >= 18) so that default shadow
+     * is applied to each individual child of {@link VerticalGridView}.
+     * Subclass may return false to disable.
+     */
+    public boolean isUsingDefaultShadow() {
+        return ShadowOverlayContainer.supportsShadow();
+    }
+
+    /**
+     * Returns true if SDK >= L, where Z shadow is enabled so that Z order is enabled
+     * on each child of vertical grid.   If subclass returns false in isUsingDefaultShadow()
+     * and does not use Z-shadow on SDK >= L, it should override isUsingZOrder() return false.
+     */
+    public boolean isUsingZOrder() {
+        return ShadowHelper.getInstance().usesZShadow();
+    }
+
+    final boolean needsDefaultShadow() {
+        return isUsingDefaultShadow() && getShadowEnabled();
+    }
+
+    @Override
+    public final ViewHolder onCreateViewHolder(ViewGroup parent) {
+        ViewHolder vh = createGridViewHolder(parent);
+        vh.mInitialized = false;
+        initializeGridViewHolder(vh);
+        if (!vh.mInitialized) {
+            throw new RuntimeException("super.initializeGridViewHolder() must be called");
+        }
+        return vh;
+    }
+
+    /**
+     * Subclass may override this to inflate a different layout.
+     */
+    protected ViewHolder createGridViewHolder(ViewGroup parent) {
+        View root = LayoutInflater.from(parent.getContext()).inflate(
+                R.layout.lb_vertical_grid, parent, false);
+        return new ViewHolder((VerticalGridView) root.findViewById(R.id.browse_grid));
+    }
+
+    private ItemBridgeAdapter.Wrapper mWrapper = new ItemBridgeAdapter.Wrapper() {
+        @Override
+        public View createWrapper(View root) {
+            ShadowOverlayContainer wrapper = new ShadowOverlayContainer(root.getContext());
+            wrapper.setLayoutParams(
+                    new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+            wrapper.initialize(needsDefaultShadow(), false);
+            return wrapper;
+        }
+        @Override
+        public void wrap(View wrapper, View wrapped) {
+            ((ShadowOverlayContainer) wrapper).wrap(wrapped);
+        }
+    };
+
+    /**
+     * Called after a {@link VerticalGridPresenter.ViewHolder} is created.
+     * Subclasses may override this method and start by calling
+     * super.initializeGridViewHolder(ViewHolder).
+     *
+     * @param vh The ViewHolder to initialize for the vertical grid.
+     */
+    protected void initializeGridViewHolder(ViewHolder vh) {
+        if (mNumColumns == -1) {
+            throw new IllegalStateException("Number of columns must be set");
+        }
+        if (DEBUG) Log.v(TAG, "mNumColumns " + mNumColumns);
+        vh.getGridView().setNumColumns(mNumColumns);
+        vh.mInitialized = true;
+
+        if (needsDefaultShadow()) {
+            vh.mItemBridgeAdapter.setWrapper(mWrapper);
+            ShadowOverlayContainer.prepareParentForShadow(vh.getGridView());
+            ((ViewGroup) vh.view).setClipChildren(false);
+        }
+        vh.getGridView().setFocusDrawingOrderEnabled(!isUsingZOrder());
+        FocusHighlightHelper.setupBrowseItemFocusHighlight(vh.mItemBridgeAdapter, mZoomFactor);
+
+        final ViewHolder gridViewHolder = vh;
+        vh.getGridView().setOnChildSelectedListener(new OnChildSelectedListener() {
+            @Override
+            public void onChildSelected(ViewGroup parent, View view, int position, long id) {
+                selectChildView(gridViewHolder, view);
+            }
+        });
+
+        vh.mItemBridgeAdapter.setAdapterListener(new ItemBridgeAdapter.AdapterListener() {
+            @Override
+            public void onCreate(final ItemBridgeAdapter.ViewHolder itemViewHolder) {
+                // Only when having an OnItemClickListner, we attach the OnClickListener.
+                if (getOnItemClickedListener() != null || getOnItemViewClickedListener() != null) {
+                    final View itemView = itemViewHolder.getViewHolder().view;
+                    itemView.setOnClickListener(new View.OnClickListener() {
+                        @Override
+                        public void onClick(View view) {
+                            if (getOnItemClickedListener() != null) {
+                                // Row is always null
+                                getOnItemClickedListener().onItemClicked(itemViewHolder.mItem,
+                                        null);
+                            }
+                            if (getOnItemViewClickedListener() != null) {
+                                // Row is always null
+                                getOnItemViewClickedListener().onItemClicked(
+                                        itemViewHolder.mHolder, itemViewHolder.mItem, null, null);
+                            }
+                        }
+                    });
+                }
+            }
+
+            @Override
+            public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) {
+                viewHolder.itemView.setActivated(true);
+            }
+        });
+    }
+
+    @Override
+    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+        if (DEBUG) Log.v(TAG, "onBindViewHolder " + item);
+        ViewHolder vh = (ViewHolder) viewHolder;
+        vh.mItemBridgeAdapter.setAdapter((ObjectAdapter) item);
+        vh.getGridView().setAdapter(vh.mItemBridgeAdapter);
+    }
+
+    @Override
+    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+        if (DEBUG) Log.v(TAG, "onUnbindViewHolder");
+        ViewHolder vh = (ViewHolder) viewHolder;
+        vh.mItemBridgeAdapter.setAdapter(null);
+        vh.getGridView().setAdapter(null);
+    }
+
+    /**
+     * Sets the item selected listener.
+     * Since this is a grid the row parameter is always null.
+     * @deprecated Use {@link #setOnItemViewSelectedListener(OnItemViewSelectedListener)}
+     */
+    public final void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mOnItemSelectedListener = listener;
+    }
+
+    /**
+     * Returns the item selected listener.
+     * @deprecated Use {@link #getOnItemViewSelectedListener()}
+     */
+    public final OnItemSelectedListener getOnItemSelectedListener() {
+        return mOnItemSelectedListener;
+    }
+
+    /**
+     * Sets the item selected listener.
+     * Since this is a grid the row parameter is always null.
+     */
+    public final void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+        mOnItemViewSelectedListener = listener;
+    }
+
+    /**
+     * Returns the item selected listener.
+     */
+    public final OnItemViewSelectedListener getOnItemViewSelectedListener() {
+        return mOnItemViewSelectedListener;
+    }
+
+    /**
+     * Sets the item clicked listener.
+     * OnItemClickedListener will override {@link View.OnClickListener} that
+     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
+     * So in general, developer should choose one of the listeners but not both.
+     * @deprecated Use {@link #setOnItemViewClickedListener(OnItemViewClickedListener)}
+     */
+    public final void setOnItemClickedListener(OnItemClickedListener listener) {
+        mOnItemClickedListener = listener;
+    }
+
+    /**
+     * Sets the item clicked listener.
+     * OnItemViewClickedListener will override {@link View.OnClickListener} that
+     * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
+     * So in general, developer should choose one of the listeners but not both.
+     */
+    public final void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+        mOnItemViewClickedListener = listener;
+    }
+
+    /**
+     * Returns the item clicked listener.
+     * @deprecated Use {@link #getOnItemViewClickedListener()}
+     */
+    public final OnItemClickedListener getOnItemClickedListener() {
+        return mOnItemClickedListener;
+    }
+
+    /**
+     * Returns the item clicked listener.
+     */
+    public final OnItemViewClickedListener getOnItemViewClickedListener() {
+        return mOnItemViewClickedListener;
+    }
+
+    private void selectChildView(ViewHolder vh, View view) {
+        if (getOnItemSelectedListener() != null) {
+            ItemBridgeAdapter.ViewHolder ibh = (view == null) ? null :
+                    (ItemBridgeAdapter.ViewHolder) vh.getGridView().getChildViewHolder(view);
+            if (ibh == null) {
+                getOnItemSelectedListener().onItemSelected(null, null);
+            } else {
+                getOnItemSelectedListener().onItemSelected(ibh.mItem, null);
+            }
+        }
+        if (getOnItemViewSelectedListener() != null) {
+            ItemBridgeAdapter.ViewHolder ibh = (view == null) ? null :
+                    (ItemBridgeAdapter.ViewHolder) vh.getGridView().getChildViewHolder(view);
+            if (ibh == null) {
+                getOnItemViewSelectedListener().onItemSelected(null, null, null, null);
+            } else {
+                getOnItemViewSelectedListener().onItemSelected(ibh.mHolder, ibh.mItem, null, null);
+            }
+        }
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridView.java b/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridView.java
new file mode 100644
index 0000000..e349354
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/VerticalGridView.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.v17.leanback.R;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+
+/**
+ * A view that shows items in a vertically scrolling list. The items come from
+ * the {@link RecyclerView.Adapter} associated with this view.
+ */
+public class VerticalGridView extends BaseGridView {
+
+    public VerticalGridView(Context context) {
+        this(context, null);
+    }
+
+    public VerticalGridView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public VerticalGridView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mLayoutManager.setOrientation(RecyclerView.VERTICAL);
+        initAttributes(context, attrs);
+    }
+
+    protected void initAttributes(Context context, AttributeSet attrs) {
+        initBaseGridViewAttributes(context, attrs);
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbVerticalGridView);
+        setColumnWidth(a);
+        setNumColumns(a.getInt(R.styleable.lbVerticalGridView_numberOfColumns, 1));
+        a.recycle();
+    }
+
+    void setColumnWidth(TypedArray array) {
+        TypedValue typedValue = array.peekValue(R.styleable.lbVerticalGridView_columnWidth);
+        int size;
+        if (typedValue != null && typedValue.type == TypedValue.TYPE_DIMENSION) {
+            size = array.getDimensionPixelSize(R.styleable.lbVerticalGridView_columnWidth, 0);
+        } else {
+            size = array.getInt(R.styleable.lbVerticalGridView_columnWidth, 0);
+        }
+        setColumnWidth(size);
+    }
+
+    /**
+     * Set the number of columns.  Defaults to one.
+     */
+    public void setNumColumns(int numColumns) {
+        mLayoutManager.setNumRows(numColumns);
+        requestLayout();
+    }
+
+    /**
+     * Set the column width.
+     *
+     * @param width May be WRAP_CONTENT, or a size in pixels. If zero,
+     * column width will be fixed based on number of columns and view width.
+     */
+    public void setColumnWidth(int width) {
+        mLayoutManager.setRowHeight(width);
+        requestLayout();
+    }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java b/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
new file mode 100644
index 0000000..79a2c1a
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/WindowAlignment.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.support.v17.leanback.widget;
+
+import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_LOW_EDGE;
+import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_HIGH_EDGE;
+import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_BOTH_EDGE;
+import static android.support.v17.leanback.widget.BaseGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED;
+
+import static android.support.v7.widget.RecyclerView.HORIZONTAL;
+
+/**
+ * Maintains Window Alignment information of two axis.
+ */
+class WindowAlignment {
+
+    /**
+     * Maintains alignment information in one direction.
+     */
+    public static class Axis {
+        /**
+         * mScrollCenter is used to calculate dynamic transformation based on how far a view
+         * is from the mScrollCenter. For example, the views with center close to mScrollCenter
+         * will be scaled up.
+         */
+        private float mScrollCenter;
+        /** 
+         * Right or bottom edge of last child. 
+         */
+        private int mMaxEdge;
+        /** 
+         * Left or top edge of first child, typically should be zero.
+         */
+        private int mMinEdge;
+        /**
+         * Max Scroll value
+         */
+        private int mMaxScroll;
+        /**
+         * Min Scroll value
+         */
+        private int mMinScroll;
+
+        private int mWindowAlignment = WINDOW_ALIGN_BOTH_EDGE;
+
+        private int mWindowAlignmentOffset = 0;
+
+        private float mWindowAlignmentOffsetPercent = 50f;
+
+        private int mSize;
+
+        private int mPaddingLow;
+
+        private int mPaddingHigh;
+
+        private String mName; // for debugging
+
+        public Axis(String name) {
+            reset();
+            mName = name;
+        }
+
+        final public int getWindowAlignment() {
+            return mWindowAlignment;
+        }
+
+        final public void setWindowAlignment(int windowAlignment) {
+            mWindowAlignment = windowAlignment;
+        }
+
+        final public int getWindowAlignmentOffset() {
+            return mWindowAlignmentOffset;
+        }
+
+        final public void setWindowAlignmentOffset(int offset) {
+            mWindowAlignmentOffset = offset;
+        }
+
+        final public void setWindowAlignmentOffsetPercent(float percent) {
+            if ((percent < 0 || percent > 100)
+                    && percent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
+                throw new IllegalArgumentException();
+            }
+            mWindowAlignmentOffsetPercent = percent;
+        }
+
+        final public float getWindowAlignmentOffsetPercent() {
+            return mWindowAlignmentOffsetPercent;
+        }
+
+        final public int getScrollCenter() {
+            return (int) mScrollCenter;
+        }
+
+        /** set minEdge,  Integer.MIN_VALUE means unknown*/
+        final public void setMinEdge(int minEdge) {
+            mMinEdge = minEdge;
+        }
+
+        final public int getMinEdge() {
+            return mMinEdge;
+        }
+
+        /** set minScroll,  Integer.MIN_VALUE means unknown*/
+        final public void setMinScroll(int minScroll) {
+            mMinScroll = minScroll;
+        }
+
+        final public int getMinScroll() {
+            return mMinScroll;
+        }
+
+        final public void invalidateScrollMin() {
+            mMinEdge = Integer.MIN_VALUE;
+            mMinScroll = Integer.MIN_VALUE;
+        }
+
+        /** update max edge,  Integer.MAX_VALUE means unknown*/
+        final public void setMaxEdge(int maxEdge) {
+            mMaxEdge = maxEdge;
+        }
+
+        final public int getMaxEdge() {
+            return mMaxEdge;
+        }
+
+        /** update max scroll,  Integer.MAX_VALUE means unknown*/
+        final public void setMaxScroll(int maxScroll) {
+            mMaxScroll = maxScroll;
+        }
+
+        final public int getMaxScroll() {
+            return mMaxScroll;
+        }
+
+        final public void invalidateScrollMax() {
+            mMaxEdge = Integer.MAX_VALUE;
+            mMaxScroll = Integer.MAX_VALUE;
+        }
+
+        final public float updateScrollCenter(float scrollTarget) {
+            mScrollCenter = scrollTarget;
+            return scrollTarget;
+        }
+
+        private void reset() {
+            mScrollCenter = Integer.MIN_VALUE;
+            mMinEdge = Integer.MIN_VALUE;
+            mMaxEdge = Integer.MAX_VALUE;
+        }
+
+        final public boolean isMinUnknown() {
+            return mMinEdge == Integer.MIN_VALUE;
+        }
+
+        final public boolean isMaxUnknown() {
+            return mMaxEdge == Integer.MAX_VALUE;
+        }
+
+        final public void setSize(int size) {
+            mSize = size;
+        }
+
+        final public int getSize() {
+            return mSize;
+        }
+
+        final public void setPadding(int paddingLow, int paddingHigh) {
+            mPaddingLow = paddingLow;
+            mPaddingHigh = paddingHigh;
+        }
+
+        final public int getPaddingLow() {
+            return mPaddingLow;
+        }
+
+        final public int getPaddingHigh() {
+            return mPaddingHigh;
+        }
+
+        final public int getClientSize() {
+            return mSize - mPaddingLow - mPaddingHigh;
+        }
+
+        final public int getSystemScrollPos(boolean isFirst, boolean isLast) {
+            return getSystemScrollPos((int) mScrollCenter, isFirst, isLast);
+        }
+
+        final public int getSystemScrollPos(int scrollCenter, boolean isFirst, boolean isLast) {
+            int middlePosition;
+            if (mWindowAlignmentOffset >= 0) {
+                middlePosition = mWindowAlignmentOffset - mPaddingLow;
+            } else {
+                middlePosition = mSize + mWindowAlignmentOffset - mPaddingLow;
+            }
+            if (mWindowAlignmentOffsetPercent != WINDOW_ALIGN_OFFSET_PERCENT_DISABLED) {
+                middlePosition += (int) (mSize * mWindowAlignmentOffsetPercent / 100);
+            }
+            int clientSize = getClientSize();
+            int afterMiddlePosition = clientSize - middlePosition;
+            boolean isMinUnknown = isMinUnknown();
+            boolean isMaxUnknown = isMaxUnknown();
+            if (!isMinUnknown && !isMaxUnknown &&
+                    (mWindowAlignment & WINDOW_ALIGN_BOTH_EDGE) == WINDOW_ALIGN_BOTH_EDGE) {
+                if (mMaxEdge - mMinEdge <= clientSize) {
+                    // total children size is less than view port and we want to align
+                    // both edge:  align first child to left edge of view port
+                    return mMinEdge - mPaddingLow;
+                }
+            }
+            if (!isMinUnknown) {
+                if ((mWindowAlignment & WINDOW_ALIGN_LOW_EDGE) != 0 &&
+                        (isFirst || scrollCenter - mMinEdge <= middlePosition)) {
+                    // scroll center is within half of view port size: align the left edge
+                    // of first child to the left edge of view port
+                    return mMinEdge - mPaddingLow;
+                }
+            }
+            if (!isMaxUnknown) {
+                if ((mWindowAlignment & WINDOW_ALIGN_HIGH_EDGE) != 0 &&
+                        (isLast || mMaxEdge - scrollCenter <= afterMiddlePosition)) {
+                    // scroll center is very close to the right edge of view port : align the
+                    // right edge of last children (plus expanded size) to view port's right
+                    return mMaxEdge -mPaddingLow - (clientSize);
+                }
+            }
+            // else put scroll center in middle of view port
+            return scrollCenter - middlePosition - mPaddingLow;
+        }
+
+        @Override
+        public String toString() {
+            return "center: " + mScrollCenter + " min:" + mMinEdge +
+                    " max:" + mMaxEdge;
+        }
+
+    }
+
+    private int mOrientation = HORIZONTAL;
+
+    final public Axis vertical = new Axis("vertical");
+
+    final public Axis horizontal = new Axis("horizontal");
+
+    private Axis mMainAxis = horizontal;
+
+    private Axis mSecondAxis = vertical;
+
+    final public Axis mainAxis() {
+        return mMainAxis;
+    }
+
+    final public Axis secondAxis() {
+        return mSecondAxis;
+    }
+
+    final public void setOrientation(int orientation) {
+        mOrientation = orientation;
+        if (mOrientation == HORIZONTAL) {
+            mMainAxis = horizontal;
+            mSecondAxis = vertical;
+        } else {
+            mMainAxis = vertical;
+            mSecondAxis = horizontal;
+        }
+    }
+
+    final public int getOrientation() {
+        return mOrientation;
+    }
+
+    final public void reset() {
+        mainAxis().reset();
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuffer().append("horizontal=")
+                .append(horizontal.toString())
+                .append("vertical=")
+                .append(vertical.toString())
+                .toString();
+    }
+
+}
diff --git a/v4/Android.mk b/v4/Android.mk
index 0cdb05d..dbebab4 100644
--- a/v4/Android.mk
+++ b/v4/Android.mk
@@ -14,11 +14,21 @@
 
 LOCAL_PATH := $(call my-dir)
 
+# A helper sub-library that makes direct use of Donut APIs.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v4-donut
+LOCAL_SDK_VERSION := 4
+LOCAL_SRC_FILES := $(call all-java-files-under, donut)
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------------------------------------------------------------------
+
 # A helper sub-library that makes direct use of Eclair APIs.
 include $(CLEAR_VARS)
 LOCAL_MODULE := android-support-v4-eclair
 LOCAL_SDK_VERSION := 5
 LOCAL_SRC_FILES := $(call all-java-files-under, eclair)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-donut
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # -----------------------------------------------------------------------
@@ -63,12 +73,22 @@
 
 # -----------------------------------------------------------------------
 
+# A helper sub-library that makes direct use of Honeycomb MR1 APIs.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v4-honeycomb-mr1
+LOCAL_SDK_VERSION := 12
+LOCAL_SRC_FILES := $(call all-java-files-under, honeycomb_mr1)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-honeycomb
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------------------------------------------------------------------
+
 # A helper sub-library that makes direct use of Honeycomb MR2 APIs.
 include $(CLEAR_VARS)
 LOCAL_MODULE := android-support-v4-honeycomb-mr2
 LOCAL_SDK_VERSION := 13
 LOCAL_SRC_FILES := $(call all-java-files-under, honeycomb_mr2)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-honeycomb
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-honeycomb-mr1
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # -----------------------------------------------------------------------
@@ -133,10 +153,35 @@
 
 # -----------------------------------------------------------------------
 
+# A helper sub-library that makes direct use of the upcoming API.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v4-api20
+LOCAL_SDK_VERSION := 20
+LOCAL_SRC_FILES := $(call all-java-files-under, api20)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-kitkat
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------------------------------------------------------------------
+
+# A helper sub-library that makes direct use of the upcoming API.
+# TODO: Apply a real name and SDK version when available
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v4-api21
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, api21)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-api20
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# -----------------------------------------------------------------------
+
 # Here is the final static library that apps can link against.
 include $(CLEAR_VARS)
 LOCAL_MODULE := android-support-v4
 LOCAL_SDK_VERSION := 4
-LOCAL_SRC_FILES := $(call all-java-files-under, java)
-LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4-kitkat
+
+LOCAL_SRC_FILES := $(call all-java-files-under, java) \
+    $(call all-Iaidl-files-under, java)
+
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4-api21
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-annotations
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v4/AndroidManifest.xml b/v4/AndroidManifest.xml
new file mode 100644
index 0000000..08dbb61
--- /dev/null
+++ b/v4/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.support.v4">
+    <application />
+</manifest>
diff --git a/v4/api20/android/support/v4/app/NotificationCompatApi20.java b/v4/api20/android/support/v4/app/NotificationCompatApi20.java
new file mode 100644
index 0000000..40b1386
--- /dev/null
+++ b/v4/api20/android/support/v4/app/NotificationCompatApi20.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.widget.RemoteViews;
+
+import java.util.ArrayList;
+
+class NotificationCompatApi20 {
+    public static class Builder implements NotificationBuilderWithBuilderAccessor,
+            NotificationBuilderWithActions {
+        private Notification.Builder b;
+        private Bundle mExtras;
+
+        public Builder(Context context, Notification n,
+                CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
+                RemoteViews tickerView, int number,
+                PendingIntent contentIntent, PendingIntent fullScreenIntent, Bitmap largeIcon,
+                int progressMax, int progress, boolean progressIndeterminate,
+                boolean useChronometer, int priority, CharSequence subText, boolean localOnly,
+                ArrayList<String> people, Bundle extras, String groupKey, boolean groupSummary,
+                String sortKey) {
+            b = new Notification.Builder(context)
+                .setWhen(n.when)
+                .setSmallIcon(n.icon, n.iconLevel)
+                .setContent(n.contentView)
+                .setTicker(n.tickerText, tickerView)
+                .setSound(n.sound, n.audioStreamType)
+                .setVibrate(n.vibrate)
+                .setLights(n.ledARGB, n.ledOnMS, n.ledOffMS)
+                .setOngoing((n.flags & Notification.FLAG_ONGOING_EVENT) != 0)
+                .setOnlyAlertOnce((n.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)
+                .setAutoCancel((n.flags & Notification.FLAG_AUTO_CANCEL) != 0)
+                .setDefaults(n.defaults)
+                .setContentTitle(contentTitle)
+                .setContentText(contentText)
+                .setSubText(subText)
+                .setContentInfo(contentInfo)
+                .setContentIntent(contentIntent)
+                .setDeleteIntent(n.deleteIntent)
+                .setFullScreenIntent(fullScreenIntent,
+                        (n.flags & Notification.FLAG_HIGH_PRIORITY) != 0)
+                .setLargeIcon(largeIcon)
+                .setNumber(number)
+                .setUsesChronometer(useChronometer)
+                .setPriority(priority)
+                .setProgress(progressMax, progress, progressIndeterminate)
+                .setLocalOnly(localOnly)
+                .setGroup(groupKey)
+                .setGroupSummary(groupSummary)
+                .setSortKey(sortKey);
+            mExtras = new Bundle();
+            if (extras != null) {
+                mExtras.putAll(extras);
+            }
+            if (people != null && !people.isEmpty()) {
+                mExtras.putStringArray(Notification.EXTRA_PEOPLE,
+                        people.toArray(new String[people.size()]));
+            }
+        }
+
+        @Override
+        public void addAction(NotificationCompatBase.Action action) {
+            NotificationCompatApi20.addAction(b, action);
+        }
+
+        @Override
+        public Notification.Builder getBuilder() {
+            return b;
+        }
+
+        public Notification build() {
+            b.setExtras(mExtras);
+            return b.build();
+        }
+    }
+
+    public static void addAction(Notification.Builder b, NotificationCompatBase.Action action) {
+        Notification.Action.Builder actionBuilder = new Notification.Action.Builder(
+                action.getIcon(), action.getTitle(), action.getActionIntent());
+        if (action.getRemoteInputs() != null) {
+            for (RemoteInput remoteInput : RemoteInputCompatApi20.fromCompat(
+                    action.getRemoteInputs())) {
+                actionBuilder.addRemoteInput(remoteInput);
+            }
+        }
+        if (action.getExtras() != null) {
+            actionBuilder.addExtras(action.getExtras());
+        }
+        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);
+        return actionFactory.build(action.icon, action.title, action.actionIntent,
+                action.getExtras(), remoteInputs);
+    }
+
+    private static Notification.Action getActionFromActionCompat(
+            NotificationCompatBase.Action actionCompat) {
+        Notification.Action.Builder actionBuilder = new Notification.Action.Builder(
+                actionCompat.getIcon(), actionCompat.getTitle(), actionCompat.getActionIntent())
+                .addExtras(actionCompat.getExtras());
+        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;
+    }
+
+    public static boolean getLocalOnly(Notification notif) {
+        return (notif.flags & Notification.FLAG_LOCAL_ONLY) != 0;
+    }
+
+    public static String getGroup(Notification notif) {
+        return notif.getGroup();
+    }
+
+    public static boolean isGroupSummary(Notification notif) {
+        return (notif.flags & Notification.FLAG_GROUP_SUMMARY) != 0;
+    }
+
+    public static String getSortKey(Notification notif) {
+        return notif.getSortKey();
+    }
+}
diff --git a/v4/api20/android/support/v4/app/RemoteInputCompatApi20.java b/v4/api20/android/support/v4/app/RemoteInputCompatApi20.java
new file mode 100644
index 0000000..5f302f1
--- /dev/null
+++ b/v4/api20/android/support/v4/app/RemoteInputCompatApi20.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.RemoteInput;
+import android.content.Intent;
+import android.os.Bundle;
+
+class RemoteInputCompatApi20 {
+    static RemoteInputCompatBase.RemoteInput[] toCompat(RemoteInput[] srcArray,
+            RemoteInputCompatBase.RemoteInput.Factory factory) {
+        if (srcArray == null) {
+            return null;
+        }
+        RemoteInputCompatBase.RemoteInput[] result = factory.newArray(srcArray.length);
+        for (int i = 0; i < srcArray.length; i++) {
+            RemoteInput src = srcArray[i];
+            result[i] = factory.build(src.getResultKey(), src.getLabel(), src.getChoices(),
+                    src.getAllowFreeFormInput(), src.getExtras());
+        }
+        return result;
+    }
+
+    static RemoteInput[] fromCompat(RemoteInputCompatBase.RemoteInput[] srcArray) {
+        if (srcArray == null) {
+            return null;
+        }
+        RemoteInput[] result = new RemoteInput[srcArray.length];
+        for (int i = 0; i < srcArray.length; i++) {
+            RemoteInputCompatBase.RemoteInput src = srcArray[i];
+            result[i] = new RemoteInput.Builder(src.getResultKey())
+                    .setLabel(src.getLabel())
+                    .setChoices(src.getChoices())
+                    .setAllowFreeFormInput(src.getAllowFreeFormInput())
+                    .addExtras(src.getExtras())
+                    .build();
+        }
+        return result;
+    }
+
+    static Bundle getResultsFromIntent(Intent intent) {
+        return RemoteInput.getResultsFromIntent(intent);
+    }
+
+    static void addResultsToIntent(RemoteInputCompatBase.RemoteInput[] remoteInputs,
+            Intent intent, Bundle results) {
+        RemoteInput.addResultsToIntent(fromCompat(remoteInputs), intent, results);
+    }
+}
diff --git a/v4/api21/android/support/v4/app/ActivityCompat21.java b/v4/api21/android/support/v4/app/ActivityCompat21.java
new file mode 100644
index 0000000..0b86b49
--- /dev/null
+++ b/v4/api21/android/support/v4/app/ActivityCompat21.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.Activity;
+import android.app.SharedElementListener;
+import android.view.View;
+
+import java.lang.Override;
+import java.lang.String;
+import java.util.List;
+import java.util.Map;
+
+class ActivityCompat21 {
+
+    public static void finishAfterTransition(Activity activity) {
+        activity.finishAfterTransition();
+    }
+
+    public static void setEnterSharedElementListener(Activity activity,
+            SharedElementListener21 listener) {
+        activity.setEnterSharedElementListener(createListener(listener));
+    }
+
+    public static void setExitSharedElementListener(Activity activity,
+            SharedElementListener21 listener) {
+        activity.setExitSharedElementListener(createListener(listener));
+    }
+
+    public static void postponeEnterTransition(Activity activity) {
+        activity.postponeEnterTransition();
+    }
+
+    public static void startPostponedEnterTransition(Activity activity) {
+        activity.startPostponedEnterTransition();
+    }
+
+    public abstract static class SharedElementListener21 {
+        public abstract void setSharedElementStart(List<String> sharedElementNames,
+                List<View> sharedElements, List<View> sharedElementSnapshots);
+
+        public abstract void setSharedElementEnd(List<String> sharedElementNames,
+                List<View> sharedElements, List<View> sharedElementSnapshots);
+
+        public abstract void handleRejectedSharedElements(List<View> rejectedSharedElements);
+
+        public abstract void remapSharedElements(List<String> names,
+                Map<String, View> sharedElements);
+    }
+
+    private static SharedElementListener createListener(SharedElementListener21 listener) {
+        SharedElementListener newListener = null;
+        if (listener != null) {
+            newListener = new SharedElementListenerImpl(listener);
+        }
+        return newListener;
+    }
+
+    private static class SharedElementListenerImpl extends SharedElementListener {
+        private SharedElementListener21 mListener;
+
+        public SharedElementListenerImpl(SharedElementListener21 listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void setSharedElementStart(List<String> sharedElementNames,
+                List<View> sharedElements, List<View> sharedElementSnapshots) {
+            mListener.setSharedElementStart(sharedElementNames, sharedElements,
+                    sharedElementSnapshots);
+        }
+
+        @Override
+        public void setSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements,
+                List<View> sharedElementSnapshots) {
+            mListener.setSharedElementEnd(sharedElementNames, sharedElements,
+                    sharedElementSnapshots);
+        }
+
+        @Override
+        public void handleRejectedSharedElements(List<View> rejectedSharedElements) {
+            mListener.handleRejectedSharedElements(rejectedSharedElements);
+        }
+
+        @Override
+        public void remapSharedElements(List<String> names, Map<String, View> sharedElements) {
+            mListener.remapSharedElements(names, sharedElements);
+        }
+    }
+}
diff --git a/v4/api21/android/support/v4/app/ActivityOptionsCompat21.java b/v4/api21/android/support/v4/app/ActivityOptionsCompat21.java
new file mode 100644
index 0000000..d629e65
--- /dev/null
+++ b/v4/api21/android/support/v4/app/ActivityOptionsCompat21.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.ActivityOptions;
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Pair;
+import android.view.View;
+
+class ActivityOptionsCompat21 {
+
+    private final ActivityOptions mActivityOptions;
+
+    public static ActivityOptionsCompat21 makeSceneTransitionAnimation(Activity activity,
+            View sharedElement, String sharedElementName) {
+        return new ActivityOptionsCompat21(
+                ActivityOptions.makeSceneTransitionAnimation(activity, sharedElement,
+                        sharedElementName));
+    }
+
+    public static ActivityOptionsCompat21 makeSceneTransitionAnimation(Activity activity,
+            View[] sharedElements, String[] sharedElementNames) {
+        Pair[] pairs = null;
+        if (sharedElements != null) {
+            pairs = new Pair[sharedElements.length];
+            for (int i = 0; i < pairs.length; i++) {
+                pairs[i] = Pair.create(sharedElements[i], sharedElementNames[i]);
+            }
+        }
+        return new ActivityOptionsCompat21(
+                ActivityOptions.makeSceneTransitionAnimation(activity, pairs));
+    }
+
+    private ActivityOptionsCompat21(ActivityOptions activityOptions) {
+        mActivityOptions = activityOptions;
+    }
+
+    public Bundle toBundle() {
+        return mActivityOptions.toBundle();
+    }
+
+    public void update(ActivityOptionsCompat21 otherOptions) {
+        mActivityOptions.update(otherOptions.mActivityOptions);
+    }
+}
diff --git a/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java b/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java
new file mode 100644
index 0000000..5b88840
--- /dev/null
+++ b/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.Activity;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.lang.Override;
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.Collection;
+import android.transition.Transition;
+import android.transition.TransitionSet;
+import android.transition.TransitionManager;
+import android.graphics.Rect;
+import android.util.ArrayMap;
+import android.transition.TransitionInflater;
+
+class FragmentTransitionCompat21 {
+
+    public static String getTransitionName(View view) {
+        return view.getTransitionName();
+    }
+
+    public static Object beginTransition(Activity activity, int transitionId, ViewGroup sceneRoot,
+            ArrayList<View> hiddenFragmentViews, ArrayList<String> sourceNames,
+            ArrayList<String> targetNames) {
+        if (transitionId <= 0 || sceneRoot == null) {
+            return null;
+        }
+        TransitionState state = new TransitionState();
+        // get Transition scene root and create Transitions
+        state.sceneRoot = sceneRoot;
+        captureTransitioningViews(state.transitioningViews, state.sceneRoot);
+
+        state.exitTransition = TransitionInflater.from(activity)
+                .inflateTransition(transitionId);
+        state.sharedElementTransition = TransitionInflater.from(activity)
+                .inflateTransition(transitionId);
+        state.enterTransition = TransitionInflater.from(activity)
+                .inflateTransition(transitionId);
+        // Adding a non-existent target view makes sure that the transitions don't target
+        // any views by default. They'll only target the views we tell add. If we don't
+        // add any, then no views will be targeted.
+        View nonExistentView = new View(activity);
+        state.enterTransition.addTarget(nonExistentView);
+        state.exitTransition.addTarget(nonExistentView);
+        state.sharedElementTransition.addTarget(nonExistentView);
+
+        setSharedElementEpicenter(state.enterTransition, state);
+
+        state.excludingTransition = new TransitionSet()
+                .addTransition(state.exitTransition)
+                .addTransition(state.enterTransition);
+        state.excludingTransition.addListener(new ResetNamesListener(state.namedViews));
+
+        if (sourceNames != null) {
+            // Map shared elements.
+            findNamedViews(state.namedViews, state.sceneRoot);
+            state.namedViews.retainAll(sourceNames);
+            View epicenterView = state.namedViews.get(sourceNames.get(0));
+            if (epicenterView != null) {
+                // The epicenter is only the first shared element.
+                setEpicenter(state.exitTransition, epicenterView);
+                setEpicenter(state.sharedElementTransition, epicenterView);
+            }
+            state.transitioningViews.removeAll(state.namedViews.values());
+            state.excludingTransition.addTransition(state.sharedElementTransition);
+            addTransitioningViews(state.sharedElementTransition, state.namedViews.values());
+        }
+
+        // Adds the (maybe) exiting views, not including the shared element.
+        // If some stay, that's ok.
+        addTransitioningViews(state.exitTransition, state.transitioningViews);
+
+        setNameOverrides(state, sourceNames, targetNames);
+
+        // Don't include any subtree in the views that are hidden when capturing the
+        // view hierarchy transitions. They should be as if not there.
+        excludeHiddenFragments(activity, hiddenFragmentViews, state, true);
+
+        TransitionManager.beginDelayedTransition(state.sceneRoot, state.excludingTransition);
+        return state;
+    }
+
+    public static void updateTransitionEndState(Activity activity,
+            ArrayList<View> shownFragmentViews, ArrayList<View> hiddenFragmentViews,
+            Object stateObj, ArrayList<String> names) {
+        if (!(stateObj instanceof TransitionState)) {
+            return;
+        }
+        TransitionState state = (TransitionState) stateObj;
+        // Find all views that are entering.
+        ArrayList<View> enteringViews = new ArrayList<View>();
+        captureTransitioningViews(enteringViews, state.sceneRoot);
+        enteringViews.removeAll(state.transitioningViews);
+
+        state.namedViews.clear();
+
+        if (names != null) {
+            // find all shared elements.
+            findNamedViews(state.namedViews, state.sceneRoot);
+            state.namedViews.retainAll(names);
+            if (!state.namedViews.isEmpty()) {
+                enteringViews.removeAll(state.namedViews.values());
+                addTransitioningViews(state.sharedElementTransition, state.namedViews.values());
+                // now we know the epicenter of the entering transition.
+                state.enteringEpicenterView = state.namedViews.get(names.get(0));
+
+                // Change the names of the shared elements temporarily so that the shared element
+                // names can match.
+                int count = state.nameOverrides.size();
+                for (int i = 0; i < count; i++) {
+                    String toName = state.nameOverrides.valueAt(i);
+                    View view = state.namedViews.get(toName);
+                    if (view != null) {
+                        view.setTransitionName(state.nameOverrides.keyAt(i));
+                    }
+                }
+            }
+        }
+
+        // Add all entering views to the enter transition.
+        addTransitioningViews(state.enterTransition, enteringViews);
+
+        // Don't allow capturing state for the newly-hidden fragments.
+        excludeHiddenFragments(activity, hiddenFragmentViews, state, false);
+
+        // Allow capturing state for the newly-shown fragments
+        includeVisibleFragments(shownFragmentViews, state.excludingTransition);
+    }
+
+    private static void addTransitioningViews(Transition transition, Collection<View> views) {
+        for (View view : views) {
+            transition.addTarget(view);
+        }
+    }
+
+    private static void excludeHiddenFragments(Activity activity,
+            ArrayList<View> hiddenFragmentViews, TransitionState state, boolean forceExclude) {
+        for (int i = hiddenFragmentViews.size() - 1; i >= 0; i--) {
+            View view = hiddenFragmentViews.get(i);
+            if (forceExclude || !state.hiddenViews.contains(view)) {
+                state.excludingTransition.excludeTarget(view, true);
+                state.hiddenViews.add(view);
+            }
+        }
+        if (forceExclude && state.hiddenViews.isEmpty()) {
+            state.excludingTransition.excludeTarget(new View(activity), true);
+        }
+    }
+
+    private static void includeVisibleFragments(ArrayList<View> shownFragmentViews,
+            Transition transition) {
+        for (int i = shownFragmentViews.size() - 1; i >= 0; i--) {
+            View view = shownFragmentViews.get(i);
+            transition.excludeTarget(view, false);
+        }
+    }
+
+    private static void setEpicenter(Transition transition, View view) {
+        final Rect epicenter = getBoundsOnScreen(view);
+
+        transition.setEpicenterCallback(new Transition.EpicenterCallback() {
+            @Override
+            public Rect onGetEpicenter(Transition transition) {
+                return epicenter;
+            }
+        });
+    }
+
+    private static void setSharedElementEpicenter(Transition transition,
+            final TransitionState state) {
+        transition.setEpicenterCallback(new Transition.EpicenterCallback() {
+            private Rect mEpicenter;
+
+            @Override
+            public Rect onGetEpicenter(Transition transition) {
+                if (mEpicenter == null && state.enteringEpicenterView != null) {
+                    mEpicenter = getBoundsOnScreen(state.enteringEpicenterView);
+                }
+                return mEpicenter;
+            }
+        });
+    }
+
+    private static Rect getBoundsOnScreen(View view) {
+        Rect epicenter = new Rect();
+        int[] loc = new int[2];
+        view.getLocationOnScreen(loc);
+        // not as good as View.getBoundsOnScreen, but that's not public
+        epicenter.set(loc[0], loc[1], loc[0] + view.getWidth(), loc[1] + view.getHeight());
+        return epicenter;
+    }
+
+    private static void setNameOverride(ArrayMap<String, String> overrides, String source,
+            String target) {
+        for (int index = 0; index < overrides.size(); index++) {
+            if (source.equals(overrides.valueAt(index))) {
+                overrides.setValueAt(index, target);
+                return;
+            }
+        }
+        overrides.put(source, target);
+    }
+
+    public static void setNameOverrides(Object stateObj, ArrayList<String> sourceNames,
+            ArrayList<String> targetNames) {
+        if (sourceNames != null && stateObj instanceof TransitionState) {
+            TransitionState state = (TransitionState) stateObj;
+            for (int i = 0; i < sourceNames.size(); i++) {
+                String source = sourceNames.get(i);
+                String target = targetNames.get(i);
+                setNameOverride(state.nameOverrides, source, target);
+            }
+        }
+    }
+
+    private static void captureTransitioningViews(ArrayList<View> transitioningViews, View view) {
+        if (view.getVisibility() == View.VISIBLE) {
+            if (view instanceof ViewGroup) {
+                ViewGroup viewGroup = (ViewGroup) view;
+                if (viewGroup.isTransitionGroup()) {
+                    transitioningViews.add(viewGroup);
+                } else {
+                    int count = viewGroup.getChildCount();
+                    for (int i = 0; i < count; i++) {
+                        View child = viewGroup.getChildAt(i);
+                        captureTransitioningViews(transitioningViews, child);
+                    }
+                }
+            } else {
+                transitioningViews.add(view);
+            }
+        }
+    }
+
+    private static void findNamedViews(ArrayMap<String, View> namedViews, View view) {
+        if (view.getVisibility() == View.VISIBLE) {
+            String transitionName = view.getTransitionName();
+            if (transitionName != null) {
+                namedViews.put(transitionName, view);
+            }
+            if (view instanceof ViewGroup) {
+                ViewGroup viewGroup = (ViewGroup) view;
+                int count = viewGroup.getChildCount();
+                for (int i = 0; i < count; i++) {
+                    View child = viewGroup.getChildAt(i);
+                    findNamedViews(namedViews, child);
+                }
+            }
+        }
+    }
+
+    private static class TransitionState {
+        public ArrayList<View> hiddenViews = new ArrayList<View>();
+        public ArrayList<View> transitioningViews = new ArrayList<View>();
+        public ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
+        public Transition exitTransition;
+        public Transition sharedElementTransition;
+        public Transition enterTransition;
+        public TransitionSet excludingTransition;
+        public ViewGroup sceneRoot;
+        public View enteringEpicenterView;
+        public ArrayMap<String, String> nameOverrides = new ArrayMap<String, String>();
+    }
+
+    private static class ResetNamesListener implements Transition.TransitionListener {
+        private ArrayMap<String, View> mRenamedViews;
+
+        public ResetNamesListener(ArrayMap<String, View> renamedViews) {
+            mRenamedViews = renamedViews;
+        }
+
+        @Override
+        public void onTransitionStart(Transition transition) {
+            transition.removeListener(this);
+            int count = mRenamedViews.size();
+            for (int i = 0; i < count; i++) {
+                View view = mRenamedViews.valueAt(i);
+                String name = mRenamedViews.keyAt(i);
+                view.setTransitionName(name);
+            }
+        }
+
+        @Override
+        public void onTransitionCancel(Transition transition) {
+        }
+
+        @Override
+        public void onTransitionEnd(Transition transition) {
+        }
+
+        @Override
+        public void onTransitionPause(Transition transition) {
+        }
+
+        @Override
+        public void onTransitionResume(Transition transition) {
+        }
+    }
+}
diff --git a/v4/api21/android/support/v4/app/NotificationCompatApi21.java b/v4/api21/android/support/v4/app/NotificationCompatApi21.java
new file mode 100644
index 0000000..cdeabb1
--- /dev/null
+++ b/v4/api21/android/support/v4/app/NotificationCompatApi21.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.widget.RemoteViews;
+
+import java.util.ArrayList;
+
+class NotificationCompatApi21 {
+
+    public static final String CATEGORY_CALL = Notification.CATEGORY_CALL;
+    public static final String CATEGORY_MESSAGE = Notification.CATEGORY_MESSAGE;
+    public static final String CATEGORY_EMAIL = Notification.CATEGORY_EMAIL;
+    public static final String CATEGORY_EVENT = Notification.CATEGORY_EVENT;
+    public static final String CATEGORY_PROMO = Notification.CATEGORY_PROMO;
+    public static final String CATEGORY_ALARM = Notification.CATEGORY_ALARM;
+    public static final String CATEGORY_PROGRESS = Notification.CATEGORY_PROGRESS;
+    public static final String CATEGORY_SOCIAL = Notification.CATEGORY_SOCIAL;
+    public static final String CATEGORY_ERROR = Notification.CATEGORY_ERROR;
+    public static final String CATEGORY_TRANSPORT = Notification.CATEGORY_TRANSPORT;
+    public static final String CATEGORY_SYSTEM = Notification.CATEGORY_SYSTEM;
+    public static final String CATEGORY_SERVICE = Notification.CATEGORY_SERVICE;
+    public static final String CATEGORY_RECOMMENDATION = Notification.CATEGORY_RECOMMENDATION;
+    public static final String CATEGORY_STATUS = Notification.CATEGORY_STATUS;
+
+    public static class Builder implements NotificationBuilderWithBuilderAccessor,
+            NotificationBuilderWithActions {
+        private Notification.Builder b;
+
+        public Builder(Context context, Notification n,
+                CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
+                RemoteViews tickerView, int number,
+                PendingIntent contentIntent, PendingIntent fullScreenIntent, Bitmap largeIcon,
+                int progressMax, int progress, boolean progressIndeterminate,
+                boolean useChronometer, int priority, CharSequence subText, boolean localOnly,
+                String category, ArrayList<String> people, Bundle extras, int color,
+                int visibility, Notification publicVersion, String groupKey, boolean groupSummary,
+                String sortKey) {
+            b = new Notification.Builder(context)
+                    .setWhen(n.when)
+                    .setSmallIcon(n.icon, n.iconLevel)
+                    .setContent(n.contentView)
+                    .setTicker(n.tickerText, tickerView)
+                    .setSound(n.sound, n.audioStreamType)
+                    .setVibrate(n.vibrate)
+                    .setLights(n.ledARGB, n.ledOnMS, n.ledOffMS)
+                    .setOngoing((n.flags & Notification.FLAG_ONGOING_EVENT) != 0)
+                    .setOnlyAlertOnce((n.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)
+                    .setAutoCancel((n.flags & Notification.FLAG_AUTO_CANCEL) != 0)
+                    .setDefaults(n.defaults)
+                    .setContentTitle(contentTitle)
+                    .setContentText(contentText)
+                    .setSubText(subText)
+                    .setContentInfo(contentInfo)
+                    .setContentIntent(contentIntent)
+                    .setDeleteIntent(n.deleteIntent)
+                    .setFullScreenIntent(fullScreenIntent,
+                            (n.flags & Notification.FLAG_HIGH_PRIORITY) != 0)
+                    .setLargeIcon(largeIcon)
+                    .setNumber(number)
+                    .setUsesChronometer(useChronometer)
+                    .setPriority(priority)
+                    .setProgress(progressMax, progress, progressIndeterminate)
+                    .setLocalOnly(localOnly)
+                    .setExtras(extras)
+                    .setGroup(groupKey)
+                    .setGroupSummary(groupSummary)
+                    .setSortKey(sortKey)
+                    .setCategory(category)
+                    .setColor(color)
+                    .setVisibility(visibility)
+                    .setPublicVersion(publicVersion);
+            for (String person: people) {
+                b.addPerson(person);
+            }
+        }
+
+        @Override
+        public void addAction(NotificationCompatBase.Action action) {
+            NotificationCompatApi20.addAction(b, action);
+        }
+
+        @Override
+        public Notification.Builder getBuilder() {
+            return b;
+        }
+
+        public Notification build() {
+            return b.build();
+        }
+    }
+
+    public static String getCategory(Notification notif) {
+        return notif.category;
+    }
+}
diff --git a/v4/api21/android/support/v4/content/ContextCompatApi21.java b/v4/api21/android/support/v4/content/ContextCompatApi21.java
new file mode 100644
index 0000000..cf93924
--- /dev/null
+++ b/v4/api21/android/support/v4/content/ContextCompatApi21.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.content;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import java.io.File;
+
+class ContextCompatApi21 {
+    public static Drawable getDrawable(Context context, int id) {
+        return context.getDrawable(id);
+    }
+
+    public static File getNoBackupFilesDir(Context context) {
+        return context.getNoBackupFilesDir();
+    }
+
+    public static File getCodeCacheDir(Context context) {
+        return context.getCodeCacheDir();
+    }
+}
diff --git a/v4/api21/android/support/v4/content/res/ResourcesCompatApi21.java b/v4/api21/android/support/v4/content/res/ResourcesCompatApi21.java
new file mode 100644
index 0000000..645b633
--- /dev/null
+++ b/v4/api21/android/support/v4/content/res/ResourcesCompatApi21.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.content.res;
+
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.graphics.drawable.Drawable;
+
+class ResourcesCompatApi21 {
+    public static Drawable getDrawable(Resources res, int id, Theme theme) {
+        return res.getDrawable(id, theme);
+    }
+}
diff --git a/v4/api21/android/support/v4/graphics/drawable/DrawableCompatL.java b/v4/api21/android/support/v4/graphics/drawable/DrawableCompatL.java
new file mode 100644
index 0000000..80e80e5
--- /dev/null
+++ b/v4/api21/android/support/v4/graphics/drawable/DrawableCompatL.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.graphics.drawable;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * Implementation of drawable compatibility that can call L APIs.
+ */
+class DrawableCompatL {
+
+    public static void setHotspot(Drawable drawable, float x, float y) {
+        drawable.setHotspot(x, y);
+    }
+
+}
diff --git a/v4/api21/android/support/v4/graphics/drawable/RoundedBitmapDrawable21.java b/v4/api21/android/support/v4/graphics/drawable/RoundedBitmapDrawable21.java
new file mode 100644
index 0000000..081feea
--- /dev/null
+++ b/v4/api21/android/support/v4/graphics/drawable/RoundedBitmapDrawable21.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.graphics.drawable;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Outline;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.View;
+
+class RoundedBitmapDrawable21 extends RoundedBitmapDrawable {
+    protected RoundedBitmapDrawable21(Resources res, Bitmap bitmap) {
+        super(res, bitmap);
+    }
+
+    @Override
+    public void getOutline(Outline outline) {
+        updateDstRect();
+        outline.setRoundRect(mDstRect, getCornerRadius());
+    }
+
+    @Override
+    public void setMipMap(boolean mipMap) {
+        if (mBitmap != null) {
+            mBitmap.setHasMipMap(mipMap);
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public boolean hasMipMap() {
+        return mBitmap != null && mBitmap.hasMipMap();
+    }
+
+    @Override
+    void gravityCompatApply(int gravity, int bitmapWidth, int bitmapHeight,
+            Rect bounds, Rect outRect) {
+        Gravity.apply(gravity, bitmapWidth, bitmapHeight,
+                bounds, outRect, View.LAYOUT_DIRECTION_LTR);
+    }
+}
diff --git a/v4/api21/android/support/v4/media/MediaMetadataCompatApi21.java b/v4/api21/android/support/v4/media/MediaMetadataCompatApi21.java
new file mode 100644
index 0000000..eddcf76
--- /dev/null
+++ b/v4/api21/android/support/v4/media/MediaMetadataCompatApi21.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media;
+
+import android.graphics.Bitmap;
+import android.media.MediaMetadata;
+import android.media.Rating;
+
+import java.util.Set;
+
+class MediaMetadataCompatApi21 {
+    public static Set<String> keySet(Object metadataObj) {
+        return ((MediaMetadata)metadataObj).keySet();
+    }
+
+    public static Bitmap getBitmap(Object metadataObj, String key) {
+        return ((MediaMetadata)metadataObj).getBitmap(key);
+    }
+
+    public static long getLong(Object metadataObj, String key) {
+        return ((MediaMetadata)metadataObj).getLong(key);
+    }
+
+    public static Object getRating(Object metadataObj, String key) {
+        return ((MediaMetadata)metadataObj).getRating(key);
+    }
+
+    public static CharSequence getText(Object metadataObj, String key) {
+        return ((MediaMetadata) metadataObj).getText(key);
+    }
+
+    public static class Builder {
+        public static Object newInstance() {
+            return new MediaMetadata.Builder();
+        }
+
+        public static void putBitmap(Object builderObj, String key, Bitmap value) {
+            ((MediaMetadata.Builder)builderObj).putBitmap(key, value);
+        }
+
+        public static void putLong(Object builderObj, String key, long value) {
+            ((MediaMetadata.Builder)builderObj).putLong(key, value);
+        }
+
+        public static void putRating(Object builderObj, String key, Object ratingObj) {
+            ((MediaMetadata.Builder)builderObj).putRating(key, (Rating)ratingObj);
+        }
+
+        public static void putText(Object builderObj, String key, CharSequence value) {
+            ((MediaMetadata.Builder) builderObj).putText(key, value);
+        }
+
+        public static void putString(Object builderObj, String key, String value) {
+            ((MediaMetadata.Builder) builderObj).putString(key, value);
+        }
+
+        public static Object build(Object builderObj) {
+            return ((MediaMetadata.Builder)builderObj).build();
+        }
+    }
+}
diff --git a/v4/api21/android/support/v4/media/RatingCompatApi21.java b/v4/api21/android/support/v4/media/RatingCompatApi21.java
new file mode 100644
index 0000000..6c8b9b2
--- /dev/null
+++ b/v4/api21/android/support/v4/media/RatingCompatApi21.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media;
+
+import android.media.Rating;
+
+class RatingCompatApi21 {
+    public static Object newUnratedRating(int ratingStyle) {
+        return Rating.newUnratedRating(ratingStyle);
+    }
+
+    public static Object newHeartRating(boolean hasHeart) {
+        return Rating.newHeartRating(hasHeart);
+    }
+
+    public static Object newThumbRating(boolean thumbIsUp) {
+        return Rating.newThumbRating(thumbIsUp);
+    }
+
+    public static Object newStarRating(int starRatingStyle, float starRating) {
+        return Rating.newStarRating(starRatingStyle, starRating);
+    }
+
+    public static Object newPercentageRating(float percent) {
+        return Rating.newPercentageRating(percent);
+    }
+
+    public static boolean isRated(Object ratingObj) {
+        return ((Rating)ratingObj).isRated();
+    }
+
+    public static int getRatingStyle(Object ratingObj) {
+        return ((Rating)ratingObj).getRatingStyle();
+    }
+
+    public static boolean hasHeart(Object ratingObj) {
+        return ((Rating)ratingObj).hasHeart();
+    }
+
+    public static boolean isThumbUp(Object ratingObj) {
+        return ((Rating)ratingObj).isThumbUp();
+    }
+
+    public static float getStarRating(Object ratingObj) {
+        return ((Rating)ratingObj).getStarRating();
+    }
+
+    public static float getPercentRating(Object ratingObj) {
+        return ((Rating)ratingObj).getPercentRating();
+    }
+}
diff --git a/v4/api21/android/support/v4/media/VolumeProviderCompatApi21.java b/v4/api21/android/support/v4/media/VolumeProviderCompatApi21.java
new file mode 100644
index 0000000..e3860ad
--- /dev/null
+++ b/v4/api21/android/support/v4/media/VolumeProviderCompatApi21.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media;
+
+import android.media.VolumeProvider;
+
+class VolumeProviderCompatApi21 {
+    public static Object createVolumeProvider(int volumeControl, int maxVolume,
+            final Delegate delegate) {
+        return new VolumeProvider(volumeControl, maxVolume) {
+            @Override
+            public int onGetCurrentVolume() {
+                return delegate.onGetCurrentVolume();
+            }
+
+            @Override
+            public void onSetVolumeTo(int volume) {
+                delegate.onSetVolumeTo(volume);
+            }
+
+            @Override
+            public void onAdjustVolume(int direction) {
+                delegate.onAdjustVolume(direction);
+            }
+        };
+    }
+
+    public static void notifyVolumeChanged(Object volumeProviderObj) {
+        ((VolumeProvider)volumeProviderObj).notifyVolumeChanged();
+    }
+
+    public interface Delegate {
+        int onGetCurrentVolume();
+        void onSetVolumeTo(int volume);
+        void onAdjustVolume(int delta);
+    }
+}
diff --git a/v4/api21/android/support/v4/media/session/MediaControllerCompatApi21.java b/v4/api21/android/support/v4/media/session/MediaControllerCompatApi21.java
new file mode 100644
index 0000000..40e4a99
--- /dev/null
+++ b/v4/api21/android/support/v4/media/session/MediaControllerCompatApi21.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media.session;
+
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.MediaMetadata;
+import android.media.Rating;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
+import android.view.KeyEvent;
+
+class MediaControllerCompatApi21 {
+    public static Object fromToken(Context context, Object sessionToken) {
+        return new MediaController(context, (MediaSession.Token) sessionToken);
+    }
+
+    public static Object createCallback(Callback callback) {
+        return new CallbackProxy<Callback>(callback);
+    }
+
+    public static void addCallback(Object controllerObj, Object callbackObj, Handler handler) {
+        ((MediaController)controllerObj).addCallback(
+                (MediaController.Callback)callbackObj, handler);
+    }
+
+    public static void removeCallback(Object controllerObj, Object callbackObj) {
+        ((MediaController)controllerObj).removeCallback((MediaController.Callback)callbackObj);
+    }
+
+    public static Object getTransportControls(Object controllerObj) {
+        return ((MediaController)controllerObj).getTransportControls();
+    }
+
+    public static Object getPlaybackState(Object controllerObj) {
+        return ((MediaController)controllerObj).getPlaybackState();
+    }
+
+    public static Object getMetadata(Object controllerObj) {
+        return ((MediaController)controllerObj).getMetadata();
+    }
+
+    public static int getRatingType(Object controllerObj) {
+        return ((MediaController)controllerObj).getRatingType();
+    }
+
+    public static Object getVolumeInfo(Object controllerObj) {
+        return ((MediaController)controllerObj).getVolumeInfo();
+    }
+
+    public static boolean dispatchMediaButtonEvent(Object controllerObj, KeyEvent event) {
+        return ((MediaController)controllerObj).dispatchMediaButtonEvent(event);
+    }
+
+    public static void sendCommand(Object controllerObj,
+            String command, Bundle params, ResultReceiver cb) {
+        ((MediaController)controllerObj).sendCommand(command, params, cb);
+    }
+
+    public static class TransportControls {
+        public static void play(Object controlsObj) {
+            ((MediaController.TransportControls)controlsObj).play();
+        }
+
+        public static void pause(Object controlsObj) {
+            ((MediaController.TransportControls)controlsObj).pause();
+        }
+
+        public static void stop(Object controlsObj) {
+            ((MediaController.TransportControls)controlsObj).stop();
+        }
+
+        public static void seekTo(Object controlsObj, long pos) {
+            ((MediaController.TransportControls)controlsObj).seekTo(pos);
+        }
+
+        public static void fastForward(Object controlsObj) {
+            ((MediaController.TransportControls)controlsObj).fastForward();
+        }
+
+        public static void rewind(Object controlsObj) {
+            ((MediaController.TransportControls)controlsObj).rewind();
+        }
+
+        public static void skipToNext(Object controlsObj) {
+            ((MediaController.TransportControls)controlsObj).skipToNext();
+        }
+
+        public static void skipToPrevious(Object controlsObj) {
+            ((MediaController.TransportControls)controlsObj).skipToPrevious();
+        }
+
+        public static void setRating(Object controlsObj, Object ratingObj) {
+            ((MediaController.TransportControls)controlsObj).setRating((Rating)ratingObj);
+        }
+    }
+
+    public static class VolumeInfo {
+        public static int getVolumeType(Object volumeInfoObj) {
+            return ((MediaController.VolumeInfo)volumeInfoObj).getVolumeType();
+        }
+
+        public static AudioAttributes getAudioAttributes(Object volumeInfoObj) {
+            return ((MediaController.VolumeInfo) volumeInfoObj).getAudioAttributes();
+        }
+
+        public static int getLegacyAudioStream(Object volumeInfoObj) {
+            AudioAttributes attrs = getAudioAttributes(volumeInfoObj);
+            return toLegacyStreamType(attrs);
+        }
+
+        public static int getVolumeControl(Object volumeInfoObj) {
+            return ((MediaController.VolumeInfo)volumeInfoObj).getVolumeControl();
+        }
+
+        public static int getMaxVolume(Object volumeInfoObj) {
+            return ((MediaController.VolumeInfo)volumeInfoObj).getMaxVolume();
+        }
+
+        public static int getCurrentVolume(Object volumeInfoObj) {
+            return ((MediaController.VolumeInfo)volumeInfoObj).getCurrentVolume();
+        }
+
+        // This is copied from AudioAttributes.toLegacyStreamType. TODO This
+        // either needs to be kept in sync with that one or toLegacyStreamType
+        // needs to be made public so it can be used by the support lib.
+        private static final int FLAG_SCO = 0x1 << 2;
+        private static final int STREAM_BLUETOOTH_SCO = 6;
+        private static final int STREAM_SYSTEM_ENFORCED = 7;
+        private static int toLegacyStreamType(AudioAttributes aa) {
+            // flags to stream type mapping
+            if ((aa.getFlags() & AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
+                    == AudioAttributes.FLAG_AUDIBILITY_ENFORCED) {
+                return STREAM_SYSTEM_ENFORCED;
+            }
+            if ((aa.getFlags() & FLAG_SCO) == FLAG_SCO) {
+                return STREAM_BLUETOOTH_SCO;
+            }
+
+            // usage to stream type mapping
+            switch (aa.getUsage()) {
+                case AudioAttributes.USAGE_MEDIA:
+                case AudioAttributes.USAGE_GAME:
+                case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY:
+                case AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
+                    return AudioManager.STREAM_MUSIC;
+                case AudioAttributes.USAGE_ASSISTANCE_SONIFICATION:
+                    return AudioManager.STREAM_SYSTEM;
+                case AudioAttributes.USAGE_VOICE_COMMUNICATION:
+                    return AudioManager.STREAM_VOICE_CALL;
+                case AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING:
+                    return AudioManager.STREAM_DTMF;
+                case AudioAttributes.USAGE_ALARM:
+                    return AudioManager.STREAM_ALARM;
+                case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
+                    return AudioManager.STREAM_RING;
+                case AudioAttributes.USAGE_NOTIFICATION:
+                case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
+                case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
+                case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
+                case AudioAttributes.USAGE_NOTIFICATION_EVENT:
+                    return AudioManager.STREAM_NOTIFICATION;
+                case AudioAttributes.USAGE_UNKNOWN:
+                default:
+                    return AudioManager.STREAM_MUSIC;
+            }
+        }
+    }
+
+    public static interface Callback {
+        public void onSessionDestroyed();
+        public void onSessionEvent(String event, Bundle extras);
+        public void onPlaybackStateChanged(Object stateObj);
+        public void onMetadataChanged(Object metadataObj);
+    }
+
+    static class CallbackProxy<T extends Callback> extends MediaController.Callback {
+        protected final T mCallback;
+
+        public CallbackProxy(T callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void onSessionDestroyed() {
+            mCallback.onSessionDestroyed();
+        }
+
+        @Override
+        public void onSessionEvent(String event, Bundle extras) {
+            mCallback.onSessionEvent(event, extras);
+        }
+
+        @Override
+        public void onPlaybackStateChanged(PlaybackState state) {
+            mCallback.onPlaybackStateChanged(state);
+        }
+
+        @Override
+        public void onMetadataChanged(MediaMetadata metadata) {
+            mCallback.onMetadataChanged(metadata);
+        }
+    }
+}
diff --git a/v4/api21/android/support/v4/media/session/MediaSessionCompatApi21.java b/v4/api21/android/support/v4/media/session/MediaSessionCompatApi21.java
new file mode 100644
index 0000000..60742c4
--- /dev/null
+++ b/v4/api21/android/support/v4/media/session/MediaSessionCompatApi21.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media.session;
+
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioAttributes;
+import android.media.MediaMetadata;
+import android.media.Rating;
+import android.media.VolumeProvider;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.os.ResultReceiver;
+
+class MediaSessionCompatApi21 {
+    public static Object createSession(Context context, String tag) {
+        return new MediaSession(context, tag);
+    }
+
+    public static Object createCallback(Callback callback) {
+        return new CallbackProxy<Callback>(callback);
+    }
+
+    public static void setCallback(Object sessionObj, Object callbackObj, Handler handler) {
+        ((MediaSession) sessionObj).setCallback((MediaSession.Callback) callbackObj, handler);
+    }
+
+    public static void setFlags(Object sessionObj, int flags) {
+        ((MediaSession)sessionObj).setFlags(flags);
+    }
+
+    public static void setPlaybackToLocal(Object sessionObj, int stream) {
+        // TODO update APIs to use support version of AudioAttributes
+        AudioAttributes.Builder bob = new AudioAttributes.Builder();
+        bob.setLegacyStreamType(stream);
+        ((MediaSession) sessionObj).setPlaybackToLocal(bob.build());
+    }
+
+    public static void setPlaybackToRemote(Object sessionObj, Object volumeProviderObj) {
+        ((MediaSession)sessionObj).setPlaybackToRemote((VolumeProvider)volumeProviderObj);
+    }
+
+    public static void setActive(Object sessionObj, boolean active) {
+        ((MediaSession)sessionObj).setActive(active);
+    }
+
+    public static boolean isActive(Object sessionObj) {
+        return ((MediaSession)sessionObj).isActive();
+    }
+
+    public static void sendSessionEvent(Object sessionObj, String event, Bundle extras) {
+        ((MediaSession)sessionObj).sendSessionEvent(event, extras);
+    }
+
+    public static void release(Object sessionObj) {
+        ((MediaSession)sessionObj).release();
+    }
+
+    public static Parcelable getSessionToken(Object sessionObj) {
+        return ((MediaSession)sessionObj).getSessionToken();
+    }
+
+    public static void setPlaybackState(Object sessionObj, Object stateObj) {
+        ((MediaSession)sessionObj).setPlaybackState((PlaybackState)stateObj);
+    }
+
+    public static void setMetadata(Object sessionObj, Object metadataObj) {
+        ((MediaSession)sessionObj).setMetadata((MediaMetadata)metadataObj);
+    }
+
+    public static interface Callback {
+        public void onCommand(String command, Bundle extras, ResultReceiver cb);
+        public boolean onMediaButtonEvent(Intent mediaButtonIntent);
+        public void onPlay();
+        public void onPause();
+        public void onSkipToNext();
+        public void onSkipToPrevious();
+        public void onFastForward();
+        public void onRewind();
+        public void onStop();
+        public void onSeekTo(long pos);
+        public void onSetRating(Object ratingObj);
+    }
+
+    static class CallbackProxy<T extends Callback> extends MediaSession.Callback {
+        protected final T mCallback;
+
+        public CallbackProxy(T callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void onCommand(String command, Bundle args, ResultReceiver cb) {
+            mCallback.onCommand(command, args, cb);
+        }
+
+        @Override
+        public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
+            return mCallback.onMediaButtonEvent(mediaButtonIntent);
+        }
+
+        @Override
+        public void onPlay() {
+            mCallback.onPlay();
+        }
+
+        @Override
+        public void onPause() {
+            mCallback.onPause();
+        }
+
+        @Override
+        public void onSkipToNext() {
+            mCallback.onSkipToNext();
+        }
+
+        @Override
+        public void onSkipToPrevious() {
+            mCallback.onSkipToPrevious();
+        }
+
+        @Override
+        public void onFastForward() {
+            mCallback.onFastForward();
+        }
+
+        @Override
+        public void onRewind() {
+            mCallback.onRewind();
+        }
+
+        @Override
+        public void onStop() {
+            mCallback.onStop();
+        }
+
+        @Override
+        public void onSeekTo(long pos) {
+            mCallback.onSeekTo(pos);
+        }
+
+        @Override
+        public void onSetRating(Rating rating) {
+            mCallback.onSetRating(rating);
+        }
+    }
+}
diff --git a/v4/api21/android/support/v4/media/session/PlaybackStateCompatApi21.java b/v4/api21/android/support/v4/media/session/PlaybackStateCompatApi21.java
new file mode 100644
index 0000000..e17b9b9
--- /dev/null
+++ b/v4/api21/android/support/v4/media/session/PlaybackStateCompatApi21.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media.session;
+
+import android.media.session.PlaybackState;
+import android.os.SystemClock;
+
+class PlaybackStateCompatApi21 {
+    public static int getState(Object stateObj) {
+        return ((PlaybackState)stateObj).getState();
+    }
+
+    public static long getPosition(Object stateObj) {
+        return ((PlaybackState)stateObj).getPosition();
+    }
+
+    public static long getBufferPosition(Object stateObj) {
+        return ((PlaybackState)stateObj).getBufferPosition();
+    }
+
+    public static float getPlaybackSpeed(Object stateObj) {
+        return ((PlaybackState)stateObj).getPlaybackSpeed();
+    }
+
+    public static long getActions(Object stateObj) {
+        return ((PlaybackState)stateObj).getActions();
+    }
+
+    public static CharSequence getErrorMessage(Object stateObj) {
+        return ((PlaybackState)stateObj).getErrorMessage();
+    }
+
+    public static long getLastPositionUpdateTime(Object stateObj) {
+        return ((PlaybackState)stateObj).getLastPositionUpdateTime();
+    }
+
+    public static Object newInstance(int state, long position, long bufferPosition,
+            float speed, long actions, CharSequence errorMessage, long updateTime) {
+        PlaybackState.Builder stateObj = new PlaybackState.Builder();
+        stateObj.setState(state, position, speed, updateTime);
+        stateObj.setBufferPosition(bufferPosition);
+        stateObj.setActions(actions);
+        stateObj.setErrorMessage(errorMessage);
+        return stateObj.build();
+    }
+}
diff --git a/v4/api21/android/support/v4/view/ViewCompatApi21.java b/v4/api21/android/support/v4/view/ViewCompatApi21.java
new file mode 100644
index 0000000..d0fd029
--- /dev/null
+++ b/v4/api21/android/support/v4/view/ViewCompatApi21.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.view.View;
+
+class ViewCompatApi21 {
+
+    public static void setTransitionName(View view, String transitionName) {
+        view.setTransitionName(transitionName);
+    }
+
+    public static String getTransitionName(View view) {
+        return view.getTransitionName();
+    }
+
+    public static void requestApplyInsets(View view) {
+        view.requestApplyInsets();
+    }
+
+    public static void setElevation(View view, float elevation) {
+        view.setElevation(elevation);
+    }
+
+    public static float getElevation(View view) {
+        return view.getElevation();
+    }
+
+    public static void setTranslationZ(View view, float translationZ) {
+        view.setTranslationZ(translationZ);
+    }
+
+    public static float getTranslationZ(View view) {
+        return view.getTranslationZ();
+    }
+}
diff --git a/v4/api21/android/support/v4/view/ViewGroupCompatApi21.java b/v4/api21/android/support/v4/view/ViewGroupCompatApi21.java
new file mode 100644
index 0000000..5ebd187
--- /dev/null
+++ b/v4/api21/android/support/v4/view/ViewGroupCompatApi21.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.view.ViewGroup;
+
+class ViewGroupCompatApi21 {
+
+    public static void setTransitionGroup(ViewGroup group, boolean isTransitionGroup) {
+        group.setTransitionGroup(isTransitionGroup);
+    }
+
+    public static boolean isTransitionGroup(ViewGroup group) {
+        return group.isTransitionGroup();
+    }
+}
diff --git a/v4/api21/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi21.java b/v4/api21/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi21.java
new file mode 100644
index 0000000..0c5d7b6
--- /dev/null
+++ b/v4/api21/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi21.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view.accessibility;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
+import java.util.List;
+
+/**
+ * Api21-specific AccessibilityNodeInfo API implementation.
+ */
+class AccessibilityNodeInfoCompatApi21 {
+    static List<Object> getActionList(Object info) {
+        Object result = ((AccessibilityNodeInfo) info).getActionList();
+        return (List<Object>) result;
+    }
+
+    static void addAction(Object info, int id, CharSequence label) {
+        AccessibilityNodeInfo.AccessibilityAction aa =
+                new AccessibilityNodeInfo.AccessibilityAction(id, label);
+        ((AccessibilityNodeInfo) info).addAction(aa);
+    }
+
+    static class AccessibilityAction {
+        static int getId(Object action) {
+            return ((AccessibilityNodeInfo.AccessibilityAction) action).getId();
+        }
+
+        static CharSequence getLabel(Object action) {
+            return ((AccessibilityNodeInfo.AccessibilityAction) action).getLabel();
+        }
+    }
+}
diff --git a/v4/build.gradle b/v4/build.gradle
index e432d56..fed5c24 100644
--- a/v4/build.gradle
+++ b/v4/build.gradle
@@ -1,27 +1,35 @@
-apply plugin: 'java'
-
+apply plugin: 'android-library'
 archivesBaseName = 'support-v4'
 
+// create a jar task for the code internal implementation
+tasks.create(name: "internalJar", type: Jar) {
+    baseName "internal_impl"
+}
+
 // --------------------------
 // TO ADD NEW PLATFORM SPECIFIC CODE, UPDATE THIS:
 // create and configure the sourcesets/dependencies for platform-specific code.
 // values are: sourceset name, source folder name, api level, previous sourceset.
-def eclairSS       = createApiSourceset('eclair',       'eclair',        '5',       null)
+
+ext.allSS = []
+
+def baseSS         = createApiSourceset('donut',         'donut',          '4',       null)
+def eclairSS       = createApiSourceset('eclair',       'eclair',        '5',       baseSS)
 def eclairMr1SS    = createApiSourceset('eclairmr1',    'eclair-mr1',    '7',       eclairSS)
 def froyoSS        = createApiSourceset('froyo',        'froyo',         '8',       eclairMr1SS)
 def gingerbreadSS  = createApiSourceset('gingerbread',  'gingerbread',   '9',       froyoSS)
 def honeycombSS    = createApiSourceset('honeycomb',    'honeycomb',     '11',      gingerbreadSS)
-def honeycombMr2SS = createApiSourceset('honeycombmr2', 'honeycomb_mr2', '13',      honeycombSS)
+def honeycombMr1SS = createApiSourceset('honeycombmr1', 'honeycomb_mr1', '12',      honeycombSS)
+def honeycombMr2SS = createApiSourceset('honeycombmr2', 'honeycomb_mr2', '13',      honeycombMr1SS)
 def icsSS          = createApiSourceset('ics',          'ics',           '14',      honeycombMr2SS)
 def icsMr1SS       = createApiSourceset('icsmr1',       'ics-mr1',       '15',      icsSS)
 def jbSS           = createApiSourceset('jellybean',    'jellybean',     '16',      icsMr1SS)
 def jbMr1SS        = createApiSourceset('jellybeanmr1', 'jellybean-mr1', '17',      jbSS)
 def jbMr2SS        = createApiSourceset('jellybeanmr2', 'jellybean-mr2', '18',      jbMr1SS)
 def kitkatSS       = createApiSourceset('kitkat',       'kitkat',        '19',      jbMr2SS)
+def api20SS        = createApiSourceset('api20',        'api20',         'current', kitkatSS)
+def api21SS        = createApiSourceset('api21',        'api21',         'current', api20SS)
 
-// setup the main code to depend on the above through the highest platform-specific sourceset.
-setupDependencies('compile', kitkatSS)
-// --------------------------
 
 def createApiSourceset(String name, String folder, String apiLevel, SourceSet previousSource) {
     def sourceSet = sourceSets.create(name)
@@ -33,10 +41,9 @@
     if (previousSource != null) {
         setupDependencies(configName, previousSource)
     }
+    ext.allSS.add(sourceSet)
 
-    project.jar {
-        from sourceSet.output
-    }
+    internalJar.from sourceSet.output
 
     return sourceSet
 }
@@ -46,15 +53,80 @@
     project.getDependencies().add(configName, previousSourceSet.compileClasspath)
 }
 
-// the main sourceset and its dependencies
-sourceSets {
-    main.java.srcDirs = ['java']
-}
-
 dependencies {
-    compile getAndroidPrebuilt('4')
+    compile project(':support-annotations')
+
+    // add the internal implementation as a dependency.
+    // this is not enough to make the regular compileJava task
+    // depend on the generation of this jar. This is done below
+    // when manipulating the libraryVariants.
+    compile files(internalJar.archivePath)
 }
 
+android {
+    compileSdkVersion 4
+    buildToolsVersion "19.0.1"
+
+    defaultConfig {
+        minSdkVersion 4
+        // TODO: get target from branch
+        //targetSdkVersion 19
+    }
+
+    sourceSets {
+        main.manifest.srcFile 'AndroidManifest.xml'
+        main.java.srcDirs = ['java']
+        main.aidl.srcDirs = ['java']
+
+        androidTest.setRoot('tests')
+        androidTest.java.srcDir 'tests/java'
+    }
+
+    lintOptions {
+        // TODO: fix errors and reenable.
+        abortOnError false
+    }
+}
+
+android.libraryVariants.all { variant ->
+    variant.javaCompile.dependsOn internalJar
+
+    def name = variant.buildType.name
+
+    if (name.equals(com.android.builder.BuilderConstants.DEBUG)) {
+        return; // Skip debug builds.
+    }
+    def suffix = name.capitalize()
+
+    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
+        dependsOn variant.javaCompile
+        from variant.javaCompile.destinationDir
+        from 'LICENSE.txt'
+    }
+    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
+        source android.sourceSets.main.allJava
+        classpath = files(variant.javaCompile.classpath.files) + files(
+                "${android.plugin.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
+    }
+
+    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
+        classifier = 'javadoc'
+        from 'build/docs/javadoc'
+    }
+
+    def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
+        classifier = 'sources'
+        from android.sourceSets.main.allSource
+    }
+
+    project.ext.allSS.each { ss ->
+        javadocTask.source ss.allJava
+        sourcesJarTask.from ss.allSource
+    }
+
+    artifacts.add('archives', javadocJarTask);
+    artifacts.add('archives', sourcesJarTask);
+}
 
 uploadArchives {
     repositories {
@@ -89,50 +161,3 @@
         }
     }
 }
-
-// configuration for the javadoc to include all source sets.
-javadoc {
-    source    sourceSets.main.allJava
-    source    sourceSets.eclair.allJava
-    source    sourceSets.eclairmr1.allJava
-    source    sourceSets.froyo.allJava
-    source    sourceSets.gingerbread.allJava
-    source    sourceSets.honeycomb.allJava
-    source    sourceSets.honeycombmr2.allJava
-    source    sourceSets.ics.allJava
-    source    sourceSets.icsmr1.allJava
-    source    sourceSets.jellybean.allJava
-    source    sourceSets.jellybeanmr1.allJava
-    source    sourceSets.jellybeanmr2.allJava
-    source    sourceSets.kitkat.allJava
-}
-
-// custom tasks for creating source/javadoc jars
-task sourcesJar(type: Jar, dependsOn:classes) {
-    classifier = 'sources'
-    from sourceSets.main.allSource
-    from sourceSets.eclair.allSource
-    from sourceSets.eclairmr1.allSource
-    from sourceSets.froyo.allSource
-    from sourceSets.gingerbread.allSource
-    from sourceSets.honeycomb.allSource
-    from sourceSets.honeycombmr2.allSource
-    from sourceSets.ics.allSource
-    from sourceSets.icsmr1.allSource
-    from sourceSets.jellybean.allSource
-    from sourceSets.jellybeanmr1.allSource
-    from sourceSets.jellybeanmr2.allSource
-    from sourceSets.kitkat.allSource
-}
-
-task javadocJar(type: Jar, dependsOn:javadoc) {
-    classifier         'javadoc'
-    from               javadoc.destinationDir
-}
-
-// add javadoc/source jar tasks as artifacts
-artifacts {
-    archives jar
-    archives sourcesJar
-    archives javadocJar
-}
diff --git a/v4/donut/android/support/v4/app/NotificationCompatBase.java b/v4/donut/android/support/v4/app/NotificationCompatBase.java
new file mode 100644
index 0000000..d8e99af
--- /dev/null
+++ b/v4/donut/android/support/v4/app/NotificationCompatBase.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.PendingIntent;
+import android.os.Bundle;
+
+class NotificationCompatBase {
+
+    public static abstract class Action {
+        protected abstract int getIcon();
+        protected abstract CharSequence getTitle();
+        protected abstract PendingIntent getActionIntent();
+        protected abstract Bundle getExtras();
+        protected abstract RemoteInputCompatBase.RemoteInput[] getRemoteInputs();
+
+        public interface Factory {
+            Action build(int icon, CharSequence title, PendingIntent actionIntent,
+                    Bundle extras, RemoteInputCompatBase.RemoteInput[] remoteInputs);
+            public Action[] newArray(int length);
+        }
+    }
+}
diff --git a/v4/donut/android/support/v4/app/RemoteInputCompatBase.java b/v4/donut/android/support/v4/app/RemoteInputCompatBase.java
new file mode 100644
index 0000000..2449336
--- /dev/null
+++ b/v4/donut/android/support/v4/app/RemoteInputCompatBase.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.os.Bundle;
+
+class RemoteInputCompatBase {
+
+    public static abstract class RemoteInput {
+        protected abstract String getResultKey();
+        protected abstract CharSequence getLabel();
+        protected abstract CharSequence[] getChoices();
+        protected abstract boolean getAllowFreeFormInput();
+        protected abstract Bundle getExtras();
+
+        public interface Factory {
+            public RemoteInput build(String resultKey, CharSequence label,
+                    CharSequence[] choices, boolean allowFreeFormInput, Bundle extras);
+            public RemoteInput[] newArray(int length);
+        }
+    }
+}
diff --git a/v4/donut/android/support/v4/graphics/drawable/RoundedBitmapDrawable.java b/v4/donut/android/support/v4/graphics/drawable/RoundedBitmapDrawable.java
new file mode 100644
index 0000000..7231a38
--- /dev/null
+++ b/v4/donut/android/support/v4/graphics/drawable/RoundedBitmapDrawable.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.graphics.drawable;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+
+/**
+ * A Drawable that wraps a bitmap and can be drawn with rounded corners. You can create a
+ * RoundedBitmapDrawable from a file path, an input stream, or from a
+ * {@link android.graphics.Bitmap} object.
+ * <p>
+ * Also see the {@link android.graphics.Bitmap} class, which handles the management and
+ * transformation of raw bitmap graphics, and should be used when drawing to a
+ * {@link android.graphics.Canvas}.
+ * </p>
+ */
+public abstract class RoundedBitmapDrawable extends Drawable {
+    private static final int DEFAULT_PAINT_FLAGS =
+            Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG;
+    Bitmap mBitmap;
+    private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
+    private int mGravity = Gravity.FILL;
+    private Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS);
+    private BitmapShader mBitmapShader;
+    private float mCornerRadius;
+
+    final Rect mDstRect = new Rect();   // Gravity.apply() sets this
+    final RectF mDstRectF = new RectF();
+
+    private boolean mApplyGravity = true;
+
+     // These are scaled to match the target density.
+    private int mBitmapWidth;
+    private int mBitmapHeight;
+
+    /**
+     * Returns the paint used to render this drawable.
+     */
+    public final Paint getPaint() {
+        return mPaint;
+    }
+
+    /**
+     * Returns the bitmap used by this drawable to render. May be null.
+     */
+    public final Bitmap getBitmap() {
+        return mBitmap;
+    }
+
+    private void computeBitmapSize() {
+        mBitmapWidth = mBitmap.getScaledWidth(mTargetDensity);
+        mBitmapHeight = mBitmap.getScaledHeight(mTargetDensity);
+    }
+
+    /**
+     * Set the density scale at which this drawable will be rendered. This
+     * method assumes the drawable will be rendered at the same density as the
+     * specified canvas.
+     *
+     * @param canvas The Canvas from which the density scale must be obtained.
+     *
+     * @see android.graphics.Bitmap#setDensity(int)
+     * @see android.graphics.Bitmap#getDensity()
+     */
+    public void setTargetDensity(Canvas canvas) {
+        setTargetDensity(canvas.getDensity());
+    }
+
+    /**
+     * Set the density scale at which this drawable will be rendered.
+     *
+     * @param metrics The DisplayMetrics indicating the density scale for this drawable.
+     *
+     * @see android.graphics.Bitmap#setDensity(int)
+     * @see android.graphics.Bitmap#getDensity()
+     */
+    public void setTargetDensity(DisplayMetrics metrics) {
+        setTargetDensity(metrics.densityDpi);
+    }
+
+    /**
+     * Set the density at which this drawable will be rendered.
+     *
+     * @param density The density scale for this drawable.
+     *
+     * @see android.graphics.Bitmap#setDensity(int)
+     * @see android.graphics.Bitmap#getDensity()
+     */
+    public void setTargetDensity(int density) {
+        if (mTargetDensity != density) {
+            mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
+            if (mBitmap != null) {
+                computeBitmapSize();
+            }
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * Get the gravity used to position/stretch the bitmap within its bounds.
+     *
+     * @return the gravity applied to the bitmap
+     *
+     * @see android.view.Gravity
+     */
+    public int getGravity() {
+        return mGravity;
+    }
+
+    /**
+     * Set the gravity used to position/stretch the bitmap within its bounds.
+     *
+     * @param gravity the gravity
+     *
+     * @see android.view.Gravity
+     */
+    public void setGravity(int gravity) {
+        if (mGravity != gravity) {
+            mGravity = gravity;
+            mApplyGravity = true;
+            invalidateSelf();
+        }
+    }
+
+    /**
+     * Enables or disables the mipmap hint for this drawable's bitmap.
+     * See {@link Bitmap#setHasMipMap(boolean)} for more information.
+     *
+     * If the bitmap is null, or the current API version does not support setting a mipmap hint,
+     * calling this method has no effect.
+     *
+     * @param mipMap True if the bitmap should use mipmaps, false otherwise.
+     *
+     * @see #hasMipMap()
+     */
+    public void setMipMap(boolean mipMap) {
+        throw new UnsupportedOperationException(); // must be overridden in subclasses
+    }
+
+    /**
+     * Indicates whether the mipmap hint is enabled on this drawable's bitmap.
+     *
+     * @return True if the mipmap hint is set, false otherwise. If the bitmap
+     *         is null, this method always returns false.
+     *
+     * @see #setMipMap(boolean)
+     */
+    public boolean hasMipMap() {
+        throw new UnsupportedOperationException(); // must be overridden in subclasses
+    }
+
+    /**
+     * Enables or disables anti-aliasing for this drawable. Anti-aliasing affects
+     * the edges of the bitmap only so it applies only when the drawable is rotated.
+     *
+     * @param aa True if the bitmap should be anti-aliased, false otherwise.
+     *
+     * @see #hasAntiAlias()
+     */
+    public void setAntiAlias(boolean aa) {
+        mPaint.setAntiAlias(aa);
+        invalidateSelf();
+    }
+
+    /**
+     * Indicates whether anti-aliasing is enabled for this drawable.
+     *
+     * @return True if anti-aliasing is enabled, false otherwise.
+     *
+     * @see #setAntiAlias(boolean)
+     */
+    public boolean hasAntiAlias() {
+        return mPaint.isAntiAlias();
+    }
+
+    @Override
+    public void setFilterBitmap(boolean filter) {
+        mPaint.setFilterBitmap(filter);
+        invalidateSelf();
+    }
+
+    @Override
+    public void setDither(boolean dither) {
+        mPaint.setDither(dither);
+        invalidateSelf();
+    }
+
+    void gravityCompatApply(int gravity, int bitmapWidth, int bitmapHeight,
+            Rect bounds, Rect outRect) {
+        throw new UnsupportedOperationException();
+    }
+
+    void updateDstRect() {
+        if (mApplyGravity) {
+            gravityCompatApply(mGravity, mBitmapWidth, mBitmapHeight,
+                    getBounds(), mDstRect);
+            mDstRectF.set(mDstRect);
+            mApplyGravity = false;
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        final Bitmap bitmap = mBitmap;
+        if (bitmap == null) {
+            return;
+        }
+
+        updateDstRect();
+
+        final Paint paint = mPaint;
+        final Shader shader = paint.getShader();
+        if (shader == null) {
+            canvas.drawBitmap(bitmap, null, mDstRect, paint);
+        } else {
+            canvas.drawRoundRect(mDstRectF, mCornerRadius, mCornerRadius, paint);
+        }
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        final int oldAlpha = mPaint.getAlpha();
+        if (alpha != oldAlpha) {
+            mPaint.setAlpha(alpha);
+            invalidateSelf();
+        }
+    }
+
+    public int getAlpha() {
+        return mPaint.getAlpha();
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter cf) {
+        mPaint.setColorFilter(cf);
+        invalidateSelf();
+    }
+
+    public ColorFilter getColorFilter() {
+        return mPaint.getColorFilter();
+    }
+
+    /**
+     * Sets the corner radius to be applied when drawing the bitmap.
+     */
+    public void setCornerRadius(float cornerRadius) {
+        if (isGreaterThanZero(cornerRadius)) {
+            mPaint.setShader(mBitmapShader);
+        } else {
+            mPaint.setShader(null);
+        }
+        mCornerRadius = cornerRadius;
+    }
+
+    /**
+     * @return The corner radius applied when drawing the bitmap.
+     */
+    public float getCornerRadius() {
+        return mCornerRadius;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mBitmapWidth;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mBitmapHeight;
+    }
+
+    @Override
+    public int getOpacity() {
+        if (mGravity != Gravity.FILL) {
+            return PixelFormat.TRANSLUCENT;
+        }
+        Bitmap bm = mBitmap;
+        return (bm == null
+                || bm.hasAlpha()
+                || mPaint.getAlpha() < 255
+                || isGreaterThanZero(mCornerRadius))
+                ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
+    }
+
+    RoundedBitmapDrawable(Resources res, Bitmap bitmap) {
+        if (res != null) {
+            mTargetDensity = res.getDisplayMetrics().densityDpi;
+        }
+
+        mBitmap = bitmap;
+        if (mBitmap != null) {
+            computeBitmapSize();
+            mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+        } else {
+            mBitmapWidth = mBitmapHeight = -1;
+        }
+    }
+
+    private static boolean isGreaterThanZero(float toCompare) {
+        return Float.compare(toCompare, +0.0f) > 0;
+    }
+}
diff --git a/v4/eclair-mr1/android/support/v4/view/ViewCompatEclairMr1.java b/v4/eclair-mr1/android/support/v4/view/ViewCompatEclairMr1.java
index 87000dd..971b70d 100644
--- a/v4/eclair-mr1/android/support/v4/view/ViewCompatEclairMr1.java
+++ b/v4/eclair-mr1/android/support/v4/view/ViewCompatEclairMr1.java
@@ -17,10 +17,40 @@
 
 package android.support.v4.view;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import android.util.Log;
 import android.view.View;
+import android.view.ViewGroup;
 
 class ViewCompatEclairMr1 {
+    public static final String TAG = "ViewCompat";
+
+    private static Method sChildrenDrawingOrderMethod;
+
     public static boolean isOpaque(View view) {
         return view.isOpaque();
     }
+
+    public static void setChildrenDrawingOrderEnabled(ViewGroup viewGroup, boolean enabled) {
+        if (sChildrenDrawingOrderMethod == null) {
+            try {
+                sChildrenDrawingOrderMethod = ViewGroup.class
+                        .getDeclaredMethod("setChildrenDrawingOrderEnabled", boolean.class);
+            } catch (NoSuchMethodException e) {
+                Log.e(TAG, "Unable to find childrenDrawingOrderEnabled", e);
+            }
+            sChildrenDrawingOrderMethod.setAccessible(true);
+        }
+        try {
+            sChildrenDrawingOrderMethod.invoke(viewGroup, enabled);
+        } catch (IllegalAccessException e) {
+            Log.e(TAG, "Unable to invoke childrenDrawingOrderEnabled", e);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Unable to invoke childrenDrawingOrderEnabled", e);
+        } catch (InvocationTargetException e) {
+            Log.e(TAG, "Unable to invoke childrenDrawingOrderEnabled", e);
+        }
+    }
 }
diff --git a/v4/eclair/android/support/v4/app/NotificationManagerCompatEclair.java b/v4/eclair/android/support/v4/app/NotificationManagerCompatEclair.java
new file mode 100644
index 0000000..45d96e4
--- /dev/null
+++ b/v4/eclair/android/support/v4/app/NotificationManagerCompatEclair.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+
+class NotificationManagerCompatEclair {
+    static void cancelNotification(NotificationManager notificationManager, String tag,
+            int id) {
+        notificationManager.cancel(tag, id);
+    }
+
+    public static void postNotification(NotificationManager notificationManager, String tag, int id,
+            Notification notification) {
+        notificationManager.notify(tag, id, notification);
+    }
+}
diff --git a/v4/froyo/android/support/v4/view/ViewConfigurationCompatFroyo.java b/v4/froyo/android/support/v4/view/ViewConfigurationCompatFroyo.java
index e97a2a2..71a4b26 100644
--- a/v4/froyo/android/support/v4/view/ViewConfigurationCompatFroyo.java
+++ b/v4/froyo/android/support/v4/view/ViewConfigurationCompatFroyo.java
@@ -19,7 +19,7 @@
 import android.view.ViewConfiguration;
 
 /**
- * Implementation of menu compatibility that can call Honeycomb APIs.
+ * Implementation of menu compatibility that can call Froyo APIs.
  */
 class ViewConfigurationCompatFroyo {
     public static int getScaledPagingTouchSlop(ViewConfiguration config) {
diff --git a/v4/honeycomb/android/support/v4/app/ActionBarDrawerToggleHoneycomb.java b/v4/honeycomb/android/support/v4/app/ActionBarDrawerToggleHoneycomb.java
index 9569656..bd27954 100644
--- a/v4/honeycomb/android/support/v4/app/ActionBarDrawerToggleHoneycomb.java
+++ b/v4/honeycomb/android/support/v4/app/ActionBarDrawerToggleHoneycomb.java
@@ -20,6 +20,7 @@
 import android.R;
 import android.app.ActionBar;
 import android.app.Activity;
+import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
diff --git a/v4/honeycomb/android/support/v4/app/NotificationBuilderWithBuilderAccessor.java b/v4/honeycomb/android/support/v4/app/NotificationBuilderWithBuilderAccessor.java
new file mode 100644
index 0000000..4de2e21
--- /dev/null
+++ b/v4/honeycomb/android/support/v4/app/NotificationBuilderWithBuilderAccessor.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.Notification;
+
+/**
+ * Interface implemented by notification compat builders that support
+ * an accessor for {@link Notification.Builder}. {@link Notification.Builder}
+ * was introduced in HoneyComb.
+ */
+interface NotificationBuilderWithBuilderAccessor {
+    public Notification.Builder getBuilder();
+}
diff --git a/v4/honeycomb/android/support/v4/os/AsyncTaskCompatHoneycomb.java b/v4/honeycomb/android/support/v4/os/AsyncTaskCompatHoneycomb.java
new file mode 100644
index 0000000..5b05a0e
--- /dev/null
+++ b/v4/honeycomb/android/support/v4/os/AsyncTaskCompatHoneycomb.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.os;
+
+import android.os.AsyncTask;
+
+/**
+ * Implementation of AsyncTask compatibility that can call Honeycomb APIs.
+ */
+class AsyncTaskCompatHoneycomb {
+
+    static <Params, Progress, Result> void executeParallel(
+            AsyncTask<Params, Progress, Result> task,
+            Params... params) {
+        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
+    }
+
+}
diff --git a/v4/honeycomb/android/support/v4/view/ViewCompatHC.java b/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
index a237831..7e211a7 100644
--- a/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
+++ b/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
@@ -52,4 +52,96 @@
     public static int getMeasuredState(View view) {
         return view.getMeasuredState();
     }
+
+    public static float getTranslationX(View view) {
+        return view.getTranslationX();
+    }
+
+    public static float getTranslationY(View view) {
+        return view.getTranslationY();
+    }
+
+    public static float getX(View view) {
+        return view.getX();
+    }
+
+    public static float getY(View view) {
+        return view.getY();
+    }
+
+    public static float getRotation(View view) {
+        return view.getRotation();
+    }
+
+    public static float getRotationX(View view) {
+        return view.getRotationX();
+    }
+
+    public static float getRotationY(View view) {
+        return view.getRotationY();
+    }
+
+    public static float getScaleX(View view) {
+        return view.getScaleX();
+    }
+
+    public static float getScaleY(View view) {
+        return view.getScaleY();
+    }
+
+    public static void setTranslationX(View view, float value) {
+        view.setTranslationX(value);
+    }
+
+    public static void setTranslationY(View view, float value) {
+        view.setTranslationY(value);
+    }
+
+    public static void setAlpha(View view, float value) {
+        view.setAlpha(value);
+    }
+
+    public static void setX(View view, float value) {
+        view.setX(value);
+    }
+
+    public static void setY(View view, float value) {
+        view.setY(value);
+    }
+
+    public static void setRotation(View view, float value) {
+        view.setRotation(value);
+    }
+
+    public static void setRotationX(View view, float value) {
+        view.setRotationX(value);
+    }
+
+    public static void setRotationY(View view, float value) {
+        view.setRotationY(value);
+    }
+
+    public static void setScaleX(View view, float value) {
+        view.setScaleX(value);
+    }
+
+    public static void setScaleY(View view, float value) {
+        view.setScaleY(value);
+    }
+
+    public static void setPivotX(View view, float value) {
+        view.setPivotX(value);
+    }
+
+    public static void setPivotY(View view, float value) {
+        view.setPivotY(value);
+    }
+
+    public static float getPivotX(View view) {
+        return view.getPivotX();
+    }
+
+    public static float getPivotY(View view) {
+        return view.getPivotY();
+    }
 }
diff --git a/v4/honeycomb_mr1/android/support/v4/graphics/BitmapCompatHoneycombMr1.java b/v4/honeycomb_mr1/android/support/v4/graphics/BitmapCompatHoneycombMr1.java
new file mode 100644
index 0000000..94358d5
--- /dev/null
+++ b/v4/honeycomb_mr1/android/support/v4/graphics/BitmapCompatHoneycombMr1.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.graphics;
+
+import android.graphics.Bitmap;
+
+/**
+ * Implementation of BitmapCompat that can use Honeycomb MR1 APIs.
+ */
+class BitmapCompatHoneycombMr1 {
+
+    static int getAllocationByteCount(Bitmap bitmap) {
+        return bitmap.getByteCount();
+    }
+
+}
diff --git a/v4/ics-mr1/android/support/v4/speech/tts/TextToSpeechICSMR1.java b/v4/ics-mr1/android/support/v4/speech/tts/TextToSpeechICSMR1.java
new file mode 100644
index 0000000..bbad4c1
--- /dev/null
+++ b/v4/ics-mr1/android/support/v4/speech/tts/TextToSpeechICSMR1.java
@@ -0,0 +1,74 @@
+package android.support.v4.speech.tts;
+
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.UtteranceProgressListener;
+import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
+
+import java.util.Locale;
+import java.util.Set;
+
+/** Helper class for TTS functionality introduced in ICS MR1 */
+class TextToSpeechICSMR1 {
+    /**
+     * Call {@link TextToSpeech#getFeatures} if available.
+     *
+     * @return {@link TextToSpeech#getFeatures} or null on older devices.
+     */
+    static Set<String> getFeatures(TextToSpeech tts, Locale locale) {
+        if (android.os.Build.VERSION.SDK_INT >=
+                android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+            return tts.getFeatures(locale);
+        }
+        return null;
+    }
+
+    public static final String KEY_FEATURE_EMBEDDED_SYNTHESIS = "embeddedTts";
+    public static final String KEY_FEATURE_NETWORK_SYNTHESIS = "networkTts";
+
+    static interface UtteranceProgressListenerICSMR1 {
+        void onDone(String utteranceId);
+        void onError(String utteranceId);
+        void onStart(String utteranceId);
+    }
+
+    /**
+     * Call {@link TextToSpeech#setOnUtteranceProgressListener} if ICS-MR1 or newer.
+     *
+     * On pre ICS-MR1 devices,{@link TextToSpeech#setOnUtteranceCompletedListener} is
+     * used to emulate its behavior - at the end of synthesis we call
+     * {@link UtteranceProgressListenerICSMR1#onStart(String)} and
+     * {@link UtteranceProgressListenerICSMR1#onDone(String)} one after the other.
+     * Errors can't be detected.
+     */
+    static void setUtteranceProgressListener(TextToSpeech tts,
+            final UtteranceProgressListenerICSMR1 listener) {
+        if (android.os.Build.VERSION.SDK_INT >=
+                android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+            tts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
+                @Override
+                public void onStart(String utteranceId) {
+                    listener.onStart(utteranceId);
+                }
+
+                @Override
+                public void onError(String utteranceId) {
+                    listener.onError(utteranceId);
+                }
+
+                @Override
+                public void onDone(String utteranceId) {
+                    listener.onDone(utteranceId);
+                }
+            });
+        } else {
+            tts.setOnUtteranceCompletedListener(new OnUtteranceCompletedListener() {
+                @Override
+                public void onUtteranceCompleted(String utteranceId) {
+                    // Emulate onStart. Clients are expecting it will happen.
+                    listener.onStart(utteranceId);
+                    listener.onDone(utteranceId);
+                }
+            });
+        }
+    }
+}
diff --git a/v4/ics/android/support/v4/app/NotificationCompatIceCreamSandwich.java b/v4/ics/android/support/v4/app/NotificationCompatIceCreamSandwich.java
index 3c74842..97912d9 100644
--- a/v4/ics/android/support/v4/app/NotificationCompatIceCreamSandwich.java
+++ b/v4/ics/android/support/v4/app/NotificationCompatIceCreamSandwich.java
@@ -27,7 +27,7 @@
             CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
             RemoteViews tickerView, int number,
             PendingIntent contentIntent, PendingIntent fullScreenIntent, Bitmap largeIcon,
-            int mProgressMax, int mProgress, boolean mProgressIndeterminate) {
+            int progressMax, int progress, boolean progressIndeterminate) {
         Notification.Builder b = new Notification.Builder(context)
                 .setWhen(n.when)
                 .setSmallIcon(n.icon, n.iconLevel)
@@ -49,7 +49,7 @@
                         (n.flags & Notification.FLAG_HIGH_PRIORITY) != 0)
                 .setLargeIcon(largeIcon)
                 .setNumber(number)
-                .setProgress(mProgressMax, mProgress, mProgressIndeterminate);
+                .setProgress(progressMax, progress, progressIndeterminate);
 
         return b.getNotification();
     }
diff --git a/v4/ics/android/support/v4/app/NotificationManagerCompatIceCreamSandwich.java b/v4/ics/android/support/v4/app/NotificationManagerCompatIceCreamSandwich.java
new file mode 100644
index 0000000..f088179
--- /dev/null
+++ b/v4/ics/android/support/v4/app/NotificationManagerCompatIceCreamSandwich.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.Service;
+
+class NotificationManagerCompatIceCreamSandwich {
+    static final int SIDE_CHANNEL_BIND_FLAGS = Service.BIND_AUTO_CREATE
+            | Service.BIND_WAIVE_PRIORITY;
+}
diff --git a/v4/ics/android/support/v4/speech/tts/TextToSpeechICS.java b/v4/ics/android/support/v4/speech/tts/TextToSpeechICS.java
new file mode 100644
index 0000000..4e0c041
--- /dev/null
+++ b/v4/ics/android/support/v4/speech/tts/TextToSpeechICS.java
@@ -0,0 +1,27 @@
+package android.support.v4.speech.tts;
+
+import android.content.Context;
+import android.os.Build;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TextToSpeech.OnInitListener;
+import android.util.Log;
+
+/** Helper class for TTS functionality introduced in ICS */
+class TextToSpeechICS {
+    private static final String TAG = "android.support.v4.speech.tts";
+
+    static TextToSpeech construct(Context context, OnInitListener onInitListener,
+            String engineName) {
+        if (Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+            if (engineName == null) {
+                return new TextToSpeech(context, onInitListener);
+            } else {
+                Log.w(TAG, "Can't specify tts engine on this device");
+                return new TextToSpeech(context, onInitListener);
+            }
+        } else {
+            return new TextToSpeech(context, onInitListener, engineName);
+        }
+    }
+
+}
diff --git a/v4/ics/android/support/v4/view/ViewConfigurationCompatICS.java b/v4/ics/android/support/v4/view/ViewConfigurationCompatICS.java
new file mode 100644
index 0000000..13a9647
--- /dev/null
+++ b/v4/ics/android/support/v4/view/ViewConfigurationCompatICS.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.view.ViewConfiguration;
+
+/**
+ * Implementation of menu compatibility that can call ICS APIs.
+ */
+class ViewConfigurationCompatICS {
+    static boolean hasPermanentMenuKey(ViewConfiguration config) {
+        return config.hasPermanentMenuKey();
+    }
+}
diff --git a/v4/ics/android/support/v4/view/ViewPropertyAnimatorCompatICS.java b/v4/ics/android/support/v4/view/ViewPropertyAnimatorCompatICS.java
new file mode 100644
index 0000000..c40adbe
--- /dev/null
+++ b/v4/ics/android/support/v4/view/ViewPropertyAnimatorCompatICS.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.view;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.TimeInterpolator;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import java.util.WeakHashMap;
+
+class ViewPropertyAnimatorCompatICS {
+
+    public static void setDuration(View view, long value) {
+        view.animate().setDuration(value);
+    }
+
+    public static void alpha(View view, float value) {
+        view.animate().alpha(value);
+    }
+
+    public static void translationX(View view, float value) {
+        view.animate().translationX(value);
+    }
+
+    public static void translationY(View view, float value) {
+        view.animate().translationY(value);
+    }
+
+    public static long getDuration(View view) {
+        return view.animate().getDuration();
+    }
+
+    public static void setInterpolator(View view, Interpolator value) {
+        view.animate().setInterpolator(value);
+    }
+
+    public static void setStartDelay(View view, long value) {
+        view.animate().setStartDelay(value);
+    }
+
+    public static long getStartDelay(View view) {
+        return view.animate().getStartDelay();
+    }
+
+    public static void alphaBy(View view, float value) {
+        view.animate().alphaBy(value);
+    }
+
+    public static void rotation(View view, float value) {
+        view.animate().rotation(value);
+    }
+
+    public static void rotationBy(View view, float value) {
+        view.animate().rotationBy(value);
+    }
+
+    public static void rotationX(View view, float value) {
+        view.animate().rotationX(value);
+    }
+
+    public static void rotationXBy(View view, float value) {
+        view.animate().rotationXBy(value);
+    }
+
+    public static void rotationY(View view, float value) {
+        view.animate().rotationY(value);
+    }
+
+    public static void rotationYBy(View view, float value) {
+        view.animate().rotationYBy(value);
+    }
+
+    public static void scaleX(View view, float value) {
+        view.animate().scaleX(value);
+    }
+
+    public static void scaleXBy(View view, float value) {
+        view.animate().scaleXBy(value);
+    }
+
+    public static void scaleY(View view, float value) {
+        view.animate().scaleY(value);
+    }
+
+    public static void scaleYBy(View view, float value) {
+        view.animate().scaleYBy(value);
+    }
+
+    public static void cancel(View view) {
+        view.animate().cancel();
+    }
+
+    public static void x(View view, float value) {
+        view.animate().x(value);
+    }
+
+    public static void xBy(View view, float value) {
+        view.animate().xBy(value);
+    }
+
+    public static void y(View view, float value) {
+        view.animate().y(value);
+    }
+
+    public static void yBy(View view, float value) {
+        view.animate().yBy(value);
+    }
+
+    public static void translationXBy(View view, float value) {
+        view.animate().translationXBy(value);
+    }
+
+    public static void translationYBy(View view, float value) {
+        view.animate().translationYBy(value);
+    }
+
+    public static void start(View view) {
+        view.animate().start();
+    }
+
+    public static void setListener(final View view,
+            final ViewPropertyAnimatorListener listener) {
+        view.animate().setListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                listener.onAnimationCancel(view);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                listener.onAnimationEnd(view);
+            }
+
+            @Override
+            public void onAnimationStart(Animator animation) {
+                listener.onAnimationStart(view);
+            }
+        });
+    }
+}
diff --git a/v4/ics/android/support/v4/view/ViewPropertyAnimatorListener.java b/v4/ics/android/support/v4/view/ViewPropertyAnimatorListener.java
new file mode 100644
index 0000000..72f1daf
--- /dev/null
+++ b/v4/ics/android/support/v4/view/ViewPropertyAnimatorListener.java
@@ -0,0 +1,47 @@
+package android.support.v4.view;/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.view.View;
+
+/**
+ * <p>An animation listener receives notifications from an animation.
+ * Notifications indicate animation related events, such as the end or the
+ * start of the animation.</p>
+ */
+public interface ViewPropertyAnimatorListener {
+    /**
+     * <p>Notifies the start of the animation.</p>
+     *
+     * @param view The view associated with the ViewPropertyAnimator
+     */
+    void onAnimationStart(View view);
+
+    /**
+     * <p>Notifies the end of the animation. This callback is not invoked
+     * for animations with repeat count set to INFINITE.</p>
+     *
+     * @param view The view associated with the ViewPropertyAnimator
+     */
+    void onAnimationEnd(View view);
+
+    /**
+     * <p>Notifies the cancellation of the animation. This callback is not invoked
+     * for animations with repeat count set to INFINITE.</p>
+     *
+     * @param view The view associated with the ViewPropertyAnimator
+     */
+    void onAnimationCancel(View view);
+}
diff --git a/v4/ics/android/support/v4/view/ViewPropertyAnimatorUpdateListener.java b/v4/ics/android/support/v4/view/ViewPropertyAnimatorUpdateListener.java
new file mode 100644
index 0000000..b189d8a
--- /dev/null
+++ b/v4/ics/android/support/v4/view/ViewPropertyAnimatorUpdateListener.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.view.View;
+
+/**
+ * Implementors of this interface can add themselves as update listeners
+ * to an <code>ViewPropertyAnimatorCompat</code> instance to receive callbacks on every animation
+ * frame, after the current frame's values have been calculated for that
+ * <code>ViewPropertyAnimatorCompat</code>.
+ */
+public interface ViewPropertyAnimatorUpdateListener {
+
+    /**
+     * <p>Notifies the occurrence of another frame of the animation.</p>
+     *
+     * @param view The view associated with the ViewPropertyAnimatorCompat
+     */
+    void onAnimationUpdate(View view);
+
+}
diff --git a/v4/java/android/support/v4/app/ActionBarDrawerToggle.java b/v4/java/android/support/v4/app/ActionBarDrawerToggle.java
index 5c7e733..5dc596b 100644
--- a/v4/java/android/support/v4/app/ActionBarDrawerToggle.java
+++ b/v4/java/android/support/v4/app/ActionBarDrawerToggle.java
@@ -18,12 +18,17 @@
 package android.support.v4.app;
 
 import android.app.Activity;
+import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.InsetDrawable;
 import android.os.Build;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
+import android.support.v4.content.ContextCompat;
 import android.support.v4.view.GravityCompat;
 import android.support.v4.view.ViewCompat;
 import android.support.v4.widget.DrawerLayout;
@@ -64,6 +69,7 @@
          * @return Delegate to use for ActionBarDrawableToggles, or null if the Activity
          *         does not wish to override the default behavior.
          */
+        @Nullable
         Delegate getDrawerToggleDelegate();
     }
 
@@ -72,6 +78,7 @@
          * @return Up indicator drawable as defined in the Activity's theme, or null if one is not
          *         defined.
          */
+        @Nullable
         Drawable getThemeUpIndicator();
 
         /**
@@ -80,14 +87,14 @@
          * @param upDrawable     - Drawable to set as up indicator
          * @param contentDescRes - Content description to set
          */
-        void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes);
+        void setActionBarUpIndicator(Drawable upDrawable, @StringRes int contentDescRes);
 
         /**
          * Set the Action Bar's up indicator content description.
          *
          * @param contentDescRes - Content description to set
          */
-        void setActionBarDescription(int contentDescRes);
+        void setActionBarDescription(@StringRes int contentDescRes);
     }
 
     private interface ActionBarDrawerToggleImpl {
@@ -181,8 +188,9 @@
     private final Delegate mActivityImpl;
     private final DrawerLayout mDrawerLayout;
     private boolean mDrawerIndicatorEnabled = true;
+    private boolean mHasCustomUpIndicator;
 
-    private Drawable mThemeImage;
+    private Drawable mHomeAsUpIndicator;
     private Drawable mDrawerImage;
     private SlideDrawable mSlider;
     private final int mDrawerImageResource;
@@ -211,7 +219,41 @@
      *                                  for accessibility
      */
     public ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout,
-            int drawerImageRes, int openDrawerContentDescRes, int closeDrawerContentDescRes) {
+            @DrawableRes int drawerImageRes, @StringRes int openDrawerContentDescRes,
+            @StringRes int closeDrawerContentDescRes) {
+        this(activity, drawerLayout, !assumeMaterial(activity), drawerImageRes,
+                openDrawerContentDescRes, closeDrawerContentDescRes);
+    }
+
+    private static boolean assumeMaterial(Context context) {
+        return context.getApplicationInfo().targetSdkVersion >= 21 &&
+                (Build.VERSION.SDK_INT >= 21 || "L".equals(Build.VERSION.CODENAME));
+    }
+
+    /**
+     * Construct a new ActionBarDrawerToggle.
+     *
+     * <p>The given {@link Activity} will be linked to the specified {@link DrawerLayout}.
+     * The provided drawer indicator drawable will animate slightly off-screen as the drawer
+     * is opened, indicating that in the open state the drawer will move off-screen when pressed
+     * and in the closed state the drawer will move on-screen when pressed.</p>
+     *
+     * <p>String resources must be provided to describe the open/close drawer actions for
+     * accessibility services.</p>
+     *
+     * @param activity The Activity hosting the drawer
+     * @param drawerLayout The DrawerLayout to link to the given Activity's ActionBar
+     * @param animate True to animate the drawer indicator along with the drawer's position.
+     *                Material apps should set this to false.
+     * @param drawerImageRes A Drawable resource to use as the drawer indicator
+     * @param openDrawerContentDescRes A String resource to describe the "open drawer" action
+     *                                 for accessibility
+     * @param closeDrawerContentDescRes A String resource to describe the "close drawer" action
+     *                                  for accessibility
+     */
+    public ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, boolean animate,
+            @DrawableRes int drawerImageRes, @StringRes int openDrawerContentDescRes,
+            @StringRes int closeDrawerContentDescRes) {
         mActivity = activity;
 
         // Allow the Activity to provide an impl
@@ -226,10 +268,10 @@
         mOpenDrawerContentDescRes = openDrawerContentDescRes;
         mCloseDrawerContentDescRes = closeDrawerContentDescRes;
 
-        mThemeImage = getThemeUpIndicator();
-        mDrawerImage = activity.getResources().getDrawable(drawerImageRes);
+        mHomeAsUpIndicator = getThemeUpIndicator();
+        mDrawerImage = ContextCompat.getDrawable(activity, drawerImageRes);
         mSlider = new SlideDrawable(mDrawerImage);
-        mSlider.setOffset(TOGGLE_DRAWABLE_OFFSET);
+        mSlider.setOffset(animate ? TOGGLE_DRAWABLE_OFFSET : 0);
     }
 
     /**
@@ -255,6 +297,51 @@
     }
 
     /**
+     * Set the up indicator to display when the drawer indicator is not
+     * enabled.
+     * <p>
+     * If you pass <code>null</code> to this method, the default drawable from
+     * the theme will be used.
+     *
+     * @param indicator A drawable to use for the up indicator, or null to use
+     *                  the theme's default
+     * @see #setDrawerIndicatorEnabled(boolean)
+     */
+    public void setHomeAsUpIndicator(Drawable indicator) {
+        if (indicator == null) {
+            mHomeAsUpIndicator = getThemeUpIndicator();
+            mHasCustomUpIndicator = false;
+        } else {
+            mHomeAsUpIndicator = indicator;
+            mHasCustomUpIndicator = true;
+        }
+
+        if (!mDrawerIndicatorEnabled) {
+            setActionBarUpIndicator(mHomeAsUpIndicator, 0);
+        }
+    }
+
+    /**
+     * Set the up indicator to display when the drawer indicator is not
+     * enabled.
+     * <p>
+     * If you pass 0 to this method, the default drawable from the theme will
+     * be used.
+     *
+     * @param resId Resource ID of a drawable to use for the up indicator, or 0
+     *              to use the theme's default
+     * @see #setDrawerIndicatorEnabled(boolean)
+     */
+    public void setHomeAsUpIndicator(int resId) {
+        Drawable indicator = null;
+        if (resId != 0) {
+            indicator = ContextCompat.getDrawable(mActivity, resId);
+        }
+
+        setHomeAsUpIndicator(indicator);
+    }
+
+    /**
      * Enable or disable the drawer indicator. The indicator defaults to enabled.
      *
      * <p>When the indicator is disabled, the <code>ActionBar</code> will revert to displaying
@@ -270,7 +357,7 @@
                 setActionBarUpIndicator(mSlider, mDrawerLayout.isDrawerOpen(GravityCompat.START) ?
                         mCloseDrawerContentDescRes : mOpenDrawerContentDescRes);
             } else {
-                setActionBarUpIndicator(mThemeImage, 0);
+                setActionBarUpIndicator(mHomeAsUpIndicator, 0);
             }
             mDrawerIndicatorEnabled = enable;
         }
@@ -293,8 +380,10 @@
      */
     public void onConfigurationChanged(Configuration newConfig) {
         // Reload drawables that can change with configuration
-        mThemeImage = getThemeUpIndicator();
-        mDrawerImage = mActivity.getResources().getDrawable(mDrawerImageResource);
+        if (!mHasCustomUpIndicator) {
+            mHomeAsUpIndicator = getThemeUpIndicator();
+        }
+        mDrawerImage = ContextCompat.getDrawable(mActivity, mDrawerImageResource);
         syncState();
     }
 
diff --git a/v4/java/android/support/v4/app/ActivityCompat.java b/v4/java/android/support/v4/app/ActivityCompat.java
index a30eff2..48f2fd9 100644
--- a/v4/java/android/support/v4/app/ActivityCompat.java
+++ b/v4/java/android/support/v4/app/ActivityCompat.java
@@ -20,7 +20,12 @@
 import android.content.Intent;
 import android.os.Build;
 import android.os.Bundle;
+import android.support.annotation.Nullable;
 import android.support.v4.content.ContextCompat;
+import android.view.View;
+
+import java.util.List;
+import java.util.Map;
 
 /**
  * Helper for accessing features in {@link android.app.Activity}
@@ -84,7 +89,7 @@
      *                supplied here; there are no supported definitions for
      *                building it manually.
      */
-    public static void startActivity(Activity activity, Intent intent, Bundle options) {
+    public static void startActivity(Activity activity, Intent intent, @Nullable Bundle options) {
         if (Build.VERSION.SDK_INT >= 16) {
             ActivityCompatJB.startActivity(activity, intent, options);
         } else {
@@ -112,7 +117,8 @@
      *                supplied here; there are no supported definitions for
      *                building it manually.
      */
-    public static void startActivityForResult(Activity activity, Intent intent, int requestCode, Bundle options) {
+    public static void startActivityForResult(Activity activity, Intent intent, int requestCode,
+            @Nullable Bundle options) {
         if (Build.VERSION.SDK_INT >= 16) {
             ActivityCompatJB.startActivityForResult(activity, intent, requestCode, options);
         } else {
@@ -135,4 +141,105 @@
         }
     }
 
+    /**
+     * Reverses the Activity Scene entry Transition and triggers the calling Activity
+     * to reverse its exit Transition. When the exit Transition completes,
+     * {@link Activity#finish()} is called. If no entry Transition was used, finish() is called
+     * immediately and the Activity exit Transition is run.
+     *
+     * <p>On Android 4.4 or lower, this method only finishes the Activity with no
+     * special exit transition.</p>
+     */
+    public static void finishAfterTransition(Activity activity) {
+        if (Build.VERSION.SDK_INT >= 21 || Build.VERSION.CODENAME.equals("L")) {
+            ActivityCompat21.finishAfterTransition(activity);
+        } else {
+            activity.finish();
+        }
+    }
+
+    /**
+     * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
+     * android.view.View, String)} was used to start an Activity, <var>listener</var>
+     * will be called to handle shared elements on the <i>launched</i> Activity. This requires
+     * {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS}.
+     *
+     * @param listener Used to manipulate shared element transitions on the launched Activity.
+     */
+    public static void setEnterSharedElementListener(Activity activity,
+            SharedElementListener listener) {
+        if (Build.VERSION.SDK_INT >= 21 || Build.VERSION.CODENAME.equals("L")) {
+            ActivityCompat21.setEnterSharedElementListener(activity, createListener(listener));
+        }
+    }
+
+    /**
+     * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
+     * android.view.View, String)} was used to start an Activity, <var>listener</var>
+     * will be called to handle shared elements on the <i>launching</i> Activity. Most
+     * calls will only come when returning from the started Activity.
+     * This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS}.
+     *
+     * @param listener Used to manipulate shared element transitions on the launching Activity.
+     */
+    public static void setExitSharedElementListener(Activity activity,
+            SharedElementListener listener) {
+        if (Build.VERSION.SDK_INT >= 21 || Build.VERSION.CODENAME.equals("L")) {
+            ActivityCompat21.setExitSharedElementListener(activity, createListener(listener));
+        }
+    }
+
+    public static void postponeEnterTransition(Activity activity) {
+        if (Build.VERSION.SDK_INT >= 21 || Build.VERSION.CODENAME.equals("L")) {
+            ActivityCompat21.postponeEnterTransition(activity);
+        }
+    }
+
+    public static void startPostponedEnterTransition(Activity activity) {
+        if (Build.VERSION.SDK_INT >= 21 || Build.VERSION.CODENAME.equals("L")) {
+            ActivityCompat21.startPostponedEnterTransition(activity);
+        }
+    }
+
+    private static ActivityCompat21.SharedElementListener21 createListener(
+            SharedElementListener listener) {
+        ActivityCompat21.SharedElementListener21 newListener = null;
+        if (listener != null) {
+            newListener = new SharedElementListener21Impl(listener);
+        }
+        return newListener;
+    }
+
+    private static class SharedElementListener21Impl
+            extends ActivityCompat21.SharedElementListener21 {
+        private SharedElementListener mListener;
+
+        public SharedElementListener21Impl(SharedElementListener listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void setSharedElementStart(List<String> sharedElementNames,
+                List<View> sharedElements, List<View> sharedElementSnapshots) {
+            mListener.setSharedElementStart(sharedElementNames, sharedElements,
+                    sharedElementSnapshots);
+        }
+
+        @Override
+        public void setSharedElementEnd(List<String> sharedElementNames, List<View> sharedElements,
+                List<View> sharedElementSnapshots) {
+            mListener.setSharedElementEnd(sharedElementNames, sharedElements,
+                    sharedElementSnapshots);
+        }
+
+        @Override
+        public void handleRejectedSharedElements(List<View> rejectedSharedElements) {
+            mListener.handleRejectedSharedElements(rejectedSharedElements);
+        }
+
+        @Override
+        public void remapSharedElements(List<String> names, Map<String, View> sharedElements) {
+            mListener.remapSharedElements(names, sharedElements);
+        }
+    }
 }
diff --git a/v4/java/android/support/v4/app/ActivityManagerCompat.java b/v4/java/android/support/v4/app/ActivityManagerCompat.java
new file mode 100644
index 0000000..ff5c180
--- /dev/null
+++ b/v4/java/android/support/v4/app/ActivityManagerCompat.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.ActivityManager;
+import android.os.Build;
+import android.support.annotation.NonNull;
+
+/**
+ * Helper for accessing features in {@link android.app.ActivityManager}
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
+public final class ActivityManagerCompat {
+    private ActivityManagerCompat() {
+    }
+
+    /**
+     * Returns true if this is a low-RAM device.  Exactly whether a device is low-RAM
+     * is ultimately up to the device configuration, but currently it generally means
+     * something in the class of a 512MB device with about a 800x480 or less screen.
+     * This is mostly intended to be used by apps to determine whether they should turn
+     * off certain features that require more RAM.
+     */
+    public static boolean isLowRamDevice(@NonNull ActivityManager am) {
+        if (Build.VERSION.SDK_INT >= 19) {
+            return ActivityManagerCompatKitKat.isLowRamDevice(am);
+        }
+        return false;
+    }
+}
diff --git a/v4/java/android/support/v4/app/ActivityOptionsCompat.java b/v4/java/android/support/v4/app/ActivityOptionsCompat.java
index f426a69..783aa4a 100644
--- a/v4/java/android/support/v4/app/ActivityOptionsCompat.java
+++ b/v4/java/android/support/v4/app/ActivityOptionsCompat.java
@@ -16,10 +16,12 @@
 
 package android.support.v4.app;
 
+import android.app.Activity;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.os.Build;
 import android.os.Bundle;
+import android.support.v4.util.Pair;
 import android.view.View;
 
 /**
@@ -108,6 +110,69 @@
         return new ActivityOptionsCompat();
     }
 
+    /**
+     * Create an ActivityOptions to transition between Activities using cross-Activity scene
+     * animations. This method carries the position of one shared element to the started Activity.
+     * The position of <code>sharedElement</code> will be used as the epicenter for the
+     * exit Transition. The position of the shared element in the launched Activity will be the
+     * epicenter of its entering Transition.
+     *
+     * <p>This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be
+     * enabled on the calling Activity to cause an exit transition. The same must be in
+     * the called Activity to get an entering transition.</p>
+     * @param activity The Activity whose window contains the shared elements.
+     * @param sharedElement The View to transition to the started Activity. sharedElement must
+     *                      have a non-null sharedElementName.
+     * @param sharedElementName The shared element name as used in the target Activity. This may
+     *                          be null if it has the same name as sharedElement.
+     * @return Returns a new ActivityOptions object that you can use to
+     *         supply these options as the options Bundle when starting an activity.
+     */
+    public static ActivityOptionsCompat makeSceneTransitionAnimation(Activity activity,
+            View sharedElement, String sharedElementName) {
+        if (Build.VERSION.SDK_INT >= 21 || Build.VERSION.CODENAME.equals("L")) {
+            return new ActivityOptionsCompat.ActivityOptionsImpl21(
+                    ActivityOptionsCompat21.makeSceneTransitionAnimation(activity,
+                            sharedElement, sharedElementName));
+        }
+        return new ActivityOptionsCompat();
+    }
+
+    /**
+     * Create an ActivityOptions to transition between Activities using cross-Activity scene
+     * animations. This method carries the position of multiple shared elements to the started
+     * Activity. The position of the first element in sharedElements
+     * will be used as the epicenter for the exit Transition. The position of the associated
+     * shared element in the launched Activity will be the epicenter of its entering Transition.
+     *
+     * <p>This requires {@link android.view.Window#FEATURE_CONTENT_TRANSITIONS} to be
+     * enabled on the calling Activity to cause an exit transition. The same must be in
+     * the called Activity to get an entering transition.</p>
+     * @param activity The Activity whose window contains the shared elements.
+     * @param sharedElements The names of the shared elements to transfer to the called
+     *                       Activity and their associated Views. The Views must each have
+     *                       a unique shared element name.
+     * @return Returns a new ActivityOptions object that you can use to
+     *         supply these options as the options Bundle when starting an activity.
+     */
+    public static ActivityOptionsCompat makeSceneTransitionAnimation(Activity activity,
+            Pair<View, String>... sharedElements) {
+        if (Build.VERSION.SDK_INT >= 21 || Build.VERSION.CODENAME.equals("L")) {
+            View[] views = null;
+            String[] names = null;
+            if (sharedElements != null) {
+                views = new View[sharedElements.length];
+                names = new String[sharedElements.length];
+                for (int i = 0; i < sharedElements.length; i++) {
+                    views[i] = sharedElements[i].first;
+                    names[i] = sharedElements[i].second;
+                }
+            }
+            return new ActivityOptionsCompat.ActivityOptionsImpl21(
+                    ActivityOptionsCompat21.makeSceneTransitionAnimation(activity, views, names));
+        }
+        return new ActivityOptionsCompat();
+    }
 
     private static class ActivityOptionsImplJB extends ActivityOptionsCompat {
         private final ActivityOptionsCompatJB mImpl;
@@ -130,6 +195,27 @@
         }
     }
 
+    private static class ActivityOptionsImpl21 extends ActivityOptionsCompat {
+        private final ActivityOptionsCompat21 mImpl;
+
+        ActivityOptionsImpl21(ActivityOptionsCompat21 impl) {
+            mImpl = impl;
+        }
+
+        @Override
+        public Bundle toBundle() {
+            return mImpl.toBundle();
+        }
+
+        @Override
+        public void update(ActivityOptionsCompat otherOptions) {
+            if (otherOptions instanceof ActivityOptionsCompat.ActivityOptionsImpl21) {
+                ActivityOptionsCompat.ActivityOptionsImpl21
+                        otherImpl = (ActivityOptionsCompat.ActivityOptionsImpl21)otherOptions;
+                mImpl.update(otherImpl.mImpl);
+            }
+        }
+    }
 
     protected ActivityOptionsCompat() {
     }
diff --git a/v4/java/android/support/v4/app/BackStackRecord.java b/v4/java/android/support/v4/app/BackStackRecord.java
index bd13dd7..62d99df 100644
--- a/v4/java/android/support/v4/app/BackStackRecord.java
+++ b/v4/java/android/support/v4/app/BackStackRecord.java
@@ -16,11 +16,15 @@
 
 package android.support.v4.app;
 
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.support.v4.util.LogWriter;
+import android.support.v4.util.Pair;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -36,6 +40,10 @@
     final CharSequence mBreadCrumbTitleText;
     final int mBreadCrumbShortTitleRes;
     final CharSequence mBreadCrumbShortTitleText;
+    final int mCustomTransition;
+    final int mSceneRoot;
+    final ArrayList<String> mSharedElementSourceNames;
+    final ArrayList<String> mSharedElementTargetNames;
 
     public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) {
         int numRemoved = 0;
@@ -78,6 +86,10 @@
         mBreadCrumbTitleText = bse.mBreadCrumbTitleText;
         mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes;
         mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText;
+        mCustomTransition = bse.mCustomTransition;
+        mSceneRoot = bse.mSceneRoot;
+        mSharedElementSourceNames = bse.mSharedElementSourceNames;
+        mSharedElementTargetNames = bse.mSharedElementTargetNames;
     }
 
     public BackStackState(Parcel in) {
@@ -90,6 +102,10 @@
         mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mBreadCrumbShortTitleRes = in.readInt();
         mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mCustomTransition = in.readInt();
+        mSceneRoot = in.readInt();
+        mSharedElementSourceNames = in.createStringArrayList();
+        mSharedElementTargetNames = in.createStringArrayList();
     }
 
     public BackStackRecord instantiate(FragmentManagerImpl fm) {
@@ -134,6 +150,10 @@
         bse.mBreadCrumbTitleText = mBreadCrumbTitleText;
         bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes;
         bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText;
+        bse.mCustomTransition = mCustomTransition;
+        bse.mSceneRoot = mSceneRoot;
+        bse.mSharedElementSourceNames = mSharedElementSourceNames;
+        bse.mSharedElementTargetNames = mSharedElementTargetNames;
         bse.bumpBackStackNesting(1);
         return bse;
     }
@@ -152,6 +172,10 @@
         TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0);
         dest.writeInt(mBreadCrumbShortTitleRes);
         TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0);
+        dest.writeInt(mCustomTransition);
+        dest.writeInt(mSceneRoot);
+        dest.writeStringList(mSharedElementSourceNames);
+        dest.writeStringList(mSharedElementTargetNames);
     }
 
     public static final Parcelable.Creator<BackStackState> CREATOR
@@ -216,6 +240,11 @@
     int mBreadCrumbShortTitleRes;
     CharSequence mBreadCrumbShortTitleText;
 
+    int mCustomTransition;
+    int mSceneRoot;
+    ArrayList<String> mSharedElementSourceNames;
+    ArrayList<String> mSharedElementTargetNames;
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder(128);
@@ -493,6 +522,54 @@
         return this;
     }
 
+    @Override
+    public FragmentTransaction setCustomTransition(int sceneRootId, int transitionId) {
+        mSceneRoot = sceneRootId;
+        mCustomTransition = transitionId;
+        return this;
+    }
+
+    @Override
+    public FragmentTransaction setSharedElement(View sharedElement, String name) {
+        if (Build.VERSION.SDK_INT >= 21 || Build.VERSION.CODENAME.equals("L")) {
+            String transitionName = FragmentTransitionCompat21.getTransitionName(sharedElement);
+            if (transitionName == null) {
+                throw new IllegalArgumentException("Unique transitionNames are required for all" +
+                        " sharedElements");
+            }
+            mSharedElementSourceNames = new ArrayList<String>(1);
+            mSharedElementSourceNames.add(transitionName);
+
+            mSharedElementTargetNames = new ArrayList<String>(1);
+            mSharedElementTargetNames.add(name);
+        }
+        return this;
+    }
+
+    @Override
+    public FragmentTransaction setSharedElements(Pair<View, String>... sharedElements) {
+        if (sharedElements == null || sharedElements.length == 0) {
+            mSharedElementSourceNames = null;
+            mSharedElementTargetNames = null;
+        } else if (Build.VERSION.SDK_INT >= 21 || Build.VERSION.CODENAME.equals("L")) {
+            ArrayList<String> sourceNames = new ArrayList<String>(sharedElements.length);
+            ArrayList<String> targetNames = new ArrayList<String>(sharedElements.length);
+            for (int i = 0; i < sharedElements.length; i++) {
+                String transitionName =
+                        FragmentTransitionCompat21.getTransitionName(sharedElements[i].first);
+                if (transitionName == null) {
+                    throw new IllegalArgumentException("Unique transitionNames are required for all"
+                            + " sharedElements");
+                }
+                sourceNames.add(transitionName);
+                targetNames.add(sharedElements[i].second);
+            }
+            mSharedElementSourceNames = sourceNames;
+            mSharedElementTargetNames = targetNames;
+        }
+        return this;
+    }
+
     public FragmentTransaction setTransitionStyle(int styleRes) {
         mTransitionStyle = styleRes;
         return this;
@@ -607,12 +684,18 @@
 
         bumpBackStackNesting(1);
 
+        Object state = beginTransition(mSharedElementSourceNames, mSharedElementTargetNames);
+
+        int transitionStyle = state != null ? 0 : mTransitionStyle;
+        int transition = state != null ? 0 : mTransition;
         Op op = mHead;
         while (op != null) {
+            int enterAnim = state != null ? 0 : op.enterAnim;
+            int exitAnim = state != null ? 0 : op.exitAnim;
             switch (op.cmd) {
                 case OP_ADD: {
                     Fragment f = op.fragment;
-                    f.mNextAnim = op.enterAnim;
+                    f.mNextAnim = enterAnim;
                     mManager.addFragment(f, false);
                 } break;
                 case OP_REPLACE: {
@@ -630,46 +713,46 @@
                                         op.removed = new ArrayList<Fragment>();
                                     }
                                     op.removed.add(old);
-                                    old.mNextAnim = op.exitAnim;
+                                    old.mNextAnim = exitAnim;
                                     if (mAddToBackStack) {
                                         old.mBackStackNesting += 1;
                                         if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
                                                 + old + " to " + old.mBackStackNesting);
                                     }
-                                    mManager.removeFragment(old, mTransition, mTransitionStyle);
+                                    mManager.removeFragment(old, transition, transitionStyle);
                                 }
                             }
                         }
                     }
                     if (f != null) {
-                        f.mNextAnim = op.enterAnim;
+                        f.mNextAnim = enterAnim;
                         mManager.addFragment(f, false);
                     }
                 } break;
                 case OP_REMOVE: {
                     Fragment f = op.fragment;
-                    f.mNextAnim = op.exitAnim;
-                    mManager.removeFragment(f, mTransition, mTransitionStyle);
+                    f.mNextAnim = exitAnim;
+                    mManager.removeFragment(f, transition, transitionStyle);
                 } break;
                 case OP_HIDE: {
                     Fragment f = op.fragment;
-                    f.mNextAnim = op.exitAnim;
-                    mManager.hideFragment(f, mTransition, mTransitionStyle);
+                    f.mNextAnim = exitAnim;
+                    mManager.hideFragment(f, transition, transitionStyle);
                 } break;
                 case OP_SHOW: {
                     Fragment f = op.fragment;
-                    f.mNextAnim = op.enterAnim;
-                    mManager.showFragment(f, mTransition, mTransitionStyle);
+                    f.mNextAnim = enterAnim;
+                    mManager.showFragment(f, transition, transitionStyle);
                 } break;
                 case OP_DETACH: {
                     Fragment f = op.fragment;
-                    f.mNextAnim = op.exitAnim;
-                    mManager.detachFragment(f, mTransition, mTransitionStyle);
+                    f.mNextAnim = exitAnim;
+                    mManager.detachFragment(f, transition, transitionStyle);
                 } break;
                 case OP_ATTACH: {
                     Fragment f = op.fragment;
-                    f.mNextAnim = op.enterAnim;
-                    mManager.attachFragment(f, mTransition, mTransitionStyle);
+                    f.mNextAnim = enterAnim;
+                    mManager.attachFragment(f, transition, transitionStyle);
                 } break;
                 default: {
                     throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
@@ -679,15 +762,66 @@
             op = op.next;
         }
 
-        mManager.moveToState(mManager.mCurState, mTransition,
-                mTransitionStyle, true);
+        mManager.moveToState(mManager.mCurState, transition, transitionStyle, true);
 
         if (mAddToBackStack) {
             mManager.addBackStackState(this);
         }
+
+        if (state != null) {
+            updateTransitionEndState(state, mSharedElementTargetNames);
+        }
     }
 
-    public void popFromBackStack(boolean doStateMove) {
+    private Object beginTransition(ArrayList<String> sourceNames, ArrayList<String> targetNames) {
+        Object state = null;
+        if ((Build.VERSION.SDK_INT >= 21 || Build.VERSION.CODENAME.equals("L"))
+                && mCustomTransition != 0 && mSceneRoot != 0) {
+            View rootView = mManager.mContainer.findViewById(mSceneRoot);
+            if (!(rootView instanceof ViewGroup)) {
+                throw new IllegalArgumentException("SceneRoot is not a ViewGroup");
+            }
+            ArrayList<View> hiddenFragmentViews = new ArrayList<View>();
+            if (mManager.mAdded != null) {
+                for (int i = mManager.mAdded.size() - 1; i >= 0; i--) {
+                    Fragment fragment = mManager.mAdded.get(i);
+                    if (fragment.mView != null) {
+                        if (fragment.mHidden) {
+                            hiddenFragmentViews.add(fragment.mView);
+                        }
+                    }
+                }
+            }
+            state = FragmentTransitionCompat21.beginTransition(mManager.mActivity,
+                    mCustomTransition, (ViewGroup) rootView, hiddenFragmentViews,
+                    sourceNames, targetNames);
+        }
+        return state;
+    }
+
+    private void updateTransitionEndState(Object stateObj, ArrayList<String> targetNames) {
+        if ((Build.VERSION.SDK_INT >= 21 || Build.VERSION.CODENAME.equals("L"))
+                && stateObj != null) {
+            ArrayList<View> hiddenFragmentViews = new ArrayList<View>();
+            ArrayList<View> shownFragmentViews = new ArrayList<View>();
+            if (mManager.mAdded != null) {
+                for (int i = mManager.mAdded.size() - 1; i >= 0; i--) {
+                    Fragment fragment = mManager.mAdded.get(i);
+                    if (fragment.mView != null) {
+                        if (fragment.mHidden) {
+                            hiddenFragmentViews.add(fragment.mView);
+                        } else {
+                            shownFragmentViews.add(fragment.mView);
+                        }
+                    }
+                }
+            }
+            FragmentTransitionCompat21.updateTransitionEndState(mManager.mActivity,
+                    shownFragmentViews, hiddenFragmentViews, stateObj, targetNames);
+        }
+    }
+
+    public Object popFromBackStack(boolean doStateMove, Object state) {
         if (FragmentManagerImpl.DEBUG) {
             Log.v(TAG, "popFromBackStack: " + this);
             LogWriter logw = new LogWriter(TAG);
@@ -695,62 +829,71 @@
             dump("  ", null, pw, null);
         }
 
+        if (state == null) {
+            state = beginTransition(mSharedElementTargetNames, mSharedElementSourceNames);
+        } else if (Build.VERSION.SDK_INT >= 21 || Build.VERSION.CODENAME.equals("L")) {
+            FragmentTransitionCompat21.setNameOverrides(state, mSharedElementTargetNames,
+                    mSharedElementSourceNames);
+        }
+
         bumpBackStackNesting(-1);
 
+        int transitionStyle = state != null ? 0 : mTransitionStyle;
+        int transition = state != null ? 0 : mTransition;
         Op op = mTail;
         while (op != null) {
+            int popEnterAnim = state != null ? 0 : op.popEnterAnim;
+            int popExitAnim= state != null ? 0 : op.popExitAnim;
             switch (op.cmd) {
                 case OP_ADD: {
                     Fragment f = op.fragment;
-                    f.mNextAnim = op.popExitAnim;
+                    f.mNextAnim = popExitAnim;
                     mManager.removeFragment(f,
-                            FragmentManagerImpl.reverseTransit(mTransition),
-                            mTransitionStyle);
+                            FragmentManagerImpl.reverseTransit(transition), transitionStyle);
                 } break;
                 case OP_REPLACE: {
                     Fragment f = op.fragment;
                     if (f != null) {
-                        f.mNextAnim = op.popExitAnim;
+                        f.mNextAnim = popExitAnim;
                         mManager.removeFragment(f,
-                                FragmentManagerImpl.reverseTransit(mTransition),
-                                mTransitionStyle);
+                                FragmentManagerImpl.reverseTransit(transition), transitionStyle);
                     }
                     if (op.removed != null) {
                         for (int i=0; i<op.removed.size(); i++) {
                             Fragment old = op.removed.get(i);
-                            old.mNextAnim = op.popEnterAnim;
+                            old.mNextAnim = popEnterAnim;
                             mManager.addFragment(old, false);
                         }
                     }
                 } break;
                 case OP_REMOVE: {
                     Fragment f = op.fragment;
-                    f.mNextAnim = op.popEnterAnim;
+                    f.mNextAnim = popEnterAnim;
                     mManager.addFragment(f, false);
                 } break;
                 case OP_HIDE: {
                     Fragment f = op.fragment;
-                    f.mNextAnim = op.popEnterAnim;
+                    f.mNextAnim = popEnterAnim;
                     mManager.showFragment(f,
-                            FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
+                            FragmentManagerImpl.reverseTransit(transition), transitionStyle);
                 } break;
                 case OP_SHOW: {
                     Fragment f = op.fragment;
-                    f.mNextAnim = op.popExitAnim;
+                    f.mNextAnim = popExitAnim;
                     mManager.hideFragment(f,
-                            FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
+                            FragmentManagerImpl.reverseTransit(transition), transitionStyle);
                 } break;
                 case OP_DETACH: {
                     Fragment f = op.fragment;
-                    f.mNextAnim = op.popEnterAnim;
+                    f.mNextAnim = popEnterAnim;
                     mManager.attachFragment(f,
-                            FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
+                            FragmentManagerImpl.reverseTransit(transition), transitionStyle);
                 } break;
                 case OP_ATTACH: {
                     Fragment f = op.fragment;
-                    f.mNextAnim = op.popEnterAnim;
+                    f.mNextAnim = popEnterAnim;
                     mManager.detachFragment(f,
-                            FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
+                            FragmentManagerImpl.reverseTransit(transition), transitionStyle);
                 } break;
                 default: {
                     throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
@@ -762,13 +905,18 @@
 
         if (doStateMove) {
             mManager.moveToState(mManager.mCurState,
-                    FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true);
+                    FragmentManagerImpl.reverseTransit(transition), transitionStyle, true);
+            if (state != null) {
+                updateTransitionEndState(state, mSharedElementSourceNames);
+                state = null;
+            }
         }
 
         if (mIndex >= 0) {
             mManager.freeBackStackIndex(mIndex);
             mIndex = -1;
         }
+        return state;
     }
 
     public String getName() {
diff --git a/v4/java/android/support/v4/app/DialogFragment.java b/v4/java/android/support/v4/app/DialogFragment.java
index 8c67bf5..3c7773a 100644
--- a/v4/java/android/support/v4/app/DialogFragment.java
+++ b/v4/java/android/support/v4/app/DialogFragment.java
@@ -21,12 +21,18 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.os.Bundle;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.StyleRes;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowManager;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Static library support version of the framework's {@link android.app.DialogFragment}.
  * Used to write apps that run on platforms prior to Android 3.0.  When running
@@ -37,6 +43,11 @@
 public class DialogFragment extends Fragment
         implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {
 
+    /** @hide */
+    @IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface DialogStyle {}
+
     /**
      * Style for {@link #setStyle(int, int)}: a basic,
      * normal dialog.
@@ -98,7 +109,7 @@
      * @param theme Optional custom theme.  If 0, an appropriate theme (based
      * on the style) will be selected for you.
      */
-    public void setStyle(int style, int theme) {
+    public void setStyle(@DialogStyle int style, @StyleRes int theme) {
         mStyle = style;
         if (mStyle == STYLE_NO_FRAME || mStyle == STYLE_NO_INPUT) {
             mTheme = android.R.style.Theme_Panel;
@@ -195,6 +206,7 @@
         return mDialog;
     }
 
+    @StyleRes
     public int getTheme() {
         return mTheme;
     }
@@ -333,6 +345,7 @@
      * 
      * @return Return a new Dialog instance to be displayed by the Fragment.
      */
+    @NonNull
     public Dialog onCreateDialog(Bundle savedInstanceState) {
         return new Dialog(getActivity(), getTheme());
     }
diff --git a/v4/java/android/support/v4/app/Fragment.java b/v4/java/android/support/v4/app/Fragment.java
index 621bbbb..74447cc 100644
--- a/v4/java/android/support/v4/app/Fragment.java
+++ b/v4/java/android/support/v4/app/Fragment.java
@@ -25,6 +25,8 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
 import android.support.v4.util.SimpleArrayMap;
 import android.support.v4.util.DebugUtils;
 import android.util.AttributeSet;
@@ -609,7 +611,7 @@
      *
      * @param resId Resource id for the CharSequence text
      */
-    public final CharSequence getText(int resId) {
+    public final CharSequence getText(@StringRes int resId) {
         return getResources().getText(resId);
     }
 
@@ -619,7 +621,7 @@
      *
      * @param resId Resource id for the string
      */
-    public final String getString(int resId) {
+    public final String getString(@StringRes int resId) {
         return getResources().getString(resId);
     }
 
@@ -632,7 +634,7 @@
      * @param formatArgs The format arguments that will be used for substitution.
      */
 
-    public final String getString(int resId, Object... formatArgs) {
+    public final String getString(@StringRes int resId, Object... formatArgs) {
         return getResources().getString(resId, formatArgs);
     }
 
@@ -912,7 +914,10 @@
      * inflation.  Maybe this should become a public API. Note sure.
      */
     public LayoutInflater getLayoutInflater(Bundle savedInstanceState) {
-        return mActivity.getLayoutInflater();
+        LayoutInflater result = mActivity.getLayoutInflater().cloneInContext(mActivity);
+        getChildFragmentManager(); // Init if needed; use raw implementation below.
+        result.setFactory(mChildFragmentManager.getLayoutInflaterFactory());
+        return result;
     }
     
     /**
@@ -1013,8 +1018,8 @@
      * 
      * @return Return the View for the fragment's UI, or null.
      */
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
         return null;
     }
 
@@ -1028,7 +1033,7 @@
      * @param savedInstanceState If non-null, this fragment is being re-constructed
      * from a previous saved state as given here.
      */
-    public void onViewCreated(View view, Bundle savedInstanceState) {
+    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
     }
 
     /**
@@ -1037,6 +1042,7 @@
      * 
      * @return The fragment's root view, or null if it has no layout.
      */
+    @Nullable
     public View getView() {
         return mView;
     }
@@ -1054,7 +1060,7 @@
      * @param savedInstanceState If the fragment is being re-created from
      * a previous saved state, this is the state.
      */
-    public void onActivityCreated(Bundle savedInstanceState) {
+    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
         mCalled = true;
     }
 
@@ -1069,7 +1075,7 @@
      * @param savedInstanceState If the fragment is being re-created from
      * a previous saved state, this is the state.
      */
-    public void onViewStateRestored(Bundle savedInstanceState) {
+    public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
         mCalled = true;
     }
 
@@ -1198,6 +1204,7 @@
         mRestored = false;
         mBackStackNesting = 0;
         mFragmentManager = null;
+        mChildFragmentManager = null;
         mActivity = null;
         mFragmentId = 0;
         mContainerId = 0;
diff --git a/v4/java/android/support/v4/app/FragmentActivity.java b/v4/java/android/support/v4/app/FragmentActivity.java
index 596653a..91e61fb 100644
--- a/v4/java/android/support/v4/app/FragmentActivity.java
+++ b/v4/java/android/support/v4/app/FragmentActivity.java
@@ -26,6 +26,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.Parcelable;
+import android.support.annotation.NonNull;
 import android.support.v4.util.SimpleArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -131,15 +132,6 @@
         SimpleArrayMap<String, LoaderManagerImpl> loaders;
     }
     
-    static class FragmentTag {
-        public static final int[] Fragment = {
-            0x01010003, 0x010100d0, 0x010100d1
-        };
-        public static final int Fragment_id = 1;
-        public static final int Fragment_name = 0;
-        public static final int Fragment_tag = 2;
-    }
-    
     // ------------------------------------------------------------------------
     // HOOKS INTO ACTIVITY
     // ------------------------------------------------------------------------
@@ -177,11 +169,65 @@
      */
     public void onBackPressed() {
         if (!mFragments.popBackStackImmediate()) {
-            finish();
+            supportFinishAfterTransition();
         }
     }
 
     /**
+     * Reverses the Activity Scene entry Transition and triggers the calling Activity
+     * to reverse its exit Transition. When the exit Transition completes,
+     * {@link #finish()} is called. If no entry Transition was used, finish() is called
+     * immediately and the Activity exit Transition is run.
+     *
+     * <p>On Android 4.4 or lower, this method only finishes the Activity with no
+     * special exit transition.</p>
+     */
+    public void supportFinishAfterTransition() {
+        ActivityCompat.finishAfterTransition(this);
+    }
+
+    /**
+     * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
+     * android.view.View, String)} was used to start an Activity, <var>listener</var>
+     * will be called to handle shared elements on the <i>launched</i> Activity. This requires
+     * {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+     *
+     * @param listener Used to manipulate shared element transitions on the launched Activity.
+     */
+    public void setEnterSharedElementListener(SharedElementListener listener) {
+        ActivityCompat.setEnterSharedElementListener(this, listener);
+    }
+
+    /**
+     * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
+     * android.view.View, String)} was used to start an Activity, <var>listener</var>
+     * will be called to handle shared elements on the <i>launching</i> Activity. Most
+     * calls will only come when returning from the started Activity.
+     * This requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+     *
+     * @param listener Used to manipulate shared element transitions on the launching Activity.
+     */
+    public void setExitSharedElementListener(SharedElementListener listener) {
+        ActivityCompat.setExitSharedElementListener(this, listener);
+    }
+
+    /**
+     * Support library version of {@link android.app.Activity#postponeEnterTransition()} that works
+     * only on API 21 and later.
+     */
+    public void supportPostponeEnterTransition() {
+        ActivityCompat.postponeEnterTransition(this);
+    }
+
+    /**
+     * Support library version of {@link android.app.Activity#startPostponedEnterTransition()}
+     * that only works with API 21 and later.
+     */
+    public void supportStartPostponedEnterTransition() {
+        ActivityCompat.startPostponedEnterTransition(this);
+    }
+
+    /**
      * Dispatch configuration change to all fragments.
      */
     @Override
@@ -238,89 +284,16 @@
      * Add support for inflating the &lt;fragment> tag.
      */
     @Override
-    public View onCreateView(String name, Context context, AttributeSet attrs) {
+    public View onCreateView(String name, @NonNull Context context, @NonNull AttributeSet attrs) {
         if (!"fragment".equals(name)) {
             return super.onCreateView(name, context, attrs);
         }
-        
-        String fname = attrs.getAttributeValue(null, "class");
-        TypedArray a =  context.obtainStyledAttributes(attrs, FragmentTag.Fragment);
-        if (fname == null) {
-            fname = a.getString(FragmentTag.Fragment_name);
-        }
-        int id = a.getResourceId(FragmentTag.Fragment_id, View.NO_ID);
-        String tag = a.getString(FragmentTag.Fragment_tag);
-        a.recycle();
 
-        if (!Fragment.isSupportFragmentClass(this, fname)) {
-            // Invalid support lib fragment; let the device's framework handle it.
-            // This will allow android.app.Fragments to do the right thing.
+        final View v = mFragments.onCreateView(name, context, attrs);
+        if (v == null) {
             return super.onCreateView(name, context, attrs);
         }
-        
-        View parent = null; // NOTE: no way to get parent pre-Honeycomb.
-        int containerId = parent != null ? parent.getId() : 0;
-        if (containerId == View.NO_ID && id == View.NO_ID && tag == null) {
-            throw new IllegalArgumentException(attrs.getPositionDescription()
-                    + ": Must specify unique android:id, android:tag, or have a parent with an id for " + fname);
-        }
-
-        // If we restored from a previous state, we may already have
-        // instantiated this fragment from the state and should use
-        // that instance instead of making a new one.
-        Fragment fragment = id != View.NO_ID ? mFragments.findFragmentById(id) : null;
-        if (fragment == null && tag != null) {
-            fragment = mFragments.findFragmentByTag(tag);
-        }
-        if (fragment == null && containerId != View.NO_ID) {
-            fragment = mFragments.findFragmentById(containerId);
-        }
-
-        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x"
-                + Integer.toHexString(id) + " fname=" + fname
-                + " existing=" + fragment);
-        if (fragment == null) {
-            fragment = Fragment.instantiate(this, fname);
-            fragment.mFromLayout = true;
-            fragment.mFragmentId = id != 0 ? id : containerId;
-            fragment.mContainerId = containerId;
-            fragment.mTag = tag;
-            fragment.mInLayout = true;
-            fragment.mFragmentManager = mFragments;
-            fragment.onInflate(this, attrs, fragment.mSavedFragmentState);
-            mFragments.addFragment(fragment, true);
-
-        } else if (fragment.mInLayout) {
-            // A fragment already exists and it is not one we restored from
-            // previous state.
-            throw new IllegalArgumentException(attrs.getPositionDescription()
-                    + ": Duplicate id 0x" + Integer.toHexString(id)
-                    + ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId)
-                    + " with another fragment for " + fname);
-        } else {
-            // This fragment was retained from a previous instance; get it
-            // going now.
-            fragment.mInLayout = true;
-            // If this fragment is newly instantiated (either right now, or
-            // from last saved state), then give it the attributes to
-            // initialize itself.
-            if (!fragment.mRetaining) {
-                fragment.onInflate(this, attrs, fragment.mSavedFragmentState);
-            }
-            mFragments.moveToState(fragment);
-        }
-
-        if (fragment.mView == null) {
-            throw new IllegalStateException("Fragment " + fname
-                    + " did not create a view.");
-        }
-        if (id != 0) {
-            fragment.mView.setId(id);
-        }
-        if (fragment.mView.getTag() == null) {
-            fragment.mView.setTag(tag);
-        }
-        return fragment.mView;
+        return v;
     }
 
     /**
diff --git a/v4/java/android/support/v4/app/FragmentManager.java b/v4/java/android/support/v4/app/FragmentManager.java
index f07d3dd..65ec3a1 100644
--- a/v4/java/android/support/v4/app/FragmentManager.java
+++ b/v4/java/android/support/v4/app/FragmentManager.java
@@ -18,15 +18,20 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
+import android.content.res.TypedArray;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.annotation.IdRes;
+import android.support.annotation.StringRes;
 import android.support.v4.util.DebugUtils;
 import android.support.v4.util.LogWriter;
+import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
+import android.view.LayoutInflater;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
@@ -90,12 +95,14 @@
          * Return the full bread crumb title resource identifier for the entry,
          * or 0 if it does not have one.
          */
+        @StringRes
         public int getBreadCrumbTitleRes();
 
         /**
          * Return the short bread crumb title resource identifier for the entry,
          * or 0 if it does not have one.
          */
+        @StringRes
         public int getBreadCrumbShortTitleRes();
 
         /**
@@ -164,7 +171,7 @@
      * on the back stack associated with this ID are searched.
      * @return The fragment if found or null otherwise.
      */
-    public abstract Fragment findFragmentById(int id);
+    public abstract Fragment findFragmentById(@IdRes int id);
 
     /**
      * Finds a fragment that was identified by the given tag either when inflated
@@ -392,13 +399,13 @@
  * Callbacks from FragmentManagerImpl to its container.
  */
 interface FragmentContainer {
-    public View findViewById(int id);
+    public View findViewById(@IdRes int id);
 }
 
 /**
  * Container for fragments associated with an activity.
  */
-final class FragmentManagerImpl extends FragmentManager {
+final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory {
     static boolean DEBUG = false;
     static final String TAG = "FragmentManager";
     
@@ -1079,7 +1086,9 @@
                                     makeInactive(f);
                                 } else {
                                     f.mActivity = null;
+                                    f.mParentFragment = null;
                                     f.mFragmentManager = null;
+                                    f.mChildFragmentManager = null;
                                 }
                             }
                         }
@@ -1525,7 +1534,7 @@
                 return false;
             }
             final BackStackRecord bss = mBackStack.remove(last);
-            bss.popFromBackStack(true);
+            bss.popFromBackStack(true, null);
             reportBackStackChanged();
         } else {
             int index = -1;
@@ -1569,9 +1578,10 @@
                 states.add(mBackStack.remove(i));
             }
             final int LAST = states.size()-1;
+            Object transitionState = null;
             for (int i=0; i<=LAST; i++) {
                 if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i));
-                states.get(i).popFromBackStack(i == LAST);
+                transitionState = states.get(i).popFromBackStack(i == LAST, transitionState);
             }
             reportBackStackChanged();
         }
@@ -1786,6 +1796,7 @@
                     fs.mSavedFragmentState.setClassLoader(mActivity.getClassLoader());
                     f.mSavedViewState = fs.mSavedFragmentState.getSparseParcelableArray(
                             FragmentManagerImpl.VIEW_STATE_TAG);
+                    f.mSavedFragmentState = fs.mSavedFragmentState;
                 }
             }
         }
@@ -2083,4 +2094,110 @@
         }
         return animAttr;
     }
+
+    @Override
+    public View onCreateView(String name, Context context, AttributeSet attrs) {
+        if (!"fragment".equals(name)) {
+            return null;
+        }
+
+        String fname = attrs.getAttributeValue(null, "class");
+        TypedArray a =  context.obtainStyledAttributes(attrs, FragmentTag.Fragment);
+        if (fname == null) {
+            fname = a.getString(FragmentTag.Fragment_name);
+        }
+        int id = a.getResourceId(FragmentTag.Fragment_id, View.NO_ID);
+        String tag = a.getString(FragmentTag.Fragment_tag);
+        a.recycle();
+
+        if (!Fragment.isSupportFragmentClass(mActivity, fname)) {
+            // Invalid support lib fragment; let the device's framework handle it.
+            // This will allow android.app.Fragments to do the right thing.
+            return null;
+        }
+
+        View parent = null; // NOTE: no way to get parent pre-Honeycomb.
+        int containerId = parent != null ? parent.getId() : 0;
+        if (containerId == View.NO_ID && id == View.NO_ID && tag == null) {
+            throw new IllegalArgumentException(attrs.getPositionDescription()
+                    + ": Must specify unique android:id, android:tag, or have a parent with an id for " + fname);
+        }
+
+        // If we restored from a previous state, we may already have
+        // instantiated this fragment from the state and should use
+        // that instance instead of making a new one.
+        Fragment fragment = id != View.NO_ID ? findFragmentById(id) : null;
+        if (fragment == null && tag != null) {
+            fragment = findFragmentByTag(tag);
+        }
+        if (fragment == null && containerId != View.NO_ID) {
+            fragment = findFragmentById(containerId);
+        }
+
+        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x"
+                + Integer.toHexString(id) + " fname=" + fname
+                + " existing=" + fragment);
+        if (fragment == null) {
+            fragment = Fragment.instantiate(context, fname);
+            fragment.mFromLayout = true;
+            fragment.mFragmentId = id != 0 ? id : containerId;
+            fragment.mContainerId = containerId;
+            fragment.mTag = tag;
+            fragment.mInLayout = true;
+            fragment.mFragmentManager = this;
+            fragment.onInflate(mActivity, attrs, fragment.mSavedFragmentState);
+            addFragment(fragment, true);
+
+        } else if (fragment.mInLayout) {
+            // A fragment already exists and it is not one we restored from
+            // previous state.
+            throw new IllegalArgumentException(attrs.getPositionDescription()
+                    + ": Duplicate id 0x" + Integer.toHexString(id)
+                    + ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId)
+                    + " with another fragment for " + fname);
+        } else {
+            // This fragment was retained from a previous instance; get it
+            // going now.
+            fragment.mInLayout = true;
+            // If this fragment is newly instantiated (either right now, or
+            // from last saved state), then give it the attributes to
+            // initialize itself.
+            if (!fragment.mRetaining) {
+                fragment.onInflate(mActivity, attrs, fragment.mSavedFragmentState);
+            }
+        }
+
+        // If we haven't finished entering the CREATED state ourselves yet,
+        // push the inflated child fragment along.
+        if (mCurState < Fragment.CREATED && fragment.mFromLayout) {
+            moveToState(fragment, Fragment.CREATED, 0, 0, false);
+        } else {
+            moveToState(fragment);
+        }
+
+        if (fragment.mView == null) {
+            throw new IllegalStateException("Fragment " + fname
+                    + " did not create a view.");
+        }
+        if (id != 0) {
+            fragment.mView.setId(id);
+        }
+        if (fragment.mView.getTag() == null) {
+            fragment.mView.setTag(tag);
+        }
+        return fragment.mView;
+    }
+
+    LayoutInflater.Factory getLayoutInflaterFactory() {
+        return this;
+    }
+
+    static class FragmentTag {
+        public static final int[] Fragment = {
+                0x01010003, 0x010100d0, 0x010100d1
+        };
+        public static final int Fragment_id = 1;
+        public static final int Fragment_name = 0;
+        public static final int Fragment_tag = 2;
+    }
 }
diff --git a/v4/java/android/support/v4/app/FragmentTransaction.java b/v4/java/android/support/v4/app/FragmentTransaction.java
index 23fedf9..41f64ce 100644
--- a/v4/java/android/support/v4/app/FragmentTransaction.java
+++ b/v4/java/android/support/v4/app/FragmentTransaction.java
@@ -16,6 +16,18 @@
 
 package android.support.v4.app;
 
+import android.support.annotation.AnimRes;
+import android.support.annotation.IdRes;
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
+import android.support.annotation.StyleRes;
+import android.support.v4.util.Pair;
+import android.view.View;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Static library support version of the framework's {@link android.app.FragmentTransaction}.
  * Used to write apps that run on platforms prior to Android 3.0.  When running
@@ -32,7 +44,7 @@
     /**
      * Calls {@link #add(int, Fragment, String)} with a null tag.
      */
-    public abstract FragmentTransaction add(int containerViewId, Fragment fragment);
+    public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment);
     
     /**
      * Add a fragment to the activity state.  This fragment may optionally
@@ -49,12 +61,13 @@
      * 
      * @return Returns the same FragmentTransaction instance.
      */
-    public abstract FragmentTransaction add(int containerViewId, Fragment fragment, String tag);
+    public abstract FragmentTransaction add(@IdRes int containerViewId, Fragment fragment,
+            @Nullable String tag);
     
     /**
      * Calls {@link #replace(int, Fragment, String)} with a null tag.
      */
-    public abstract FragmentTransaction replace(int containerViewId, Fragment fragment);
+    public abstract FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment);
     
     /**
      * Replace an existing fragment that was added to a container.  This is
@@ -72,7 +85,8 @@
      * 
      * @return Returns the same FragmentTransaction instance.
      */
-    public abstract FragmentTransaction replace(int containerViewId, Fragment fragment, String tag);
+    public abstract FragmentTransaction replace(@IdRes int containerViewId, Fragment fragment,
+            @Nullable String tag);
     
     /**
      * Remove an existing fragment.  If it was added to a container, its view
@@ -146,7 +160,12 @@
      * Bit mask that is set for all exit transitions.
      */
     public static final int TRANSIT_EXIT_MASK = 0x2000;
-    
+
+    /** @hide */
+    @IntDef({TRANSIT_NONE, TRANSIT_FRAGMENT_OPEN, TRANSIT_FRAGMENT_CLOSE})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface Transit {}
+
     /** Not set up for a transition. */
     public static final int TRANSIT_UNSET = -1;
     /** No animation for transition. */
@@ -164,7 +183,8 @@
      * entering and exiting in this transaction. These animations will not be
      * played when popping the back stack.
      */
-    public abstract FragmentTransaction setCustomAnimations(int enter, int exit);
+    public abstract FragmentTransaction setCustomAnimations(@AnimRes int enter,
+            @AnimRes int exit);
 
     /**
      * Set specific animation resources to run for the fragments that are
@@ -172,21 +192,51 @@
      * and <code>popExit</code> animations will be played for enter/exit
      * operations specifically when popping the back stack.
      */
-    public abstract FragmentTransaction setCustomAnimations(int enter, int exit,
-            int popEnter, int popExit);
-    
+    public abstract FragmentTransaction setCustomAnimations(@AnimRes int enter,
+            @AnimRes int exit, @AnimRes int popEnter, @AnimRes int popExit);
+
+    /**
+     * Set a {@link android.transition.Transition} resource id to use with this transaction.
+     * <var>transitionId</var> will be played for fragments when going forward and when popping
+     * the back stack.
+     * @param sceneRootId The ID of the element acting as the scene root for the transition.
+     *                    This should be a ViewGroup containing all Fragments in the transaction.
+     * @param transitionId The resource ID for the Transition used during the Fragment transaction.
+     */
+    public abstract FragmentTransaction setCustomTransition(int sceneRootId, int transitionId);
+
+    /**
+     * Used with {@link #setCustomTransition(int, int)} to map a View from a removed or hidden
+     * Fragment to a View from a shown or added Fragment.
+     * <var>sharedElement</var> must have a unique transitionName in the View hierarchy.
+     * @param sharedElement A View in a disappearing Fragment to match with a View in an
+     *                      appearing Fragment.
+     * @param name The transitionName for a View in an appearing Fragment to match to the shared
+     *             element.
+     */
+    public abstract FragmentTransaction setSharedElement(View sharedElement, String name);
+
+    /**
+     * Used with {@link #setCustomTransition(int, int)} to map multiple Views from removed or hidden
+     * Fragments to a Views from a shown or added Fragments. Views in
+     * <var>sharedElements</var> must have unique transitionNames in the View hierarchy.
+     * @param sharedElements Pairs of Views in disappearing Fragments to transitionNames in
+     *                       appearing Fragments.
+     */
+    public abstract FragmentTransaction setSharedElements(Pair<View, String>... sharedElements);
+
     /**
      * Select a standard transition animation for this transaction.  May be
      * one of {@link #TRANSIT_NONE}, {@link #TRANSIT_FRAGMENT_OPEN},
      * or {@link #TRANSIT_FRAGMENT_CLOSE}
      */
-    public abstract FragmentTransaction setTransition(int transit);
+    public abstract FragmentTransaction setTransition(@Transit int transit);
 
     /**
      * Set a custom style resource that will be used for resolving transit
      * animations.
      */
-    public abstract FragmentTransaction setTransitionStyle(int styleRes);
+    public abstract FragmentTransaction setTransitionStyle(@StyleRes int styleRes);
     
     /**
      * Add this transaction to the back stack.  This means that the transaction
@@ -195,7 +245,7 @@
      *
      * @param name An optional name for this back stack state, or null.
      */
-    public abstract FragmentTransaction addToBackStack(String name);
+    public abstract FragmentTransaction addToBackStack(@Nullable String name);
 
     /**
      * Returns true if this FragmentTransaction is allowed to be added to the back
@@ -219,7 +269,7 @@
      *
      * @param res A string resource containing the title.
      */
-    public abstract FragmentTransaction setBreadCrumbTitle(int res);
+    public abstract FragmentTransaction setBreadCrumbTitle(@StringRes int res);
 
     /**
      * Like {@link #setBreadCrumbTitle(int)} but taking a raw string; this
@@ -234,7 +284,7 @@
      *
      * @param res A string resource containing the title.
      */
-    public abstract FragmentTransaction setBreadCrumbShortTitle(int res);
+    public abstract FragmentTransaction setBreadCrumbShortTitle(@StringRes int res);
 
     /**
      * Like {@link #setBreadCrumbShortTitle(int)} but taking a raw string; this
diff --git a/v4/java/android/support/v4/app/INotificationSideChannel.aidl b/v4/java/android/support/v4/app/INotificationSideChannel.aidl
new file mode 100644
index 0000000..9df1577
--- /dev/null
+++ b/v4/java/android/support/v4/app/INotificationSideChannel.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.Notification;
+
+/**
+ * Interface used for delivering notifications via a side channel that bypasses
+ * the NotificationManagerService.
+ *
+ * @hide
+ */
+oneway interface INotificationSideChannel {
+    /**
+     * Send an ambient notification to the service.
+     */
+    void notify(String packageName, int id, String tag, in Notification notification);
+
+    /**
+     * Cancel an already-notified notification.
+     */
+    void cancel(String packageName, int id, String tag);
+
+    /**
+     * Cancel all notifications for the given package.
+     */
+    void cancelAll(String packageName);
+}
diff --git a/v4/java/android/support/v4/app/NavUtils.java b/v4/java/android/support/v4/app/NavUtils.java
index ea034be..841bc56 100644
--- a/v4/java/android/support/v4/app/NavUtils.java
+++ b/v4/java/android/support/v4/app/NavUtils.java
@@ -23,6 +23,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.support.annotation.Nullable;
 import android.support.v4.content.IntentCompat;
 import android.util.Log;
 
@@ -274,6 +275,7 @@
      * @return The fully qualified class name of sourceActivity's parent activity or null if
      *         it was not specified
      */
+    @Nullable
     public static String getParentActivityName(Activity sourceActivity) {
         try {
             return getParentActivityName(sourceActivity, sourceActivity.getComponentName());
@@ -292,6 +294,7 @@
      * @return The fully qualified class name of sourceActivity's parent activity or null if
      *         it was not specified
      */
+    @Nullable
     public static String getParentActivityName(Context context, ComponentName componentName)
             throws NameNotFoundException {
         PackageManager pm = context.getPackageManager();
diff --git a/v4/java/android/support/v4/app/NotificationCompat.java b/v4/java/android/support/v4/app/NotificationCompat.java
index f2bc034..c3e6c5b 100644
--- a/v4/java/android/support/v4/app/NotificationCompat.java
+++ b/v4/java/android/support/v4/app/NotificationCompat.java
@@ -17,27 +17,142 @@
 package android.support.v4.app;
 
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.media.AudioManager;
 import android.net.Uri;
 import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.support.v4.view.GravityCompat;
+import android.view.Gravity;
 import android.widget.RemoteViews;
+
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Helper for accessing features in {@link android.app.Notification}
  * introduced after API level 4 in a backwards compatible fashion.
  */
 public class NotificationCompat {
+
+    /**
+     * Use all default values (where applicable).
+     */
+    public static final int DEFAULT_ALL = ~0;
+
+    /**
+     * Use the default notification sound. This will ignore any sound set using
+     * {@link Builder#setSound}
+     *
+     * <p>
+     * A notification that is noisy is more likely to be presented as a heads-up notification,
+     * on some platforms.
+     * </p>
+     *
+     * @see Builder#setDefaults
+     */
+    public static final int DEFAULT_SOUND = 1;
+
+    /**
+     * Use the default notification vibrate. This will ignore any vibrate set using
+     * {@link Builder#setVibrate}. Using phone vibration requires the
+     * {@link android.Manifest.permission#VIBRATE VIBRATE} permission.
+     *
+     * <p>
+     * A notification that vibrates is more likely to be presented as a heads-up notification,
+     * on some platforms.
+     * </p>
+     *
+     * @see Builder#setDefaults
+     */
+    public static final int DEFAULT_VIBRATE = 2;
+
+    /**
+     * Use the default notification lights. This will ignore the
+     * {@link #FLAG_SHOW_LIGHTS} bit, and values set with {@link Builder#setLights}.
+     *
+     * @see Builder#setDefaults
+     */
+    public static final int DEFAULT_LIGHTS = 4;
+
+    /**
+     * Use this constant as the value for audioStreamType to request that
+     * the default stream type for notifications be used.  Currently the
+     * default stream type is {@link AudioManager#STREAM_NOTIFICATION}.
+     */
+    public static final int STREAM_DEFAULT = -1;
+
+    /**
+     * Bit set in the Notification flags field when LEDs should be turned on
+     * for this notification.
+     */
+    public static final int FLAG_SHOW_LIGHTS        = 0x00000001;
+
+    /**
+     * Bit set in the Notification flags field if this notification is in
+     * reference to something that is ongoing, like a phone call.  It should
+     * not be set if this notification is in reference to something that
+     * happened at a particular point in time, like a missed phone call.
+     */
+    public static final int FLAG_ONGOING_EVENT      = 0x00000002;
+
+    /**
+     * Bit set in the Notification flags field if
+     * the audio will be repeated until the notification is
+     * cancelled or the notification window is opened.
+     */
+    public static final int FLAG_INSISTENT          = 0x00000004;
+
+    /**
+     * Bit set in the Notification flags field if the notification's sound,
+     * vibrate and ticker should only be played if the notification is not already showing.
+     */
+    public static final int FLAG_ONLY_ALERT_ONCE    = 0x00000008;
+
+    /**
+     * Bit set in the Notification flags field if the notification should be canceled when
+     * it is clicked by the user.
+     */
+    public static final int FLAG_AUTO_CANCEL        = 0x00000010;
+
+    /**
+     * Bit set in the Notification flags field if the notification should not be canceled
+     * when the user clicks the Clear all button.
+     */
+    public static final int FLAG_NO_CLEAR           = 0x00000020;
+
+    /**
+     * Bit set in the Notification flags field if this notification represents a currently
+     * running service.  This will normally be set for you by
+     * {@link android.app.Service#startForeground}.
+     */
+    public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;
+
     /**
      * Obsolete flag indicating high-priority notifications; use the priority field instead.
      *
      * @deprecated Use {@link NotificationCompat.Builder#setPriority(int)} with a positive value.
      */
-    public static final int FLAG_HIGH_PRIORITY = 0x00000080;
+    public static final int FLAG_HIGH_PRIORITY      = 0x00000080;
+
+    /**
+     * Bit set in the Notification flags field if this notification is relevant to the current
+     * device only and it is not recommended that it bridge to other devices.
+     */
+    public static final int FLAG_LOCAL_ONLY         = 0x00000100;
+
+    /**
+     * Bit set in the Notification flags field if this notification is the group summary for a
+     * group of notifications. Grouped notifications may display in a cluster or stack on devices
+     * which support such rendering. Requires a group key also be set using
+     * {@link Builder#setGroup}.
+     */
+    public static final int FLAG_GROUP_SUMMARY      = 0x00000200;
 
     /**
      * Default notification priority for {@link NotificationCompat.Builder#setPriority(int)}.
@@ -76,15 +191,236 @@
      */
     public static final int PRIORITY_MAX = 2;
 
+    /**
+     * Notification extras key: this is the title of the notification,
+     * as supplied to {@link Builder#setContentTitle(CharSequence)}.
+     */
+    public static final String EXTRA_TITLE = "android.title";
+
+    /**
+     * Notification extras key: this is the title of the notification when shown in expanded form,
+     * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}.
+     */
+    public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big";
+
+    /**
+     * Notification extras key: this is the main text payload, as supplied to
+     * {@link Builder#setContentText(CharSequence)}.
+     */
+    public static final String EXTRA_TEXT = "android.text";
+
+    /**
+     * Notification extras key: this is a third line of text, as supplied to
+     * {@link Builder#setSubText(CharSequence)}.
+     */
+    public static final String EXTRA_SUB_TEXT = "android.subText";
+
+    /**
+     * Notification extras key: this is a small piece of additional text as supplied to
+     * {@link Builder#setContentInfo(CharSequence)}.
+     */
+    public static final String EXTRA_INFO_TEXT = "android.infoText";
+
+    /**
+     * Notification extras key: this is a line of summary information intended to be shown
+     * alongside expanded notifications, as supplied to (e.g.)
+     * {@link BigTextStyle#setSummaryText(CharSequence)}.
+     */
+    public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
+
+    /**
+     * Notification extras key: this is the resource ID of the notification's main small icon, as
+     * supplied to {@link Builder#setSmallIcon(int)}.
+     */
+    public static final String EXTRA_SMALL_ICON = "android.icon";
+
+    /**
+     * Notification extras key: this is a bitmap to be used instead of the small icon when showing the
+     * notification payload, as
+     * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}.
+     */
+    public static final String EXTRA_LARGE_ICON = "android.largeIcon";
+
+    /**
+     * Notification extras key: this is a bitmap to be used instead of the one from
+     * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is
+     * shown in its expanded form, as supplied to
+     * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}.
+     */
+    public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big";
+
+    /**
+     * Notification extras key: this is the progress value supplied to
+     * {@link Builder#setProgress(int, int, boolean)}.
+     */
+    public static final String EXTRA_PROGRESS = "android.progress";
+
+    /**
+     * Notification extras key: this is the maximum value supplied to
+     * {@link Builder#setProgress(int, int, boolean)}.
+     */
+    public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
+
+    /**
+     * Notification extras key: whether the progress bar is indeterminate, supplied to
+     * {@link Builder#setProgress(int, int, boolean)}.
+     */
+    public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
+
+    /**
+     * Notification extras key: whether the when field set using {@link Builder#setWhen} should
+     * be shown as a count-up timer (specifically a {@link android.widget.Chronometer}) instead
+     * of a timestamp, as supplied to {@link Builder#setUsesChronometer(boolean)}.
+     */
+    public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
+
+    /**
+     * Notification extras key: this is a bitmap to be shown in {@link BigPictureStyle} expanded
+     * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}.
+     */
+    public static final String EXTRA_PICTURE = "android.picture";
+
+    /**
+     * Notification extras key: An array of CharSequences to show in {@link InboxStyle} expanded
+     * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
+     */
+    public static final String EXTRA_TEXT_LINES = "android.textLines";
+
+    /**
+     * Notification extras key: An array of people that this notification relates to, specified
+     * by contacts provider contact URI.
+     */
+    public static final String EXTRA_PEOPLE = "android.people";
+
+    /**
+     * Notification extras key: the indices of actions to be shown in the compact view,
+     * as supplied to (e.g.) {@link Notification.MediaStyle#setShowActionsInCompactView(int...)}.
+     */
+    public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
+
+    /**
+     * Value of {@link Notification#color} equal to 0 (also known as
+     * {@link android.graphics.Color#TRANSPARENT Color.TRANSPARENT}),
+     * telling the system not to decorate this notification with any special color but instead use
+     * default colors when presenting this notification.
+     */
+    public static final int COLOR_DEFAULT = Color.TRANSPARENT;
+
+    /**
+     * Notification visibility: Show this notification in its entirety on all lockscreens.
+     *
+     * {@see android.app.Notification#visibility}
+     */
+    public static final int VISIBILITY_PUBLIC = 1;
+
+    /**
+     * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or
+     * private information on secure lockscreens.
+     *
+     * {@see android.app.Notification#visibility}
+     */
+    public static final int VISIBILITY_PRIVATE = 0;
+
+    /**
+     * Notification visibility: Do not reveal any part of this notification on a secure lockscreen.
+     *
+     * {@see android.app.Notification#visibility}
+     */
+    public static final int VISIBILITY_SECRET = -1;
+
+    /**
+     * Notification category: incoming call (voice or video) or similar synchronous communication request.
+     */
+    public static final String CATEGORY_CALL = NotificationCompatApi21.CATEGORY_CALL;
+
+    /**
+     * Notification category: incoming direct message (SMS, instant message, etc.).
+     */
+    public static final String CATEGORY_MESSAGE = NotificationCompatApi21.CATEGORY_MESSAGE;
+
+    /**
+     * Notification category: asynchronous bulk message (email).
+     */
+    public static final String CATEGORY_EMAIL = NotificationCompatApi21.CATEGORY_EMAIL;
+
+    /**
+     * Notification category: calendar event.
+     */
+    public static final String CATEGORY_EVENT = NotificationCompatApi21.CATEGORY_EVENT;
+
+    /**
+     * Notification category: promotion or advertisement.
+     */
+    public static final String CATEGORY_PROMO = NotificationCompatApi21.CATEGORY_PROMO;
+
+    /**
+     * Notification category: alarm or timer.
+     */
+    public static final String CATEGORY_ALARM = NotificationCompatApi21.CATEGORY_ALARM;
+
+    /**
+     * Notification category: progress of a long-running background operation.
+     */
+    public static final String CATEGORY_PROGRESS = NotificationCompatApi21.CATEGORY_PROGRESS;
+
+    /**
+     * Notification category: social network or sharing update.
+     */
+    public static final String CATEGORY_SOCIAL = NotificationCompatApi21.CATEGORY_SOCIAL;
+
+    /**
+     * Notification category: error in background operation or authentication status.
+     */
+    public static final String CATEGORY_ERROR = NotificationCompatApi21.CATEGORY_ERROR;
+
+    /**
+     * Notification category: media transport control for playback.
+     */
+    public static final String CATEGORY_TRANSPORT = NotificationCompatApi21.CATEGORY_TRANSPORT;
+
+    /**
+     * Notification category: system or device status update.  Reserved for system use.
+     */
+    public static final String CATEGORY_SYSTEM = NotificationCompatApi21.CATEGORY_SYSTEM;
+
+    /**
+     * Notification category: indication of running background service.
+     */
+    public static final String CATEGORY_SERVICE = NotificationCompatApi21.CATEGORY_SERVICE;
+
+    /**
+     * Notification category: a specific, timely recommendation for a single thing.
+     * For example, a news app might want to recommend a news story it believes the user will
+     * want to read next.
+     */
+    public static final String CATEGORY_RECOMMENDATION =
+            NotificationCompatApi21.CATEGORY_RECOMMENDATION;
+
+    /**
+     * Notification category: ongoing information about device or contextual status.
+     */
+    public static final String CATEGORY_STATUS = NotificationCompatApi21.CATEGORY_STATUS;
+
     private static final NotificationCompatImpl IMPL;
 
     interface NotificationCompatImpl {
         public Notification build(Builder b);
+        public Bundle getExtras(Notification n);
+        public int getActionCount(Notification n);
+        public Action getAction(Notification n, int actionIndex);
+        public Action[] getActionsFromParcelableArrayList(ArrayList<Parcelable> parcelables);
+        public ArrayList<Parcelable> getParcelableArrayListForActions(Action[] actions);
+        public String getCategory(Notification n);
+        public boolean getLocalOnly(Notification n);
+        public String getGroup(Notification n);
+        public boolean isGroupSummary(Notification n);
+        public String getSortKey(Notification n);
     }
 
     static class NotificationCompatImplBase implements NotificationCompatImpl {
+        @Override
         public Notification build(Builder b) {
-            Notification result = (Notification) b.mNotification;
+            Notification result = b.mNotification;
             result.setLatestEventInfo(b.mContext, b.mContentTitle,
                     b.mContentText, b.mContentIntent);
             // translate high priority requests into legacy flag
@@ -93,11 +429,63 @@
             }
             return result;
         }
+
+        @Override
+        public Bundle getExtras(Notification n) {
+            return null;
+        }
+
+        @Override
+        public int getActionCount(Notification n) {
+            return 0;
+        }
+
+        @Override
+        public Action getAction(Notification n, int actionIndex) {
+            return null;
+        }
+
+        @Override
+        public Action[] getActionsFromParcelableArrayList(
+                ArrayList<Parcelable> parcelables) {
+            return null;
+        }
+
+        @Override
+        public ArrayList<Parcelable> getParcelableArrayListForActions(Action[] actions) {
+            return null;
+        }
+
+        @Override
+        public String getCategory(Notification n) {
+            return null;
+        }
+
+        @Override
+        public boolean getLocalOnly(Notification n) {
+            return false;
+        }
+
+        @Override
+        public String getGroup(Notification n) {
+            return null;
+        }
+
+        @Override
+        public boolean isGroupSummary(Notification n) {
+            return false;
+        }
+
+        @Override
+        public String getSortKey(Notification n) {
+            return null;
+        }
     }
 
     static class NotificationCompatImplGingerbread extends NotificationCompatImplBase {
+        @Override
         public Notification build(Builder b) {
-            Notification result = (Notification) b.mNotification;
+            Notification result = b.mNotification;
             result.setLatestEventInfo(b.mContext, b.mContentTitle,
                     b.mContentText, b.mContentIntent);
             result = NotificationCompatGingerbread.add(result, b.mContext,
@@ -110,7 +498,8 @@
         }
     }
 
-    static class NotificationCompatImplHoneycomb implements NotificationCompatImpl {
+    static class NotificationCompatImplHoneycomb extends NotificationCompatImplBase {
+        @Override
         public Notification build(Builder b) {
             return NotificationCompatHoneycomb.add(b.mContext, b.mNotification,
                     b.mContentTitle, b.mContentText, b.mContentInfo, b.mTickerView,
@@ -118,7 +507,8 @@
         }
     }
 
-    static class NotificationCompatImplIceCreamSandwich implements NotificationCompatImpl {
+    static class NotificationCompatImplIceCreamSandwich extends NotificationCompatImplBase {
+        @Override
         public Notification build(Builder b) {
             return NotificationCompatIceCreamSandwich.add(b.mContext, b.mNotification,
                     b.mContentTitle, b.mContentText, b.mContentInfo, b.mTickerView,
@@ -127,45 +517,242 @@
         }
     }
 
-    static class NotificationCompatImplJellybean implements NotificationCompatImpl {
+    static class NotificationCompatImplJellybean extends NotificationCompatImplBase {
+        @Override
         public Notification build(Builder b) {
-            NotificationCompatJellybean jbBuilder = new NotificationCompatJellybean(
+            NotificationCompatJellybean.Builder builder = new NotificationCompatJellybean.Builder(
                     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);
-            for (Action action: b.mActions) {
-                jbBuilder.addAction(action.icon, action.title, action.actionIntent);
+                    b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly, b.mExtras,
+                    b.mGroupKey, b.mGroupSummary, b.mSortKey);
+            addActionsToBuilder(builder, b.mActions);
+            addStyleToBuilderJellybean(builder, b.mStyle);
+            return builder.build();
+        }
+
+        @Override
+        public Bundle getExtras(Notification n) {
+            return NotificationCompatJellybean.getExtras(n);
+        }
+
+        @Override
+        public int getActionCount(Notification n) {
+            return NotificationCompatJellybean.getActionCount(n);
+        }
+
+        @Override
+        public Action getAction(Notification n, int actionIndex) {
+            return (Action) NotificationCompatJellybean.getAction(n, actionIndex, Action.FACTORY,
+                    RemoteInput.FACTORY);
+        }
+
+        @Override
+        public Action[] getActionsFromParcelableArrayList(
+                ArrayList<Parcelable> parcelables) {
+            return (Action[]) NotificationCompatJellybean.getActionsFromParcelableArrayList(
+                    parcelables, Action.FACTORY, RemoteInput.FACTORY);
+        }
+
+        @Override
+        public ArrayList<Parcelable> getParcelableArrayListForActions(
+                Action[] actions) {
+            return NotificationCompatJellybean.getParcelableArrayListForActions(actions);
+        }
+
+        @Override
+        public boolean getLocalOnly(Notification n) {
+            return NotificationCompatJellybean.getLocalOnly(n);
+        }
+
+        @Override
+        public String getGroup(Notification n) {
+            return NotificationCompatJellybean.getGroup(n);
+        }
+
+        @Override
+        public boolean isGroupSummary(Notification n) {
+            return NotificationCompatJellybean.isGroupSummary(n);
+        }
+
+        @Override
+        public String getSortKey(Notification n) {
+            return NotificationCompatJellybean.getSortKey(n);
+        }
+    }
+
+    static class NotificationCompatImplKitKat extends NotificationCompatImplJellybean {
+        @Override
+        public Notification build(Builder b) {
+            NotificationCompatKitKat.Builder builder = new NotificationCompatKitKat.Builder(
+                    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.mPeople, b.mExtras,
+                    b.mGroupKey, b.mGroupSummary, b.mSortKey);
+            addActionsToBuilder(builder, b.mActions);
+            addStyleToBuilderJellybean(builder, b.mStyle);
+            return builder.build();
+        }
+
+        @Override
+        public Bundle getExtras(Notification n) {
+            return NotificationCompatKitKat.getExtras(n);
+        }
+
+        @Override
+        public int getActionCount(Notification n) {
+            return NotificationCompatKitKat.getActionCount(n);
+        }
+
+        @Override
+        public Action getAction(Notification n, int actionIndex) {
+            return (Action) NotificationCompatKitKat.getAction(n, actionIndex, Action.FACTORY,
+                    RemoteInput.FACTORY);
+        }
+
+        @Override
+        public boolean getLocalOnly(Notification n) {
+            return NotificationCompatKitKat.getLocalOnly(n);
+        }
+
+        @Override
+        public String getGroup(Notification n) {
+            return NotificationCompatKitKat.getGroup(n);
+        }
+
+        @Override
+        public boolean isGroupSummary(Notification n) {
+            return NotificationCompatKitKat.isGroupSummary(n);
+        }
+
+        @Override
+        public String getSortKey(Notification n) {
+            return NotificationCompatKitKat.getSortKey(n);
+        }
+    }
+
+    static class NotificationCompatImplApi20 extends NotificationCompatImplKitKat {
+        @Override
+        public Notification build(Builder b) {
+            NotificationCompatApi20.Builder builder = new NotificationCompatApi20.Builder(
+                    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.mPeople, b.mExtras,
+                    b.mGroupKey, b.mGroupSummary, b.mSortKey);
+            addActionsToBuilder(builder, b.mActions);
+            addStyleToBuilderJellybean(builder, b.mStyle);
+            return builder.build();
+        }
+
+        @Override
+        public Action getAction(Notification n, int actionIndex) {
+            return (Action) NotificationCompatApi20.getAction(n, actionIndex, Action.FACTORY,
+                    RemoteInput.FACTORY);
+        }
+
+        @Override
+        public Action[] getActionsFromParcelableArrayList(
+                ArrayList<Parcelable> parcelables) {
+            return (Action[]) NotificationCompatApi20.getActionsFromParcelableArrayList(
+                    parcelables, Action.FACTORY, RemoteInput.FACTORY);
+        }
+
+        @Override
+        public ArrayList<Parcelable> getParcelableArrayListForActions(
+                Action[] actions) {
+            return NotificationCompatApi20.getParcelableArrayListForActions(actions);
+        }
+
+        @Override
+        public boolean getLocalOnly(Notification n) {
+            return NotificationCompatApi20.getLocalOnly(n);
+        }
+
+        @Override
+        public String getGroup(Notification n) {
+            return NotificationCompatApi20.getGroup(n);
+        }
+
+        @Override
+        public boolean isGroupSummary(Notification n) {
+            return NotificationCompatApi20.isGroupSummary(n);
+        }
+
+        @Override
+        public String getSortKey(Notification n) {
+            return NotificationCompatApi20.getSortKey(n);
+        }
+    }
+
+    static class NotificationCompatImplApi21 extends NotificationCompatImplApi20 {
+        @Override
+        public Notification build(Builder b) {
+            NotificationCompatApi21.Builder builder = new NotificationCompatApi21.Builder(
+                    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.mCategory,
+                    b.mPeople, b.mExtras, b.mColor, b.mVisibility, b.mPublicVersion,
+                    b.mGroupKey, b.mGroupSummary, b.mSortKey);
+            addActionsToBuilder(builder, b.mActions);
+            addStyleToBuilderJellybean(builder, b.mStyle);
+            return builder.build();
+        }
+
+        @Override
+        public String getCategory(Notification notif) {
+            return NotificationCompatApi21.getCategory(notif);
+        }
+    }
+
+    private static void addActionsToBuilder(NotificationBuilderWithActions builder,
+            ArrayList<Action> actions) {
+        for (Action action : actions) {
+            builder.addAction(action);
+        }
+    }
+
+    private 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);
             }
-            if (b.mStyle != null) {
-                if (b.mStyle instanceof BigTextStyle) {
-                    BigTextStyle style = (BigTextStyle) b.mStyle;
-                    jbBuilder.addBigTextStyle(style.mBigContentTitle,
-                            style.mSummaryTextSet,
-                            style.mSummaryText,
-                            style.mBigText);
-                } else if (b.mStyle instanceof InboxStyle) {
-                    InboxStyle style = (InboxStyle) b.mStyle;
-                    jbBuilder.addInboxStyle(style.mBigContentTitle,
-                            style.mSummaryTextSet,
-                            style.mSummaryText,
-                            style.mTexts);
-                } else if (b.mStyle instanceof BigPictureStyle) {
-                    BigPictureStyle style = (BigPictureStyle) b.mStyle;
-                    jbBuilder.addBigPictureStyle(style.mBigContentTitle,
-                            style.mSummaryTextSet,
-                            style.mSummaryText,
-                            style.mPicture,
-                            style.mBigLargeIcon,
-                            style.mBigLargeIconSet);
-                }
-            }
-            return(jbBuilder.build());
         }
     }
 
     static {
-        if (Build.VERSION.SDK_INT >= 16) {
+        // TODO: Replace this if clause when SDK_INT is incremented to 21.
+        if (Build.VERSION.RELEASE.equals("L")) {
+            IMPL = new NotificationCompatImplApi21();
+        } else if (Build.VERSION.SDK_INT >= 20) {
+            IMPL = new NotificationCompatImplApi20();
+        } else if (Build.VERSION.SDK_INT >= 19) {
+            IMPL = new NotificationCompatImplKitKat();
+        } else if (Build.VERSION.SDK_INT >= 16) {
             IMPL = new NotificationCompatImplJellybean();
         } else if (Build.VERSION.SDK_INT >= 14) {
             IMPL = new NotificationCompatImplIceCreamSandwich();
@@ -216,9 +803,19 @@
         int mProgressMax;
         int mProgress;
         boolean mProgressIndeterminate;
+        String mGroupKey;
+        boolean mGroupSummary;
+        String mSortKey;
         ArrayList<Action> mActions = new ArrayList<Action>();
+        boolean mLocalOnly = false;
+        String mCategory;
+        Bundle mExtras;
+        int mColor = COLOR_DEFAULT;
+        int mVisibility = VISIBILITY_PRIVATE;
+        Notification mPublicVersion;
 
         Notification mNotification = new Notification();
+        public ArrayList<String> mPeople;
 
         /**
          * Constructor.
@@ -238,6 +835,7 @@
             mNotification.when = System.currentTimeMillis();
             mNotification.audioStreamType = Notification.STREAM_DEFAULT;
             mPriority = PRIORITY_DEFAULT;
+            mPeople = new ArrayList<String>();
         }
 
         /**
@@ -377,8 +975,8 @@
          * Supply a {@link PendingIntent} to send when the notification is cleared by the user
          * directly from the notification panel.  For example, this intent is sent when the user
          * clicks the "Clear all" button, or the individual "X" buttons on notifications.  This
-         * intent is not sent when the application calls {@link NotificationManager#cancel
-         * NotificationManager.cancel(int)}.
+         * intent is not sent when the application calls
+         * {@link android.app.NotificationManager#cancel NotificationManager.cancel(int)}.
          */
         public Builder setDeleteIntent(PendingIntent intent) {
             mNotification.deleteIntent = intent;
@@ -394,6 +992,11 @@
          * to turn it off and use a normal notification, as this can be extremely
          * disruptive.
          *
+         * <p>
+         * On some platforms, the system UI may choose to display a heads-up notification,
+         * instead of launching this intent, while the user is using the device.
+         * </p>
+         *
          * @param intent The pending intent to launch.
          * @param highPriority Passing true will cause this notification to be sent
          *          even if other notifications are suppressed.
@@ -434,6 +1037,11 @@
 
         /**
          * Set the sound to play.  It will play on the default stream.
+         *
+         * <p>
+         * On some platforms, a notification that is noisy is more likely to be presented
+         * as a heads-up notification.
+         * </p>
          */
         public Builder setSound(Uri sound) {
             mNotification.sound = sound;
@@ -444,7 +1052,12 @@
         /**
          * Set the sound to play.  It will play on the stream you supply.
          *
-         * @see #STREAM_DEFAULT
+         * <p>
+         * On some platforms, a notification that is noisy is more likely to be presented
+         * as a heads-up notification.
+         * </p>
+         *
+         * @see Notification#STREAM_DEFAULT
          * @see AudioManager for the <code>STREAM_</code> constants.
          */
         public Builder setSound(Uri sound, int streamType) {
@@ -456,6 +1069,11 @@
         /**
          * Set the vibration pattern to use.
          *
+         * <p>
+         * On some platforms, a notification that vibrates is more likely to be presented
+         * as a heads-up notification.
+         * </p>
+         *
          * @see android.os.Vibrator for a discussion of the <code>pattern</code>
          * parameter.
          */
@@ -516,6 +1134,29 @@
         }
 
         /**
+         * Set whether or not this notification is only relevant to the current device.
+         *
+         * <p>Some notifications can be bridged to other devices for remote display.
+         * This hint can be set to recommend this notification not be bridged.
+         */
+        public Builder setLocalOnly(boolean b) {
+            mLocalOnly = b;
+            return this;
+        }
+
+        /**
+         * Set the notification category.
+         *
+         * <p>Must be one of the predefined notification categories (see the <code>CATEGORY_*</code>
+         * constants in {@link Notification}) that best describes this notification.
+         * May be used by the system for ranking and filtering.
+         */
+        public Builder setCategory(String category) {
+            mCategory = category;
+            return this;
+        }
+
+        /**
          * Set the default notification options that will be used.
          * <p>
          * The value should be one or more of the following fields combined with
@@ -551,6 +1192,12 @@
          * interrupted for a higher-priority notification.
          * The system sets a notification's priority based on various factors including the
          * setPriority value. The effect may differ slightly on different platforms.
+         *
+         * @param pri Relative priority for this notification. Must be one of
+         *     the priority constants defined by {@link NotificationCompat}.
+         *     Acceptable values range from {@link
+         *     NotificationCompat#PRIORITY_MIN} (-2) to {@link
+         *     NotificationCompat#PRIORITY_MAX} (2).
          */
         public Builder setPriority(int pri) {
             mPriority = pri;
@@ -558,6 +1205,113 @@
         }
 
         /**
+         * Add a person that is relevant to this notification.
+         *
+         * @see Notification#EXTRA_PEOPLE
+         */
+        public Builder addPerson(String handle) {
+            mPeople.add(handle);
+            return this;
+        }
+
+        /**
+         * Set this notification to be part of a group of notifications sharing the same key.
+         * Grouped notifications may display in a cluster or stack on devices which
+         * support such rendering.
+         *
+         * <p>To make this notification the summary for its group, also call
+         * {@link #setGroupSummary}. A sort order can be specified for group members by using
+         * {@link #setSortKey}.
+         * @param groupKey The group key of the group.
+         * @return this object for method chaining
+         */
+        public Builder setGroup(String groupKey) {
+            mGroupKey = groupKey;
+            return this;
+        }
+
+        /**
+         * Set this notification to be the group summary for a group of notifications.
+         * Grouped notifications may display in a cluster or stack on devices which
+         * support such rendering. Requires a group key also be set using {@link #setGroup}.
+         * @param isGroupSummary Whether this notification should be a group summary.
+         * @return this object for method chaining
+         */
+        public Builder setGroupSummary(boolean isGroupSummary) {
+            mGroupSummary = isGroupSummary;
+            return this;
+        }
+
+        /**
+         * Set a sort key that orders this notification among other notifications from the
+         * same package. This can be useful if an external sort was already applied and an app
+         * would like to preserve this. Notifications will be sorted lexicographically using this
+         * value, although providing different priorities in addition to providing sort key may
+         * cause this value to be ignored.
+         *
+         * <p>This sort key can also be used to order members of a notification group. See
+         * {@link Builder#setGroup}.
+         *
+         * @see String#compareTo(String)
+         */
+        public Builder setSortKey(String sortKey) {
+            mSortKey = sortKey;
+            return this;
+        }
+
+        /**
+         * Merge additional metadata into this notification.
+         *
+         * <p>Values within the Bundle will replace existing extras values in this Builder.
+         *
+         * @see Notification#extras
+         */
+        public Builder addExtras(Bundle extras) {
+            if (extras != null) {
+                if (mExtras == null) {
+                    mExtras = new Bundle(extras);
+                } else {
+                    mExtras.putAll(extras);
+                }
+            }
+            return this;
+        }
+
+        /**
+         * Set metadata for this notification.
+         *
+         * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's
+         * current contents are copied into the Notification each time {@link #build()} is
+         * called.
+         *
+         * <p>Replaces any existing extras values with those from the provided Bundle.
+         * Use {@link #addExtras} to merge in metadata instead.
+         *
+         * @see Notification#extras
+         */
+        public Builder setExtras(Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Get the current metadata Bundle used by this notification Builder.
+         *
+         * <p>The returned Bundle is shared with this Builder.
+         *
+         * <p>The current contents of this Bundle are copied into the Notification each time
+         * {@link #build()} is called.
+         *
+         * @see Notification#extras
+         */
+        public Bundle getExtras() {
+            if (mExtras == null) {
+                mExtras = new Bundle();
+            }
+            return mExtras;
+        }
+
+        /**
          * Add an action to this notification. Actions are typically displayed by
          * the system as a button adjacent to the notification content.
          * <br>
@@ -579,6 +1333,25 @@
         }
 
         /**
+         * Add an action to this notification. Actions are typically displayed by
+         * the system as a button adjacent to the notification content.
+         * <br>
+         * Action buttons won't appear on platforms prior to Android 4.1. Action
+         * buttons depend on expanded notifications, which are only available in Android 4.1
+         * and later. To ensure that an action button's functionality is always available, first
+         * implement the functionality in the {@link android.app.Activity} that starts when a user
+         * clicks the  notification (see {@link #setContentIntent setContentIntent()}), and then
+         * enhance the notification by implementing the same functionality with
+         * {@link #addAction addAction()}.
+         *
+         * @param action The action to add.
+         */
+        public Builder addAction(Action action) {
+            mActions.add(action);
+            return this;
+        }
+
+        /**
          * Add a rich notification style to be applied at build time.
          * <br>
          * If the platform does not provide rich notification styles, this method has no effect. The
@@ -597,11 +1370,57 @@
         }
 
         /**
+         * Sets {@link Notification#color}.
+         *
+         * @param argb The accent color to use
+         *
+         * @return The same Builder.
+         */
+        public Builder setColor(int argb) {
+            mColor = argb;
+            return this;
+        }
+
+        /**
+         * Sets {@link Notification#visibility}.
+         *
+         * @param visibility One of {@link Notification#VISIBILITY_PRIVATE} (the default),
+         *                   {@link Notification#VISIBILITY_PUBLIC}, or
+         *                   {@link Notification#VISIBILITY_SECRET}.
+         */
+        public Builder setVisibility(int visibility) {
+            mVisibility = visibility;
+            return this;
+        }
+
+        /**
+         * Supply a replacement Notification whose contents should be shown in insecure contexts
+         * (i.e. atop the secure lockscreen). See {@link Notification#visibility} and
+         * {@link #VISIBILITY_PUBLIC}.
+         *
+         * @param n A replacement notification, presumably with some or all info redacted.
+         * @return The same Builder.
+         */
+        public Builder setPublicVersion(Notification n) {
+            mPublicVersion = n;
+            return this;
+        }
+
+        /**
+         * Apply an extender to this notification builder. Extenders may be used to add
+         * metadata or change options on this builder.
+         */
+        public Builder extend(Extender extender) {
+            extender.extend(this);
+            return this;
+        }
+
+        /**
          * @deprecated Use {@link #build()} instead.
          */
         @Deprecated
         public Notification getNotification() {
-            return (Notification) IMPL.build(this);
+            return IMPL.build(this);
         }
 
         /**
@@ -609,7 +1428,7 @@
          * object.
          */
         public Notification build() {
-            return (Notification) IMPL.build(this);
+            return IMPL.build(this);
         }
     }
 
@@ -620,8 +1439,7 @@
      * If the platform does not provide rich notification styles, methods in this class have no
      * effect.
      */
-    public static abstract class Style
-    {
+    public static abstract class Style {
         Builder mBuilder;
         CharSequence mBigContentTitle;
         CharSequence mSummaryText;
@@ -653,7 +1471,7 @@
      * <br>
      * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like so:
      * <pre class="prettyprint">
-     * Notification noti = new Notification.Builder()
+     * Notification notif = new Notification.Builder(mContext)
      *     .setContentTitle(&quot;New photo from &quot; + sender.toString())
      *     .setContentText(subject)
      *     .setSmallIcon(R.drawable.new_post)
@@ -722,7 +1540,7 @@
      * <br>
      * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like so:
      * <pre class="prettyprint">
-     * Notification noti = new Notification.Builder()
+     * Notification notif = new Notification.Builder(mContext)
      *     .setContentTitle(&quot;New mail from &quot; + sender.toString())
      *     .setContentText(subject)
      *     .setSmallIcon(R.drawable.new_mail)
@@ -833,15 +1651,1055 @@
         }
     }
 
-    public static class Action {
+    /**
+     * Structure to encapsulate a named action that can be shown as part of this notification.
+     * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
+     * selected by the user. Action buttons won't appear on platforms prior to Android 4.1.
+     * <p>
+     * Apps should use {@link NotificationCompat.Builder#addAction(int, CharSequence, PendingIntent)}
+     * or {@link NotificationCompat.Builder#addAction(NotificationCompat.Action)}
+     * to attach actions.
+     */
+    public static class Action extends NotificationCompatBase.Action {
+        private final Bundle mExtras;
+        private final RemoteInput[] mRemoteInputs;
+
+        /**
+         * Small icon representing the action.
+         */
         public int icon;
+        /**
+         * Title of the action.
+         */
         public CharSequence title;
+        /**
+         * Intent to send when the user invokes this action. May be null, in which case the action
+         * may be rendered in a disabled presentation.
+         */
         public PendingIntent actionIntent;
 
-        public Action(int icon_, CharSequence title_, PendingIntent intent_) {
-            this.icon = icon_;
-            this.title = title_;
-            this.actionIntent = intent_;
+        public Action(int icon, CharSequence title, PendingIntent intent) {
+            this(icon, title, intent, new Bundle(), null);
         }
+
+        private Action(int icon, CharSequence title, PendingIntent intent, Bundle extras,
+                RemoteInput[] remoteInputs) {
+            this.icon = icon;
+            this.title = title;
+            this.actionIntent = intent;
+            this.mExtras = extras != null ? extras : new Bundle();
+            this.mRemoteInputs = remoteInputs;
+        }
+
+        @Override
+        protected int getIcon() {
+            return icon;
+        }
+
+        @Override
+        protected CharSequence getTitle() {
+            return title;
+        }
+
+        @Override
+        protected PendingIntent getActionIntent() {
+            return actionIntent;
+        }
+
+        /**
+         * Get additional metadata carried around with this Action.
+         */
+        public Bundle getExtras() {
+            return mExtras;
+        }
+
+        /**
+         * Get the list of inputs to be collected from the user when this action is sent.
+         * May return null if no remote inputs were added.
+         */
+        public RemoteInput[] getRemoteInputs() {
+            return mRemoteInputs;
+        }
+
+        /**
+         * Builder class for {@link Action} objects.
+         */
+        public static final class Builder {
+            private final int mIcon;
+            private final CharSequence mTitle;
+            private final PendingIntent mIntent;
+            private final Bundle mExtras;
+            private ArrayList<RemoteInput> mRemoteInputs;
+
+            /**
+             * Construct a new builder for {@link Action} object.
+             * @param icon icon to show for this action
+             * @param title the title of the action
+             * @param intent the {@link PendingIntent} to fire when users trigger this action
+             */
+            public Builder(int icon, CharSequence title, PendingIntent intent) {
+                this(icon, title, intent, new Bundle());
+            }
+
+            /**
+             * Construct a new builder for {@link Action} object using the fields from an
+             * {@link Action}.
+             * @param action the action to read fields from.
+             */
+            public Builder(Action action) {
+                this(action.icon, action.title, action.actionIntent, new Bundle(action.mExtras));
+            }
+
+            private Builder(int icon, CharSequence title, PendingIntent intent, Bundle extras) {
+                mIcon = icon;
+                mTitle = title;
+                mIntent = intent;
+                mExtras = extras;
+            }
+
+            /**
+             * Merge additional metadata into this builder.
+             *
+             * <p>Values within the Bundle will replace existing extras values in this Builder.
+             *
+             * @see NotificationCompat.Action#getExtras
+             */
+            public Builder addExtras(Bundle extras) {
+                if (extras != null) {
+                    mExtras.putAll(extras);
+                }
+                return this;
+            }
+
+            /**
+             * Get the metadata Bundle used by this Builder.
+             *
+             * <p>The returned Bundle is shared with this Builder.
+             */
+            public Bundle getExtras() {
+                return mExtras;
+            }
+
+            /**
+             * Add an input to be collected from the user when this action is sent.
+             * Response values can be retrieved from the fired intent by using the
+             * {@link RemoteInput#getResultsFromIntent} function.
+             * @param remoteInput a {@link RemoteInput} to add to the action
+             * @return this object for method chaining
+             */
+            public Builder addRemoteInput(RemoteInput remoteInput) {
+                if (mRemoteInputs == null) {
+                    mRemoteInputs = new ArrayList<RemoteInput>();
+                }
+                mRemoteInputs.add(remoteInput);
+                return this;
+            }
+
+            /**
+             * Apply an extender to this action builder. Extenders may be used to add
+             * metadata or change options on this builder.
+             */
+            public Builder extend(Extender extender) {
+                extender.extend(this);
+                return this;
+            }
+
+            /**
+             * Combine all of the options that have been set and return a new {@link Action}
+             * object.
+             * @return the built action
+             */
+            public Action build() {
+                RemoteInput[] remoteInputs = mRemoteInputs != null
+                        ? mRemoteInputs.toArray(new RemoteInput[mRemoteInputs.size()]) : null;
+                return new Action(mIcon, mTitle, mIntent, mExtras, remoteInputs);
+            }
+        }
+
+
+        /**
+         * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
+         * metadata or change options on an action builder.
+         */
+        public interface Extender {
+            /**
+             * Apply this extender to a notification action builder.
+             * @param builder the builder to be modified.
+             * @return the build object for chaining.
+             */
+            public Builder extend(Builder builder);
+        }
+
+        /**
+         * Wearable extender for notification actions. To add extensions to an action,
+         * create a new {@link NotificationCompat.Action.WearableExtender} object using
+         * the {@code WearableExtender()} constructor and apply it to a
+         * {@link NotificationCompat.Action.Builder} using
+         * {@link NotificationCompat.Action.Builder#extend}.
+         *
+         * <pre class="prettyprint">
+         * NotificationCompat.Action action = new NotificationCompat.Action.Builder(
+         *         R.drawable.archive_all, "Archive all", actionIntent)
+         *         .extend(new NotificationCompat.Action.WearableExtender()
+         *                 .setAvailableOffline(false))
+         *         .build();</pre>
+         */
+        public static final class WearableExtender implements Extender {
+            /** Notification action extra which contains wearable extensions */
+            private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
+
+            private static final String KEY_FLAGS = "flags";
+
+            // Flags bitwise-ored to mFlags
+            private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
+
+            // Default value for flags integer
+            private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
+
+            private int mFlags = DEFAULT_FLAGS;
+
+            /**
+             * Create a {@link NotificationCompat.Action.WearableExtender} with default
+             * options.
+             */
+            public WearableExtender() {
+            }
+
+            /**
+             * Create a {@link NotificationCompat.Action.WearableExtender} by reading
+             * wearable options present in an existing notification action.
+             * @param action the notification action to inspect.
+             */
+            public WearableExtender(Action action) {
+                Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS);
+                if (wearableBundle != null) {
+                    mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
+                }
+            }
+
+            /**
+             * Apply wearable extensions to a notification action that is being built. This is
+             * typically called by the {@link NotificationCompat.Action.Builder#extend}
+             * method of {@link NotificationCompat.Action.Builder}.
+             */
+            @Override
+            public Action.Builder extend(Action.Builder builder) {
+                Bundle wearableBundle = new Bundle();
+
+                if (mFlags != DEFAULT_FLAGS) {
+                    wearableBundle.putInt(KEY_FLAGS, mFlags);
+                }
+
+                builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
+                return builder;
+            }
+
+            @Override
+            public WearableExtender clone() {
+                WearableExtender that = new WearableExtender();
+                that.mFlags = this.mFlags;
+                return that;
+            }
+
+            /**
+             * Set whether this action is available when the wearable device is not connected to
+             * a companion device. The user can still trigger this action when the wearable device
+             * is offline, but a visual hint will indicate that the action may not be available.
+             * Defaults to true.
+             */
+            public WearableExtender setAvailableOffline(boolean availableOffline) {
+                setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline);
+                return this;
+            }
+
+            /**
+             * Get whether this action is available when the wearable device is not connected to
+             * a companion device. The user can still trigger this action when the wearable device
+             * is offline, but a visual hint will indicate that the action may not be available.
+             * Defaults to true.
+             */
+            public boolean isAvailableOffline() {
+                return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0;
+            }
+
+            private void setFlag(int mask, boolean value) {
+                if (value) {
+                    mFlags |= mask;
+                } else {
+                    mFlags &= ~mask;
+                }
+            }
+        }
+
+        /** @hide */
+        public static final Factory FACTORY = new Factory() {
+            @Override
+            public Action build(int icon, CharSequence title,
+                    PendingIntent actionIntent, Bundle extras,
+                    RemoteInputCompatBase.RemoteInput[] remoteInputs) {
+                return new Action(icon, title, actionIntent, extras,
+                        (RemoteInput[]) remoteInputs);
+            }
+
+            @Override
+            public Action[] newArray(int length) {
+                return new Action[length];
+            }
+        };
+    }
+
+
+    /**
+     * Extender interface for use with {@link Builder#extend}. Extenders may be used to add
+     * metadata or change options on a notification builder.
+     */
+    public interface Extender {
+        /**
+         * Apply this extender to a notification builder.
+         * @param builder the builder to be modified.
+         * @return the build object for chaining.
+         */
+        public Builder extend(Builder builder);
+    }
+
+    /**
+     * Helper class to add wearable extensions to notifications.
+     * <p class="note"> See
+     * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications
+     * for Android Wear</a> for more information on how to use this class.
+     * <p>
+     * To create a notification with wearable extensions:
+     * <ol>
+     *   <li>Create a {@link NotificationCompat.Builder}, setting any desired
+     *   properties.
+     *   <li>Create a {@link NotificationCompat.WearableExtender}.
+     *   <li>Set wearable-specific properties using the
+     *   {@code add} and {@code set} methods of {@link NotificationCompat.WearableExtender}.
+     *   <li>Call {@link NotificationCompat.Builder#extend} to apply the extensions to a
+     *   notification.
+     *   <li>Post the notification to the notification
+     *   system with the {@code NotificationManagerCompat.notify(...)} methods
+     *   and not the {@code NotificationManager.notify(...)} methods.
+     * </ol>
+     *
+     * <pre class="prettyprint">
+     * Notification notif = new NotificationCompat.Builder(mContext)
+     *         .setContentTitle(&quot;New mail from &quot; + sender.toString())
+     *         .setContentText(subject)
+     *         .setSmallIcon(R.drawable.new_mail)
+     *         .extend(new NotificationCompat.WearableExtender()
+     *                 .setContentIcon(R.drawable.new_mail))
+     *         .build();
+     * NotificationManagerCompat.from(mContext).notify(0, notif);</pre>
+     *
+     * <p>Wearable extensions can be accessed on an existing notification by using the
+     * {@code WearableExtender(Notification)} constructor,
+     * and then using the {@code get} methods to access values.
+     *
+     * <pre class="prettyprint">
+     * NotificationCompat.WearableExtender wearableExtender =
+     *         new NotificationCompat.WearableExtender(notification);
+     * List&lt;Notification&gt; pages = wearableExtender.getPages();</pre>
+     */
+    public static final class WearableExtender implements Extender {
+        /**
+         * Sentinel value for an action index that is unset.
+         */
+        public static final int UNSET_ACTION_INDEX = -1;
+
+        /**
+         * Size value for use with {@link #setCustomSizePreset} to show this notification with
+         * default sizing.
+         * <p>For custom display notifications created using {@link #setDisplayIntent},
+         * the default is {@link #SIZE_LARGE}. All other notifications size automatically based
+         * on their content.
+         */
+        public static final int SIZE_DEFAULT = 0;
+
+        /**
+         * Size value for use with {@link #setCustomSizePreset} to show this notification
+         * with an extra small size.
+         * <p>This value is only applicable for custom display notifications created using
+         * {@link #setDisplayIntent}.
+         */
+        public static final int SIZE_XSMALL = 1;
+
+        /**
+         * Size value for use with {@link #setCustomSizePreset} to show this notification
+         * with a small size.
+         * <p>This value is only applicable for custom display notifications created using
+         * {@link #setDisplayIntent}.
+         */
+        public static final int SIZE_SMALL = 2;
+
+        /**
+         * Size value for use with {@link #setCustomSizePreset} to show this notification
+         * with a medium size.
+         * <p>This value is only applicable for custom display notifications created using
+         * {@link #setDisplayIntent}.
+         */
+        public static final int SIZE_MEDIUM = 3;
+
+        /**
+         * Size value for use with {@link #setCustomSizePreset} to show this notification
+         * with a large size.
+         * <p>This value is only applicable for custom display notifications created using
+         * {@link #setDisplayIntent}.
+         */
+        public static final int SIZE_LARGE = 4;
+
+        /**
+         * Size value for use with {@link #setCustomSizePreset} to show this notification
+         * full screen.
+         * <p>This value is only applicable for custom display notifications created using
+         * {@link #setDisplayIntent}.
+         */
+        public static final int SIZE_FULL_SCREEN = 5;
+
+        /** Notification extra which contains wearable extensions */
+        private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
+
+        // Keys within EXTRA_WEARABLE_OPTIONS for wearable options.
+        private static final String KEY_ACTIONS = "actions";
+        private static final String KEY_FLAGS = "flags";
+        private static final String KEY_DISPLAY_INTENT = "displayIntent";
+        private static final String KEY_PAGES = "pages";
+        private static final String KEY_BACKGROUND = "background";
+        private static final String KEY_CONTENT_ICON = "contentIcon";
+        private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity";
+        private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex";
+        private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset";
+        private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
+        private static final String KEY_GRAVITY = "gravity";
+
+        // Flags bitwise-ored to mFlags
+        private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
+        private static final int FLAG_HINT_HIDE_ICON = 1 << 1;
+        private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
+        private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
+
+        // Default value for flags integer
+        private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
+
+        private static final int DEFAULT_CONTENT_ICON_GRAVITY = GravityCompat.END;
+        private static final int DEFAULT_GRAVITY = Gravity.BOTTOM;
+
+        private ArrayList<Action> mActions = new ArrayList<Action>();
+        private int mFlags = DEFAULT_FLAGS;
+        private PendingIntent mDisplayIntent;
+        private ArrayList<Notification> mPages = new ArrayList<Notification>();
+        private Bitmap mBackground;
+        private int mContentIcon;
+        private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY;
+        private int mContentActionIndex = UNSET_ACTION_INDEX;
+        private int mCustomSizePreset = SIZE_DEFAULT;
+        private int mCustomContentHeight;
+        private int mGravity = DEFAULT_GRAVITY;
+
+        /**
+         * Create a {@link NotificationCompat.WearableExtender} with default
+         * options.
+         */
+        public WearableExtender() {
+        }
+
+        public WearableExtender(Notification notif) {
+            Bundle extras = getExtras(notif);
+            Bundle wearableBundle = extras != null ? extras.getBundle(EXTRA_WEARABLE_EXTENSIONS)
+                    : null;
+            if (wearableBundle != null) {
+                Action[] actions = IMPL.getActionsFromParcelableArrayList(
+                        wearableBundle.getParcelableArrayList(KEY_ACTIONS));
+                if (actions != null) {
+                    Collections.addAll(mActions, actions);
+                }
+
+                mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS);
+                mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT);
+
+                Notification[] pages = getNotificationArrayFromBundle(
+                        wearableBundle, KEY_PAGES);
+                if (pages != null) {
+                    Collections.addAll(mPages, pages);
+                }
+
+                mBackground = wearableBundle.getParcelable(KEY_BACKGROUND);
+                mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON);
+                mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY,
+                        DEFAULT_CONTENT_ICON_GRAVITY);
+                mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX,
+                        UNSET_ACTION_INDEX);
+                mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET,
+                        SIZE_DEFAULT);
+                mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
+                mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
+            }
+        }
+
+        /**
+         * Apply wearable extensions to a notification that is being built. This is typically
+         * called by the {@link NotificationCompat.Builder#extend} method of
+         * {@link NotificationCompat.Builder}.
+         */
+        @Override
+        public NotificationCompat.Builder extend(NotificationCompat.Builder builder) {
+            Bundle wearableBundle = new Bundle();
+
+            if (!mActions.isEmpty()) {
+                wearableBundle.putParcelableArrayList(KEY_ACTIONS,
+                        IMPL.getParcelableArrayListForActions(mActions.toArray(
+                                new Action[mActions.size()])));
+            }
+            if (mFlags != DEFAULT_FLAGS) {
+                wearableBundle.putInt(KEY_FLAGS, mFlags);
+            }
+            if (mDisplayIntent != null) {
+                wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent);
+            }
+            if (!mPages.isEmpty()) {
+                wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray(
+                        new Notification[mPages.size()]));
+            }
+            if (mBackground != null) {
+                wearableBundle.putParcelable(KEY_BACKGROUND, mBackground);
+            }
+            if (mContentIcon != 0) {
+                wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon);
+            }
+            if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) {
+                wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity);
+            }
+            if (mContentActionIndex != UNSET_ACTION_INDEX) {
+                wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX,
+                        mContentActionIndex);
+            }
+            if (mCustomSizePreset != SIZE_DEFAULT) {
+                wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset);
+            }
+            if (mCustomContentHeight != 0) {
+                wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight);
+            }
+            if (mGravity != DEFAULT_GRAVITY) {
+                wearableBundle.putInt(KEY_GRAVITY, mGravity);
+            }
+
+            builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
+            return builder;
+        }
+
+        @Override
+        public WearableExtender clone() {
+            WearableExtender that = new WearableExtender();
+            that.mActions = new ArrayList<Action>(this.mActions);
+            that.mFlags = this.mFlags;
+            that.mDisplayIntent = this.mDisplayIntent;
+            that.mPages = new ArrayList<Notification>(this.mPages);
+            that.mBackground = this.mBackground;
+            that.mContentIcon = this.mContentIcon;
+            that.mContentIconGravity = this.mContentIconGravity;
+            that.mContentActionIndex = this.mContentActionIndex;
+            that.mCustomSizePreset = this.mCustomSizePreset;
+            that.mCustomContentHeight = this.mCustomContentHeight;
+            that.mGravity = this.mGravity;
+            return that;
+        }
+
+        /**
+         * Add a wearable action to this notification.
+         *
+         * <p>When wearable actions are added using this method, the set of actions that
+         * show on a wearable device splits from devices that only show actions added
+         * using {@link NotificationCompat.Builder#addAction}. This allows for customization
+         * of which actions display on different devices.
+         *
+         * @param action the action to add to this notification
+         * @return this object for method chaining
+         * @see NotificationCompat.Action
+         */
+        public WearableExtender addAction(Action action) {
+            mActions.add(action);
+            return this;
+        }
+
+        /**
+         * Adds wearable actions to this notification.
+         *
+         * <p>When wearable actions are added using this method, the set of actions that
+         * show on a wearable device splits from devices that only show actions added
+         * using {@link NotificationCompat.Builder#addAction}. This allows for customization
+         * of which actions display on different devices.
+         *
+         * @param actions the actions to add to this notification
+         * @return this object for method chaining
+         * @see NotificationCompat.Action
+         */
+        public WearableExtender addActions(List<Action> actions) {
+            mActions.addAll(actions);
+            return this;
+        }
+
+        /**
+         * Clear all wearable actions present on this builder.
+         * @return this object for method chaining.
+         * @see #addAction
+         */
+        public WearableExtender clearActions() {
+            mActions.clear();
+            return this;
+        }
+
+        /**
+         * Get the wearable actions present on this notification.
+         */
+        public List<Action> getActions() {
+            return mActions;
+        }
+
+        /**
+         * Set an intent to launch inside of an activity view when displaying
+         * this notification. The {@link PendingIntent} provided should be for an activity.
+         *
+         * <pre class="prettyprint">
+         * Intent displayIntent = new Intent(context, MyDisplayActivity.class);
+         * PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
+         *         0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+         * Notification notif = new NotificationCompat.Builder(context)
+         *         .extend(new NotificationCompat.WearableExtender()
+         *                 .setDisplayIntent(displayPendingIntent)
+         *                 .setCustomSizePreset(NotificationCompat.WearableExtender.SIZE_MEDIUM))
+         *         .build();</pre>
+         *
+         * <p>The activity to launch needs to allow embedding, must be exported, and
+         * should have an empty task affinity. It is also recommended to use the device
+         * default light theme.
+         *
+         * <p>Example AndroidManifest.xml entry:
+         * <pre class="prettyprint">
+         * &lt;activity android:name=&quot;com.example.MyDisplayActivity&quot;
+         *     android:exported=&quot;true&quot;
+         *     android:allowEmbedded=&quot;true&quot;
+         *     android:taskAffinity=&quot;&quot;
+         *     android:theme=&quot;@android:style/Theme.DeviceDefault.Light&quot; /&gt;</pre>
+         *
+         * @param intent the {@link PendingIntent} for an activity
+         * @return this object for method chaining
+         * @see NotificationCompat.WearableExtender#getDisplayIntent
+         */
+        public WearableExtender setDisplayIntent(PendingIntent intent) {
+            mDisplayIntent = intent;
+            return this;
+        }
+
+        /**
+         * Get the intent to launch inside of an activity view when displaying this
+         * notification. This {@code PendingIntent} should be for an activity.
+         */
+        public PendingIntent getDisplayIntent() {
+            return mDisplayIntent;
+        }
+
+        /**
+         * Add an additional page of content to display with this notification. The current
+         * notification forms the first page, and pages added using this function form
+         * subsequent pages. This field can be used to separate a notification into multiple
+         * sections.
+         *
+         * @param page the notification to add as another page
+         * @return this object for method chaining
+         * @see NotificationCompat.WearableExtender#getPages
+         */
+        public WearableExtender addPage(Notification page) {
+            mPages.add(page);
+            return this;
+        }
+
+        /**
+         * Add additional pages of content to display with this notification. The current
+         * notification forms the first page, and pages added using this function form
+         * subsequent pages. This field can be used to separate a notification into multiple
+         * sections.
+         *
+         * @param pages a list of notifications
+         * @return this object for method chaining
+         * @see NotificationCompat.WearableExtender#getPages
+         */
+        public WearableExtender addPages(List<Notification> pages) {
+            mPages.addAll(pages);
+            return this;
+        }
+
+        /**
+         * Clear all additional pages present on this builder.
+         * @return this object for method chaining.
+         * @see #addPage
+         */
+        public WearableExtender clearPages() {
+            mPages.clear();
+            return this;
+        }
+
+        /**
+         * Get the array of additional pages of content for displaying this notification. The
+         * current notification forms the first page, and elements within this array form
+         * subsequent pages. This field can be used to separate a notification into multiple
+         * sections.
+         * @return the pages for this notification
+         */
+        public List<Notification> getPages() {
+            return mPages;
+        }
+
+        /**
+         * Set a background image to be displayed behind the notification content.
+         * Contrary to the {@link NotificationCompat.BigPictureStyle}, this background
+         * will work with any notification style.
+         *
+         * @param background the background bitmap
+         * @return this object for method chaining
+         * @see NotificationCompat.WearableExtender#getBackground
+         */
+        public WearableExtender setBackground(Bitmap background) {
+            mBackground = background;
+            return this;
+        }
+
+        /**
+         * Get a background image to be displayed behind the notification content.
+         * Contrary to the {@link NotificationCompat.BigPictureStyle}, this background
+         * will work with any notification style.
+         *
+         * @return the background image
+         * @see NotificationCompat.WearableExtender#setBackground
+         */
+        public Bitmap getBackground() {
+            return mBackground;
+        }
+
+        /**
+         * Set an icon that goes with the content of this notification.
+         */
+        public WearableExtender setContentIcon(int icon) {
+            mContentIcon = icon;
+            return this;
+        }
+
+        /**
+         * Get an icon that goes with the content of this notification.
+         */
+        public int getContentIcon() {
+            return mContentIcon;
+        }
+
+        /**
+         * Set the gravity that the content icon should have within the notification display.
+         * Supported values include {@link android.view.Gravity#START} and
+         * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
+         * @see #setContentIcon
+         */
+        public WearableExtender setContentIconGravity(int contentIconGravity) {
+            mContentIconGravity = contentIconGravity;
+            return this;
+        }
+
+        /**
+         * Get the gravity that the content icon should have within the notification display.
+         * Supported values include {@link android.view.Gravity#START} and
+         * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}.
+         * @see #getContentIcon
+         */
+        public int getContentIconGravity() {
+            return mContentIconGravity;
+        }
+
+        /**
+         * Set an action from this notification's actions to be clickable with the content of
+         * this notification. This action will no longer display separately from the
+         * notification's content.
+         *
+         * <p>For notifications with multiple pages, child pages can also have content actions
+         * set, although the list of available actions comes from the main notification and not
+         * from the child page's notification.
+         *
+         * @param actionIndex The index of the action to hoist onto the current notification page.
+         *                    If wearable actions were added to the main notification, this index
+         *                    will apply to that list, otherwise it will apply to the regular
+         *                    actions list.
+         */
+        public WearableExtender setContentAction(int actionIndex) {
+            mContentActionIndex = actionIndex;
+            return this;
+        }
+
+        /**
+         * Get the index of the notification action, if any, that was specified as being clickable
+         * with the content of this notification. This action will no longer display separately
+         * from the notification's content.
+         *
+         * <p>For notifications with multiple pages, child pages can also have content actions
+         * set, although the list of available actions comes from the main notification and not
+         * from the child page's notification.
+         *
+         * <p>If wearable specific actions were added to the main notification, this index will
+         * apply to that list, otherwise it will apply to the regular actions list.
+         *
+         * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
+         */
+        public int getContentAction() {
+            return mContentActionIndex;
+        }
+
+        /**
+         * Set the gravity that this notification should have within the available viewport space.
+         * Supported values include {@link android.view.Gravity#TOP},
+         * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
+         * The default value is {@link android.view.Gravity#BOTTOM}.
+         */
+        public WearableExtender setGravity(int gravity) {
+            mGravity = gravity;
+            return this;
+        }
+
+        /**
+         * Get the gravity that this notification should have within the available viewport space.
+         * Supported values include {@link android.view.Gravity#TOP},
+         * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}.
+         * The default value is {@link android.view.Gravity#BOTTOM}.
+         */
+        public int getGravity() {
+            return mGravity;
+        }
+
+        /**
+         * Set the custom size preset for the display of this notification out of the available
+         * presets found in {@link NotificationCompat.WearableExtender}, e.g.
+         * {@link #SIZE_LARGE}.
+         * <p>Some custom size presets are only applicable for custom display notifications created
+         * using {@link NotificationCompat.WearableExtender#setDisplayIntent}. Check the
+         * documentation for the preset in question. See also
+         * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}.
+         */
+        public WearableExtender setCustomSizePreset(int sizePreset) {
+            mCustomSizePreset = sizePreset;
+            return this;
+        }
+
+        /**
+         * Get the custom size preset for the display of this notification out of the available
+         * presets found in {@link NotificationCompat.WearableExtender}, e.g.
+         * {@link #SIZE_LARGE}.
+         * <p>Some custom size presets are only applicable for custom display notifications created
+         * using {@link #setDisplayIntent}. Check the documentation for the preset in question.
+         * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}.
+         */
+        public int getCustomSizePreset() {
+            return mCustomSizePreset;
+        }
+
+        /**
+         * Set the custom height in pixels for the display of this notification's content.
+         * <p>This option is only available for custom display notifications created
+         * using {@link NotificationCompat.WearableExtender#setDisplayIntent}. See also
+         * {@link NotificationCompat.WearableExtender#setCustomSizePreset} and
+         * {@link #getCustomContentHeight}.
+         */
+        public WearableExtender setCustomContentHeight(int height) {
+            mCustomContentHeight = height;
+            return this;
+        }
+
+        /**
+         * Get the custom height in pixels for the display of this notification's content.
+         * <p>This option is only available for custom display notifications created
+         * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and
+         * {@link #setCustomContentHeight}.
+         */
+        public int getCustomContentHeight() {
+            return mCustomContentHeight;
+        }
+
+        /**
+         * Set whether the scrolling position for the contents of this notification should start
+         * at the bottom of the contents instead of the top when the contents are too long to
+         * display within the screen.  Default is false (start scroll at the top).
+         */
+        public WearableExtender setStartScrollBottom(boolean startScrollBottom) {
+            setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom);
+            return this;
+        }
+
+        /**
+         * Get whether the scrolling position for the contents of this notification should start
+         * at the bottom of the contents instead of the top when the contents are too long to
+         * display within the screen. Default is false (start scroll at the top).
+         */
+        public boolean getStartScrollBottom() {
+            return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0;
+        }
+
+        /**
+         * Set whether the content intent is available when the wearable device is not connected
+         * to a companion device.  The user can still trigger this intent when the wearable device
+         * is offline, but a visual hint will indicate that the content intent may not be available.
+         * Defaults to true.
+         */
+        public WearableExtender setContentIntentAvailableOffline(
+                boolean contentIntentAvailableOffline) {
+            setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline);
+            return this;
+        }
+
+        /**
+         * Get whether the content intent is available when the wearable device is not connected
+         * to a companion device.  The user can still trigger this intent when the wearable device
+         * is offline, but a visual hint will indicate that the content intent may not be available.
+         * Defaults to true.
+         */
+        public boolean getContentIntentAvailableOffline() {
+            return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0;
+        }
+
+        /**
+         * Set a hint that this notification's icon should not be displayed.
+         * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise.
+         * @return this object for method chaining
+         */
+        public WearableExtender setHintHideIcon(boolean hintHideIcon) {
+            setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon);
+            return this;
+        }
+
+        /**
+         * Get a hint that this notification's icon should not be displayed.
+         * @return {@code true} if this icon should not be displayed, false otherwise.
+         * The default value is {@code false} if this was never set.
+         */
+        public boolean getHintHideIcon() {
+            return (mFlags & FLAG_HINT_HIDE_ICON) != 0;
+        }
+
+        /**
+         * Set a visual hint that only the background image of this notification should be
+         * displayed, and other semantic content should be hidden. This hint is only applicable
+         * to sub-pages added using {@link #addPage}.
+         */
+        public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) {
+            setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly);
+            return this;
+        }
+
+        /**
+         * Get a visual hint that only the background image of this notification should be
+         * displayed, and other semantic content should be hidden. This hint is only applicable
+         * to sub-pages added using {@link NotificationCompat.WearableExtender#addPage}.
+         */
+        public boolean getHintShowBackgroundOnly() {
+            return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0;
+        }
+
+        private void setFlag(int mask, boolean value) {
+            if (value) {
+                mFlags |= mask;
+            } else {
+                mFlags &= ~mask;
+            }
+        }
+    }
+
+    /**
+     * Get an array of Notification objects from a parcelable array bundle field.
+     * Update the bundle to have a typed array so fetches in the future don't need
+     * to do an array copy.
+     */
+    private static Notification[] getNotificationArrayFromBundle(Bundle bundle, String key) {
+        Parcelable[] array = bundle.getParcelableArray(key);
+        if (array instanceof Notification[] || array == null) {
+            return (Notification[]) array;
+        }
+        Notification[] typedArray = new Notification[array.length];
+        for (int i = 0; i < array.length; i++) {
+            typedArray[i] = (Notification) array[i];
+        }
+        bundle.putParcelableArray(key, typedArray);
+        return typedArray;
+    }
+
+    /**
+     * Gets the {@link Notification#extras} field from a notification in a backwards
+     * compatible manner. Extras field was supported from JellyBean (Api level 16)
+     * forwards. This function will return null on older api levels.
+     */
+    public static Bundle getExtras(Notification notif) {
+        return IMPL.getExtras(notif);
+    }
+
+    /**
+     * Get the number of actions in this notification in a backwards compatible
+     * manner. Actions were supported from JellyBean (Api level 16) forwards.
+     */
+    public static int getActionCount(Notification notif) {
+        return IMPL.getActionCount(notif);
+    }
+
+    /**
+     * Get an action on this notification in a backwards compatible
+     * manner. Actions were supported from JellyBean (Api level 16) forwards.
+     * @param notif The notification to inspect.
+     * @param actionIndex The index of the action to retrieve.
+     */
+    public static Action getAction(Notification notif, int actionIndex) {
+        return IMPL.getAction(notif, actionIndex);
+    }
+
+    /**
+    * Get the category of this notification in a backwards compatible
+    * manner.
+    * @param notif The notification to inspect.
+    */
+    public static String getCategory(Notification notif) {
+        return IMPL.getCategory(notif);
+    }
+
+    /**
+     * Get whether or not this notification is only relevant to the current device.
+     *
+     * <p>Some notifications can be bridged to other devices for remote display.
+     * If this hint is set, it is recommend that this notification not be bridged.
+     */
+    public static boolean getLocalOnly(Notification notif) {
+        return IMPL.getLocalOnly(notif);
+    }
+
+    /**
+     * Get the key used to group this notification into a cluster or stack
+     * with other notifications on devices which support such rendering.
+     */
+    public static String getGroup(Notification notif) {
+        return IMPL.getGroup(notif);
+    }
+
+    /**
+     * Get whether this notification to be the group summary for a group of notifications.
+     * Grouped notifications may display in a cluster or stack on devices which
+     * support such rendering. Requires a group key also be set using {@link Builder#setGroup}.
+     * @return Whether this notification is a group summary.
+     */
+    public static boolean isGroupSummary(Notification notif) {
+        return IMPL.isGroupSummary(notif);
+    }
+
+    /**
+     * Get a sort key that orders this notification among other notifications from the
+     * same package. This can be useful if an external sort was already applied and an app
+     * would like to preserve this. Notifications will be sorted lexicographically using this
+     * value, although providing different priorities in addition to providing sort key may
+     * cause this value to be ignored.
+     *
+     * <p>This sort key can also be used to order members of a notification group. See
+     * {@link Builder#setGroup}.
+     *
+     * @see String#compareTo(String)
+     */
+    public static String getSortKey(Notification notif) {
+        return IMPL.getSortKey(notif);
     }
 }
diff --git a/v4/java/android/support/v4/app/NotificationCompatExtras.java b/v4/java/android/support/v4/app/NotificationCompatExtras.java
new file mode 100644
index 0000000..6a2ee93
--- /dev/null
+++ b/v4/java/android/support/v4/app/NotificationCompatExtras.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+/**
+ * Well-known extras used by {@link NotificationCompat} for backwards compatibility.
+ */
+public final class NotificationCompatExtras {
+    /**
+     * Extras key used internally by {@link NotificationCompat} to store the value of
+     * the {@link android.app.Notification#FLAG_LOCAL_ONLY} field before it was available.
+     * If possible, use {@link NotificationCompat#getLocalOnly} to access this field.
+     */
+    public static final String EXTRA_LOCAL_ONLY = NotificationCompatJellybean.EXTRA_LOCAL_ONLY;
+
+    /**
+     * Extras key used internally by {@link NotificationCompat} to store the value set
+     * by {@link android.app.Notification.Builder#setGroup} before it was available.
+     * If possible, use {@link NotificationCompat#getGroup} to access this value.
+     */
+    public static final String EXTRA_GROUP_KEY = NotificationCompatJellybean.EXTRA_GROUP_KEY;
+
+    /**
+     * Extras key used internally by {@link NotificationCompat} to store the value set
+     * by {@link android.app.Notification.Builder#setGroupSummary} before it was available.
+     * If possible, use {@link NotificationCompat#isGroupSummary} to access this value.
+     */
+    public static final String EXTRA_GROUP_SUMMARY =
+            NotificationCompatJellybean.EXTRA_GROUP_SUMMARY;
+
+    /**
+     * Extras key used internally by {@link NotificationCompat} to store the value set
+     * by {@link android.app.Notification.Builder#setSortKey} before it was available.
+     * If possible, use {@link NotificationCompat#getSortKey} to access this value.
+     */
+    public static final String EXTRA_SORT_KEY = NotificationCompatJellybean.EXTRA_SORT_KEY;
+
+    /**
+     * Extras key used internally by {@link NotificationCompat} to store the value of
+     * the {@link android.app.Notification.Action#extras} field before it was available.
+     * If possible, use {@link NotificationCompat#getAction} to access this field.
+     */
+    public static final String EXTRA_ACTION_EXTRAS =
+            NotificationCompatJellybean.EXTRA_ACTION_EXTRAS;
+
+    /**
+     * Extras key used internally by {@link NotificationCompat} to store the value of
+     * the {@link android.app.Notification.Action#getRemoteInputs} before the field
+     * was available.
+     * If possible, use {@link NotificationCompat.Action#getRemoteInputs to access this field.
+     */
+    public static final String EXTRA_REMOTE_INPUTS =
+            NotificationCompatJellybean.EXTRA_REMOTE_INPUTS;
+
+    private NotificationCompatExtras() {}
+}
diff --git a/v4/java/android/support/v4/app/NotificationCompatSideChannelService.java b/v4/java/android/support/v4/app/NotificationCompatSideChannelService.java
new file mode 100644
index 0000000..ee4c1e6
--- /dev/null
+++ b/v4/java/android/support/v4/app/NotificationCompatSideChannelService.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.Notification;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * Abstract service to receive side channel notifications sent from
+ * {@link android.support.v4.app.NotificationManagerCompat}.
+ *
+ * <p>To receive side channel notifications, extend this service and register it in your
+ * android manifest with an intent filter for the BIND_NOTIFICATION_SIDE_CHANNEL action.
+ * Note: you must also have an enabled
+ * {@link android.service.notification.NotificationListenerService} within your package.
+ *
+ * <p>Example AndroidManifest.xml addition:
+ * <pre>
+ * &lt;service android:name="com.example.NotificationSideChannelService"&gt;
+ *     &lt;intent-filter&gt;
+ *         &lt;action android:name="android.support.BIND_NOTIFICATION_SIDE_CHANNEL" /&gt;
+ *     &lt;/intent-filter&gt;
+ * &lt;/service&gt;</pre>
+ *
+ */
+public abstract class NotificationCompatSideChannelService extends Service {
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (intent.getAction().equals(NotificationManagerCompat.ACTION_BIND_SIDE_CHANNEL)) {
+            // Block side channel service connections if the current sdk has no need for
+            // side channeling.
+            if (Build.VERSION.SDK_INT > NotificationManagerCompat.MAX_SIDE_CHANNEL_SDK_VERSION) {
+                return null;
+            }
+            return new NotificationSideChannelStub();
+        }
+        return null;
+    }
+
+    /**
+     * Handle a side-channeled notification being posted.
+     */
+    public abstract void notify(String packageName, int id, String tag, Notification notification);
+
+    /**
+     * Handle a side-channelled notification being cancelled.
+     */
+    public abstract void cancel(String packageName, int id, String tag);
+
+    /**
+     * Handle the side-channelled cancelling of all notifications for a package.
+     */
+    public abstract void cancelAll(String packageName);
+
+    private class NotificationSideChannelStub extends INotificationSideChannel.Stub {
+        @Override
+        public void notify(String packageName, int id, String tag, Notification notification)
+                throws RemoteException {
+            checkPermission(getCallingUid(), packageName);
+            long idToken = clearCallingIdentity();
+            try {
+                NotificationCompatSideChannelService.this.notify(packageName, id, tag, notification);
+            } finally {
+                restoreCallingIdentity(idToken);
+            }
+        }
+
+        @Override
+        public void cancel(String packageName, int id, String tag) throws RemoteException {
+            checkPermission(getCallingUid(), packageName);
+            long idToken = clearCallingIdentity();
+            try {
+                NotificationCompatSideChannelService.this.cancel(packageName, id, tag);
+            } finally {
+                restoreCallingIdentity(idToken);
+            }
+        }
+
+        @Override
+        public void cancelAll(String packageName) {
+            checkPermission(getCallingUid(), packageName);
+            long idToken = clearCallingIdentity();
+            try {
+                NotificationCompatSideChannelService.this.cancelAll(packageName);
+            } finally {
+                restoreCallingIdentity(idToken);
+            }
+        }
+    }
+
+    private void checkPermission(int callingUid, String packageName) {
+        for (String validPackage : getPackageManager().getPackagesForUid(callingUid)) {
+            if (validPackage.equals(packageName)) {
+                return;
+            }
+        }
+        throw new SecurityException("NotificationSideChannelService: Uid " + callingUid
+                + " is not authorized for package " + packageName);
+    }
+}
diff --git a/v4/java/android/support/v4/app/NotificationManagerCompat.java b/v4/java/android/support/v4/app/NotificationManagerCompat.java
new file mode 100644
index 0000000..a848007
--- /dev/null
+++ b/v4/java/android/support/v4/app/NotificationManagerCompat.java
@@ -0,0 +1,628 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Compatibility library for NotificationManager with fallbacks for older platforms.
+ *
+ * <p>To use this class, call the static function {@link #from} to get a
+ * {@link NotificationManagerCompat} object, and then call one of its
+ * methods to post or cancel notifications.
+ */
+public class NotificationManagerCompat {
+    private static final String TAG = "NotifManCompat";
+
+    /**
+     * Notification extras key: if set to true, the posted notification should use
+     * the side channel for delivery instead of using notification manager.
+     */
+    public static final String EXTRA_USE_SIDE_CHANNEL =
+            NotificationCompatJellybean.EXTRA_USE_SIDE_CHANNEL;
+
+    /**
+     * Intent action to register for on a service to receive side channel
+     * notifications. The listening service must be in the same package as an enabled
+     * {@link android.service.notification.NotificationListenerService}.
+     */
+    public static final String ACTION_BIND_SIDE_CHANNEL =
+            "android.support.BIND_NOTIFICATION_SIDE_CHANNEL";
+
+    /**
+     * Maximum sdk build version which needs support for side channeled notifications.
+     * Currently the only needed use is for side channeling group children before KITKAT_WATCH.
+     */
+    static final int MAX_SIDE_CHANNEL_SDK_VERSION = 19;
+
+    /** Base time delay for a side channel listener queue retry. */
+    private static final int SIDE_CHANNEL_RETRY_BASE_INTERVAL_MS = 1000;
+    /** Maximum retries for a side channel listener before dropping tasks. */
+    private static final int SIDE_CHANNEL_RETRY_MAX_COUNT = 6;
+    /** Hidden field Settings.Secure.ENABLED_NOTIFICATION_LISTENERS */
+    private static final String SETTING_ENABLED_NOTIFICATION_LISTENERS =
+            "enabled_notification_listeners";
+    private static final int SIDE_CHANNEL_BIND_FLAGS;
+
+    /** Cache of enabled notification listener components */
+    private static final Object sEnabledNotificationListenersLock = new Object();
+    /** Guarded by {@link #sEnabledNotificationListenersLock} */
+    private static String sEnabledNotificationListeners;
+    /** Guarded by {@link #sEnabledNotificationListenersLock} */
+    private static Set<String> sEnabledNotificationListenerPackages = new HashSet<String>();
+
+    private final Context mContext;
+    private final NotificationManager mNotificationManager;
+    /** Lock for mutable static fields */
+    private static final Object sLock = new Object();
+    /** Guarded by {@link #sLock} */
+    private static SideChannelManager sSideChannelManager;
+
+    /** Get a {@link NotificationManagerCompat} instance for a provided context. */
+    public static NotificationManagerCompat from(Context context) {
+        return new NotificationManagerCompat(context);
+    }
+
+    private NotificationManagerCompat(Context context) {
+        mContext = context;
+        mNotificationManager = (NotificationManager) mContext.getSystemService(
+                Context.NOTIFICATION_SERVICE);
+    }
+
+    private static final Impl IMPL;
+
+    interface Impl {
+        void cancelNotification(NotificationManager notificationManager, String tag, int id);
+
+        void postNotification(NotificationManager notificationManager, String tag, int id,
+                Notification notification);
+
+        int getSideChannelBindFlags();
+    }
+
+    static class ImplBase implements Impl {
+        @Override
+        public void cancelNotification(NotificationManager notificationManager, String tag,
+                int id) {
+            notificationManager.cancel(id);
+        }
+
+        @Override
+        public void postNotification(NotificationManager notificationManager, String tag, int id,
+                Notification notification) {
+            notificationManager.notify(id, notification);
+        }
+
+        @Override
+        public int getSideChannelBindFlags() {
+            return Service.BIND_AUTO_CREATE;
+        }
+    }
+
+    static class ImplEclair extends ImplBase {
+        @Override
+        public void cancelNotification(NotificationManager notificationManager, String tag,
+                int id) {
+            NotificationManagerCompatEclair.cancelNotification(notificationManager, tag, id);
+        }
+
+        @Override
+        public void postNotification(NotificationManager notificationManager, String tag, int id,
+                Notification notification) {
+            NotificationManagerCompatEclair.postNotification(notificationManager, tag, id,
+                    notification);
+        }
+    }
+
+    static class ImplIceCreamSandwich extends ImplEclair {
+        @Override
+        public int getSideChannelBindFlags() {
+            return NotificationManagerCompatIceCreamSandwich.SIDE_CHANNEL_BIND_FLAGS;
+        }
+    }
+
+    static {
+        if (Build.VERSION.SDK_INT >= 14) {
+            IMPL = new ImplIceCreamSandwich();
+        } else if (Build.VERSION.SDK_INT >= 5) {
+            IMPL = new ImplEclair();
+        } else {
+            IMPL = new ImplBase();
+        }
+        SIDE_CHANNEL_BIND_FLAGS = IMPL.getSideChannelBindFlags();
+    }
+
+    /**
+     * Cancel a previously shown notification.
+     * @param id the ID of the notification
+     */
+    public void cancel(int id) {
+        cancel(null, id);
+    }
+
+    /**
+     * Cancel a previously shown notification.
+     * @param tag the string identifier of the notification.
+     * @param id the ID of the notification
+     */
+    public void cancel(String tag, int id) {
+        IMPL.cancelNotification(mNotificationManager, tag, id);
+        if (Build.VERSION.SDK_INT <= MAX_SIDE_CHANNEL_SDK_VERSION) {
+            pushSideChannelQueue(new CancelTask(mContext.getPackageName(), id, tag));
+        }
+    }
+
+    /** Cancel all previously shown notifications. */
+    public void cancelAll() {
+        mNotificationManager.cancelAll();
+        if (Build.VERSION.SDK_INT <= MAX_SIDE_CHANNEL_SDK_VERSION) {
+            pushSideChannelQueue(new CancelTask(mContext.getPackageName()));
+        }
+    }
+
+    /**
+     * Post a notification to be shown in the status bar, stream, etc.
+     * @param id the ID of the notification
+     * @param notification the notification to post to the system
+     */
+    public void notify(int id, Notification notification) {
+        notify(null, id, notification);
+    }
+
+    /**
+     * Post a notification to be shown in the status bar, stream, etc.
+     * @param tag the string identifier for a notification. Can be {@code null}.
+     * @param id the ID of the notification. The pair (tag, id) must be unique within your app.
+     * @param notification the notification to post to the system
+    */
+    public void notify(String tag, int id, Notification notification) {
+        if (useSideChannelForNotification(notification)) {
+            pushSideChannelQueue(new NotifyTask(mContext.getPackageName(), id, tag, notification));
+            // Cancel this notification in notification manager if it just transitioned to being
+            // side channelled.
+            IMPL.cancelNotification(mNotificationManager, tag, id);
+        } else {
+            IMPL.postNotification(mNotificationManager, tag, id, notification);
+        }
+    }
+
+    /**
+     * Get the set of packages that have an enabled notification listener component within them.
+     */
+    public static Set<String> getEnabledListenerPackages(Context context) {
+        final String enabledNotificationListeners = Settings.Secure.getString(
+                context.getContentResolver(),
+                SETTING_ENABLED_NOTIFICATION_LISTENERS);
+        // Parse the string again if it is different from the last time this method was called.
+        if (enabledNotificationListeners != null
+                && !enabledNotificationListeners.equals(sEnabledNotificationListeners)) {
+            final String[] components = enabledNotificationListeners.split(":");
+            Set<String> packageNames = new HashSet<String>(components.length);
+            for (String component : components) {
+                ComponentName componentName = ComponentName.unflattenFromString(component);
+                if (componentName != null) {
+                    packageNames.add(componentName.getPackageName());
+                }
+            }
+            synchronized (sEnabledNotificationListenersLock) {
+                sEnabledNotificationListenerPackages = packageNames;
+                sEnabledNotificationListeners = enabledNotificationListeners;
+            }
+        }
+        return sEnabledNotificationListenerPackages;
+    }
+
+    /**
+     * Returns true if this notification should use the side channel for delivery.
+     */
+    private static boolean useSideChannelForNotification(Notification notification) {
+        Bundle extras = NotificationCompat.getExtras(notification);
+        return extras != null && extras.getBoolean(EXTRA_USE_SIDE_CHANNEL);
+    }
+
+    /**
+     * Push a notification task for distribution to notification side channels.
+     */
+    private void pushSideChannelQueue(Task task) {
+        synchronized (sLock) {
+            if (sSideChannelManager == null) {
+                sSideChannelManager = new SideChannelManager(mContext.getApplicationContext());
+            }
+        }
+        sSideChannelManager.queueTask(task);
+    }
+
+    /**
+     * Helper class to manage a queue of pending tasks to send to notification side channel
+     * listeners.
+     */
+    private static class SideChannelManager implements Handler.Callback, ServiceConnection {
+        private static final int MSG_QUEUE_TASK = 0;
+        private static final int MSG_SERVICE_CONNECTED = 1;
+        private static final int MSG_SERVICE_DISCONNECTED = 2;
+        private static final int MSG_RETRY_LISTENER_QUEUE = 3;
+
+        private static final String KEY_BINDER = "binder";
+
+        private final Context mContext;
+        private final HandlerThread mHandlerThread;
+        private final Handler mHandler;
+        private final Map<ComponentName, ListenerRecord> mRecordMap =
+                new HashMap<ComponentName, ListenerRecord>();
+        private Set<String> mCachedEnabledPackages = new HashSet<String>();
+
+        public SideChannelManager(Context context) {
+            mContext = context;
+            mHandlerThread = new HandlerThread("NotificationManagerCompat");
+            mHandlerThread.start();
+            mHandler = new Handler(mHandlerThread.getLooper(), this);
+        }
+
+        /**
+         * Queue a new task to be sent to all listeners. This function can be called
+         * from any thread.
+         */
+        public void queueTask(Task task) {
+            mHandler.obtainMessage(MSG_QUEUE_TASK, task).sendToTarget();
+        }
+
+        @Override
+        public boolean handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_QUEUE_TASK:
+                    handleQueueTask((Task) msg.obj);
+                    return true;
+                case MSG_SERVICE_CONNECTED:
+                    ServiceConnectedEvent event = (ServiceConnectedEvent) msg.obj;
+                    handleServiceConnected(event.componentName, event.iBinder);
+                    return true;
+                case MSG_SERVICE_DISCONNECTED:
+                    handleServiceDisconnected((ComponentName) msg.obj);
+                    return true;
+                case MSG_RETRY_LISTENER_QUEUE:
+                    handleRetryListenerQueue((ComponentName) msg.obj);
+                    return true;
+            }
+            return false;
+        }
+
+        private void handleQueueTask(Task task) {
+            updateListenerMap();
+            for (ListenerRecord record : mRecordMap.values()) {
+                record.taskQueue.add(task);
+                processListenerQueue(record);
+            }
+        }
+
+        private void handleServiceConnected(ComponentName componentName, IBinder iBinder) {
+            ListenerRecord record = mRecordMap.get(componentName);
+            if (record != null) {
+                record.service = INotificationSideChannel.Stub.asInterface(iBinder);
+                record.retryCount = 0;
+                processListenerQueue(record);
+            }
+        }
+
+        private void handleServiceDisconnected(ComponentName componentName) {
+            ListenerRecord record = mRecordMap.get(componentName);
+            if (record != null) {
+                ensureServiceUnbound(record);
+            }
+        }
+
+        private void handleRetryListenerQueue(ComponentName componentName) {
+            ListenerRecord record = mRecordMap.get(componentName);
+            if (record != null) {
+                processListenerQueue(record);
+            }
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Connected to service " + componentName);
+            }
+            mHandler.obtainMessage(MSG_SERVICE_CONNECTED,
+                    new ServiceConnectedEvent(componentName, iBinder))
+                    .sendToTarget();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName componentName) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Disconnected from service " + componentName);
+            }
+            mHandler.obtainMessage(MSG_SERVICE_DISCONNECTED, componentName).sendToTarget();
+        }
+
+        /**
+         * Check the current list of enabled listener packages and update the records map
+         * accordingly.
+         */
+        private void updateListenerMap() {
+            Set<String> enabledPackages = getEnabledListenerPackages(mContext);
+            if (enabledPackages.equals(mCachedEnabledPackages)) {
+                // Short-circuit when the list of enabled packages has not changed.
+                return;
+            }
+            mCachedEnabledPackages = enabledPackages;
+            List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServices(
+                    new Intent().setAction(ACTION_BIND_SIDE_CHANNEL), PackageManager.GET_SERVICES);
+            Set<ComponentName> enabledComponents = new HashSet<ComponentName>();
+            for (ResolveInfo resolveInfo : resolveInfos) {
+                if (!enabledPackages.contains(resolveInfo.serviceInfo.packageName)) {
+                    continue;
+                }
+                ComponentName componentName = new ComponentName(
+                        resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
+                if (resolveInfo.serviceInfo.permission != null) {
+                    Log.w(TAG, "Permission present on component " + componentName
+                            + ", not adding listener record.");
+                    continue;
+                }
+                enabledComponents.add(componentName);
+            }
+            // Ensure all enabled components have a record in the listener map.
+            for (ComponentName componentName : enabledComponents) {
+                if (!mRecordMap.containsKey(componentName)) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Adding listener record for " + componentName);
+                    }
+                    mRecordMap.put(componentName, new ListenerRecord(componentName));
+                }
+            }
+            // Remove listener records that are no longer for enabled components.
+            Iterator<Map.Entry<ComponentName, ListenerRecord>> it =
+                    mRecordMap.entrySet().iterator();
+            while (it.hasNext()) {
+                Map.Entry<ComponentName, ListenerRecord> entry = it.next();
+                if (!enabledComponents.contains(entry.getKey())) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Removing listener record for " + entry.getKey());
+                    }
+                    ensureServiceUnbound(entry.getValue());
+                    it.remove();
+                }
+            }
+        }
+
+        /**
+         * Ensure we are already attempting to bind to a service, or start a new binding if not.
+         * @return Whether the service bind attempt was successful.
+         */
+        private boolean ensureServiceBound(ListenerRecord record) {
+            if (record.bound) {
+                return true;
+            }
+            Intent intent = new Intent(ACTION_BIND_SIDE_CHANNEL).setComponent(record.componentName);
+            record.bound = mContext.bindService(intent, this, SIDE_CHANNEL_BIND_FLAGS);
+            if (record.bound) {
+                record.retryCount = 0;
+            } else {
+                Log.w(TAG, "Unable to bind to listener " + record.componentName);
+                mContext.unbindService(this);
+            }
+            return record.bound;
+        }
+
+        /**
+         * Ensure we have unbound from a service.
+         */
+        private void ensureServiceUnbound(ListenerRecord record) {
+            if (record.bound) {
+                mContext.unbindService(this);
+                record.bound = false;
+            }
+            record.service = null;
+        }
+
+        /**
+         * Schedule a delayed retry to communicate with a listener service.
+         * After a maximum number of attempts (with exponential back-off), start
+         * dropping pending tasks for this listener.
+         */
+        private void scheduleListenerRetry(ListenerRecord record) {
+            if (mHandler.hasMessages(MSG_RETRY_LISTENER_QUEUE, record.componentName)) {
+                return;
+            }
+            record.retryCount++;
+            if (record.retryCount > SIDE_CHANNEL_RETRY_MAX_COUNT) {
+                Log.w(TAG, "Giving up on delivering " + record.taskQueue.size() + " tasks to "
+                        + record.componentName + " after " + record.retryCount + " retries");
+                record.taskQueue.clear();
+                return;
+            }
+            int delayMs = SIDE_CHANNEL_RETRY_BASE_INTERVAL_MS * (1 << (record.retryCount - 1));
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Scheduling retry for " + delayMs + " ms");
+            }
+            Message msg = mHandler.obtainMessage(MSG_RETRY_LISTENER_QUEUE, record.componentName);
+            mHandler.sendMessageDelayed(msg, delayMs);
+        }
+
+        /**
+         * Perform a processing step for a listener. First check the bind state, then attempt
+         * to flush the task queue, and if an error is encountered, schedule a retry.
+         */
+        private void processListenerQueue(ListenerRecord record) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Processing component " + record.componentName + ", "
+                        + record.taskQueue.size() + " queued tasks");
+            }
+            if (record.taskQueue.isEmpty()) {
+                return;
+            }
+            if (!ensureServiceBound(record) || record.service == null) {
+                // Ensure bind has started and that a service interface is ready to use.
+                scheduleListenerRetry(record);
+                return;
+            }
+            // Attempt to flush all items in the task queue.
+            while (true) {
+                Task task = record.taskQueue.peek();
+                if (task == null) {
+                    break;
+                }
+                try {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Sending task " + task);
+                    }
+                    task.send(record.service);
+                    record.taskQueue.remove();
+                } catch (DeadObjectException e) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Remote service has died: " + record.componentName);
+                    }
+                    break;
+                } catch (RemoteException e) {
+                    Log.w(TAG, "RemoteException communicating with " + record.componentName, e);
+                    break;
+                }
+            }
+            if (!record.taskQueue.isEmpty()) {
+                // Some tasks were not sent, meaning an error was encountered, schedule a retry.
+                scheduleListenerRetry(record);
+            }
+        }
+
+        /** A per-side-channel-service listener state record */
+        private static class ListenerRecord {
+            public final ComponentName componentName;
+            /** Whether the service is currently bound to. */
+            public boolean bound = false;
+            /** The service stub provided by onServiceConnected */
+            public INotificationSideChannel service;
+            /** Queue of pending tasks to send to this listener service */
+            public LinkedList<Task> taskQueue = new LinkedList<Task>();
+            /** Number of retries attempted while connecting to this listener service */
+            public int retryCount = 0;
+
+            public ListenerRecord(ComponentName componentName) {
+                this.componentName = componentName;
+            }
+        }
+    }
+
+    private static class ServiceConnectedEvent {
+        final ComponentName componentName;
+        final IBinder iBinder;
+
+        public ServiceConnectedEvent(ComponentName componentName,
+                final IBinder iBinder) {
+            this.componentName = componentName;
+            this.iBinder = iBinder;
+        }
+    }
+
+    private interface Task {
+        public void send(INotificationSideChannel service) throws RemoteException;
+    }
+
+    private static class NotifyTask implements Task {
+        final String packageName;
+        final int id;
+        final String tag;
+        final Notification notif;
+
+        public NotifyTask(String packageName, int id, String tag, Notification notif) {
+            this.packageName = packageName;
+            this.id = id;
+            this.tag = tag;
+            this.notif = notif;
+        }
+
+        @Override
+        public void send(INotificationSideChannel service) throws RemoteException {
+            service.notify(packageName, id, tag, notif);
+        }
+
+        public String toString() {
+            StringBuilder sb = new StringBuilder("NotifyTask[");
+            sb.append("packageName:").append(packageName);
+            sb.append(", id:").append(id);
+            sb.append(", tag:").append(tag);
+            sb.append("]");
+            return sb.toString();
+        }
+    }
+
+    private static class CancelTask implements Task {
+        final String packageName;
+        final int id;
+        final String tag;
+        final boolean all;
+
+        public CancelTask(String packageName) {
+            this.packageName = packageName;
+            this.id = 0;
+            this.tag = null;
+            this.all = true;
+        }
+
+        public CancelTask(String packageName, int id, String tag) {
+            this.packageName = packageName;
+            this.id = id;
+            this.tag = tag;
+            this.all = false;
+        }
+
+        @Override
+        public void send(INotificationSideChannel service) throws RemoteException {
+            if (all) {
+                service.cancelAll(packageName);
+            } else {
+                service.cancel(packageName, id, tag);
+            }
+        }
+
+        public String toString() {
+            StringBuilder sb = new StringBuilder("CancelTask[");
+            sb.append("packageName:").append(packageName);
+            sb.append(", id:").append(id);
+            sb.append(", tag:").append(tag);
+            sb.append(", all:").append(all);
+            sb.append("]");
+            return sb.toString();
+        }
+    }
+}
diff --git a/v4/java/android/support/v4/app/RemoteInput.java b/v4/java/android/support/v4/app/RemoteInput.java
new file mode 100644
index 0000000..38ecad0
--- /dev/null
+++ b/v4/java/android/support/v4/app/RemoteInput.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Helper for using the {@link android.app.RemoteInput} API
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
+public class RemoteInput extends RemoteInputCompatBase.RemoteInput {
+    private static final String TAG = "RemoteInput";
+
+    /** Label used to denote the clip data type used for remote input transport */
+    public static final String RESULTS_CLIP_LABEL = RemoteInputCompatJellybean.RESULTS_CLIP_LABEL;
+
+    /** Extra added to a clip data intent object to hold the results bundle. */
+    public static final String EXTRA_RESULTS_DATA = RemoteInputCompatJellybean.EXTRA_RESULTS_DATA;
+
+    private final String mResultKey;
+    private final CharSequence mLabel;
+    private final CharSequence[] mChoices;
+    private final boolean mAllowFreeFormInput;
+    private final Bundle mExtras;
+
+    RemoteInput(String resultKey, CharSequence label, CharSequence[] choices,
+            boolean allowFreeFormInput, Bundle extras) {
+        this.mResultKey = resultKey;
+        this.mLabel = label;
+        this.mChoices = choices;
+        this.mAllowFreeFormInput = allowFreeFormInput;
+        this.mExtras = extras;
+    }
+
+    /**
+     * Get the key that the result of this input will be set in from the Bundle returned by
+     * {@link #getResultsFromIntent} when the {@link android.app.PendingIntent} is sent.
+     */
+    public String getResultKey() {
+        return mResultKey;
+    }
+
+    /**
+     * Get the label to display to users when collecting this input.
+     */
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+
+    /**
+     * Get possible input choices. This can be {@code null} if there are no choices to present.
+     */
+    public CharSequence[] getChoices() {
+        return mChoices;
+    }
+
+    /**
+     * Get whether or not users can provide an arbitrary value for
+     * input. If you set this to {@code false}, users must select one of the
+     * choices in {@link #getChoices}. An {@link IllegalArgumentException} is thrown
+     * if you set this to false and {@link #getChoices} returns {@code null} or empty.
+     */
+    public boolean getAllowFreeFormInput() {
+        return mAllowFreeFormInput;
+    }
+
+    /**
+     * Get additional metadata carried around with this remote input.
+     */
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /**
+     * Builder class for {@link android.support.v4.app.RemoteInput} objects.
+     */
+    public static final class Builder {
+        private final String mResultKey;
+        private CharSequence mLabel;
+        private CharSequence[] mChoices;
+        private boolean mAllowFreeFormInput = true;
+        private Bundle mExtras = new Bundle();
+
+        /**
+         * Create a builder object for {@link android.support.v4.app.RemoteInput} objects.
+         * @param resultKey the Bundle key that refers to this input when collected from the user
+         */
+        public Builder(String resultKey) {
+            if (resultKey == null) {
+                throw new IllegalArgumentException("Result key can't be null");
+            }
+            mResultKey = resultKey;
+        }
+
+        /**
+         * Set a label to be displayed to the user when collecting this input.
+         * @param label The label to show to users when they input a response.
+         * @return this object for method chaining
+         */
+        public Builder setLabel(CharSequence label) {
+            mLabel = label;
+            return this;
+        }
+
+        /**
+         * Specifies choices available to the user to satisfy this input.
+         * @param choices an array of pre-defined choices for users input.
+         *        You must provide a non-null and non-empty array if
+         *        you disabled free form input using {@link #setAllowFreeFormInput}.
+         * @return this object for method chaining
+         */
+        public Builder setChoices(CharSequence[] choices) {
+            mChoices = choices;
+            return this;
+        }
+
+        /**
+         * Specifies whether the user can provide arbitrary values.
+         *
+         * @param allowFreeFormInput The default is {@code true}.
+         *         If you specify {@code false}, you must provide a non-null
+         *         and non-empty array to {@link #setChoices} or an
+         *         {@link IllegalArgumentException} is thrown.
+         * @return this object for method chaining
+         */
+        public Builder setAllowFreeFormInput(boolean allowFreeFormInput) {
+            mAllowFreeFormInput = allowFreeFormInput;
+            return this;
+        }
+
+        /**
+         * Merge additional metadata into this builder.
+         *
+         * <p>Values within the Bundle will replace existing extras values in this Builder.
+         *
+         * @see RemoteInput#getExtras
+         */
+        public Builder addExtras(Bundle extras) {
+            if (extras != null) {
+                mExtras.putAll(extras);
+            }
+            return this;
+        }
+
+        /**
+         * Get the metadata Bundle used by this Builder.
+         *
+         * <p>The returned Bundle is shared with this Builder.
+         */
+        public Bundle getExtras() {
+            return mExtras;
+        }
+
+        /**
+         * Combine all of the options that have been set and return a new
+         * {@link android.support.v4.app.RemoteInput} object.
+         */
+        public RemoteInput build() {
+            return new RemoteInput(mResultKey, mLabel, mChoices, mAllowFreeFormInput, mExtras);
+        }
+    }
+
+    /**
+     * Get the remote input results bundle from an intent. The returned Bundle will
+     * contain a key/value for every result key populated by remote input collector.
+     * Use the {@link Bundle#getCharSequence(String)} method to retrieve a value.
+     * @param intent The intent object that fired in response to an action or content intent
+     *               which also had one or more remote input requested.
+     */
+    public static Bundle getResultsFromIntent(Intent intent) {
+        return IMPL.getResultsFromIntent(intent);
+    }
+
+    /**
+     * Populate an intent object with the results gathered from remote input. This method
+     * should only be called by remote input collection services when sending results to a
+     * pending intent.
+     * @param remoteInputs The remote inputs for which results are being provided
+     * @param intent The intent to add remote inputs to. The {@link android.content.ClipData}
+     *               field of the intent will be modified to contain the results.
+     * @param results A bundle holding the remote input results. This bundle should
+     *                be populated with keys matching the result keys specified in
+     *                {@code remoteInputs} with values being the result per key.
+     */
+    public static void addResultsToIntent(RemoteInput[] remoteInputs, Intent intent,
+            Bundle results) {
+        IMPL.addResultsToIntent(remoteInputs, intent, results);
+    }
+
+    private static final Impl IMPL;
+
+    interface Impl {
+        Bundle getResultsFromIntent(Intent intent);
+        void addResultsToIntent(RemoteInput[] remoteInputs, Intent intent,
+                Bundle results);
+    }
+
+    static class ImplBase implements Impl {
+        @Override
+        public Bundle getResultsFromIntent(Intent intent) {
+            Log.w(TAG, "RemoteInput is only supported from API Level 16");
+            return null;
+        }
+
+        @Override
+        public void addResultsToIntent(RemoteInput[] remoteInputs, Intent intent, Bundle results) {
+            Log.w(TAG, "RemoteInput is only supported from API Level 16");
+        }
+    }
+
+    static class ImplJellybean implements Impl {
+        @Override
+        public Bundle getResultsFromIntent(Intent intent) {
+            return RemoteInputCompatJellybean.getResultsFromIntent(intent);
+        }
+
+        @Override
+        public void addResultsToIntent(RemoteInput[] remoteInputs, Intent intent, Bundle results) {
+            RemoteInputCompatJellybean.addResultsToIntent(remoteInputs, intent, results);
+        }
+    }
+
+    static class ImplApi20 implements Impl {
+        @Override
+        public Bundle getResultsFromIntent(Intent intent) {
+            return RemoteInputCompatApi20.getResultsFromIntent(intent);
+        }
+
+        @Override
+        public void addResultsToIntent(RemoteInput[] remoteInputs, Intent intent, Bundle results) {
+            RemoteInputCompatApi20.addResultsToIntent(remoteInputs, intent, results);
+        }
+    }
+
+    static {
+        if (Build.VERSION.SDK_INT >= 20) {
+            IMPL = new ImplApi20();
+        } else if (Build.VERSION.SDK_INT >= 16) {
+            IMPL = new ImplJellybean();
+        } else {
+            IMPL = new ImplBase();
+        }
+    }
+
+    /** @hide */
+    public static final Factory FACTORY = new Factory() {
+        @Override
+        public RemoteInput build(String resultKey,
+                CharSequence label, CharSequence[] choices, boolean allowFreeFormInput,
+                Bundle extras) {
+            return new RemoteInput(resultKey, label, choices, allowFreeFormInput, extras);
+        }
+
+        @Override
+        public RemoteInput[] newArray(int size) {
+            return new RemoteInput[size];
+        }
+    };
+}
diff --git a/v4/java/android/support/v4/app/ShareCompat.java b/v4/java/android/support/v4/app/ShareCompat.java
index 52c4b12..87ebc49 100644
--- a/v4/java/android/support/v4/app/ShareCompat.java
+++ b/v4/java/android/support/v4/app/ShareCompat.java
@@ -24,6 +24,7 @@
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Build;
+import android.support.annotation.StringRes;
 import android.support.v4.content.IntentCompat;
 import android.support.v4.view.MenuItemCompat;
 import android.text.Html;
@@ -403,7 +404,7 @@
          * @param resId Resource ID of the title string to use
          * @return This IntentBuilder for method chaining
          */
-        public IntentBuilder setChooserTitle(int resId) {
+        public IntentBuilder setChooserTitle(@StringRes int resId) {
             return setChooserTitle(mActivity.getText(resId));
         }
 
diff --git a/v4/java/android/support/v4/app/SharedElementListener.java b/v4/java/android/support/v4/app/SharedElementListener.java
new file mode 100644
index 0000000..06b1c9b
--- /dev/null
+++ b/v4/java/android/support/v4/app/SharedElementListener.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.app;
+
+import android.view.View;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Listener provided in
+ * {@link FragmentActivity#setEnterSharedElementListener(SharedElementListener)} and
+ * {@link FragmentActivity#setExitSharedElementListener(SharedElementListener)}
+ * to monitor the Activity transitions. The events can be used to customize Activity
+ * Transition behavior.
+ */
+public abstract class SharedElementListener {
+
+    /**
+     * Called to allow the listener to customize the start state of the shared element when
+     * transferring in shared element state.
+     * <p>
+     *     The shared element will start at the size and position of the shared element
+     *     in the launching Activity or Fragment. It will also transfer ImageView scaleType
+     *     and imageMatrix if the shared elements in the calling and called Activities are
+     *     ImageViews. Some applications may want to make additional changes, such as
+     *     changing the clip bounds, scaling, or rotation if the shared element end state
+     *     does not map well to the start state.
+     * </p>
+     *
+     * @param sharedElementNames The names of the shared elements that were accepted into
+     *                           the View hierarchy.
+     * @param sharedElements The shared elements that are part of the View hierarchy.
+     * @param sharedElementSnapshots The Views containing snap shots of the shared element
+     *                               from the launching Window. These elements will not
+     *                               be part of the scene, but will be positioned relative
+     *                               to the Window decor View.
+     */
+    public void setSharedElementStart(List<String> sharedElementNames,
+            List<View> sharedElements, List<View> sharedElementSnapshots) {}
+
+    /**
+     * Called to allow the listener to customize the end state of the shared element when
+     * transferring in shared element state.
+     * <p>
+     *     Any customization done in
+     *     {@link #setSharedElementStart(java.util.List, java.util.List, java.util.List)}
+     *     may need to be modified to the final state of the shared element if it is not
+     *     automatically corrected by layout. For example, rotation or scale will not
+     *     be affected by layout and if changed in {@link #setSharedElementStart(java.util.List,
+     *     java.util.List, java.util.List)}, it will also have to be set here again to correct
+     *     the end state.
+     * </p>
+     *
+     * @param sharedElementNames The names of the shared elements that were accepted into
+     *                           the View hierarchy.
+     * @param sharedElements The shared elements that are part of the View hierarchy.
+     * @param sharedElementSnapshots The Views containing snap shots of the shared element
+     *                               from the launching Window. These elements will not
+     *                               be part of the scene, but will be positioned relative
+     *                               to the Window decor View.
+     */
+    public void setSharedElementEnd(List<String> sharedElementNames,
+            List<View> sharedElements, List<View> sharedElementSnapshots) {}
+
+    /**
+     * Called after {@link #remapSharedElements(java.util.List, java.util.Map)} when
+     * transferring shared elements in. Any shared elements that have no mapping will be in
+     * <var>rejectedSharedElements</var>. The elements remaining in
+     * <var>rejectedSharedElements</var> will be transitioned out of the Scene. If a
+     * View is removed from <var>rejectedSharedElements</var>, it must be handled by the
+     * <code>SharedElementListener</code>.
+     * <p>
+     * Views in rejectedSharedElements will have their position and size set to the
+     * position of the calling shared element, relative to the Window decor View and contain
+     * snapshots of the View from the calling Activity or Fragment. This
+     * view may be safely added to the decor View's overlay to remain in position.
+     * </p>
+     *
+     * @param rejectedSharedElements Views containing visual information of shared elements
+     *                               that are not part of the entering scene. These Views
+     *                               are positioned relative to the Window decor View. A
+     *                               View removed from this list will not be transitioned
+     *                               automatically.
+     */
+    public void handleRejectedSharedElements(List<View> rejectedSharedElements) {}
+
+    /**
+     * Lets the ActivityTransitionListener adjust the mapping of shared element names to
+     * Views.
+     *
+     * @param names The names of all shared elements transferred from the calling Activity
+     *              to the started Activity.
+     * @param sharedElements The mapping of shared element names to Views. The best guess
+     *                       will be filled into sharedElements based on the transitionNames.
+     */
+    public void remapSharedElements(List<String> names, Map<String, View> sharedElements) {}
+}
diff --git a/v4/java/android/support/v4/content/ContextCompat.java b/v4/java/android/support/v4/content/ContextCompat.java
index 8fbf4bb..766e9a9 100644
--- a/v4/java/android/support/v4/content/ContextCompat.java
+++ b/v4/java/android/support/v4/content/ContextCompat.java
@@ -18,11 +18,14 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.StatFs;
 import android.support.v4.os.EnvironmentCompat;
+import android.util.Log;
 
 import java.io.File;
 
@@ -31,6 +34,7 @@
  * introduced after API level 4 in a backwards compatible fashion.
  */
 public class ContextCompat {
+    private static final String TAG = "ContextCompat";
 
     private static final String DIR_ANDROID = "Android";
     private static final String DIR_DATA = "data";
@@ -297,4 +301,89 @@
         }
         return cur;
     }
+
+    /**
+     * Return a drawable object associated with a particular resource ID.
+     * <p>
+     * Starting in {@link android.os.Build.VERSION_CODES#L}, the returned
+     * drawable will be styled for the specified Context's theme.
+     *
+     * @param id The desired resource identifier, as generated by the aapt tool.
+     *            This integer encodes the package, type, and resource entry.
+     *            The value 0 is an invalid identifier.
+     * @return Drawable An object that can be used to draw this resource.
+     */
+    public static final Drawable getDrawable(Context context, int id) {
+        final int version = Build.VERSION.SDK_INT;
+        if (version >= 21) {
+            return ContextCompatApi21.getDrawable(context, id);
+        } else {
+            return context.getResources().getDrawable(id);
+        }
+    }
+
+    /**
+     * Returns the absolute path to the directory on the filesystem similar to
+     * {@link Context#getFilesDir()}.  The difference is that files placed under this
+     * directory will be excluded from automatic backup to remote storage on
+     * devices running {@link android.os.Build.VERSION_CODES#L} or later.  See
+     * {@link android.app.backup.BackupAgent BackupAgent} for a full discussion
+     * of the automatic backup mechanism in Android.
+     *
+     * <p>No permissions are required to read or write to the returned path, since this
+     * path is internal storage.
+     *
+     * @return The path of the directory holding application files that will not be
+     *         automatically backed up to remote storage.
+     *
+     * @see android.content.Context.getFilesDir
+     */
+    public final File getNoBackupFilesDir(Context context) {
+        final int version = Build.VERSION.SDK_INT;
+        if (version >= 21) {
+            return ContextCompatApi21.getNoBackupFilesDir(context);
+        } else {
+            ApplicationInfo appInfo = context.getApplicationInfo();
+            return createFilesDir(new File(appInfo.dataDir, "no_backup"));
+        }
+    }
+
+    /**
+     * Returns the absolute path to the application specific cache directory on
+     * the filesystem designed for storing cached code. On devices running
+     * {@link android.os.Build.VERSION_CODES#L} or later, the system will delete
+     * any files stored in this location both when your specific application is
+     * upgraded, and when the entire platform is upgraded.
+     * <p>
+     * This location is optimal for storing compiled or optimized code generated
+     * by your application at runtime.
+     * <p>
+     * Apps require no extra permissions to read or write to the returned path,
+     * since this path lives in their private storage.
+     *
+     * @return The path of the directory holding application code cache files.
+     */
+    public final File getCodeCacheDir(Context context) {
+        final int version = Build.VERSION.SDK_INT;
+        if (version >= 21) {
+            return ContextCompatApi21.getCodeCacheDir(context);
+        } else {
+            ApplicationInfo appInfo = context.getApplicationInfo();
+            return createFilesDir(new File(appInfo.dataDir, "code_cache"));
+        }
+    }
+
+    private synchronized static File createFilesDir(File file) {
+        if (!file.exists()) {
+            if (!file.mkdirs()) {
+                if (file.exists()) {
+                    // spurious failure; probably racing with another process for this app
+                    return file;
+                }
+                Log.w(TAG, "Unable to create files subdir " + file.getPath());
+                return null;
+            }
+        }
+        return file;
+    }
 }
diff --git a/v4/java/android/support/v4/content/res/ResourcesCompat.java b/v4/java/android/support/v4/content/res/ResourcesCompat.java
new file mode 100644
index 0000000..2dbd334
--- /dev/null
+++ b/v4/java/android/support/v4/content/res/ResourcesCompat.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.content.res;
+
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.Resources.Theme;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+
+/**
+ * Helper for accessing features in {@link android.content.res.Resources}
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
+public class ResourcesCompat {
+    /**
+     * Return a drawable object associated with a particular resource ID and
+     * styled for the specified theme. Various types of objects will be
+     * returned depending on the underlying resource -- for example, a solid
+     * color, PNG image, scalable image, etc.
+     * <p>
+     * Prior to API level 21, the theme will not be applied and this method
+     * simply calls through to {@link Resources#getDrawable(int)}.
+     *
+     * @param id The desired resource identifier, as generated by the aapt
+     *           tool. This integer encodes the package, type, and resource
+     *           entry. The value 0 is an invalid identifier.
+     * @param theme The theme used to style the drawable attributes, may be {@code null}.
+     * @return Drawable An object that can be used to draw this resource.
+     * @throws NotFoundException Throws NotFoundException if the given ID does
+     *         not exist.
+     */
+    public Drawable getDrawable(Resources res, int id, Theme theme)
+            throws NotFoundException {
+        final int version = Build.VERSION.SDK_INT;
+        if (version >= 21) {
+            return ResourcesCompatApi21.getDrawable(res, id, theme);
+        } else {
+            return res.getDrawable(id);
+        }
+    }
+}
diff --git a/v4/java/android/support/v4/graphics/BitmapCompat.java b/v4/java/android/support/v4/graphics/BitmapCompat.java
new file mode 100644
index 0000000..5b1cb68
--- /dev/null
+++ b/v4/java/android/support/v4/graphics/BitmapCompat.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.graphics;
+
+import android.graphics.Bitmap;
+
+/**
+ * Helper for accessing features in {@link android.graphics.Bitmap}
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
+public class BitmapCompat {
+    /**
+     * Interface for the full API.
+     */
+    interface BitmapImpl {
+        public boolean hasMipMap(Bitmap bitmap);
+        public void setHasMipMap(Bitmap bitmap, boolean hasMipMap);
+        public int getAllocationByteCount(Bitmap bitmap);
+    }
+
+    static class BaseBitmapImpl implements BitmapImpl {
+        @Override
+        public boolean hasMipMap(Bitmap bitmap) {
+            return false;
+        }
+
+        @Override
+        public void setHasMipMap(Bitmap bitmap, boolean hasMipMap) {
+        }
+
+        @Override
+        public int getAllocationByteCount(Bitmap bitmap) {
+            return bitmap.getRowBytes() * bitmap.getHeight();
+        }
+    }
+
+    static class HcMr1BitmapCompatImpl extends BaseBitmapImpl {
+        @Override
+        public int getAllocationByteCount(Bitmap bitmap) {
+            return BitmapCompatHoneycombMr1.getAllocationByteCount(bitmap);
+        }
+    }
+
+    static class JbMr2BitmapCompatImpl extends HcMr1BitmapCompatImpl {
+        @Override
+        public boolean hasMipMap(Bitmap bitmap){
+            return BitmapCompatJellybeanMR2.hasMipMap(bitmap);
+        }
+
+        @Override
+        public void setHasMipMap(Bitmap bitmap, boolean hasMipMap) {
+            BitmapCompatJellybeanMR2.setHasMipMap(bitmap, hasMipMap);
+        }
+    }
+
+    static class KitKatBitmapCompatImpl extends JbMr2BitmapCompatImpl {
+        @Override
+        public int getAllocationByteCount(Bitmap bitmap) {
+            return BitmapCompatKitKat.getAllocationByteCount(bitmap);
+        }
+    }
+
+    /**
+     * Select the correct implementation to use for the current platform.
+     */
+    static final BitmapImpl IMPL;
+    static {
+        final int version = android.os.Build.VERSION.SDK_INT;
+        if (version >= 19) {
+            IMPL = new KitKatBitmapCompatImpl();
+        } else if (version >= 18) {
+            IMPL = new JbMr2BitmapCompatImpl();
+        } else if (version >= 12) {
+            IMPL = new HcMr1BitmapCompatImpl();
+        } else {
+            IMPL = new BaseBitmapImpl();
+        }
+    }
+
+    public static boolean hasMipMap(Bitmap bitmap) {
+        return IMPL.hasMipMap(bitmap);
+    }
+
+    public static void setHasMipMap(Bitmap bitmap, boolean hasMipMap) {
+        IMPL.setHasMipMap(bitmap, hasMipMap);
+    }
+
+    /**
+     * Returns the size of the allocated memory used to store this bitmap's pixels in a backwards
+     * compatible way.
+     *
+     * @param bitmap the bitmap in which to return it's allocation size
+     * @return the allocation size in bytes
+     */
+    public static int getAllocationByteCount(Bitmap bitmap) {
+        return IMPL.getAllocationByteCount(bitmap);
+    }
+}
\ No newline at end of file
diff --git a/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java b/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java
index 572a343..a465e55 100644
--- a/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java
+++ b/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java
@@ -17,6 +17,7 @@
 package android.support.v4.graphics.drawable;
 
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 
 /**
  * Helper for accessing features in {@link android.graphics.drawable.Drawable}
@@ -30,6 +31,7 @@
         void jumpToCurrentState(Drawable drawable);
         void setAutoMirrored(Drawable drawable, boolean mirrored);
         boolean isAutoMirrored(Drawable drawable);
+        void setHotspot(Drawable drawable, float x, float y);
     }
 
     /**
@@ -48,6 +50,10 @@
         public boolean isAutoMirrored(Drawable drawable) {
             return false;
         }
+
+        @Override
+        public void setHotspot(Drawable drawable, float x, float y) {
+        }
     }
 
     /**
@@ -61,7 +67,7 @@
     }
 
     /**
-     * Interface implementation for devices with at least v11 APIs.
+     * Interface implementation for devices with at least KitKat APIs.
      */
     static class KitKatDrawableImpl extends HoneycombDrawableImpl {
         @Override
@@ -76,12 +82,24 @@
     }
 
     /**
+     * Interface implementation for devices with at least L APIs.
+     */
+    static class LDrawableImpl extends KitKatDrawableImpl {
+        @Override
+        public void setHotspot(Drawable drawable, float x, float y) {
+            DrawableCompatL.setHotspot(drawable, x, y);
+        }
+    }
+
+    /**
      * Select the correct implementation to use for the current platform.
      */
     static final DrawableImpl IMPL;
     static {
         final int version = android.os.Build.VERSION.SDK_INT;
-        if (version >= 19) {
+        if (version >= 21 || Build.VERSION.CODENAME.equals("L")) {
+            IMPL = new LDrawableImpl();
+        } else if (version >= 19) {
             IMPL = new KitKatDrawableImpl();
         } else if (version >= 11) {
             IMPL = new HoneycombDrawableImpl();
@@ -132,4 +150,14 @@
     public static boolean isAutoMirrored(Drawable drawable) {
         return IMPL.isAutoMirrored(drawable);
     }
+
+    /**
+     * Specifies the hotspot's location within the drawable.
+     *
+     * @param x The X coordinate of the center of the hotspot
+     * @param y The Y coordinate of the center of the hotspot
+     */
+    public static void setHotspot(Drawable drawable, float x, float y) {
+        IMPL.setHotspot(drawable, x, y);
+    }
 }
diff --git a/v4/java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java b/v4/java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
new file mode 100644
index 0000000..1b19da2
--- /dev/null
+++ b/v4/java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.graphics.drawable;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Rect;
+import android.os.Build;
+import android.support.v4.graphics.BitmapCompat;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
+import android.util.Log;
+
+/**
+ * Constructs {@link RoundedBitmapDrawable RoundedBitmapDrawable} objects,
+ * either from Bitmaps directly, or from streams and files.
+ */
+public class RoundedBitmapDrawableFactory {
+    private static final String TAG = "RoundedBitmapDrawableFactory";
+
+    private static class DefaultRoundedBitmapDrawable extends RoundedBitmapDrawable {
+        DefaultRoundedBitmapDrawable(Resources res, Bitmap bitmap) {
+            super(res, bitmap);
+        }
+
+        @Override
+        public void setMipMap(boolean mipMap) {
+            if (mBitmap != null) {
+                BitmapCompat.setHasMipMap(mBitmap, mipMap);
+                invalidateSelf();
+            }
+        }
+
+        @Override
+        public boolean hasMipMap() {
+            return mBitmap != null && BitmapCompat.hasMipMap(mBitmap);
+        }
+
+        @Override
+        void gravityCompatApply(int gravity, int bitmapWidth, int bitmapHeight,
+                Rect bounds, Rect outRect) {
+            GravityCompat.apply(gravity, bitmapWidth, bitmapHeight,
+                    bounds, outRect, ViewCompat.LAYOUT_DIRECTION_LTR);
+        }
+    }
+
+    /**
+     * Returns a new drawable by creating it from a bitmap, setting initial target density based on
+     * the display metrics of the resources.
+     */
+    public static RoundedBitmapDrawable create(Resources res, Bitmap bitmap) {
+        if ("L".equals(Build.VERSION.CODENAME) || Build.VERSION.SDK_INT >= 21) {
+            return new RoundedBitmapDrawable21(res, bitmap);
+        }
+        return new DefaultRoundedBitmapDrawable(res, bitmap);
+    }
+
+    /**
+     * Returns a new drawable, creating it by opening a given file path and decoding the bitmap.
+     */
+    public static RoundedBitmapDrawable create(Resources res,
+            String filepath) {
+        final RoundedBitmapDrawable drawable = create(res, BitmapFactory.decodeFile(filepath));
+        if (drawable.getBitmap() == null) {
+            Log.w(TAG, "BitmapDrawable cannot decode " + filepath);
+        }
+        return drawable;
+    }
+
+
+    /**
+     * Returns a new drawable, creating it by decoding a bitmap from the given input stream.
+     */
+    public static RoundedBitmapDrawable create(Resources res,
+            java.io.InputStream is) {
+        final RoundedBitmapDrawable drawable = create(res, BitmapFactory.decodeStream(is));
+        if (drawable.getBitmap() == null) {
+            Log.w(TAG, "BitmapDrawable cannot decode " + is);
+        }
+        return drawable;
+    }
+
+}
diff --git a/v4/java/android/support/v4/media/MediaMetadataCompat.java b/v4/java/android/support/v4/media/MediaMetadataCompat.java
new file mode 100644
index 0000000..d289cad
--- /dev/null
+++ b/v4/java/android/support/v4/media/MediaMetadataCompat.java
@@ -0,0 +1,630 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.media;
+
+import android.graphics.Bitmap;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v4.util.ArrayMap;
+import android.util.Log;
+
+import java.util.Set;
+
+/**
+ * Contains metadata about an item, such as the title, artist, etc.
+ */
+public final class MediaMetadataCompat implements Parcelable {
+    private static final String TAG = "MediaMetadata";
+
+    /**
+     * The title of the media.
+     */
+    public static final String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
+
+    /**
+     * The artist of the media.
+     */
+    public static final String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
+
+    /**
+     * The duration of the media in ms. A negative duration indicates that the
+     * duration is unknown (or infinite).
+     */
+    public static final String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
+
+    /**
+     * The album title for the media.
+     */
+    public static final String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
+
+    /**
+     * The author of the media.
+     */
+    public static final String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+
+    /**
+     * The writer of the media.
+     */
+    public static final String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
+
+    /**
+     * The composer of the media.
+     */
+    public static final String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
+
+    /**
+     * The compilation status of the media.
+     */
+    public static final String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
+
+    /**
+     * The date the media was created or published as TODO determine format.
+     */
+    public static final String METADATA_KEY_DATE = "android.media.metadata.DATE";
+
+    /**
+     * The year the media was created or published as a long.
+     */
+    public static final String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
+
+    /**
+     * The genre of the media.
+     */
+    public static final String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
+
+    /**
+     * The track number for the media.
+     */
+    public static final String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+
+    /**
+     * The number of tracks in the media's original source.
+     */
+    public static final String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+
+    /**
+     * The disc number for the media's original source.
+     */
+    public static final String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+
+    /**
+     * The artist for the album of the media's original source.
+     */
+    public static final String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+
+    /**
+     * The artwork for the media as a {@link Bitmap}.
+     */
+    public static final String METADATA_KEY_ART = "android.media.metadata.ART";
+
+    /**
+     * The artwork for the media as a Uri style String.
+     */
+    public static final String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
+
+    /**
+     * The artwork for the album of the media's original source as a
+     * {@link Bitmap}.
+     */
+    public static final String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
+
+    /**
+     * The artwork for the album of the media's original source as a Uri style
+     * String.
+     */
+    public static final String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
+
+    /**
+     * The user's rating for the media.
+     *
+     * @see RatingCompat
+     */
+    public static final String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
+
+    /**
+     * The overall rating for the media.
+     *
+     * @see RatingCompat
+     */
+    public static final String METADATA_KEY_RATING = "android.media.metadata.RATING";
+
+    /**
+     * A title that is suitable for display to the user. This will generally be
+     * the same as {@link #METADATA_KEY_TITLE} but may differ for some formats.
+     * When displaying media described by this metadata this should be preferred
+     * if present.
+     */
+    public static final String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
+
+    /**
+     * A subtitle that is suitable for display to the user. When displaying a
+     * second line for media described by this metadata this should be preferred
+     * to other fields if present.
+     */
+    public static final String METADATA_KEY_DISPLAY_SUBTITLE
+            = "android.media.metadata.DISPLAY_SUBTITLE";
+
+    /**
+     * A description that is suitable for display to the user. When displaying
+     * more information for media described by this metadata this should be
+     * preferred to other fields if present.
+     */
+    public static final String METADATA_KEY_DISPLAY_DESCRIPTION
+            = "android.media.metadata.DISPLAY_DESCRIPTION";
+
+    /**
+     * An icon or thumbnail that is suitable for display to the user. When
+     * displaying an icon for media described by this metadata this should be
+     * preferred to other fields if present. This must be a {@link Bitmap}.
+     */
+    public static final String METADATA_KEY_DISPLAY_ICON
+            = "android.media.metadata.DISPLAY_ICON";
+
+    /**
+     * An icon or thumbnail that is suitable for display to the user. When
+     * displaying more information for media described by this metadata the
+     * display description should be preferred to other fields when present.
+     * This must be a Uri style String.
+     */
+    public static final String METADATA_KEY_DISPLAY_ICON_URI
+            = "android.media.metadata.DISPLAY_ICON_URI";
+
+    private static final int METADATA_TYPE_LONG = 0;
+    private static final int METADATA_TYPE_TEXT = 1;
+    private static final int METADATA_TYPE_BITMAP = 2;
+    private static final int METADATA_TYPE_RATING = 3;
+    private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
+
+    static {
+        METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
+        METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_DURATION, METADATA_TYPE_LONG);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_COMPILATION, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_LONG);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_TRACK_NUMBER, METADATA_TYPE_LONG);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_NUM_TRACKS, METADATA_TYPE_LONG);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_TITLE, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_SUBTITLE, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT);
+    }
+
+    private final Bundle mBundle;
+    private Object mMetadataObj;
+
+    private MediaMetadataCompat(Bundle bundle) {
+        mBundle = new Bundle(bundle);
+    }
+
+    private MediaMetadataCompat(Parcel in) {
+        mBundle = in.readBundle();
+    }
+
+    /**
+     * Returns true if the given key is contained in the metadata
+     *
+     * @param key a String key
+     * @return true if the key exists in this metadata, false otherwise
+     */
+    public boolean containsKey(String key) {
+        return mBundle.containsKey(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if no mapping of
+     * the desired type exists for the given key or a null value is explicitly
+     * associated with the key.
+     *
+     * @param key The key the value is stored under
+     * @return a CharSequence value, or null
+     */
+    public CharSequence getText(String key) {
+        return mBundle.getCharSequence(key);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if no mapping of
+     * the desired type exists for the given key or a null value is explicitly
+     * associated with the key.
+     *
+     * @param key The key the value is stored under
+     * @return a String value, or null
+     */
+    public String getString(String key) {
+        CharSequence text = mBundle.getCharSequence(key);
+        if (text != null) {
+            return text.toString();
+        }
+        return null;
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0L if no long exists
+     * for the given key.
+     *
+     * @param key The key the value is stored under
+     * @return a long value
+     */
+    public long getLong(String key) {
+        return mBundle.getLong(key, 0);
+    }
+
+    /**
+     * Return a {@link RatingCompat} for the given key or null if no rating exists for
+     * the given key.
+     *
+     * @param key The key the value is stored under
+     * @return A {@link RatingCompat} or null
+     */
+    public RatingCompat getRating(String key) {
+        RatingCompat rating = null;
+        try {
+            rating = mBundle.getParcelable(key);
+        } catch (Exception e) {
+            // ignore, value was not a bitmap
+            Log.w(TAG, "Failed to retrieve a key as Rating.", e);
+        }
+        return rating;
+    }
+
+    /**
+     * Return a {@link Bitmap} for the given key or null if no bitmap exists for
+     * the given key.
+     *
+     * @param key The key the value is stored under
+     * @return A {@link Bitmap} or null
+     */
+    public Bitmap getBitmap(String key) {
+        Bitmap bmp = null;
+        try {
+            bmp = mBundle.getParcelable(key);
+        } catch (Exception e) {
+            // ignore, value was not a bitmap
+            Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
+        }
+        return bmp;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeBundle(mBundle);
+    }
+
+    /**
+     * Get the number of fields in this metadata.
+     *
+     * @return The number of fields in the metadata.
+     */
+    public int size() {
+        return mBundle.size();
+    }
+
+    /**
+     * Returns a Set containing the Strings used as keys in this metadata.
+     *
+     * @return a Set of String keys
+     */
+    public Set<String> keySet() {
+        return mBundle.keySet();
+    }
+
+    /**
+     * Creates an instance from a framework {@link android.media.MediaMetadata} object.
+     * <p>
+     * This method is only supported on API 21+.
+     * </p>
+     *
+     * @param metadataObj A {@link android.media.MediaMetadata} object, or null if none.
+     * @return An equivalent {@link MediaMetadataCompat} object, or null if none.
+     */
+    public static MediaMetadataCompat fromMediaMetadata(Object metadataObj) {
+        if (metadataObj == null || Build.VERSION.SDK_INT < 21) {
+            return null;
+        }
+
+        Builder builder = new Builder();
+        for (String key : MediaMetadataCompatApi21.keySet(metadataObj)) {
+            Integer type = METADATA_KEYS_TYPE.get(key);
+            if (type != null) {
+                switch (type) {
+                    case METADATA_TYPE_BITMAP:
+                        builder.putBitmap(key,
+                                MediaMetadataCompatApi21.getBitmap(metadataObj, key));
+                        break;
+                    case METADATA_TYPE_LONG:
+                        builder.putLong(key,
+                                MediaMetadataCompatApi21.getLong(metadataObj, key));
+                        break;
+                    case METADATA_TYPE_RATING:
+                        builder.putRating(key, RatingCompat.fromRating(
+                                MediaMetadataCompatApi21.getRating(metadataObj, key)));
+                        break;
+                    case METADATA_TYPE_TEXT:
+                        builder.putText(key,
+                                MediaMetadataCompatApi21.getText(metadataObj, key));
+                        break;
+                }
+            }
+        }
+        MediaMetadataCompat metadata = builder.build();
+        metadata.mMetadataObj = metadataObj;
+        return metadata;
+    }
+
+    /**
+     * Gets the underlying framework {@link android.media.MediaMetadata} object.
+     * <p>
+     * This method is only supported on API 21+.
+     * </p>
+     *
+     * @return An equivalent {@link android.media.MediaMetadata} object, or null if none.
+     */
+    public Object getMediaMetadata() {
+        if (mMetadataObj != null || Build.VERSION.SDK_INT < 21) {
+            return mMetadataObj;
+        }
+
+        Object builderObj = MediaMetadataCompatApi21.Builder.newInstance();
+        for (String key : keySet()) {
+            Integer type = METADATA_KEYS_TYPE.get(key);
+            if (type != null) {
+                switch (type) {
+                    case METADATA_TYPE_BITMAP:
+                        MediaMetadataCompatApi21.Builder.putBitmap(builderObj, key,
+                                getBitmap(key));
+                        break;
+                    case METADATA_TYPE_LONG:
+                        MediaMetadataCompatApi21.Builder.putLong(builderObj, key,
+                                getLong(key));
+                        break;
+                    case METADATA_TYPE_RATING:
+                        MediaMetadataCompatApi21.Builder.putRating(builderObj, key,
+                                getRating(key).getRating());
+                        break;
+                    case METADATA_TYPE_TEXT:
+                        MediaMetadataCompatApi21.Builder.putText(builderObj, key,
+                                getText(key));
+                        break;
+                }
+            }
+        }
+        mMetadataObj = MediaMetadataCompatApi21.Builder.build(builderObj);
+        return mMetadataObj;
+    }
+
+    public static final Parcelable.Creator<MediaMetadataCompat> CREATOR =
+            new Parcelable.Creator<MediaMetadataCompat>() {
+                @Override
+                public MediaMetadataCompat createFromParcel(Parcel in) {
+                    return new MediaMetadataCompat(in);
+                }
+
+                @Override
+                public MediaMetadataCompat[] newArray(int size) {
+                    return new MediaMetadataCompat[size];
+                }
+            };
+
+    /**
+     * Use to build MediaMetadata objects. The system defined metadata keys must
+     * use the appropriate data type.
+     */
+    public static final class Builder {
+        private final Bundle mBundle;
+
+        /**
+         * Create an empty Builder. Any field that should be included in the
+         * {@link MediaMetadataCompat} must be added.
+         */
+        public Builder() {
+            mBundle = new Bundle();
+        }
+
+        /**
+         * Create a Builder using a {@link MediaMetadataCompat} instance to set the
+         * initial values. All fields in the source metadata will be included in
+         * the new metadata. Fields can be overwritten by adding the same key.
+         *
+         * @param source
+         */
+        public Builder(MediaMetadataCompat source) {
+            mBundle = new Bundle(source.mBundle);
+        }
+
+        /**
+         * Put a CharSequence value into the metadata. Custom keys may be used,
+         * but if the METADATA_KEYs defined in this class are used they may only
+         * be one of the following:
+         * <ul>
+         * <li>{@link #METADATA_KEY_TITLE}</li>
+         * <li>{@link #METADATA_KEY_ARTIST}</li>
+         * <li>{@link #METADATA_KEY_ALBUM}</li>
+         * <li>{@link #METADATA_KEY_AUTHOR}</li>
+         * <li>{@link #METADATA_KEY_WRITER}</li>
+         * <li>{@link #METADATA_KEY_COMPOSER}</li>
+         * <li>{@link #METADATA_KEY_DATE}</li>
+         * <li>{@link #METADATA_KEY_GENRE}</li>
+         * <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
+         * <li>{@link #METADATA_KEY_ART_URI}</li>
+         * <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
+         * <li>{@link #METADATA_KEY_DISPLAY_TITLE}</li>
+         * <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}</li>
+         * <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}</li>
+         * <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}</li>
+         * </ul>
+         *
+         * @param key The key for referencing this value
+         * @param value The CharSequence value to store
+         * @return The Builder to allow chaining
+         */
+        public Builder putText(String key, CharSequence value) {
+            if (METADATA_KEYS_TYPE.containsKey(key)) {
+                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
+                    throw new IllegalArgumentException("The " + key
+                            + " key cannot be used to put a CharSequence");
+                }
+            }
+            mBundle.putCharSequence(key, value);
+            return this;
+        }
+
+        /**
+         * Put a String value into the metadata. Custom keys may be used, but if
+         * the METADATA_KEYs defined in this class are used they may only be one
+         * of the following:
+         * <ul>
+         * <li>{@link #METADATA_KEY_TITLE}</li>
+         * <li>{@link #METADATA_KEY_ARTIST}</li>
+         * <li>{@link #METADATA_KEY_ALBUM}</li>
+         * <li>{@link #METADATA_KEY_AUTHOR}</li>
+         * <li>{@link #METADATA_KEY_WRITER}</li>
+         * <li>{@link #METADATA_KEY_COMPOSER}</li>
+         * <li>{@link #METADATA_KEY_DATE}</li>
+         * <li>{@link #METADATA_KEY_GENRE}</li>
+         * <li>{@link #METADATA_KEY_ALBUM_ARTIST}</li>
+         * <li>{@link #METADATA_KEY_ART_URI}</li>
+         * <li>{@link #METADATA_KEY_ALBUM_ART_URI}</li>
+         * <li>{@link #METADATA_KEY_DISPLAY_TITLE}</li>
+         * <li>{@link #METADATA_KEY_DISPLAY_SUBTITLE}</li>
+         * <li>{@link #METADATA_KEY_DISPLAY_DESCRIPTION}</li>
+         * <li>{@link #METADATA_KEY_DISPLAY_ICON_URI}</li>
+         * </ul>
+         *
+         * @param key The key for referencing this value
+         * @param value The String value to store
+         * @return The Builder to allow chaining
+         */
+        public Builder putString(String key, String value) {
+            if (METADATA_KEYS_TYPE.containsKey(key)) {
+                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
+                    throw new IllegalArgumentException("The " + key
+                            + " key cannot be used to put a String");
+                }
+            }
+            mBundle.putCharSequence(key, value);
+            return this;
+        }
+
+        /**
+         * Put a long value into the metadata. Custom keys may be used, but if
+         * the METADATA_KEYs defined in this class are used they may only be one
+         * of the following:
+         * <ul>
+         * <li>{@link #METADATA_KEY_DURATION}</li>
+         * <li>{@link #METADATA_KEY_TRACK_NUMBER}</li>
+         * <li>{@link #METADATA_KEY_NUM_TRACKS}</li>
+         * <li>{@link #METADATA_KEY_DISC_NUMBER}</li>
+         * <li>{@link #METADATA_KEY_YEAR}</li>
+         * </ul>
+         *
+         * @param key The key for referencing this value
+         * @param value The String value to store
+         * @return The Builder to allow chaining
+         */
+        public Builder putLong(String key, long value) {
+            if (METADATA_KEYS_TYPE.containsKey(key)) {
+                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_LONG) {
+                    throw new IllegalArgumentException("The " + key
+                            + " key cannot be used to put a long");
+                }
+            }
+            mBundle.putLong(key, value);
+            return this;
+        }
+
+        /**
+         * Put a {@link RatingCompat} into the metadata. Custom keys may be used, but
+         * if the METADATA_KEYs defined in this class are used they may only be
+         * one of the following:
+         * <ul>
+         * <li>{@link #METADATA_KEY_RATING}</li>
+         * <li>{@link #METADATA_KEY_USER_RATING}</li>
+         * </ul>
+         *
+         * @param key The key for referencing this value
+         * @param value The String value to store
+         * @return The Builder to allow chaining
+         */
+        public Builder putRating(String key, RatingCompat value) {
+            if (METADATA_KEYS_TYPE.containsKey(key)) {
+                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_RATING) {
+                    throw new IllegalArgumentException("The " + key
+                            + " key cannot be used to put a Rating");
+                }
+            }
+            mBundle.putParcelable(key, value);
+            return this;
+        }
+
+        /**
+         * Put a {@link Bitmap} into the metadata. Custom keys may be used, but
+         * if the METADATA_KEYs defined in this class are used they may only be
+         * one of the following:
+         * <ul>
+         * <li>{@link #METADATA_KEY_ART}</li>
+         * <li>{@link #METADATA_KEY_ALBUM_ART}</li>
+         * <li>{@link #METADATA_KEY_DISPLAY_ICON}</li>
+         * </ul>
+         *
+         * @param key The key for referencing this value
+         * @param value The Bitmap to store
+         * @return The Builder to allow chaining
+         */
+        public Builder putBitmap(String key, Bitmap value) {
+            if (METADATA_KEYS_TYPE.containsKey(key)) {
+                if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
+                    throw new IllegalArgumentException("The " + key
+                            + " key cannot be used to put a Bitmap");
+                }
+            }
+            mBundle.putParcelable(key, value);
+            return this;
+        }
+
+        /**
+         * Creates a {@link MediaMetadataCompat} instance with the specified fields.
+         *
+         * @return The new MediaMetadata instance
+         */
+        public MediaMetadataCompat build() {
+            return new MediaMetadataCompat(mBundle);
+        }
+    }
+
+}
diff --git a/v4/java/android/support/v4/media/RatingCompat.java b/v4/java/android/support/v4/media/RatingCompat.java
new file mode 100644
index 0000000..c7da6ec
--- /dev/null
+++ b/v4/java/android/support/v4/media/RatingCompat.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media;
+
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * A class to encapsulate rating information used as content metadata.
+ * A rating is defined by its rating style (see {@link #RATING_HEART},
+ * {@link #RATING_THUMB_UP_DOWN}, {@link #RATING_3_STARS}, {@link #RATING_4_STARS},
+ * {@link #RATING_5_STARS} or {@link #RATING_PERCENTAGE}) and the actual rating value (which may
+ * be defined as "unrated"), both of which are defined when the rating instance is constructed
+ * through one of the factory methods.
+ */
+public final class RatingCompat implements Parcelable {
+    private final static String TAG = "Rating";
+
+    /**
+     * Indicates a rating style is not supported. A Rating will never have this
+     * type, but can be used by other classes to indicate they do not support
+     * Rating.
+     */
+    public final static int RATING_NONE = 0;
+
+    /**
+     * A rating style with a single degree of rating, "heart" vs "no heart". Can be used to
+     * indicate the content referred to is a favorite (or not).
+     */
+    public final static int RATING_HEART = 1;
+
+    /**
+     * A rating style for "thumb up" vs "thumb down".
+     */
+    public final static int RATING_THUMB_UP_DOWN = 2;
+
+    /**
+     * A rating style with 0 to 3 stars.
+     */
+    public final static int RATING_3_STARS = 3;
+
+    /**
+     * A rating style with 0 to 4 stars.
+     */
+    public final static int RATING_4_STARS = 4;
+
+    /**
+     * A rating style with 0 to 5 stars.
+     */
+    public final static int RATING_5_STARS = 5;
+
+    /**
+     * A rating style expressed as a percentage.
+     */
+    public final static int RATING_PERCENTAGE = 6;
+
+    private final static float RATING_NOT_RATED = -1.0f;
+
+    private final int mRatingStyle;
+    private final float mRatingValue;
+
+    private Object mRatingObj; // framework Rating object
+
+    private RatingCompat(int ratingStyle, float rating) {
+        mRatingStyle = ratingStyle;
+        mRatingValue = rating;
+    }
+
+    @Override
+    public String toString() {
+        return "Rating:style=" + mRatingStyle + " rating="
+                + (mRatingValue < 0.0f ? "unrated" : String.valueOf(mRatingValue));
+    }
+
+    @Override
+    public int describeContents() {
+        return mRatingStyle;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mRatingStyle);
+        dest.writeFloat(mRatingValue);
+    }
+
+    public static final Parcelable.Creator<RatingCompat> CREATOR
+            = new Parcelable.Creator<RatingCompat>() {
+        /**
+         * Rebuilds a Rating previously stored with writeToParcel().
+         * @param p    Parcel object to read the Rating from
+         * @return a new Rating created from the data in the parcel
+         */
+        @Override
+        public RatingCompat createFromParcel(Parcel p) {
+            return new RatingCompat(p.readInt(), p.readFloat());
+        }
+
+        @Override
+        public RatingCompat[] newArray(int size) {
+            return new RatingCompat[size];
+        }
+    };
+
+    /**
+     * Return a Rating instance with no rating.
+     * Create and return a new Rating instance with no rating known for the given
+     * rating style.
+     * @param ratingStyle one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN},
+     *    {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link #RATING_5_STARS},
+     *    or {@link #RATING_PERCENTAGE}.
+     * @return null if an invalid rating style is passed, a new Rating instance otherwise.
+     */
+    public static RatingCompat newUnratedRating(int ratingStyle) {
+        switch(ratingStyle) {
+            case RATING_HEART:
+            case RATING_THUMB_UP_DOWN:
+            case RATING_3_STARS:
+            case RATING_4_STARS:
+            case RATING_5_STARS:
+            case RATING_PERCENTAGE:
+                return new RatingCompat(ratingStyle, RATING_NOT_RATED);
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * Return a Rating instance with a heart-based rating.
+     * Create and return a new Rating instance with a rating style of {@link #RATING_HEART},
+     * and a heart-based rating.
+     * @param hasHeart true for a "heart selected" rating, false for "heart unselected".
+     * @return a new Rating instance.
+     */
+    public static RatingCompat newHeartRating(boolean hasHeart) {
+        return new RatingCompat(RATING_HEART, hasHeart ? 1.0f : 0.0f);
+    }
+
+    /**
+     * Return a Rating instance with a thumb-based rating.
+     * Create and return a new Rating instance with a {@link #RATING_THUMB_UP_DOWN}
+     * rating style, and a "thumb up" or "thumb down" rating.
+     * @param thumbIsUp true for a "thumb up" rating, false for "thumb down".
+     * @return a new Rating instance.
+     */
+    public static RatingCompat newThumbRating(boolean thumbIsUp) {
+        return new RatingCompat(RATING_THUMB_UP_DOWN, thumbIsUp ? 1.0f : 0.0f);
+    }
+
+    /**
+     * Return a Rating instance with a star-based rating.
+     * Create and return a new Rating instance with one of the star-base rating styles
+     * and the given integer or fractional number of stars. Non integer values can for instance
+     * be used to represent an average rating value, which might not be an integer number of stars.
+     * @param starRatingStyle one of {@link #RATING_3_STARS}, {@link #RATING_4_STARS},
+     *     {@link #RATING_5_STARS}.
+     * @param starRating a number ranging from 0.0f to 3.0f, 4.0f or 5.0f according to
+     *     the rating style.
+     * @return null if the rating style is invalid, or the rating is out of range,
+     *     a new Rating instance otherwise.
+     */
+    public static RatingCompat newStarRating(int starRatingStyle, float starRating) {
+        float maxRating = -1.0f;
+        switch(starRatingStyle) {
+            case RATING_3_STARS:
+                maxRating = 3.0f;
+                break;
+            case RATING_4_STARS:
+                maxRating = 4.0f;
+                break;
+            case RATING_5_STARS:
+                maxRating = 5.0f;
+                break;
+            default:
+                Log.e(TAG, "Invalid rating style (" + starRatingStyle + ") for a star rating");
+                        return null;
+        }
+        if ((starRating < 0.0f) || (starRating > maxRating)) {
+            Log.e(TAG, "Trying to set out of range star-based rating");
+            return null;
+        }
+        return new RatingCompat(starRatingStyle, starRating);
+    }
+
+    /**
+     * Return a Rating instance with a percentage-based rating.
+     * Create and return a new Rating instance with a {@link #RATING_PERCENTAGE}
+     * rating style, and a rating of the given percentage.
+     * @param percent the value of the rating
+     * @return null if the rating is out of range, a new Rating instance otherwise.
+     */
+    public static RatingCompat newPercentageRating(float percent) {
+        if ((percent < 0.0f) || (percent > 100.0f)) {
+            Log.e(TAG, "Invalid percentage-based rating value");
+            return null;
+        } else {
+            return new RatingCompat(RATING_PERCENTAGE, percent);
+        }
+    }
+
+    /**
+     * Return whether there is a rating value available.
+     * @return true if the instance was not created with {@link #newUnratedRating(int)}.
+     */
+    public boolean isRated() {
+        return mRatingValue >= 0.0f;
+    }
+
+    /**
+     * Return the rating style.
+     * @return one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN},
+     *    {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link #RATING_5_STARS},
+     *    or {@link #RATING_PERCENTAGE}.
+     */
+    public int getRatingStyle() {
+        return mRatingStyle;
+    }
+
+    /**
+     * Return whether the rating is "heart selected".
+     * @return true if the rating is "heart selected", false if the rating is "heart unselected",
+     *    if the rating style is not {@link #RATING_HEART} or if it is unrated.
+     */
+    public boolean hasHeart() {
+        if (mRatingStyle != RATING_HEART) {
+            return false;
+        } else {
+            return (mRatingValue == 1.0f);
+        }
+    }
+
+    /**
+     * Return whether the rating is "thumb up".
+     * @return true if the rating is "thumb up", false if the rating is "thumb down",
+     *    if the rating style is not {@link #RATING_THUMB_UP_DOWN} or if it is unrated.
+     */
+    public boolean isThumbUp() {
+        if (mRatingStyle != RATING_THUMB_UP_DOWN) {
+            return false;
+        } else {
+            return (mRatingValue == 1.0f);
+        }
+    }
+
+    /**
+     * Return the star-based rating value.
+     * @return a rating value greater or equal to 0.0f, or a negative value if the rating style is
+     *    not star-based, or if it is unrated.
+     */
+    public float getStarRating() {
+        switch (mRatingStyle) {
+            case RATING_3_STARS:
+            case RATING_4_STARS:
+            case RATING_5_STARS:
+                if (isRated()) {
+                    return mRatingValue;
+                }
+            default:
+                return -1.0f;
+        }
+    }
+
+    /**
+     * Return the percentage-based rating value.
+     * @return a rating value greater or equal to 0.0f, or a negative value if the rating style is
+     *    not percentage-based, or if it is unrated.
+     */
+    public float getPercentRating() {
+        if ((mRatingStyle != RATING_PERCENTAGE) || !isRated()) {
+            return -1.0f;
+        } else {
+            return mRatingValue;
+        }
+    }
+
+    /**
+     * Creates an instance from a framework {@link android.media.Rating} object.
+     * <p>
+     * This method is only supported on API 21+.
+     * </p>
+     *
+     * @param ratingObj A {@link android.media.Rating} object, or null if none.
+     * @return An equivalent {@link RatingCompat} object, or null if none.
+     */
+    public static RatingCompat fromRating(Object ratingObj) {
+        if (ratingObj == null || Build.VERSION.SDK_INT < 21) {
+            return null;
+        }
+
+        final int ratingStyle = RatingCompatApi21.getRatingStyle(ratingObj);
+        final RatingCompat rating;
+        if (RatingCompatApi21.isRated(ratingObj)) {
+            switch (ratingStyle) {
+                case RATING_HEART:
+                    rating = newHeartRating(RatingCompatApi21.hasHeart(ratingObj));
+                    break;
+                case RATING_THUMB_UP_DOWN:
+                    rating = newThumbRating(RatingCompatApi21.isThumbUp(ratingObj));
+                    break;
+                case RATING_3_STARS:
+                case RATING_4_STARS:
+                case RATING_5_STARS:
+                    rating = newStarRating(ratingStyle,
+                            RatingCompatApi21.getStarRating(ratingObj));
+                    break;
+                case RATING_PERCENTAGE:
+                    rating = newPercentageRating(RatingCompatApi21.getPercentRating(ratingObj));
+                    break;
+                default:
+                    return null;
+            }
+        } else {
+            rating = newUnratedRating(ratingStyle);
+        }
+        rating.mRatingObj = ratingObj;
+        return rating;
+    }
+
+    /**
+     * Gets the underlying framework {@link android.media.Rating} object.
+     * <p>
+     * This method is only supported on API 21+.
+     * </p>
+     *
+     * @return An equivalent {@link android.media.Rating} object, or null if none.
+     */
+    public Object getRating() {
+        if (mRatingObj != null || Build.VERSION.SDK_INT < 21) {
+            return mRatingObj;
+        }
+
+        if (isRated()) {
+            switch (mRatingStyle) {
+                case RATING_HEART:
+                    mRatingObj = RatingCompatApi21.newHeartRating(hasHeart());
+                    break;
+                case RATING_THUMB_UP_DOWN:
+                    mRatingObj = RatingCompatApi21.newThumbRating(isThumbUp());
+                    break;
+                case RATING_3_STARS:
+                case RATING_4_STARS:
+                case RATING_5_STARS:
+                    mRatingObj = RatingCompatApi21.newStarRating(mRatingStyle, getStarRating());
+                    break;
+                case RATING_PERCENTAGE:
+                    mRatingObj = RatingCompatApi21.newPercentageRating(getPercentRating());
+                default:
+                    return null;
+            }
+        } else {
+            mRatingObj = RatingCompatApi21.newUnratedRating(mRatingStyle);
+        }
+        return mRatingObj;
+    }
+}
\ No newline at end of file
diff --git a/v4/java/android/support/v4/media/TransportMediator.java b/v4/java/android/support/v4/media/TransportMediator.java
index 789ea7b..16bf94b 100644
--- a/v4/java/android/support/v4/media/TransportMediator.java
+++ b/v4/java/android/support/v4/media/TransportMediator.java
@@ -84,28 +84,28 @@
     public static final int KEYCODE_MEDIA_RECORD = 130;
 
     /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PREVIOUS
-     * RemoveControlClient.FLAG_KEY_MEDIA_PREVIOUS */
+     * RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS */
     public final static int FLAG_KEY_MEDIA_PREVIOUS = 1 << 0;
     /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_REWIND
-     * RemoveControlClient.FLAG_KEY_MEDIA_REWIND */
+     * RemoteControlClient.FLAG_KEY_MEDIA_REWIND */
     public final static int FLAG_KEY_MEDIA_REWIND = 1 << 1;
     /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PLAY
-     * RemoveControlClient.FLAG_KEY_MEDIA_PLAY */
+     * RemoteControlClient.FLAG_KEY_MEDIA_PLAY */
     public final static int FLAG_KEY_MEDIA_PLAY = 1 << 2;
     /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PLAY_PAUSE
-     * RemoveControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE */
+     * RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE */
     public final static int FLAG_KEY_MEDIA_PLAY_PAUSE = 1 << 3;
     /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PAUSE
-     * RemoveControlClient.FLAG_KEY_MEDIA_PAUSE */
+     * RemoteControlClient.FLAG_KEY_MEDIA_PAUSE */
     public final static int FLAG_KEY_MEDIA_PAUSE = 1 << 4;
     /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_STOP
-     * RemoveControlClient.FLAG_KEY_MEDIA_STOP */
+     * RemoteControlClient.FLAG_KEY_MEDIA_STOP */
     public final static int FLAG_KEY_MEDIA_STOP = 1 << 5;
     /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_FAST_FORWARD
-     * RemoveControlClient.FLAG_KEY_MEDIA_FAST_FORWARD */
+     * RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD */
     public final static int FLAG_KEY_MEDIA_FAST_FORWARD = 1 << 6;
     /** Synonym for {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_NEXT
-     * RemoveControlClient.FLAG_KEY_MEDIA_NEXT */
+     * RemoteControlClient.FLAG_KEY_MEDIA_NEXT */
     public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7;
 
     static boolean isMediaKey(int keyCode) {
diff --git a/v4/java/android/support/v4/media/VolumeProviderCompat.java b/v4/java/android/support/v4/media/VolumeProviderCompat.java
new file mode 100644
index 0000000..2db0cc6
--- /dev/null
+++ b/v4/java/android/support/v4/media/VolumeProviderCompat.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media;
+
+import android.os.Build;
+import android.support.v4.media.session.MediaSessionCompat;
+
+/**
+ * Handles requests to adjust or set the volume on a session. This is also used
+ * to push volume updates back to the session after a request has been handled.
+ * You can set a volume provider on a session by calling
+ * {@link MediaSessionCompat#setPlaybackToRemote}.
+ */
+public abstract class VolumeProviderCompat {
+    /**
+     * The volume is fixed and can not be modified. Requests to change volume
+     * should be ignored.
+     */
+    public static final int VOLUME_CONTROL_FIXED = 0;
+
+    /**
+     * The volume control uses relative adjustment via
+     * {@link #onAdjustVolume(int)}. Attempts to set the volume to a specific
+     * value should be ignored.
+     */
+    public static final int VOLUME_CONTROL_RELATIVE = 1;
+
+    /**
+     * The volume control uses an absolute value. It may be adjusted using
+     * {@link #onAdjustVolume(int)} or set directly using
+     * {@link #onSetVolumeTo(int)}.
+     */
+    public static final int VOLUME_CONTROL_ABSOLUTE = 2;
+
+    private final int mControlType;
+    private final int mMaxVolume;
+    private Callback mCallback;
+
+    private Object mVolumeProviderObj;
+
+    /**
+     * Create a new volume provider for handling volume events. You must specify
+     * the type of volume control and the maximum volume that can be used.
+     *
+     * @param volumeControl The method for controlling volume that is used by
+     *            this provider.
+     * @param maxVolume The maximum allowed volume.
+     */
+    public VolumeProviderCompat(int volumeControl, int maxVolume) {
+        mControlType = volumeControl;
+        mMaxVolume = maxVolume;
+    }
+
+    /**
+     * Get the current volume of the remote playback.
+     *
+     * @return The current volume.
+     */
+    public abstract int onGetCurrentVolume();
+
+    /**
+     * Get the volume control type that this volume provider uses.
+     *
+     * @return The volume control type for this volume provider
+     */
+    public final int getVolumeControl() {
+        return mControlType;
+    }
+
+    /**
+     * Get the maximum volume this provider allows.
+     *
+     * @return The max allowed volume.
+     */
+    public final int getMaxVolume() {
+        return mMaxVolume;
+    }
+
+    /**
+     * Notify the callback that the remote playback's volume has been changed.
+     */
+    public final void notifyVolumeChanged() {
+        if (mCallback != null) {
+            mCallback.onVolumeChanged(this);
+        }
+    }
+
+    /**
+     * Override to handle requests to set the volume of the current output.
+     *
+     * @param volume The volume to set the output to.
+     */
+    public void onSetVolumeTo(int volume) {
+    }
+
+    /**
+     * Override to handle requests to adjust the volume of the current output.
+     *
+     * @param direction The direction to adjust the volume in.
+     */
+    public void onAdjustVolume(int direction) {
+    }
+
+    /**
+     * Sets a callback to receive volume changes.
+     * <p>
+     * Used internally by the support library.
+     * <p>
+     */
+    public void setCallback(Callback callback) {
+        mCallback = callback;
+    }
+
+    /**
+     * Gets the underlying framework {@link android.media.VolumeProvider} object.
+     * <p>
+     * This method is only supported on API 21+.
+     * </p>
+     *
+     * @return An equivalent {@link android.media.VolumeProvider} object, or null if none.
+     */
+    public Object getVolumeProvider() {
+        if (mVolumeProviderObj != null || Build.VERSION.SDK_INT < 21) {
+            return mVolumeProviderObj;
+        }
+
+        mVolumeProviderObj = VolumeProviderCompatApi21.createVolumeProvider(
+                mControlType, mMaxVolume, new VolumeProviderCompatApi21.Delegate() {
+            @Override
+            public int onGetCurrentVolume() {
+                return VolumeProviderCompat.this.onGetCurrentVolume();
+            }
+
+            @Override
+            public void onSetVolumeTo(int volume) {
+                VolumeProviderCompat.this.onSetVolumeTo(volume);
+            }
+
+            @Override
+            public void onAdjustVolume(int direction) {
+                VolumeProviderCompat.this.onAdjustVolume(direction);
+            }
+        });
+        return mVolumeProviderObj;
+    }
+
+    /**
+     * Listens for changes to the volume.
+     */
+    public static abstract class Callback {
+        public abstract void onVolumeChanged(VolumeProviderCompat volumeProvider);
+    }
+}
\ No newline at end of file
diff --git a/v4/java/android/support/v4/media/session/MediaControllerCompat.java b/v4/java/android/support/v4/media/session/MediaControllerCompat.java
new file mode 100644
index 0000000..47ce529
--- /dev/null
+++ b/v4/java/android/support/v4/media/session/MediaControllerCompat.java
@@ -0,0 +1,629 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media.session;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.VolumeProviderCompat;
+import android.text.TextUtils;
+import android.view.KeyEvent;
+
+/**
+ * Allows an app to interact with an ongoing media session. Media buttons and
+ * other commands can be sent to the session. A callback may be registered to
+ * receive updates from the session, such as metadata and play state changes.
+ * <p>
+ * A MediaController can be created if you have a {@link MediaSessionCompat.Token}
+ * from the session owner.
+ * <p>
+ * MediaController objects are thread-safe.
+ * <p>
+ * This is a helper for accessing features in {@link android.media.session.MediaSession}
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
+public final class MediaControllerCompat {
+    private final MediaControllerImpl mImpl;
+
+    /**
+     * Creates a media controller from a session.
+     *
+     * @param session The session to be controlled.
+     */
+    public MediaControllerCompat(Context context, MediaSessionCompat session) {
+        if (session == null) {
+            throw new IllegalArgumentException("session must not be null");
+        }
+
+        if (android.os.Build.VERSION.SDK_INT >= 21) {
+            mImpl = new MediaControllerImplApi21(context, session);
+        } else {
+            mImpl = new MediaControllerImplBase();
+        }
+    }
+
+    /**
+     * Creates a media controller from a session token which may have
+     * been obtained from another process.
+     *
+     * @param sessionToken The token of the session to be controlled.
+     * @throws RemoteException if the session is not accessible.
+     */
+    public MediaControllerCompat(Context context, MediaSessionCompat.Token sessionToken)
+            throws RemoteException {
+        if (sessionToken == null) {
+            throw new IllegalArgumentException("sessionToken must not be null");
+        }
+
+        if (android.os.Build.VERSION.SDK_INT >= 21) {
+            mImpl = new MediaControllerImplApi21(context, sessionToken);
+        } else {
+            mImpl = new MediaControllerImplBase();
+        }
+    }
+
+    /**
+     * Get a {@link TransportControls} instance for this session.
+     *
+     * @return A controls instance
+     */
+    public TransportControls getTransportControls() {
+        return mImpl.getTransportControls();
+    }
+
+    /**
+     * Send 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.
+     * @return true if the event was sent to the session, false otherwise.
+     */
+    public boolean dispatchMediaButtonEvent(KeyEvent keyEvent) {
+        if (keyEvent == null) {
+            throw new IllegalArgumentException("KeyEvent may not be null");
+        }
+        return mImpl.dispatchMediaButtonEvent(keyEvent);
+    }
+
+    /**
+     * Get the current playback state for this session.
+     *
+     * @return The current PlaybackState or null
+     */
+    public PlaybackStateCompat getPlaybackState() {
+        return mImpl.getPlaybackState();
+    }
+
+    /**
+     * Get the current metadata for this session.
+     *
+     * @return The current MediaMetadata or null.
+     */
+    public MediaMetadataCompat getMetadata() {
+        return mImpl.getMetadata();
+    }
+
+    /**
+     * Get the rating type supported by the session. One of:
+     * <ul>
+     * <li>{@link RatingCompat#RATING_NONE}</li>
+     * <li>{@link RatingCompat#RATING_HEART}</li>
+     * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li>
+     * <li>{@link RatingCompat#RATING_3_STARS}</li>
+     * <li>{@link RatingCompat#RATING_4_STARS}</li>
+     * <li>{@link RatingCompat#RATING_5_STARS}</li>
+     * <li>{@link RatingCompat#RATING_PERCENTAGE}</li>
+     * </ul>
+     *
+     * @return The supported rating type
+     */
+    public int getRatingType() {
+        return mImpl.getRatingType();
+    }
+
+    /**
+     * Get the current volume info for this session.
+     *
+     * @return The current volume info or null.
+     */
+    public VolumeInfo getVolumeInfo() {
+        return mImpl.getVolumeInfo();
+    }
+
+    /**
+     * Adds a callback to receive updates from the Session. Updates will be
+     * posted on the caller's thread.
+     *
+     * @param callback The callback object, must not be null.
+     */
+    public void addCallback(Callback callback) {
+        addCallback(callback, null);
+    }
+
+    /**
+     * Adds a callback to receive updates from the session. Updates will be
+     * posted on the specified handler's thread.
+     *
+     * @param callback The callback object, must not be null.
+     * @param handler The handler to post updates on. If null the callers thread
+     *            will be used.
+     */
+    public void addCallback(Callback callback, Handler handler) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback cannot be null");
+        }
+        if (handler == null) {
+            handler = new Handler();
+        }
+        mImpl.addCallback(callback, handler);
+    }
+
+    /**
+     * Stop 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
+     */
+    public void removeCallback(Callback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback cannot be null");
+        }
+        mImpl.removeCallback(callback);
+    }
+
+    /**
+     * Sends a generic command to the session. It is up to the session creator
+     * to decide what commands and parameters they will support. As such,
+     * commands should only be sent to sessions that the controller owns.
+     *
+     * @param command The command to send
+     * @param params Any parameters to include with the command
+     * @param cb The callback to receive the result on
+     */
+    public void sendCommand(String command, Bundle params, ResultReceiver cb) {
+        if (TextUtils.isEmpty(command)) {
+            throw new IllegalArgumentException("command cannot be null or empty");
+        }
+        mImpl.sendCommand(command, params, cb);
+    }
+
+    /**
+     * Gets the underlying framework {@link android.media.session.MediaController} object.
+     * <p>
+     * This method is only supported on API 21+.
+     * </p>
+     *
+     * @return The underlying {@link android.media.session.MediaController} object,
+     * or null if none.
+     */
+    public Object getMediaController() {
+        return mImpl.getMediaController();
+    }
+
+    /**
+     * Callback for receiving updates on from the session. A Callback can be
+     * registered using {@link #addCallback}
+     */
+    public static abstract class Callback {
+        final Object mCallbackObj;
+
+        public Callback() {
+            if (android.os.Build.VERSION.SDK_INT >= 21) {
+                mCallbackObj = MediaControllerCompatApi21.createCallback(new StubApi21());
+            } else {
+                mCallbackObj = null;
+            }
+        }
+
+        /**
+         * Override to handle the session being destroyed. The session is no
+         * longer valid after this call and calls to it will be ignored.
+         */
+        public void onSessionDestroyed() {
+        }
+
+        /**
+         * Override to handle custom events sent by the session owner without a
+         * specified interface. Controllers should only handle these for
+         * sessions they own.
+         *
+         * @param event The event from the session.
+         * @param extras Optional parameters for the event.
+         */
+        public void onSessionEvent(String event, Bundle extras) {
+        }
+
+        /**
+         * Override to handle changes in playback state.
+         *
+         * @param state The new playback state of the session
+         */
+        public void onPlaybackStateChanged(PlaybackStateCompat state) {
+        }
+
+        /**
+         * Override to handle changes to the current metadata.
+         *
+         * @param metadata The current metadata for the session or null if none.
+         * @see MediaMetadata
+         */
+        public void onMetadataChanged(MediaMetadataCompat metadata) {
+        }
+
+        private class StubApi21 implements MediaControllerCompatApi21.Callback {
+            @Override
+            public void onSessionDestroyed() {
+                Callback.this.onSessionDestroyed();
+            }
+
+            @Override
+            public void onSessionEvent(String event, Bundle extras) {
+                Callback.this.onSessionEvent(event, extras);
+            }
+
+            @Override
+            public void onPlaybackStateChanged(Object stateObj) {
+                Callback.this.onPlaybackStateChanged(
+                        PlaybackStateCompat.fromPlaybackState(stateObj));
+            }
+
+            @Override
+            public void onMetadataChanged(Object metadataObj) {
+                Callback.this.onMetadataChanged(
+                        MediaMetadataCompat.fromMediaMetadata(metadataObj));
+            }
+        }
+    }
+
+    /**
+     * Interface for controlling media playback on a session. This allows an app
+     * to send media transport commands to the session.
+     */
+    public static abstract class TransportControls {
+        TransportControls() {
+        }
+
+        /**
+         * Request that the player start its playback at its current position.
+         */
+        public abstract void play();
+
+        /**
+         * Request that the player pause its playback and stay at its current
+         * position.
+         */
+        public abstract void pause();
+
+        /**
+         * Request that the player stop its playback; it may clear its state in
+         * whatever way is appropriate.
+         */
+        public abstract void stop();
+
+        /**
+         * Move 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
+         * may increase the rate.
+         */
+        public abstract void fastForward();
+
+        /**
+         * Skip to the next item.
+         */
+        public abstract void skipToNext();
+
+        /**
+         * Start rewinding. If playback is already rewinding this may increase
+         * the rate.
+         */
+        public abstract void rewind();
+
+        /**
+         * Skip to the previous item.
+         */
+        public abstract void skipToPrevious();
+
+        /**
+         * Rate 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()}.
+         *
+         * @param rating The rating to set for the current content
+         */
+        public abstract void setRating(RatingCompat rating);
+    }
+
+    /**
+     * Holds information about the way volume is handled for this session.
+     */
+    public static final class VolumeInfo {
+        private final int mVolumeType;
+        // TODO update audio stream with AudioAttributes support version
+        private final int mAudioStream;
+        private final int mVolumeControl;
+        private final int mMaxVolume;
+        private final int mCurrentVolume;
+
+        VolumeInfo(int type, int stream, int control, int max, int current) {
+            mVolumeType = type;
+            mAudioStream = stream;
+            mVolumeControl = control;
+            mMaxVolume = max;
+            mCurrentVolume = current;
+        }
+
+        /**
+         * Get the type of volume handling, either local or remote. One of:
+         * <ul>
+         * <li>{@link MediaSessionCompat#VOLUME_TYPE_LOCAL}</li>
+         * <li>{@link MediaSessionCompat#VOLUME_TYPE_REMOTE}</li>
+         * </ul>
+         *
+         * @return The type of volume handling this session is using.
+         */
+        public int getVolumeType() {
+            return mVolumeType;
+        }
+
+        /**
+         * Get the stream this is currently controlling volume on. When the volume
+         * type is {@link MediaSessionCompat#VOLUME_TYPE_REMOTE} this value does not
+         * have meaning and should be ignored.
+         *
+         * @return The stream this session is playing on.
+         */
+        public int getAudioStream() {
+            return mAudioStream;
+        }
+
+        /**
+         * Get 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>
+         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_FIXED}</li>
+         * </ul>
+         *
+         * @return The type of volume control that may be used with this
+         *         session.
+         */
+        public int getVolumeControl() {
+            return mVolumeControl;
+        }
+
+        /**
+         * Get the maximum volume that may be set for this session.
+         *
+         * @return The maximum allowed volume where this session is playing.
+         */
+        public int getMaxVolume() {
+            return mMaxVolume;
+        }
+
+        /**
+         * Get the current volume for this session.
+         *
+         * @return The current volume where this session is playing.
+         */
+        public int getCurrentVolume() {
+            return mCurrentVolume;
+        }
+    }
+
+    interface MediaControllerImpl {
+        void addCallback(Callback callback, Handler handler);
+        void removeCallback(Callback callback);
+        boolean dispatchMediaButtonEvent(KeyEvent keyEvent);
+        TransportControls getTransportControls();
+        PlaybackStateCompat getPlaybackState();
+        MediaMetadataCompat getMetadata();
+        int getRatingType();
+        VolumeInfo getVolumeInfo();
+        void sendCommand(String command, Bundle params, ResultReceiver cb);
+        Object getMediaController();
+    }
+
+    // TODO: compatibility implementation
+    static class MediaControllerImplBase implements MediaControllerImpl {
+        @Override
+        public void addCallback(Callback callback, Handler handler) {
+        }
+
+        @Override
+        public void removeCallback(Callback callback) {
+        }
+
+        @Override
+        public boolean dispatchMediaButtonEvent(KeyEvent event) {
+            return false;
+        }
+
+        @Override
+        public TransportControls getTransportControls() {
+            return null;
+        }
+
+        @Override
+        public PlaybackStateCompat getPlaybackState() {
+            return null;
+        }
+
+        @Override
+        public MediaMetadataCompat getMetadata() {
+            return null;
+        }
+
+        @Override
+        public int getRatingType() {
+            return 0;
+        }
+
+        @Override
+        public VolumeInfo getVolumeInfo() {
+            return null;
+        }
+
+        @Override
+        public void sendCommand(String command, Bundle params, ResultReceiver cb) {
+        }
+
+        @Override
+        public Object getMediaController() {
+            return null;
+        }
+    }
+
+    static class MediaControllerImplApi21 implements MediaControllerImpl {
+        private final Object mControllerObj;
+
+        public MediaControllerImplApi21(Context context, MediaSessionCompat session) {
+            mControllerObj = MediaControllerCompatApi21.fromToken(context,
+                    session.getSessionToken().getToken());
+        }
+
+        public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken)
+                throws RemoteException {
+            // TODO: refactor framework implementation
+            mControllerObj = MediaControllerCompatApi21.fromToken(context,
+                    sessionToken.getToken());
+            if (mControllerObj == null) throw new RemoteException();
+        }
+
+        @Override
+        public void addCallback(Callback callback, Handler handler) {
+            MediaControllerCompatApi21.addCallback(mControllerObj, callback.mCallbackObj, handler);
+        }
+
+        @Override
+        public void removeCallback(Callback callback) {
+            MediaControllerCompatApi21.removeCallback(mControllerObj, callback.mCallbackObj);
+        }
+
+        @Override
+        public boolean dispatchMediaButtonEvent(KeyEvent event) {
+            return MediaControllerCompatApi21.dispatchMediaButtonEvent(mControllerObj, event);
+        }
+
+        @Override
+        public TransportControls getTransportControls() {
+            Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
+            return controlsObj != null ? new TransportControlsApi21(controlsObj) : null;
+        }
+
+        @Override
+        public PlaybackStateCompat getPlaybackState() {
+            Object stateObj = MediaControllerCompatApi21.getPlaybackState(mControllerObj);
+            return stateObj != null ? PlaybackStateCompat.fromPlaybackState(stateObj) : null;
+        }
+
+        @Override
+        public MediaMetadataCompat getMetadata() {
+            Object metadataObj = MediaControllerCompatApi21.getMetadata(mControllerObj);
+            return metadataObj != null ? MediaMetadataCompat.fromMediaMetadata(metadataObj) : null;
+        }
+
+        @Override
+        public int getRatingType() {
+            return MediaControllerCompatApi21.getRatingType(mControllerObj);
+        }
+
+        @Override
+        public VolumeInfo getVolumeInfo() {
+            Object volumeInfoObj = MediaControllerCompatApi21.getVolumeInfo(mControllerObj);
+            return volumeInfoObj != null ? new VolumeInfo(
+                    MediaControllerCompatApi21.VolumeInfo.getVolumeType(volumeInfoObj),
+                    MediaControllerCompatApi21.VolumeInfo.getLegacyAudioStream(volumeInfoObj),
+                    MediaControllerCompatApi21.VolumeInfo.getVolumeControl(volumeInfoObj),
+                    MediaControllerCompatApi21.VolumeInfo.getMaxVolume(volumeInfoObj),
+                    MediaControllerCompatApi21.VolumeInfo.getCurrentVolume(volumeInfoObj)) : null;
+        }
+
+        @Override
+        public void sendCommand(String command, Bundle params, ResultReceiver cb) {
+            MediaControllerCompatApi21.sendCommand(mControllerObj, command, params, cb);
+        }
+
+        @Override
+        public Object getMediaController() {
+            return mControllerObj;
+        }
+    }
+
+    static class TransportControlsApi21 extends TransportControls {
+        private final Object mControlsObj;
+
+        public TransportControlsApi21(Object controlsObj) {
+            mControlsObj = controlsObj;
+        }
+
+        @Override
+        public void play() {
+            MediaControllerCompatApi21.TransportControls.play(mControlsObj);
+        }
+
+        @Override
+        public void pause() {
+            MediaControllerCompatApi21.TransportControls.pause(mControlsObj);
+        }
+
+        @Override
+        public void stop() {
+            MediaControllerCompatApi21.TransportControls.stop(mControlsObj);
+        }
+
+        @Override
+        public void seekTo(long pos) {
+            MediaControllerCompatApi21.TransportControls.seekTo(mControlsObj, pos);
+        }
+
+        @Override
+        public void fastForward() {
+            MediaControllerCompatApi21.TransportControls.fastForward(mControlsObj);
+        }
+
+        @Override
+        public void rewind() {
+            MediaControllerCompatApi21.TransportControls.rewind(mControlsObj);
+        }
+
+        @Override
+        public void skipToNext() {
+            MediaControllerCompatApi21.TransportControls.skipToNext(mControlsObj);
+        }
+
+        @Override
+        public void skipToPrevious() {
+            MediaControllerCompatApi21.TransportControls.skipToPrevious(mControlsObj);
+        }
+
+        @Override
+        public void setRating(RatingCompat rating) {
+            MediaControllerCompatApi21.TransportControls.setRating(mControlsObj,
+                    rating != null ? rating.getRating() : null);
+        }
+    }
+}
diff --git a/v4/java/android/support/v4/media/session/MediaSessionCompat.java b/v4/java/android/support/v4/media/session/MediaSessionCompat.java
new file mode 100644
index 0000000..103a406
--- /dev/null
+++ b/v4/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -0,0 +1,598 @@
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media.session;
+
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ResultReceiver;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.VolumeProviderCompat;
+import android.text.TextUtils;
+
+/**
+ * Allows interaction with media controllers, volume keys, media buttons, and
+ * transport controls.
+ * <p>
+ * A MediaSession should be created when an app wants to publish media playback
+ * information or handle media keys. In general an app only needs one session
+ * for all playback, though multiple sessions can be created to provide finer
+ * grain controls of media.
+ * <p>
+ * Once a session is created the owner of the session may pass its
+ * {@link #getSessionToken() session token} to other processes to allow them to
+ * create a {@link MediaControllerCompat} to interact with the session.
+ * <p>
+ * To receive commands, media keys, and other events a {@link Callback} must be
+ * set with {@link #setCallback(Callback)}.
+ * <p>
+ * When an app is finished performing playback it must call {@link #release()}
+ * to clean up the session and notify any controllers.
+ * <p>
+ * MediaSession objects are thread safe.
+ * <p>
+ * This is a helper for accessing features in
+ * {@link android.media.session.MediaSession} introduced after API level 4 in a
+ * backwards compatible fashion.
+ */
+public class MediaSessionCompat {
+    private final MediaSessionImpl mImpl;
+
+    /**
+     * Set 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
+     * control commands through its {@link Callback}.
+     */
+    public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
+
+    /**
+     * The session uses local playback.
+     */
+    public static final int VOLUME_TYPE_LOCAL = 1;
+
+    /**
+     * The session uses remote playback.
+     */
+    public static final int VOLUME_TYPE_REMOTE = 2;
+
+    /**
+     * Creates a new session.
+     *
+     * @param context The context.
+     * @param tag A short name for debugging purposes.
+     */
+    public MediaSessionCompat(Context context, String tag) {
+        if (context == null) {
+            throw new IllegalArgumentException("context must not be null");
+        }
+        if (TextUtils.isEmpty(tag)) {
+            throw new IllegalArgumentException("tag must not be null or empty");
+        }
+
+        if (android.os.Build.VERSION.SDK_INT >= 21) {
+            mImpl = new MediaSessionImplApi21(context, tag);
+        } else {
+            mImpl = new MediaSessionImplBase();
+        }
+    }
+
+    /**
+     * Add 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.
+     *
+     * @param callback The callback object
+     */
+    public void setCallback(Callback callback) {
+        setCallback(callback, null);
+    }
+
+    /**
+     * Set the callback to receive updates for the MediaSession. This includes
+     * media button and volume events. Set the callback to null to stop
+     * receiving events.
+     *
+     * @param callback The callback to receive updates on.
+     * @param handler The handler that events should be posted on.
+     */
+    public void setCallback(Callback callback, Handler handler) {
+        mImpl.setCallback(callback, handler != null ? handler : new Handler());
+    }
+
+    /**
+     * Set any flags for the session.
+     *
+     * @param flags The flags to set for this session.
+     */
+    public void setFlags(int flags) {
+        mImpl.setFlags(flags);
+    }
+
+    /**
+     * Set 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.
+     * <p>
+     * By default sessions are on {@link AudioManager#STREAM_MUSIC}.
+     *
+     * @param stream The {@link AudioManager} stream this session is playing on.
+     */
+    public void setPlaybackToLocal(int stream) {
+        mImpl.setPlaybackToLocal(stream);
+    }
+
+    /**
+     * Configure 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
+     * this session.
+     *
+     * @param volumeProvider The provider that will handle volume changes. May
+     *            not be null.
+     */
+    public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
+        if (volumeProvider == null) {
+            throw new IllegalArgumentException("volumeProvider may not be null!");
+        }
+        mImpl.setPlaybackToRemote(volumeProvider);
+    }
+
+    /**
+     * Set 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.
+     *
+     * @param active Whether this session is active or not.
+     */
+    public void setActive(boolean active) {
+        mImpl.setActive(active);
+    }
+
+    /**
+     * Get the current active state of this session.
+     *
+     * @return True if the session is active, false otherwise.
+     */
+    public boolean isActive() {
+        return mImpl.isActive();
+    }
+
+    /**
+     * Send 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.
+     *
+     * @param event The name of the event to send
+     * @param extras Any extras included with the event
+     */
+    public void sendSessionEvent(String event, Bundle extras) {
+        if (TextUtils.isEmpty(event)) {
+            throw new IllegalArgumentException("event cannot be null or empty");
+        }
+        mImpl.sendSessionEvent(event, extras);
+    }
+
+    /**
+     * This must be called when an app has finished performing playback. If
+     * playback is expected to start again shortly the session can be left open,
+     * but it must be released if your activity or service is being destroyed.
+     */
+    public void release() {
+        mImpl.release();
+    }
+
+    /**
+     * Retrieve 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.
+     *
+     * @return A token that can be used to create a MediaController for this
+     *         session.
+     */
+    public Token getSessionToken() {
+        return mImpl.getSessionToken();
+    }
+
+    /**
+     * Update the current playback state.
+     *
+     * @param state The current state of playback
+     */
+    public void setPlaybackState(PlaybackStateCompat state) {
+        mImpl.setPlaybackState(state);
+    }
+
+    /**
+     * Update the current metadata. New metadata can be created using
+     * {@link android.media.MediaMetadata.Builder}.
+     *
+     * @param metadata The new metadata
+     */
+    public void setMetadata(MediaMetadataCompat metadata) {
+        mImpl.setMetadata(metadata);
+    }
+
+    /**
+     * Gets the underlying framework {@link android.media.session.MediaSession} object.
+     * <p>
+     * This method is only supported on API 21+.
+     * </p>
+     *
+     * @return The underlying {@link android.media.session.MediaSession} object,
+     * or null if none.
+     */
+    public Object getMediaSession() {
+        return mImpl.getMediaSession();
+    }
+
+    /**
+     * Receives transport controls, media buttons, and commands from controllers
+     * and the system. The callback may be set using {@link #setCallback}.
+     */
+    public abstract static class Callback {
+        final Object mCallbackObj;
+
+        public Callback() {
+            if (android.os.Build.VERSION.SDK_INT >= 21) {
+                mCallbackObj = MediaSessionCompatApi21.createCallback(new StubApi21());
+            } else {
+                mCallbackObj = null;
+            }
+        }
+
+        /**
+         * Called when a controller has sent a custom command to this session.
+         * The owner of the session may handle custom commands but is not
+         * required to.
+         *
+         * @param command The command name.
+         * @param extras Optional parameters for the command, may be null.
+         * @param cb A result receiver to which a result may be sent by the command, may be null.
+         */
+        public void onCommand(String command, Bundle extras, ResultReceiver cb) {
+        }
+
+        /**
+         * Override to handle media button events.
+         *
+         * @param mediaButtonEvent The media button event intent.
+         * @return True if the event was handled, false otherwise.
+         */
+        public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
+            return false;
+        }
+
+        /**
+         * Override to handle requests to begin playback.
+         */
+        public void onPlay() {
+        }
+
+        /**
+         * Override to handle requests to pause playback.
+         */
+        public void onPause() {
+        }
+
+        /**
+         * Override to handle requests to skip to the next media item.
+         */
+        public void onSkipToNext() {
+        }
+
+        /**
+         * Override to handle requests to skip to the previous media item.
+         */
+        public void onSkipToPrevious() {
+        }
+
+        /**
+         * Override to handle requests to fast forward.
+         */
+        public void onFastForward() {
+        }
+
+        /**
+         * Override to handle requests to rewind.
+         */
+        public void onRewind() {
+        }
+
+        /**
+         * Override to handle requests to stop playback.
+         */
+        public void onStop() {
+        }
+
+        /**
+         * Override to handle requests to seek to a specific position in ms.
+         *
+         * @param pos New position to move to, in milliseconds.
+         */
+        public void onSeekTo(long pos) {
+        }
+
+        /**
+         * Override to handle the item being rated.
+         *
+         * @param rating
+         */
+        public void onSetRating(RatingCompat rating) {
+        }
+
+        private class StubApi21 implements MediaSessionCompatApi21.Callback {
+
+            @Override
+            public void onCommand(String command, Bundle extras, ResultReceiver cb) {
+                Callback.this.onCommand(command, extras, cb);
+            }
+
+            @Override
+            public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
+                return Callback.this.onMediaButtonEvent(mediaButtonIntent);
+            }
+
+            @Override
+            public void onPlay() {
+                Callback.this.onPlay();
+            }
+
+            @Override
+            public void onPause() {
+                Callback.this.onPause();
+            }
+
+            @Override
+            public void onSkipToNext() {
+                Callback.this.onSkipToNext();
+            }
+
+            @Override
+            public void onSkipToPrevious() {
+                Callback.this.onSkipToPrevious();
+            }
+
+            @Override
+            public void onFastForward() {
+                Callback.this.onFastForward();
+            }
+
+            @Override
+            public void onRewind() {
+                Callback.this.onRewind();
+            }
+
+            @Override
+            public void onStop() {
+                Callback.this.onStop();
+            }
+
+            @Override
+            public void onSeekTo(long pos) {
+                Callback.this.onSeekTo(pos);
+            }
+
+            @Override
+            public void onSetRating(Object ratingObj) {
+                Callback.this.onSetRating(RatingCompat.fromRating(ratingObj));
+            }
+        }
+    }
+
+    /**
+     * Represents an ongoing session. This may be passed to apps by the session
+     * owner to allow them to create a {@link MediaControllerCompat} to communicate with
+     * the session.
+     */
+    public static final class Token implements Parcelable {
+        private final Parcelable mInner;
+
+        Token(Parcelable inner) {
+            mInner = inner;
+        }
+
+        @Override
+        public int describeContents() {
+            return mInner.describeContents();
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeParcelable(mInner, flags);
+        }
+
+        /**
+         * Gets the underlying framework {@link android.media.session.MediaSession.Token} object.
+         * <p>
+         * This method is only supported on API 21+.
+         * </p>
+         *
+         * @return The underlying {@link android.media.session.MediaSession.Token} object,
+         * or null if none.
+         */
+        public Object getToken() {
+            return mInner;
+        }
+
+        public static final Parcelable.Creator<Token> CREATOR
+                = new Parcelable.Creator<Token>() {
+            @Override
+            public Token createFromParcel(Parcel in) {
+                return new Token(in.readParcelable(null));
+            }
+
+            @Override
+            public Token[] newArray(int size) {
+                return new Token[size];
+            }
+        };
+    }
+
+    interface MediaSessionImpl {
+        void setCallback(Callback callback, Handler handler);
+        void setFlags(int flags);
+        void setPlaybackToLocal(int stream);
+        void setPlaybackToRemote(VolumeProviderCompat volumeProvider);
+        void setActive(boolean active);
+        boolean isActive();
+        void sendSessionEvent(String event, Bundle extras);
+        void release();
+        Token getSessionToken();
+        void setPlaybackState(PlaybackStateCompat state);
+        void setMetadata(MediaMetadataCompat metadata);
+        Object getMediaSession();
+    }
+
+    // TODO: compatibility implementation
+    static class MediaSessionImplBase implements MediaSessionImpl {
+        @Override
+        public void setCallback(Callback callback, Handler handler) {
+        }
+
+        @Override
+        public void setFlags(int flags) {
+        }
+
+        @Override
+        public void setPlaybackToLocal(int stream) {
+        }
+
+        @Override
+        public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
+        }
+
+        @Override
+        public void setActive(boolean active) {
+        }
+
+        @Override
+        public boolean isActive() {
+            return false;
+        }
+
+        @Override
+        public void sendSessionEvent(String event, Bundle extras) {
+        }
+
+        @Override
+        public void release() {
+        }
+
+        @Override
+        public Token getSessionToken() {
+            return null;
+        }
+
+        @Override
+        public void setPlaybackState(PlaybackStateCompat state) {
+        }
+
+        @Override
+        public void setMetadata(MediaMetadataCompat metadata) {
+        }
+
+        @Override
+        public Object getMediaSession() {
+            return null;
+        }
+    }
+
+    static class MediaSessionImplApi21 implements MediaSessionImpl {
+        private final Object mSessionObj;
+        private final Token mToken;
+
+        public MediaSessionImplApi21(Context context, String tag) {
+            mSessionObj = MediaSessionCompatApi21.createSession(context, tag);
+            mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj));
+        }
+
+        @Override
+        public void setCallback(Callback callback, Handler handler) {
+            MediaSessionCompatApi21.setCallback(mSessionObj, callback.mCallbackObj, handler);
+        }
+
+        @Override
+        public void setFlags(int flags) {
+            MediaSessionCompatApi21.setFlags(mSessionObj, flags);
+        }
+
+        @Override
+        public void setPlaybackToLocal(int stream) {
+            MediaSessionCompatApi21.setPlaybackToLocal(mSessionObj, stream);
+        }
+
+        @Override
+        public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
+            MediaSessionCompatApi21.setPlaybackToRemote(mSessionObj,
+                    volumeProvider.getVolumeProvider());
+        }
+
+        @Override
+        public void setActive(boolean active) {
+            MediaSessionCompatApi21.setActive(mSessionObj, active);
+        }
+
+        @Override
+        public boolean isActive() {
+            return MediaSessionCompatApi21.isActive(mSessionObj);
+        }
+
+        @Override
+        public void sendSessionEvent(String event, Bundle extras) {
+            MediaSessionCompatApi21.sendSessionEvent(mSessionObj, event, extras);
+        }
+
+        @Override
+        public void release() {
+            MediaSessionCompatApi21.release(mSessionObj);
+        }
+
+        @Override
+        public Token getSessionToken() {
+            return mToken;
+        }
+
+        @Override
+        public void setPlaybackState(PlaybackStateCompat state) {
+            MediaSessionCompatApi21.setPlaybackState(mSessionObj, state.getPlaybackState());
+        }
+
+        @Override
+        public void setMetadata(MediaMetadataCompat metadata) {
+            MediaSessionCompatApi21.setMetadata(mSessionObj, metadata.getMediaMetadata());
+        }
+
+        @Override
+        public Object getMediaSession() {
+            return mSessionObj;
+        }
+    }
+}
diff --git a/v4/java/android/support/v4/media/session/PlaybackStateCompat.java b/v4/java/android/support/v4/media/session/PlaybackStateCompat.java
new file mode 100644
index 0000000..9e75ab3
--- /dev/null
+++ b/v4/java/android/support/v4/media/session/PlaybackStateCompat.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.media.session;
+
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.text.TextUtils;
+
+/**
+ * Playback state for a {@link MediaSessionCompat}. This includes a state like
+ * {@link PlaybackStateCompat#STATE_PLAYING}, the current playback position,
+ * and the current control capabilities.
+ */
+public final class PlaybackStateCompat implements Parcelable {
+    /**
+     * Indicates this performer supports the stop command.
+     *
+     * @see Builder#setActions
+     */
+    public static final long ACTION_STOP = 1 << 0;
+
+    /**
+     * Indicates this performer supports the pause command.
+     *
+     * @see Builder#setActions
+     */
+    public static final long ACTION_PAUSE = 1 << 1;
+
+    /**
+     * Indicates this performer supports the play command.
+     *
+     * @see Builder#setActions
+     */
+    public static final long ACTION_PLAY = 1 << 2;
+
+    /**
+     * Indicates this performer supports the rewind command.
+     *
+     * @see Builder#setActions
+     */
+    public static final long ACTION_REWIND = 1 << 3;
+
+    /**
+     * Indicates this performer supports the previous command.
+     *
+     * @see Builder#setActions
+     */
+    public static final long ACTION_SKIP_TO_PREVIOUS = 1 << 4;
+
+    /**
+     * Indicates this performer supports the next command.
+     *
+     * @see Builder#setActions
+     */
+    public static final long ACTION_SKIP_TO_NEXT = 1 << 5;
+
+    /**
+     * Indicates this performer supports the fast forward command.
+     *
+     * @see Builder#setActions
+     */
+    public static final long ACTION_FAST_FORWARD = 1 << 6;
+
+    /**
+     * Indicates this performer supports the set rating command.
+     *
+     * @see Builder#setActions
+     */
+    public static final long ACTION_SET_RATING = 1 << 7;
+
+    /**
+     * Indicates this performer supports the seek to command.
+     *
+     * @see Builder#setActions
+     */
+    public static final long ACTION_SEEK_TO = 1 << 8;
+
+    /**
+     * Indicates this performer supports the play/pause toggle command.
+     *
+     * @see Builder#setActions
+     */
+    public static final long ACTION_PLAY_PAUSE = 1 << 9;
+
+    /**
+     * This is the default playback state and indicates that no media has been
+     * added yet, or the performer has been reset and has no content to play.
+     *
+     * @see Builder#setState
+     */
+    public final static int STATE_NONE = 0;
+
+    /**
+     * State indicating this item is currently stopped.
+     *
+     * @see Builder#setState
+     */
+    public final static int STATE_STOPPED = 1;
+
+    /**
+     * State indicating this item is currently paused.
+     *
+     * @see Builder#setState
+     */
+    public final static int STATE_PAUSED = 2;
+
+    /**
+     * State indicating this item is currently playing.
+     *
+     * @see Builder#setState
+     */
+    public final static int STATE_PLAYING = 3;
+
+    /**
+     * State indicating this item is currently fast forwarding.
+     *
+     * @see Builder#setState
+     */
+    public final static int STATE_FAST_FORWARDING = 4;
+
+    /**
+     * State indicating this item is currently rewinding.
+     *
+     * @see Builder#setState
+     */
+    public final static int STATE_REWINDING = 5;
+
+    /**
+     * State indicating this item is currently buffering and will begin playing
+     * when enough data has buffered.
+     *
+     * @see Builder#setState
+     */
+    public final static int STATE_BUFFERING = 6;
+
+    /**
+     * State indicating this item is currently in an error state. The error
+     * message should also be set when entering this state.
+     *
+     * @see Builder#setState
+     */
+    public final static int STATE_ERROR = 7;
+
+    /**
+     * State indicating the class doing playback is currently connecting to a
+     * route. Depending on the implementation you may return to the previous
+     * state when the connection finishes or enter {@link #STATE_NONE}. If
+     * the connection failed {@link #STATE_ERROR} should be used.
+     * @hide
+     */
+    public final static int STATE_CONNECTING = 8;
+
+    /**
+     * State indicating the player is currently skipping to the previous item.
+     *
+     * @see Builder#setState
+     */
+    public final static int STATE_SKIPPING_TO_PREVIOUS = 9;
+
+    /**
+     * State indicating the player is currently skipping to the next item.
+     *
+     * @see Builder#setState
+     */
+    public final static int STATE_SKIPPING_TO_NEXT = 10;
+
+    /**
+     * Use this value for the position to indicate the position is not known.
+     */
+    public final static long PLAYBACK_POSITION_UNKNOWN = -1;
+
+    private final int mState;
+    private final long mPosition;
+    private final long mBufferPosition;
+    private final float mSpeed;
+    private final long mActions;
+    private final CharSequence mErrorMessage;
+    private final long mUpdateTime;
+
+    private Object mStateObj;
+
+    private PlaybackStateCompat(int state, long position, long bufferPosition,
+            float rate, long actions, CharSequence errorMessage, long updateTime) {
+        mState = state;
+        mPosition = position;
+        mBufferPosition = bufferPosition;
+        mSpeed = rate;
+        mActions = actions;
+        mErrorMessage = errorMessage;
+        mUpdateTime = updateTime;
+    }
+
+    private PlaybackStateCompat(Parcel in) {
+        mState = in.readInt();
+        mPosition = in.readLong();
+        mSpeed = in.readFloat();
+        mUpdateTime = in.readLong();
+        mBufferPosition = in.readLong();
+        mActions = in.readLong();
+        mErrorMessage = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder bob = new StringBuilder("PlaybackState {");
+        bob.append("state=").append(mState);
+        bob.append(", position=").append(mPosition);
+        bob.append(", buffered position=").append(mBufferPosition);
+        bob.append(", speed=").append(mSpeed);
+        bob.append(", updated=").append(mUpdateTime);
+        bob.append(", actions=").append(mActions);
+        bob.append(", error=").append(mErrorMessage);
+        bob.append("}");
+        return bob.toString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mState);
+        dest.writeLong(mPosition);
+        dest.writeFloat(mSpeed);
+        dest.writeLong(mUpdateTime);
+        dest.writeLong(mBufferPosition);
+        dest.writeLong(mActions);
+        TextUtils.writeToParcel(mErrorMessage, dest, flags);
+    }
+
+    /**
+     * Get the current state of playback. One of the following:
+     * <ul>
+     * <li> {@link PlaybackStateCompat#STATE_NONE}</li>
+     * <li> {@link PlaybackStateCompat#STATE_STOPPED}</li>
+     * <li> {@link PlaybackStateCompat#STATE_PLAYING}</li>
+     * <li> {@link PlaybackStateCompat#STATE_PAUSED}</li>
+     * <li> {@link PlaybackStateCompat#STATE_FAST_FORWARDING}</li>
+     * <li> {@link PlaybackStateCompat#STATE_REWINDING}</li>
+     * <li> {@link PlaybackStateCompat#STATE_BUFFERING}</li>
+     * <li> {@link PlaybackStateCompat#STATE_ERROR}</li>
+     */
+    public int getState() {
+        return mState;
+    }
+
+    /**
+     * Get the current playback position in ms.
+     */
+    public long getPosition() {
+        return mPosition;
+    }
+
+    /**
+     * Get the current buffer position in ms. This is the farthest playback
+     * point that can be reached from the current position using only buffered
+     * content.
+     */
+    public long getBufferPosition() {
+        return mBufferPosition;
+    }
+
+    /**
+     * Get the current playback speed as a multiple of normal playback. This
+     * should be negative when rewinding. A value of 1 means normal playback and
+     * 0 means paused.
+     *
+     * @return The current speed of playback.
+     */
+    public float getPlaybackSpeed() {
+        return mSpeed;
+    }
+
+    /**
+     * Get the current actions available on this session. This should use a
+     * bitmask of the available actions.
+     * <ul>
+     * <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_PREVIOUS}</li>
+     * <li> {@link PlaybackStateCompat#ACTION_REWIND}</li>
+     * <li> {@link PlaybackStateCompat#ACTION_PLAY}</li>
+     * <li> {@link PlaybackStateCompat#ACTION_PAUSE}</li>
+     * <li> {@link PlaybackStateCompat#ACTION_STOP}</li>
+     * <li> {@link PlaybackStateCompat#ACTION_FAST_FORWARD}</li>
+     * <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_NEXT}</li>
+     * <li> {@link PlaybackStateCompat#ACTION_SEEK_TO}</li>
+     * <li> {@link PlaybackStateCompat#ACTION_SET_RATING}</li>
+     * </ul>
+     */
+    public long getActions() {
+        return mActions;
+    }
+
+    /**
+     * Get a user readable error message. This should be set when the state is
+     * {@link PlaybackStateCompat#STATE_ERROR}.
+     */
+    public CharSequence getErrorMessage() {
+        return mErrorMessage;
+    }
+
+    /**
+     * Get the elapsed real time at which position was last updated. If the
+     * position has never been set this will return 0;
+     *
+     * @return The last time the position was updated.
+     */
+    public long getLastPositionUpdateTime() {
+        return mUpdateTime;
+    }
+
+    /**
+     * Creates an instance from a framework {@link android.media.session.PlaybackState} object.
+     * <p>
+     * This method is only supported on API 21+.
+     * </p>
+     *
+     * @param stateObj A {@link android.media.session.PlaybackState} object, or null if none.
+     * @return An equivalent {@link PlaybackStateCompat} object, or null if none.
+     */
+    public static PlaybackStateCompat fromPlaybackState(Object stateObj) {
+        if (stateObj == null || Build.VERSION.SDK_INT < 21) {
+            return null;
+        }
+
+        PlaybackStateCompat state = new PlaybackStateCompat(
+                PlaybackStateCompatApi21.getState(stateObj),
+                PlaybackStateCompatApi21.getPosition(stateObj),
+                PlaybackStateCompatApi21.getBufferPosition(stateObj),
+                PlaybackStateCompatApi21.getPlaybackSpeed(stateObj),
+                PlaybackStateCompatApi21.getActions(stateObj),
+                PlaybackStateCompatApi21.getErrorMessage(stateObj),
+                PlaybackStateCompatApi21.getLastPositionUpdateTime(stateObj));
+        state.mStateObj = stateObj;
+        return state;
+    }
+
+    /**
+     * Gets the underlying framework {@link android.media.session.PlaybackState} object.
+     * <p>
+     * This method is only supported on API 21+.
+     * </p>
+     *
+     * @return An equivalent {@link android.media.session.PlaybackState} object, or null if none.
+     */
+    public Object getPlaybackState() {
+        if (mStateObj != null || Build.VERSION.SDK_INT < 21) {
+            return mStateObj;
+        }
+
+        mStateObj = PlaybackStateCompatApi21.newInstance(mState, mPosition, mBufferPosition,
+                mSpeed, mActions, mErrorMessage, mUpdateTime);
+        return mStateObj;
+    }
+
+    public static final Parcelable.Creator<PlaybackStateCompat> CREATOR =
+            new Parcelable.Creator<PlaybackStateCompat>() {
+        @Override
+        public PlaybackStateCompat createFromParcel(Parcel in) {
+            return new PlaybackStateCompat(in);
+        }
+
+        @Override
+        public PlaybackStateCompat[] newArray(int size) {
+            return new PlaybackStateCompat[size];
+        }
+    };
+
+    /**
+     * Builder for {@link PlaybackStateCompat} objects.
+     */
+    public static final class Builder {
+        private int mState;
+        private long mPosition;
+        private long mBufferPosition;
+        private float mRate;
+        private long mActions;
+        private CharSequence mErrorMessage;
+        private long mUpdateTime;
+
+        /**
+         * Create an empty Builder.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Create a Builder using a {@link PlaybackStateCompat} instance to set the
+         * initial values.
+         *
+         * @param source The playback state to copy.
+         */
+        public Builder(PlaybackStateCompat source) {
+            mState = source.mState;
+            mPosition = source.mPosition;
+            mRate = source.mSpeed;
+            mUpdateTime = source.mUpdateTime;
+            mBufferPosition = source.mBufferPosition;
+            mActions = source.mActions;
+            mErrorMessage = source.mErrorMessage;
+        }
+
+        /**
+         * Set the current state of playback.
+         * <p>
+         * The position must be in ms and indicates the current playback position
+         * within the track. If the position is unknown use
+         * {@link #PLAYBACK_POSITION_UNKNOWN}.
+         * <p>
+         * The rate is a multiple of normal playback and should be 0 when paused and
+         * negative when rewinding. Normal playback rate is 1.0.
+         * <p>
+         * The state must be one of the following:
+         * <ul>
+         * <li> {@link PlaybackStateCompat#STATE_NONE}</li>
+         * <li> {@link PlaybackStateCompat#STATE_STOPPED}</li>
+         * <li> {@link PlaybackStateCompat#STATE_PLAYING}</li>
+         * <li> {@link PlaybackStateCompat#STATE_PAUSED}</li>
+         * <li> {@link PlaybackStateCompat#STATE_FAST_FORWARDING}</li>
+         * <li> {@link PlaybackStateCompat#STATE_REWINDING}</li>
+         * <li> {@link PlaybackStateCompat#STATE_BUFFERING}</li>
+         * <li> {@link PlaybackStateCompat#STATE_ERROR}</li>
+         * </ul>
+         *
+         * @param state The current state of playback.
+         * @param position The position in the current track in ms.
+         * @param playbackRate The current rate of playback as a multiple of normal
+         *            playback.
+         */
+        public void setState(int state, long position, float playbackRate) {
+            this.mState = state;
+            this.mPosition = position;
+            this.mRate = playbackRate;
+            mUpdateTime = SystemClock.elapsedRealtime();
+        }
+
+        /**
+         * Set the current buffer position in ms. This is the farthest playback
+         * point that can be reached from the current position using only buffered
+         * content.
+         */
+        public void setBufferPosition(long bufferPosition) {
+            mBufferPosition = bufferPosition;
+        }
+
+        /**
+         * Set the current capabilities available on this session. This should use a
+         * bitmask of the available capabilities.
+         * <ul>
+         * <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_PREVIOUS}</li>
+         * <li> {@link PlaybackStateCompat#ACTION_REWIND}</li>
+         * <li> {@link PlaybackStateCompat#ACTION_PLAY}</li>
+         * <li> {@link PlaybackStateCompat#ACTION_PAUSE}</li>
+         * <li> {@link PlaybackStateCompat#ACTION_STOP}</li>
+         * <li> {@link PlaybackStateCompat#ACTION_FAST_FORWARD}</li>
+         * <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_NEXT}</li>
+         * <li> {@link PlaybackStateCompat#ACTION_SEEK_TO}</li>
+         * <li> {@link PlaybackStateCompat#ACTION_SET_RATING}</li>
+         * </ul>
+         */
+        public void setActions(long capabilities) {
+            mActions = capabilities;
+        }
+
+        /**
+         * Set a user readable error message. This should be set when the state is
+         * {@link PlaybackStateCompat#STATE_ERROR}.
+         */
+        public void setErrorMessage(CharSequence errorMessage) {
+            mErrorMessage = errorMessage;
+        }
+
+        /**
+         * Creates the playback state object.
+         */
+        public PlaybackStateCompat build() {
+            return new PlaybackStateCompat(mState, mPosition, mBufferPosition,
+                    mRate, mActions, mErrorMessage, mUpdateTime);
+        }
+    }
+}
diff --git a/v4/java/android/support/v4/net/ConnectivityManagerCompat.java b/v4/java/android/support/v4/net/ConnectivityManagerCompat.java
index 2170ab6..14f150a 100644
--- a/v4/java/android/support/v4/net/ConnectivityManagerCompat.java
+++ b/v4/java/android/support/v4/net/ConnectivityManagerCompat.java
@@ -108,10 +108,14 @@
      * {@link ConnectivityManager#CONNECTIVITY_ACTION} broadcast. This obtains
      * the current state from {@link ConnectivityManager} instead of using the
      * potentially-stale value from
-     * {@link ConnectivityManager#EXTRA_NETWORK_INFO}.
+     * {@link ConnectivityManager#EXTRA_NETWORK_INFO}. May be {@code null}.
      */
     public static NetworkInfo getNetworkInfoFromBroadcast(ConnectivityManager cm, Intent intent) {
         final NetworkInfo info = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
-        return cm.getNetworkInfo(info.getType());
+        if (info != null) {
+            return cm.getNetworkInfo(info.getType());
+        } else {
+            return null;
+        }
     }
 }
diff --git a/v4/java/android/support/v4/os/AsyncTaskCompat.java b/v4/java/android/support/v4/os/AsyncTaskCompat.java
new file mode 100644
index 0000000..2af8905
--- /dev/null
+++ b/v4/java/android/support/v4/os/AsyncTaskCompat.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.os;
+
+import android.os.AsyncTask;
+import android.os.Build;
+
+/**
+ * Helper for accessing features in {@link android.os.AsyncTask}
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
+public class AsyncTaskCompat {
+
+    /**
+     * Executes the task with the specified parameters, allowing multiple tasks to run in parallel
+     * on a pool of threads managed by {@link android.os.AsyncTask}.
+     *
+     * @param task The {@link android.os.AsyncTask} to execute.
+     * @param params The parameters of the task.
+     * @return the instance of AsyncTask.
+     */
+    public static <Params, Progress, Result> AsyncTask<Params, Progress, Result> executeParallel(
+            AsyncTask<Params, Progress, Result> task,
+            Params... params) {
+        if (task == null) {
+            throw new IllegalArgumentException("task can not be null");
+        }
+
+        if (Build.VERSION.SDK_INT >= 11) {
+            // From API 11 onwards, we need to manually select the THREAD_POOL_EXECUTOR
+            AsyncTaskCompatHoneycomb.executeParallel(task, params);
+        } else {
+            // Before API 11, all tasks were run in parallel
+            task.execute(params);
+        }
+
+        return task;
+    }
+
+}
diff --git a/v4/java/android/support/v4/text/TextUtilsCompat.java b/v4/java/android/support/v4/text/TextUtilsCompat.java
index 3400866..436d72f 100644
--- a/v4/java/android/support/v4/text/TextUtilsCompat.java
+++ b/v4/java/android/support/v4/text/TextUtilsCompat.java
@@ -16,6 +16,8 @@
 
 package android.support.v4.text;
 
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.view.ViewCompat;
 
 import java.util.Locale;
@@ -27,7 +29,8 @@
      * @param s the string to be encoded
      * @return the encoded string
      */
-    public static String htmlEncode(String s) {
+    @NonNull
+    public static String htmlEncode(@NonNull String s) {
         StringBuilder sb = new StringBuilder();
         char c;
         for (int i = 0; i < s.length(); i++) {
@@ -69,7 +72,7 @@
      *
      * Be careful: this code will need to be updated when vertical scripts will be supported
      */
-    public static int getLayoutDirectionFromLocale(Locale locale) {
+    public static int getLayoutDirectionFromLocale(@Nullable Locale locale) {
         if (locale != null && !locale.equals(ROOT)) {
             final String scriptSubtag = ICUCompat.getScript(
                     ICUCompat.addLikelySubtags(locale.toString()));
diff --git a/v4/java/android/support/v4/util/CircularArray.java b/v4/java/android/support/v4/util/CircularArray.java
new file mode 100644
index 0000000..91a27da
--- /dev/null
+++ b/v4/java/android/support/v4/util/CircularArray.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v4.util;
+
+/**
+ * A circular array implementation that provides O(1) random read and O(1)
+ * prepend and O(1) append.
+ */
+public class CircularArray<E>
+{
+    private E[] mElements;
+    private int mHead;
+    private int mTail;
+    private int mCapacityBitmask;
+
+    private void doubleCapacity() {
+        int n = mElements.length;
+        int r = n - mHead;
+        int newCapacity = n << 1;
+        if (newCapacity < 0) {
+            throw new RuntimeException("Too big");
+        }
+        Object[] a = new Object[newCapacity];
+        System.arraycopy(mElements, mHead, a, 0, r);
+        System.arraycopy(mElements, 0, a, r, mHead);
+        mElements = (E[])a;
+        mHead = 0;
+        mTail = n;
+        mCapacityBitmask = newCapacity - 1;
+    }
+
+    /**
+     * Create a CircularArray with default capacity.
+     */
+    public CircularArray() {
+        this(8);
+    }
+
+    /**
+     * Create a CircularArray with capacity for at least minCapacity elements.
+     *
+     * @param minCapacity The minimum capacity required for the circular array.
+     */
+    public CircularArray(int minCapacity) {
+        if (minCapacity <= 0) {
+            throw new IllegalArgumentException("capacity must be positive");
+        }
+        int arrayCapacity = minCapacity;
+        // If minCapacity isn't a power of 2, round up to the next highest power
+        // of 2.
+        if (Integer.bitCount(minCapacity) != 1) {
+            arrayCapacity = 1 << (Integer.highestOneBit(minCapacity) + 1);
+        }
+        mCapacityBitmask = arrayCapacity - 1;
+        mElements = (E[]) new Object[arrayCapacity];
+    }
+
+    public final void addFirst(E e) {
+        mHead = (mHead - 1) & mCapacityBitmask;
+        mElements[mHead] = e;
+        if (mHead == mTail) {
+            doubleCapacity();
+        }
+    }
+
+    public final void addLast(E e) {
+        mElements[mTail] = e;
+        mTail = (mTail + 1) & mCapacityBitmask;
+        if (mTail == mHead) {
+            doubleCapacity();
+        }
+    }
+
+    public final E popFirst() {
+        if (mHead == mTail) throw new ArrayIndexOutOfBoundsException();
+        E result = mElements[mHead];
+        mElements[mHead] = null;
+        mHead = (mHead + 1) & mCapacityBitmask;
+        return result;
+    }
+
+    public final E popLast() {
+        if (mHead == mTail) throw new ArrayIndexOutOfBoundsException();
+        int t = (mTail - 1) & mCapacityBitmask;
+        E result = mElements[t];
+        mElements[t] = null;
+        mTail = t;
+        return result;
+    }
+
+    public final E getFirst() {
+        if (mHead == mTail) throw new ArrayIndexOutOfBoundsException();
+        return mElements[mHead];
+    }
+
+    public final E getLast() {
+        if (mHead == mTail) throw new ArrayIndexOutOfBoundsException();
+        return mElements[(mTail - 1) & mCapacityBitmask];
+    }
+
+    public final E get(int i) {
+        if (i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException();
+        int p = (mHead + i) & mCapacityBitmask;
+        return mElements[p];
+    }
+
+    public final int size() {
+        return (mTail - mHead) & mCapacityBitmask;
+    }
+
+    public final boolean isEmpty() {
+        return mHead == mTail;
+    }
+
+}
diff --git a/v4/java/android/support/v4/util/Pair.java b/v4/java/android/support/v4/util/Pair.java
new file mode 100644
index 0000000..7f9e61e
--- /dev/null
+++ b/v4/java/android/support/v4/util/Pair.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.util;
+
+/**
+ * Container to ease passing around a tuple of two objects. This object provides a sensible
+ * implementation of equals(), returning true if equals() is true on each of the contained
+ * objects.
+ */
+public class Pair<F, S> {
+    public final F first;
+    public final S second;
+
+    /**
+     * Constructor for a Pair.
+     *
+     * @param first the first object in the Pair
+     * @param second the second object in the pair
+     */
+    public Pair(F first, S second) {
+        this.first = first;
+        this.second = second;
+    }
+
+    /**
+     * Checks the two objects for equality by delegating to their respective
+     * {@link Object#equals(Object)} methods.
+     *
+     * @param o the {@link Pair} to which this one is to be checked for equality
+     * @return true if the underlying objects of the Pair are both considered
+     *         equal
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof Pair)) {
+            return false;
+        }
+        Pair<?, ?> p = (Pair<?, ?>) o;
+        return objectsEqual(p.first, first) && objectsEqual(p.second, second);
+    }
+
+    private static boolean objectsEqual(Object a, Object b) {
+        return a == b || (a != null && a.equals(b));
+    }
+
+    /**
+     * Compute a hash code using the hash codes of the underlying objects
+     *
+     * @return a hashcode of the Pair
+     */
+    @Override
+    public int hashCode() {
+        return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode());
+    }
+
+    /**
+     * Convenience method for creating an appropriately typed pair.
+     * @param a the first object in the Pair
+     * @param b the second object in the pair
+     * @return a Pair that is templatized with the types of a and b
+     */
+    public static <A, B> Pair <A, B> create(A a, B b) {
+        return new Pair<A, B>(a, b);
+    }
+}
diff --git a/v4/java/android/support/v4/util/Pools.java b/v4/java/android/support/v4/util/Pools.java
new file mode 100644
index 0000000..e3907a1
--- /dev/null
+++ b/v4/java/android/support/v4/util/Pools.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v4.util;
+
+
+/**
+ * Helper class for crating pools of objects. An example use looks like this:
+ * <pre>
+ * public class MyPooledClass {
+ *
+ *     private static final SynchronizedPool<MyPooledClass> sPool =
+ *             new SynchronizedPool<MyPooledClass>(10);
+ *
+ *     public static MyPooledClass obtain() {
+ *         MyPooledClass instance = sPool.acquire();
+ *         return (instance != null) ? instance : new MyPooledClass();
+ *     }
+ *
+ *     public void recycle() {
+ *          // Clear state if needed.
+ *          sPool.release(this);
+ *     }
+ *
+ *     . . .
+ * }
+ * </pre>
+ *
+ */
+public final class Pools {
+
+    /**
+     * Interface for managing a pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static interface Pool<T> {
+
+        /**
+         * @return An instance from the pool if such, null otherwise.
+         */
+        public T acquire();
+
+        /**
+         * Release an instance to the pool.
+         *
+         * @param instance The instance to release.
+         * @return Whether the instance was put in the pool.
+         *
+         * @throws IllegalStateException If the instance is already in the pool.
+         */
+        public boolean release(T instance);
+    }
+
+    private Pools() {
+        /* do nothing - hiding constructor */
+    }
+
+    /**
+     * Simple (non-synchronized) pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static class SimplePool<T> implements Pool<T> {
+        private final Object[] mPool;
+
+        private int mPoolSize;
+
+        /**
+         * Creates a new instance.
+         *
+         * @param maxPoolSize The max pool size.
+         *
+         * @throws IllegalArgumentException If the max pool size is less than zero.
+         */
+        public SimplePool(int maxPoolSize) {
+            if (maxPoolSize <= 0) {
+                throw new IllegalArgumentException("The max pool size must be > 0");
+            }
+            mPool = new Object[maxPoolSize];
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public T acquire() {
+            if (mPoolSize > 0) {
+                final int lastPooledIndex = mPoolSize - 1;
+                T instance = (T) mPool[lastPooledIndex];
+                mPool[lastPooledIndex] = null;
+                mPoolSize--;
+                return instance;
+            }
+            return null;
+        }
+
+        @Override
+        public boolean release(T instance) {
+            if (isInPool(instance)) {
+                throw new IllegalStateException("Already in the pool!");
+            }
+            if (mPoolSize < mPool.length) {
+                mPool[mPoolSize] = instance;
+                mPoolSize++;
+                return true;
+            }
+            return false;
+        }
+
+        private boolean isInPool(T instance) {
+            for (int i = 0; i < mPoolSize; i++) {
+                if (mPool[i] == instance) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Synchronized) pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static class SynchronizedPool<T> extends SimplePool<T> {
+        private final Object mLock = new Object();
+
+        /**
+         * Creates a new instance.
+         *
+         * @param maxPoolSize The max pool size.
+         *
+         * @throws IllegalArgumentException If the max pool size is less than zero.
+         */
+        public SynchronizedPool(int maxPoolSize) {
+            super(maxPoolSize);
+        }
+
+        @Override
+        public T acquire() {
+            synchronized (mLock) {
+                return super.acquire();
+            }
+        }
+
+        @Override
+        public boolean release(T element) {
+            synchronized (mLock) {
+                return super.release(element);
+            }
+        }
+    }
+}
diff --git a/v4/java/android/support/v4/view/PagerTabStrip.java b/v4/java/android/support/v4/view/PagerTabStrip.java
index 21488b8..834035c 100644
--- a/v4/java/android/support/v4/view/PagerTabStrip.java
+++ b/v4/java/android/support/v4/view/PagerTabStrip.java
@@ -21,6 +21,8 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorRes;
+import android.support.annotation.DrawableRes;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
@@ -136,7 +138,7 @@
      *
      * @param resId Resource ID of a color resource to load
      */
-    public void setTabIndicatorColorResource(int resId) {
+    public void setTabIndicatorColorResource(@ColorRes int resId) {
         setTabIndicatorColor(getContext().getResources().getColor(resId));
     }
 
@@ -180,7 +182,7 @@
     }
 
     @Override
-    public void setBackgroundResource(int resId) {
+    public void setBackgroundResource(@DrawableRes int resId) {
         super.setBackgroundResource(resId);
         if (!mDrawFullUnderlineSet) {
             mDrawFullUnderline = resId == 0;
diff --git a/v4/java/android/support/v4/view/ViewCompat.java b/v4/java/android/support/v4/view/ViewCompat.java
index bb64afc..0f290e7 100644
--- a/v4/java/android/support/v4/view/ViewCompat.java
+++ b/v4/java/android/support/v4/view/ViewCompat.java
@@ -21,17 +21,33 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.support.annotation.IdRes;
+import android.support.annotation.IntDef;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
+import android.util.Log;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.view.accessibility.AccessibilityEvent;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Method;
+import java.util.WeakHashMap;
+
 /**
  * Helper for accessing features in {@link View} introduced after API
  * level 4 in a backwards compatible fashion.
  */
 public class ViewCompat {
+    private static final String TAG = "ViewCompat";
+
+    /** @hide */
+    @IntDef({OVER_SCROLL_ALWAYS, OVER_SCROLL_IF_CONTENT_SCROLLS, OVER_SCROLL_IF_CONTENT_SCROLLS})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface OverScroll {}
+
     /**
      * Always allow a user to over-scroll this view, provided it is a
      * view that can scroll.
@@ -51,6 +67,16 @@
 
     private static final long FAKE_FRAME_TIME = 10;
 
+    /** @hide */
+    @IntDef({
+            IMPORTANT_FOR_ACCESSIBILITY_AUTO,
+            IMPORTANT_FOR_ACCESSIBILITY_YES,
+            IMPORTANT_FOR_ACCESSIBILITY_NO,
+            IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface ImportantForAccessibility {}
+
     /**
      * Automatically determine whether a view is important for accessibility.
      */
@@ -72,6 +98,15 @@
      */
     public static final int IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS = 0x00000004;
 
+    /** @hide */
+    @IntDef({
+            ACCESSIBILITY_LIVE_REGION_NONE,
+            ACCESSIBILITY_LIVE_REGION_POLITE,
+            ACCESSIBILITY_LIVE_REGION_ASSERTIVE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface AccessibilityLiveRegion {}
+
     /**
      * Live region mode specifying that accessibility services should not
      * automatically announce changes to this view. This is the default live
@@ -97,6 +132,11 @@
      */
     public static final int ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 0x00000002;
 
+    /** @hide */
+    @IntDef({LAYER_TYPE_NONE, LAYER_TYPE_SOFTWARE, LAYER_TYPE_HARDWARE})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface LayerType {}
+
     /**
      * Indicates that the view does not have a layer.
      */
@@ -145,6 +185,23 @@
      */
     public static final int LAYER_TYPE_HARDWARE = 2;
 
+    /** @hide */
+    @IntDef({
+            LAYOUT_DIRECTION_LTR,
+            LAYOUT_DIRECTION_RTL,
+            LAYOUT_DIRECTION_INHERIT,
+            LAYOUT_DIRECTION_LOCALE})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface LayoutDirectionMode {}
+
+    /** @hide */
+    @IntDef({
+            LAYOUT_DIRECTION_LTR,
+            LAYOUT_DIRECTION_RTL
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface ResolvedLayoutDirectionMode {}
+
     /**
      * Horizontal layout direction of this view is from Left to Right.
      */
@@ -229,9 +286,55 @@
         public int getMeasuredState(View view);
         public int getAccessibilityLiveRegion(View view);
         public void setAccessibilityLiveRegion(View view, int mode);
+        public int getPaddingStart(View view);
+        public int getPaddingEnd(View view);
+        public void setPaddingRelative(View view, int start, int top, int end, int bottom);
+        public void dispatchStartTemporaryDetach(View view);
+        public void dispatchFinishTemporaryDetach(View view);
+        public float getX(View view);
+        public float getY(View view);
+        public float getRotation(View view);
+        public float getRotationX(View view);
+        public float getRotationY(View view);
+        public float getScaleX(View view);
+        public float getScaleY(View view);
+        public float getTranslationX(View view);
+        public float getTranslationY(View view);
+        public int getMinimumWidth(View view);
+        public int getMinimumHeight(View view);
+        public ViewPropertyAnimatorCompat animate(View view);
+        public void setRotation(View view, float value);
+        public void setRotationX(View view, float value);
+        public void setRotationY(View view, float value);
+        public void setScaleX(View view, float value);
+        public void setScaleY(View view, float value);
+        public void setTranslationX(View view, float value);
+        public void setTranslationY(View view, float value);
+        public void setX(View view, float value);
+        public void setY(View view, float value);
+        public void setAlpha(View view, float value);
+        public void setPivotX(View view, float value);
+        public void setPivotY(View view, float value);
+        public float getPivotX(View view);
+        public float getPivotY(View view);
+        public void setElevation(View view, float elevation);
+        public float getElevation(View view);
+        public void setTranslationZ(View view, float translationZ);
+        public float getTranslationZ(View view);
+        public void setTransitionName(View view, String transitionName);
+        public String getTransitionName(View view);
+        public int getWindowSystemUiVisibility(View view);
+        public void requestApplyInsets(View view);
+        public void setChildrenDrawingOrderEnabled(ViewGroup viewGroup, boolean enabled);
     }
 
     static class BaseViewCompatImpl implements ViewCompatImpl {
+        private Method mDispatchStartTemporaryDetach;
+        private Method mDispatchFinishTemporaryDetach;
+        private boolean mTempDetachBound;
+        WeakHashMap<View, ViewPropertyAnimatorCompat> mViewPropertyAnimatorCompatMap = null;
+
+
         public boolean canScrollHorizontally(View v, int direction) {
             return false;
         }
@@ -264,10 +367,10 @@
             // Do nothing; API doesn't exist
         }
         public void postInvalidateOnAnimation(View view) {
-            view.postInvalidateDelayed(getFrameTime());
+            view.invalidate();
         }
         public void postInvalidateOnAnimation(View view, int left, int top, int right, int bottom) {
-            view.postInvalidateDelayed(getFrameTime(), left, top, right, bottom);
+            view.invalidate(left, top, right, bottom);
         }
         public void postOnAnimation(View view, Runnable action) {
             view.postDelayed(action, getFrameTime());
@@ -361,6 +464,238 @@
         public void setAccessibilityLiveRegion(View view, int mode) {
             // No-op
         }
+
+        @Override
+        public int getPaddingStart(View view) {
+            return view.getPaddingLeft();
+        }
+
+        @Override
+        public int getPaddingEnd(View view) {
+            return view.getPaddingRight();
+        }
+
+        @Override
+        public void setPaddingRelative(View view, int start, int top, int end, int bottom) {
+            view.setPadding(start, top, end, bottom);
+        }
+
+        @Override
+        public void dispatchStartTemporaryDetach(View view) {
+            if (!mTempDetachBound) {
+                bindTempDetach();
+            }
+            if (mDispatchStartTemporaryDetach != null) {
+                try {
+                    mDispatchStartTemporaryDetach.invoke(view);
+                } catch (Exception e) {
+                    Log.d(TAG, "Error calling dispatchStartTemporaryDetach", e);
+                }
+            } else {
+                // Try this instead
+                view.onStartTemporaryDetach();
+            }
+        }
+
+        @Override
+        public void dispatchFinishTemporaryDetach(View view) {
+            if (!mTempDetachBound) {
+                bindTempDetach();
+            }
+            if (mDispatchFinishTemporaryDetach != null) {
+                try {
+                    mDispatchFinishTemporaryDetach.invoke(view);
+                } catch (Exception e) {
+                    Log.d(TAG, "Error calling dispatchFinishTemporaryDetach", e);
+                }
+            } else {
+                // Try this instead
+                view.onFinishTemporaryDetach();
+            }
+        }
+
+        private void bindTempDetach() {
+            try {
+                mDispatchStartTemporaryDetach = View.class.getDeclaredMethod(
+                        "dispatchStartTemporaryDetach");
+                mDispatchFinishTemporaryDetach = View.class.getDeclaredMethod(
+                        "dispatchFinishTemporaryDetach");
+            } catch (NoSuchMethodException e) {
+                Log.e(TAG, "Couldn't find method", e);
+            }
+            mTempDetachBound = true;
+        }
+
+        @Override
+        public float getTranslationX(View view) {
+            return 0;
+        }
+
+        @Override
+        public float getTranslationY(View view) {
+            return 0;
+        }
+
+        @Override
+        public float getX(View view) {
+            return 0;
+        }
+
+        @Override
+        public float getY(View view) {
+            return 0;
+        }
+
+        @Override
+        public float getRotation(View view) {
+            return 0;
+        }
+
+        @Override
+        public float getRotationX(View view) {
+            return 0;
+        }
+
+        @Override
+        public float getRotationY(View view) {
+            return 0;
+        }
+
+        @Override
+        public float getScaleX(View view) {
+            return 0;
+        }
+
+        @Override
+        public float getScaleY(View view) {
+            return 0;
+        }
+
+        @Override
+        public int getMinimumWidth(View view) {
+            return 0;
+        }
+
+        @Override
+        public int getMinimumHeight(View view) {
+            return 0;
+        }
+
+        @Override
+        public ViewPropertyAnimatorCompat animate(View view) {
+            return new ViewPropertyAnimatorCompat(view);
+        }
+
+        @Override
+        public void setRotation(View view, float value) {
+            // noop
+        }
+
+        @Override
+        public void setTranslationX(View view, float value) {
+            // noop
+        }
+
+        @Override
+        public void setTranslationY(View view, float value) {
+            // noop
+        }
+
+        @Override
+        public void setAlpha(View view, float value) {
+            // noop
+        }
+
+        @Override
+        public void setRotationX(View view, float value) {
+            // noop
+        }
+
+        @Override
+        public void setRotationY(View view, float value) {
+            // noop
+        }
+
+        @Override
+        public void setScaleX(View view, float value) {
+            // noop
+        }
+
+        @Override
+        public void setScaleY(View view, float value) {
+            // noop
+        }
+
+        @Override
+        public void setX(View view, float value) {
+            // noop
+        }
+
+        @Override
+        public void setY(View view, float value) {
+            // noop
+        }
+
+        @Override
+        public void setPivotX(View view, float value) {
+            // noop
+        }
+
+        @Override
+        public void setPivotY(View view, float value) {
+            // noop
+        }
+
+        @Override
+        public float getPivotX(View view) {
+            return 0;
+        }
+
+        @Override
+        public float getPivotY(View view) {
+            return 0;
+        }
+
+        @Override
+        public void setTransitionName(View view, String transitionName) {
+        }
+
+        @Override
+        public String getTransitionName(View view) {
+            return null;
+        }
+
+        @Override
+        public int getWindowSystemUiVisibility(View view) {
+            return 0;
+        }
+
+        @Override
+        public void requestApplyInsets(View view) {
+        }
+
+        @Override
+        public void setElevation(View view, float elevation) {
+        }
+
+        @Override
+        public float getElevation(View view) {
+            return 0f;
+        }
+
+        @Override
+        public void setTranslationZ(View view, float translationZ) {
+        }
+
+        @Override
+        public float getTranslationZ(View view) {
+            return 0f;
+        }
+
+        @Override
+        public void setChildrenDrawingOrderEnabled(ViewGroup viewGroup, boolean enabled) {
+            // noop
+        }
     }
 
     static class EclairMr1ViewCompatImpl extends BaseViewCompatImpl {
@@ -368,6 +703,11 @@
         public boolean isOpaque(View view) {
             return ViewCompatEclairMr1.isOpaque(view);
         }
+
+        @Override
+        public void setChildrenDrawingOrderEnabled(ViewGroup viewGroup, boolean enabled) {
+            ViewCompatEclairMr1.setChildrenDrawingOrderEnabled(viewGroup, enabled);
+        }
     }
 
     static class GBViewCompatImpl extends EclairMr1ViewCompatImpl {
@@ -422,6 +762,105 @@
         public int getMeasuredState(View view) {
             return ViewCompatHC.getMeasuredState(view);
         }
+        @Override
+        public float getTranslationX(View view) {
+            return ViewCompatHC.getTranslationX(view);
+        }
+        @Override
+        public float getTranslationY(View view) {
+            return ViewCompatHC.getTranslationY(view);
+        }
+        @Override
+        public void setTranslationX(View view, float value) {
+            ViewCompatHC.setTranslationX(view, value);
+        }
+        @Override
+        public void setTranslationY(View view, float value) {
+            ViewCompatHC.setTranslationY(view, value);
+        }
+        @Override
+        public void setAlpha(View view, float value) {
+            ViewCompatHC.setAlpha(view, value);
+        }
+        @Override
+        public void setX(View view, float value) {
+            ViewCompatHC.setX(view, value);
+        }
+        @Override
+        public void setY(View view, float value) {
+            ViewCompatHC.setY(view, value);
+        }
+        @Override
+        public void setRotation(View view, float value) {
+            ViewCompatHC.setRotation(view, value);
+        }
+        @Override
+        public void setRotationX(View view, float value) {
+            ViewCompatHC.setRotationX(view, value);
+        }
+        @Override
+        public void setRotationY(View view, float value) {
+            ViewCompatHC.setRotationY(view, value);
+        }
+        @Override
+        public void setScaleX(View view, float value) {
+            ViewCompatHC.setScaleX(view, value);
+        }
+        @Override
+        public void setScaleY(View view, float value) {
+            ViewCompatHC.setScaleY(view, value);
+        }
+        @Override
+        public void setPivotX(View view, float value) {
+            ViewCompatHC.setPivotX(view, value);
+        }
+        @Override
+        public void setPivotY(View view, float value) {
+            ViewCompatHC.setPivotY(view, value);
+        }
+        @Override
+        public float getX(View view) {
+            return ViewCompatHC.getX(view);
+        }
+
+        @Override
+        public float getY(View view) {
+            return ViewCompatHC.getY(view);
+        }
+
+        @Override
+        public float getRotation(View view) {
+            return ViewCompatHC.getRotation(view);
+        }
+
+        @Override
+        public float getRotationX(View view) {
+            return ViewCompatHC.getRotationX(view);
+        }
+
+        @Override
+        public float getRotationY(View view) {
+            return ViewCompatHC.getRotationY(view);
+        }
+
+        @Override
+        public float getScaleX(View view) {
+            return ViewCompatHC.getScaleX(view);
+        }
+
+        @Override
+        public float getScaleY(View view) {
+            return ViewCompatHC.getScaleY(view);
+        }
+
+        @Override
+        public float getPivotX(View view) {
+            return ViewCompatHC.getPivotX(view);
+        }
+        @Override
+        public float getPivotY(View view) {
+            return ViewCompatHC.getPivotY(view);
+        }
     }
 
     static class ICSViewCompatImpl extends HCViewCompatImpl {
@@ -449,6 +888,19 @@
         public void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate) {
             ViewCompatICS.setAccessibilityDelegate(v, delegate.getBridge());
         }
+        @Override
+        public ViewPropertyAnimatorCompat animate(View view) {
+            if (mViewPropertyAnimatorCompatMap == null) {
+                mViewPropertyAnimatorCompatMap =
+                        new WeakHashMap<View, ViewPropertyAnimatorCompat>();
+            }
+            ViewPropertyAnimatorCompat vpa = mViewPropertyAnimatorCompatMap.get(view);
+            if (vpa == null) {
+                vpa = new ViewPropertyAnimatorCompat(view);
+                mViewPropertyAnimatorCompatMap.put(view, vpa);
+            }
+            return vpa;
+        }
     }
 
     static class JBViewCompatImpl extends ICSViewCompatImpl {
@@ -482,6 +934,12 @@
         }
         @Override
         public void setImportantForAccessibility(View view, int mode) {
+            // IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS is not available
+            // on this platform so replace with IMPORTANT_FOR_ACCESSIBILITY_NO
+            // which is closer semantically.
+            if (mode == IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {
+                mode = IMPORTANT_FOR_ACCESSIBILITY_NO;
+            }
             ViewCompatJB.setImportantForAccessibility(view, mode);
         }
         @Override
@@ -501,6 +959,21 @@
         public ViewParent getParentForAccessibility(View view) {
             return ViewCompatJB.getParentForAccessibility(view);
         }
+
+        @Override
+        public int getMinimumWidth(View view) {
+            return ViewCompatJB.getMinimumWidth(view);
+        }
+
+        @Override
+        public int getMinimumHeight(View view) {
+            return ViewCompatJB.getMinimumHeight(view);
+        }
+
+        @Override
+        public void requestApplyInsets(View view) {
+            ViewCompatJB.requestApplyInsets(view);
+        }
     }
 
     static class JbMr1ViewCompatImpl extends JBViewCompatImpl {
@@ -529,6 +1002,26 @@
         public void setLayoutDirection(View view, int layoutDirection) {
             ViewCompatJellybeanMr1.setLayoutDirection(view, layoutDirection);
         }
+
+        @Override
+        public int getPaddingStart(View view) {
+            return ViewCompatJellybeanMr1.getPaddingStart(view);
+        }
+
+        @Override
+        public int getPaddingEnd(View view) {
+            return ViewCompatJellybeanMr1.getPaddingEnd(view);
+        }
+
+        @Override
+        public void setPaddingRelative(View view, int start, int top, int end, int bottom) {
+            ViewCompatJellybeanMr1.setPaddingRelative(view, start, top, end, bottom);
+        }
+
+        @Override
+        public int getWindowSystemUiVisibility(View view) {
+            return ViewCompatJellybeanMr1.getWindowSystemUiVisibility(view);
+        }
     }
 
     static class KitKatViewCompatImpl extends JbMr1ViewCompatImpl {
@@ -541,12 +1034,56 @@
         public void setAccessibilityLiveRegion(View view, int mode) {
             ViewCompatKitKat.setAccessibilityLiveRegion(view, mode);
         }
+
+        @Override
+        public void setImportantForAccessibility(View view, int mode) {
+            ViewCompatJB.setImportantForAccessibility(view, mode);
+        }
+    }
+
+    static class Api21ViewCompatImpl extends KitKatViewCompatImpl {
+        @Override
+        public void setTransitionName(View view, String transitionName) {
+            ViewCompatApi21.setTransitionName(view, transitionName);
+        }
+
+        @Override
+        public String getTransitionName(View view) {
+            return ViewCompatApi21.getTransitionName(view);
+        }
+
+        @Override
+        public void requestApplyInsets(View view) {
+            ViewCompatApi21.requestApplyInsets(view);
+        }
+
+        @Override
+        public void setElevation(View view, float elevation) {
+            ViewCompatApi21.setElevation(view, elevation);
+        }
+
+        @Override
+        public float getElevation(View view) {
+            return ViewCompatApi21.getElevation(view);
+        }
+
+        @Override
+        public void setTranslationZ(View view, float translationZ) {
+            ViewCompatApi21.setTranslationZ(view, translationZ);
+        }
+
+        @Override
+        public float getTranslationZ(View view) {
+            return ViewCompatApi21.getTranslationZ(view);
+        }
     }
 
     static final ViewCompatImpl IMPL;
     static {
         final int version = android.os.Build.VERSION.SDK_INT;
-        if (version >= 19) {
+        if (version >= 21 || android.os.Build.VERSION.CODENAME.equals("L")) {
+            IMPL = new Api21ViewCompatImpl();
+        } else if (version >= 19) {
             IMPL = new KitKatViewCompatImpl();
         } else if (version >= 17) {
             IMPL = new JbMr1ViewCompatImpl();
@@ -558,6 +1095,8 @@
             IMPL = new HCViewCompatImpl();
         } else if (version >= 9) {
             IMPL = new GBViewCompatImpl();
+        } else if (version >= 7) {
+            IMPL = new EclairMr1ViewCompatImpl();
         } else {
             IMPL = new BaseViewCompatImpl();
         }
@@ -594,6 +1133,7 @@
      * @param v The View against which to invoke the method.
      * @return This view's over-scroll mode.
      */
+    @OverScroll
     public static int getOverScrollMode(View v) {
         return IMPL.getOverScrollMode(v);
     }
@@ -610,7 +1150,7 @@
      * @param v The View against which to invoke the method.
      * @param overScrollMode The new over-scroll mode for this view.
      */
-    public static void setOverScrollMode(View v, int overScrollMode) {
+    public static void setOverScrollMode(View v, @OverScroll int overScrollMode) {
         IMPL.setOverScrollMode(v, overScrollMode);
     }
 
@@ -833,6 +1373,7 @@
      * @see #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
      * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO
      */
+    @ImportantForAccessibility
     public static int getImportantForAccessibility(View view) {
         return IMPL.getImportantForAccessibility(view);
     }
@@ -841,6 +1382,12 @@
      * Sets how to determine whether this view is important for accessibility
      * which is if it fires accessibility events and if it is reported to
      * accessibility services that query the screen.
+     * <p>
+     * <em>Note:</em> If the current paltform version does not support the
+     *  {@link #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS} mode, then
+     *  {@link #IMPORTANT_FOR_ACCESSIBILITY_NO} will be used as it is the
+     *  closest terms of semantics.
+     * </p>
      *
      * @param view The view whose property to set.
      * @param mode How to determine whether this view is important for accessibility.
@@ -850,7 +1397,8 @@
      * @see #IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
      * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO
      */
-    public static void setImportantForAccessibility(View view, int mode) {
+    public static void setImportantForAccessibility(View view,
+            @ImportantForAccessibility int mode) {
         IMPL.setImportantForAccessibility(view, mode);
     }
 
@@ -949,7 +1497,7 @@
      *        and can be null. It is ignored when the layer type is
      *        {@link #LAYER_TYPE_NONE}
      */
-    public static void setLayerType(View view, int layerType, Paint paint) {
+    public static void setLayerType(View view, @LayerType int layerType, Paint paint) {
         IMPL.setLayerType(view, layerType, paint);
     }
 
@@ -969,6 +1517,7 @@
      * @see #LAYER_TYPE_SOFTWARE
      * @see #LAYER_TYPE_HARDWARE
      */
+    @LayerType
     public static int getLayerType(View view) {
         return IMPL.getLayerType(view);
     }
@@ -991,7 +1540,7 @@
      * @param view The view on which to invoke the corresponding method.
      * @param labeledId The labeled view id.
      */
-    public static void setLabelFor(View view, int labeledId) {
+    public static void setLabelFor(View view, @IdRes int labeledId) {
         IMPL.setLabelFor(view, labeledId);
     }
 
@@ -1039,6 +1588,7 @@
      * For compatibility, this will return {@link #LAYOUT_DIRECTION_LTR} if API version
      * is lower than Jellybean MR1 (API 17)
      */
+    @ResolvedLayoutDirectionMode
     public static int getLayoutDirection(View view) {
         return IMPL.getLayoutDirection(view);
     }
@@ -1059,7 +1609,7 @@
      * proceeds up the parent chain of the view to get the value. If there is no parent, then it
      * will return the default {@link #LAYOUT_DIRECTION_LTR}.
      */
-    public static void setLayoutDirection(View view, int layoutDirection) {
+    public static void setLayoutDirection(View view, @LayoutDirectionMode int layoutDirection) {
         IMPL.setLayoutDirection(view, layoutDirection);
     }
 
@@ -1152,7 +1702,8 @@
      *
      * @see ViewCompat#setAccessibilityLiveRegion(View, int)
      */
-    public int getAccessibilityLiveRegion(View view) {
+    @AccessibilityLiveRegion
+    public static int getAccessibilityLiveRegion(View view) {
         return IMPL.getAccessibilityLiveRegion(view);
     }
 
@@ -1184,7 +1735,426 @@
      *        <li>{@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}
      *        </ul>
      */
-    public void setAccessibilityLiveRegion(View view, int mode) {
+    public static void setAccessibilityLiveRegion(View view, @AccessibilityLiveRegion int mode) {
         IMPL.setAccessibilityLiveRegion(view, mode);
     }
+
+    /**
+     * Returns the start padding of the specified view depending on its resolved layout direction.
+     * If there are inset and enabled scrollbars, this value may include the space
+     * required to display the scrollbars as well.
+     *
+     * @param view The view to get padding for
+     * @return the start padding in pixels
+     */
+    public static int getPaddingStart(View view) {
+        return IMPL.getPaddingStart(view);
+    }
+
+    /**
+     * Returns the end padding of the specified view depending on its resolved layout direction.
+     * If there are inset and enabled scrollbars, this value may include the space
+     * required to display the scrollbars as well.
+     *
+     * @param view The view to get padding for
+     * @return the end padding in pixels
+     */
+    public static int getPaddingEnd(View view) {
+        return IMPL.getPaddingEnd(view);
+    }
+
+    /**
+     * Sets the relative padding. The view may add on the space required to display
+     * the scrollbars, depending on the style and visibility of the scrollbars.
+     * So the values returned from {@link #getPaddingStart}, {@link View#getPaddingTop},
+     * {@link #getPaddingEnd} and {@link View#getPaddingBottom} may be different
+     * from the values set in this call.
+     *
+     * @param view The view on which to set relative padding
+     * @param start the start padding in pixels
+     * @param top the top padding in pixels
+     * @param end the end padding in pixels
+     * @param bottom the bottom padding in pixels
+     */
+    public static void setPaddingRelative(View view, int start, int top, int end, int bottom) {
+        IMPL.setPaddingRelative(view, start, top, end, bottom);
+    }
+
+    /**
+     * Notify a view that it is being temporarily detached.
+     */
+    public static void dispatchStartTemporaryDetach(View view) {
+        IMPL.dispatchStartTemporaryDetach(view);
+    }
+
+    /**
+     * Notify a view that its temporary detach has ended; the view is now reattached.
+     */
+    public static void dispatchFinishTemporaryDetach(View view) {
+        IMPL.dispatchFinishTemporaryDetach(view);
+    }
+
+    /**
+     * The horizontal location of this view relative to its {@link View#getLeft() left} position.
+     * This position is post-layout, in addition to wherever the object's
+     * layout placed it.
+     *
+     * <p>Prior to API 11 this will return 0.</p>
+     *
+     * @return The horizontal position of this view relative to its left position, in pixels.
+     */
+    public static float getTranslationX(View view) {
+        return IMPL.getTranslationX(view);
+    }
+
+    /**
+     * The vertical location of this view relative to its {@link View#getTop() left} position.
+     * This position is post-layout, in addition to wherever the object's
+     * layout placed it.
+     *
+     * <p>Prior to API 11 this will return 0.</p>
+     *
+     * @return The vertical position of this view relative to its top position, in pixels.
+     */
+    public static float getTranslationY(View view) {
+        return IMPL.getTranslationY(view);
+    }
+
+    /**
+     * Returns the minimum width of the view.
+     *
+     * <p>Prior to API 16 this will return 0.</p>
+     *
+     * @return the minimum width the view will try to be.
+     */
+    public static int getMinimumWidth(View view) {
+        return IMPL.getMinimumWidth(view);
+    }
+
+    /**
+     * Returns the minimum height of the view.
+     *
+     * <p>Prior to API 16 this will return 0.</p>
+     *
+     * @return the minimum height the view will try to be.
+     */
+    public static int getMinimumHeight(View view) {
+        return IMPL.getMinimumHeight(view);
+    }
+
+    /**
+     * This method returns a ViewPropertyAnimator object, which can be used to animate
+     * specific properties on this View.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @return ViewPropertyAnimator The ViewPropertyAnimator associated with this View.
+     */
+    public static ViewPropertyAnimatorCompat animate(View view) {
+        return IMPL.animate(view);
+    }
+
+    /**
+     * Sets the horizontal location of this view relative to its left position.
+     * This effectively positions the object post-layout, in addition to wherever the object's
+     * layout placed it.
+     *
+     * <p>Prior to API 11 this will have no effect.</p>
+     *
+     * @param value The horizontal position of this view relative to its left position,
+     * in pixels.
+     */
+    public static void setTranslationX(View view, float value) {
+        IMPL.setTranslationX(view, value);
+    }
+
+    /**
+     * Sets the vertical location of this view relative to its top position.
+     * This effectively positions the object post-layout, in addition to wherever the object's
+     * layout placed it.
+     *
+     * <p>Prior to API 11 this will have no effect.</p>
+     *
+     * @param value The vertical position of this view relative to its top position,
+     * in pixels.
+     *
+     * @attr ref android.R.styleable#View_translationY
+     */
+    public static void setTranslationY(View view, float value) {
+        IMPL.setTranslationY(view, value);
+    }
+
+    /**
+     * <p>Sets the opacity of the view. This is a value from 0 to 1, where 0 means the view is
+     * completely transparent and 1 means the view is completely opaque.</p>
+     *
+     * <p> Note that setting alpha to a translucent value (0 < alpha < 1) can have significant
+     * performance implications, especially for large views. It is best to use the alpha property
+     * sparingly and transiently, as in the case of fading animations.</p>
+     *
+     * <p>Prior to API 11 this will have no effect.</p>
+     *
+     * @param value The opacity of the view.
+     */
+    public static void setAlpha(View view, float value) {
+        IMPL.setAlpha(view, value);
+    }
+
+    /**
+     * Sets the visual x position of this view, in pixels. This is equivalent to setting the
+     * {@link #setTranslationX(View, float) translationX} property to be the difference between
+     * the x value passed in and the current left property of the view as determined
+     * by the layout bounds.
+     *
+     * <p>Prior to API 11 this will have no effect.</p>
+     *
+     * @param value The visual x position of this view, in pixels.
+     */
+    public static void setX(View view, float value) {
+        IMPL.setX(view, value);
+    }
+
+    /**
+     * Sets the visual y position of this view, in pixels. This is equivalent to setting the
+     * {@link #setTranslationY(View, float) translationY} property to be the difference between
+     * the y value passed in and the current top property of the view as determined by the
+     * layout bounds.
+     *
+     * <p>Prior to API 11 this will have no effect.</p>
+     *
+     * @param value The visual y position of this view, in pixels.
+     */
+    public static void setY(View view, float value) {
+        IMPL.setY(view, value);
+    }
+
+    /**
+     * Sets the degrees that the view is rotated around the pivot point. Increasing values
+     * result in clockwise rotation.
+     *
+     * <p>Prior to API 11 this will have no effect.</p>
+     *
+     * @param value The degrees of rotation.
+     */
+    public static void setRotation(View view, float value) {
+        IMPL.setRotation(view, value);
+    }
+
+    /**
+     * Sets the degrees that the view is rotated around the horizontal axis through the pivot point.
+     * Increasing values result in clockwise rotation from the viewpoint of looking down the
+     * x axis.
+     *
+     * <p>Prior to API 11 this will have no effect.</p>
+     *
+     * @param value The degrees of X rotation.
+     */
+    public static void setRotationX(View view, float value) {
+        IMPL.setRotationX(view, value);
+    }
+
+    /**
+     * Sets the degrees that the view is rotated around the vertical axis through the pivot point.
+     * Increasing values result in counter-clockwise rotation from the viewpoint of looking
+     * down the y axis.
+     *
+     * <p>Prior to API 11 this will have no effect.</p>
+     *
+     * @param value The degrees of Y rotation.
+     */
+    public static void setRotationY(View view, float value) {
+        IMPL.setRotationY(view, value);
+    }
+
+    /**
+     * Sets the amount that the view is scaled in x around the pivot point, as a proportion of
+     * the view's unscaled width. A value of 1 means that no scaling is applied.
+     *
+     * <p>Prior to API 11 this will have no effect.</p>
+     *
+     * @param value The scaling factor.
+     */
+    public static void setScaleX(View view, float value) {
+        IMPL.setScaleX(view, value);
+    }
+
+    /**
+     * Sets the amount that the view is scaled in Y around the pivot point, as a proportion of
+     * the view's unscaled width. A value of 1 means that no scaling is applied.
+     *
+     * <p>Prior to API 11 this will have no effect.</p>
+     *
+     * @param value The scaling factor.
+     */
+    public static void setScaleY(View view, float value) {
+        IMPL.setScaleY(view, value);
+    }
+
+    /**
+     * The x location of the point around which the view is
+     * {@link #setRotation(View, float) rotated} and {@link #setScaleX(View, float) scaled}.
+     *
+     * <p>Prior to API 11 this will have no effect.</p>
+     *
+     */
+    public static float getPivotX(View view) {
+        return IMPL.getPivotX(view);
+    }
+
+    /**
+     * Sets the x location of the point around which the view is
+     * {@link #setRotation(View, float) rotated} and {@link #setScaleX(View, float) scaled}.
+     * By default, the pivot point is centered on the object.
+     * Setting this property disables this behavior and causes the view to use only the
+     * explicitly set pivotX and pivotY values.
+     *
+     * <p>Prior to API 11 this will have no effect.</p>
+     *
+     * @param value The x location of the pivot point.
+     */
+    public static void setPivotX(View view, float value) {
+        IMPL.setPivotX(view, value);
+    }
+
+    /**
+     * The y location of the point around which the view is {@link #setRotation(View,
+     * float) rotated} and {@link #setScaleY(View, float) scaled}.
+     *
+     * <p>Prior to API 11 this will return 0.</p>
+     *
+     * @return The y location of the pivot point.
+     */
+    public static float getPivotY(View view) {
+        return IMPL.getPivotY(view);
+    }
+
+    /**
+     * Sets the y location of the point around which the view is
+     * {@link #setRotation(View, float) rotated} and {@link #setScaleY(View, float) scaled}.
+     * By default, the pivot point is centered on the object.
+     * Setting this property disables this behavior and causes the view to use only the
+     * explicitly set pivotX and pivotY values.
+     *
+     * <p>Prior to API 11 this will have no effect.</p>
+     *
+     * @param value The y location of the pivot point.
+     */
+    public static void setPivotY(View view, float value) {
+        IMPL.setPivotX(view, value);
+    }
+
+    public static float getRotation(View view) {
+        return IMPL.getRotation(view);
+    }
+
+    public static float getRotationX(View view) {
+        return IMPL.getRotationX(view);
+    }
+
+    public static float getRotationY(View view) {
+        return IMPL.getRotationY(view);
+    }
+
+    public static float getScaleX(View view) {
+        return IMPL.getScaleX(view);
+    }
+
+    public static float getScaleY(View view) {
+        return IMPL.getScaleY(view);
+    }
+
+    public static float getX(View view) {
+        return IMPL.getX(view);
+    }
+
+    public static float getY(View view) {
+        return IMPL.getY(view);
+    }
+
+    /**
+     * Sets the base elevation of this view, in pixels.
+     */
+    public static void setElevation(View view, float elevation) {
+        IMPL.setElevation(view, elevation);
+    }
+
+    /**
+     * The base elevation of this view relative to its parent, in pixels.
+     *
+     * @return The base depth position of the view, in pixels.
+     */
+    public static float getElevation(View view) {
+        return IMPL.getElevation(view);
+    }
+
+    /**
+     * Sets the depth location of this view relative to its {@link #getElevation(View) elevation}.
+     */
+    public static void setTranslationZ(View view, float translationZ) {
+        IMPL.setTranslationZ(view, translationZ);
+    }
+
+    /**
+     * The depth location of this view relative to its {@link #getElevation(View) elevation}.
+     *
+     * @return The depth of this view relative to its elevation.
+     */
+    public static float getTranslationZ(View view) {
+        return IMPL.getTranslationZ(view);
+    }
+
+    /**
+     * Sets the name of the View to be used to identify Views in Transitions.
+     * Names should be unique in the View hierarchy.
+     *
+     * @param view The View against which to invoke the method.
+     * @param transitionName The name of the View to uniquely identify it for Transitions.
+     */
+    public static void setTransitionName(View view, String transitionName) {
+        IMPL.setTransitionName(view, transitionName);
+    }
+
+    /**
+     * Returns the name of the View to be used to identify Views in Transitions.
+     * Names should be unique in the View hierarchy.
+     *
+     * <p>This returns null if the View has not been given a name.</p>
+     *
+     * @param view The View against which to invoke the method.
+     * @return The name used of the View to be used to identify Views in Transitions or null
+     * if no name has been given.
+     */
+    public static String getTransitionName(View view) {
+        return IMPL.getTransitionName(view);
+    }
+
+    /**
+     * Returns the current system UI visibility that is currently set for the entire window.
+     */
+    public static int getWindowSystemUiVisibility(View view) {
+        return IMPL.getWindowSystemUiVisibility(view);
+    }
+
+    /**
+     * Ask that a new dispatch of {@code View.onApplyWindowInsets(WindowInsets)} be performed. This
+     * falls back to {@code View.requestFitSystemWindows()} where available.
+     */
+    public static void requestApplyInsets(View view) {
+        IMPL.requestApplyInsets(view);
+    }
+
+    /**
+     * Tells the ViewGroup whether to draw its children in the order defined by the method
+     * {@code ViewGroup.getChildDrawingOrder(int, int)}.
+     *
+     * @param enabled true if the order of the children when drawing is determined by
+     *        {@link ViewGroup#getChildDrawingOrder(int, int)}, false otherwise
+     *
+     * <p>Prior to API 7 this will have no effect.</p>
+     */
+    public static void setChildrenDrawingOrderEnabled(ViewGroup viewGroup, boolean enabled) {
+       IMPL.setChildrenDrawingOrderEnabled(viewGroup, enabled);
+    }
+
+    // TODO: getters for various view properties (rotation, etc)
 }
diff --git a/v4/java/android/support/v4/view/ViewConfigurationCompat.java b/v4/java/android/support/v4/view/ViewConfigurationCompat.java
index fd26a27..86d19bb 100644
--- a/v4/java/android/support/v4/view/ViewConfigurationCompat.java
+++ b/v4/java/android/support/v4/view/ViewConfigurationCompat.java
@@ -28,6 +28,7 @@
      */
     interface ViewConfigurationVersionImpl {
         public int getScaledPagingTouchSlop(ViewConfiguration config);
+        public boolean hasPermanentMenuKey(ViewConfiguration config);
     }
 
     /**
@@ -38,15 +39,42 @@
         public int getScaledPagingTouchSlop(ViewConfiguration config) {
             return config.getScaledTouchSlop();
         }
+
+        @Override
+        public boolean hasPermanentMenuKey(ViewConfiguration config) {
+            // Pre-HC devices will always have a menu button
+            return true;
+        }
+    }
+
+    /**
+     * Interface implementation for devices with at least v8 APIs.
+     */
+    static class FroyoViewConfigurationVersionImpl extends BaseViewConfigurationVersionImpl {
+        @Override
+        public int getScaledPagingTouchSlop(ViewConfiguration config) {
+            return ViewConfigurationCompatFroyo.getScaledPagingTouchSlop(config);
+        }
     }
 
     /**
      * Interface implementation for devices with at least v11 APIs.
      */
-    static class FroyoViewConfigurationVersionImpl implements ViewConfigurationVersionImpl {
+    static class HoneycombViewConfigurationVersionImpl extends FroyoViewConfigurationVersionImpl {
         @Override
-        public int getScaledPagingTouchSlop(ViewConfiguration config) {
-            return ViewConfigurationCompatFroyo.getScaledPagingTouchSlop(config);
+        public boolean hasPermanentMenuKey(ViewConfiguration config) {
+            // There is no way to check on Honeycomb so we assume false
+            return false;
+        }
+    }
+
+    /**
+     * Interface implementation for devices with at least v14 APIs.
+     */
+    static class IcsViewConfigurationVersionImpl extends HoneycombViewConfigurationVersionImpl {
+        @Override
+        public boolean hasPermanentMenuKey(ViewConfiguration config) {
+            return ViewConfigurationCompatICS.hasPermanentMenuKey(config);
         }
     }
 
@@ -55,7 +83,11 @@
      */
     static final ViewConfigurationVersionImpl IMPL;
     static {
-        if (android.os.Build.VERSION.SDK_INT >= 11) {
+        if (android.os.Build.VERSION.SDK_INT >= 14) {
+            IMPL = new IcsViewConfigurationVersionImpl();
+        } else if (android.os.Build.VERSION.SDK_INT >= 11) {
+            IMPL = new HoneycombViewConfigurationVersionImpl();
+        } else if (android.os.Build.VERSION.SDK_INT >= 8) {
             IMPL = new FroyoViewConfigurationVersionImpl();
         } else {
             IMPL = new BaseViewConfigurationVersionImpl();
@@ -72,4 +104,12 @@
     public static int getScaledPagingTouchSlop(ViewConfiguration config) {
         return IMPL.getScaledPagingTouchSlop(config);
     }
+
+    /**
+     * Report if the device has a permanent menu key available to the user, in a backwards
+     * compatible way.
+     */
+    public static boolean hasPermanentMenuKey(ViewConfiguration config) {
+        return IMPL.hasPermanentMenuKey(config);
+    }
 }
diff --git a/v4/java/android/support/v4/view/ViewGroupCompat.java b/v4/java/android/support/v4/view/ViewGroupCompat.java
index 2c9150b..07dde0c 100644
--- a/v4/java/android/support/v4/view/ViewGroupCompat.java
+++ b/v4/java/android/support/v4/view/ViewGroupCompat.java
@@ -50,6 +50,8 @@
         public void setMotionEventSplittingEnabled(ViewGroup group, boolean split);
         public int getLayoutMode(ViewGroup group);
         public void setLayoutMode(ViewGroup group, int mode);
+        public void setTransitionGroup(ViewGroup group, boolean isTransitionGroup);
+        public boolean isTransitionGroup(ViewGroup group);
     }
 
     static class ViewGroupCompatStubImpl implements ViewGroupCompatImpl {
@@ -71,6 +73,15 @@
         public void setLayoutMode(ViewGroup group, int mode) {
             // no-op, didn't exist. Views only support clip bounds.
         }
+
+        @Override
+        public void setTransitionGroup(ViewGroup group, boolean isTransitionGroup) {
+        }
+
+        @Override
+        public boolean isTransitionGroup(ViewGroup group) {
+            return false;
+        }
     }
 
     static class ViewGroupCompatHCImpl extends ViewGroupCompatStubImpl {
@@ -100,10 +111,24 @@
         }
     }
 
+    static class ViewGroupCompatApi21Impl extends ViewGroupCompatJellybeanMR2Impl {
+        @Override
+        public void setTransitionGroup(ViewGroup group, boolean isTransitionGroup) {
+            ViewGroupCompatApi21.setTransitionGroup(group, isTransitionGroup);
+        }
+
+        @Override
+        public boolean isTransitionGroup(ViewGroup group) {
+            return ViewGroupCompatApi21.isTransitionGroup(group);
+        }
+    }
+
     static final ViewGroupCompatImpl IMPL;
     static {
         final int version = Build.VERSION.SDK_INT;
-        if (version >= 18) {
+        if (version >= 21 || android.os.Build.VERSION.CODENAME.equals("L")) {
+            IMPL = new ViewGroupCompatApi21Impl();
+        } else if (version >= 18) {
             IMPL = new ViewGroupCompatJellybeanMR2Impl();
         } else if (version >= 14) {
             IMPL = new ViewGroupCompatIcsImpl();
@@ -189,4 +214,25 @@
     public static void setLayoutMode(ViewGroup group, int mode) {
         IMPL.setLayoutMode(group, mode);
     }
+
+    /**
+     * Changes whether or not this ViewGroup should be treated as a single entity during
+     * Activity Transitions.
+     * @param isTransitionGroup Whether or not the ViewGroup should be treated as a unit
+     *                          in Activity transitions. If false, the ViewGroup won't transition,
+     *                          only its children. If true, the entire ViewGroup will transition
+     *                          together.
+     */
+    public static void setTransitionGroup(ViewGroup group, boolean isTransitionGroup) {
+        IMPL.setTransitionGroup(group, isTransitionGroup);
+    }
+
+    /**
+     * Returns true if this ViewGroup should be considered as a single entity for removal
+     * when executing an Activity transition. If this is false, child elements will move
+     * individually during the transition.
+     */
+    public static boolean isTransitionGroup(ViewGroup group) {
+        return IMPL.isTransitionGroup(group);
+    }
 }
diff --git a/v4/java/android/support/v4/view/ViewPager.java b/v4/java/android/support/v4/view/ViewPager.java
index e90744c..163649a 100644
--- a/v4/java/android/support/v4/view/ViewPager.java
+++ b/v4/java/android/support/v4/view/ViewPager.java
@@ -28,6 +28,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
+import android.support.annotation.DrawableRes;
 import android.support.v4.os.ParcelableCompat;
 import android.support.v4.os.ParcelableCompatCreatorCallbacks;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
@@ -74,7 +75,11 @@
  * classes have simple code showing how to build a full user interface
  * with them.
  *
- * <p>Here is a more complicated example of ViewPager, using it in conjuction
+ * <p>For more information about how to use ViewPager, read <a
+ * href="{@docRoot}training/implementing-navigation/lateral.html">Creating Swipe Views with
+ * Tabs</a>.</p>
+ *
+ * <p>Below is a more complicated example of ViewPager, using it in conjunction
  * with {@link android.app.ActionBar} tabs.  You can find other examples of using
  * ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code.
  *
@@ -740,7 +745,7 @@
      *
      * @param resId Resource ID of a drawable to display between pages
      */
-    public void setPageMarginDrawable(int resId) {
+    public void setPageMarginDrawable(@DrawableRes int resId) {
         setPageMarginDrawable(getContext().getResources().getDrawable(resId));
     }
 
diff --git a/v4/java/android/support/v4/view/ViewPropertyAnimatorCompat.java b/v4/java/android/support/v4/view/ViewPropertyAnimatorCompat.java
new file mode 100644
index 0000000..5b247fa
--- /dev/null
+++ b/v4/java/android/support/v4/view/ViewPropertyAnimatorCompat.java
@@ -0,0 +1,1101 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.view;
+
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import java.lang.ref.WeakReference;
+
+public class ViewPropertyAnimatorCompat {
+    private static final String TAG = "ViewAnimatorCompat";
+    private WeakReference<View> mView;
+
+    ViewPropertyAnimatorCompat(View view) {
+        mView = new WeakReference<View>(view);
+    }
+
+    interface ViewPropertyAnimatorCompatImpl {
+        public void setDuration(View view, long value);
+        public long getDuration(View view);
+        public void setInterpolator(View view, Interpolator value);
+        public Interpolator getInterpolator(View view);
+        public void setStartDelay(View view, long value);
+        public long getStartDelay(View view);
+        public void alpha(View view, float value);
+        public void alphaBy(View view, float value);
+        public void rotation(View view, float value);
+        public void rotationBy(View view, float value);
+        public void rotationX(View view, float value);
+        public void rotationXBy(View view, float value);
+        public void rotationY(View view, float value);
+        public void rotationYBy(View view, float value);
+        public void scaleX(View view, float value);
+        public void scaleXBy(View view, float value);
+        public void scaleY(View view, float value);
+        public void scaleYBy(View view, float value);
+        public void cancel(View view);
+        public void x(View view, float value);
+        public void xBy(View view, float value);
+        public void y(View view, float value);
+        public void yBy(View view, float value);
+        public void translationX(View view, float value);
+        public void translationXBy(View view, float value);
+        public void translationY(View view, float value);
+        public void translationYBy(View view, float value);
+        public void start(View view);
+        public void withLayer(View view);
+        public void withStartAction(View view, Runnable runnable);
+        public void withEndAction(View view, Runnable runnable);
+        public void setListener(View view, ViewPropertyAnimatorListener listener);
+        public void setUpdateListener(View view, ViewPropertyAnimatorUpdateListener listener);
+    };
+
+    static class BaseViewPropertyAnimatorCompatImpl implements ViewPropertyAnimatorCompatImpl {
+        private ViewPropertyAnimatorListener mListener;
+
+        @Override
+        public void setDuration(View view, long value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void alpha(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void translationX(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void translationY(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void withEndAction(View view, Runnable runnable) {
+            // Other VPA calls are noops pre-ICS; just run the runnable immediately
+            runnable.run();
+        }
+
+        @Override
+        public long getDuration(View view) {
+            return 0;
+        }
+
+        @Override
+        public void setInterpolator(View view, Interpolator value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public Interpolator getInterpolator(View view) {
+            return null;
+        }
+
+        @Override
+        public void setStartDelay(View view, long value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public long getStartDelay(View view) {
+            return 0;
+        }
+
+        @Override
+        public void alphaBy(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void rotation(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void rotationBy(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void rotationX(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void rotationXBy(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void rotationY(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void rotationYBy(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void scaleX(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void scaleXBy(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void scaleY(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void scaleYBy(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void cancel(View view) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void x(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void xBy(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void y(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void yBy(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void translationXBy(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void translationYBy(View view, float value) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void start(View view) {
+            if (mListener != null) {
+                // If a listener has been set, start and then end it immediately
+                mListener.onAnimationStart(view);
+                mListener.onAnimationEnd(view);
+            }
+        }
+
+        @Override
+        public void withLayer(View view) {
+            // noop on versions prior to ICS
+        }
+
+        @Override
+        public void withStartAction(View view, Runnable runnable) {
+            // Other VPA calls are noops pre-ICS; just run the runnable immediately
+            runnable.run();
+        }
+
+        @Override
+        public void setListener(View view, ViewPropertyAnimatorListener listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void setUpdateListener(View view, ViewPropertyAnimatorUpdateListener listener) {
+            // noop
+        }
+    }
+
+    static class ICSViewPropertyAnimatorCompatImpl extends BaseViewPropertyAnimatorCompatImpl {
+
+        @Override
+        public void setDuration(View view, long value) {
+            ViewPropertyAnimatorCompatICS.setDuration(view, value);
+        }
+
+        @Override
+        public void alpha(View view, float value) {
+            ViewPropertyAnimatorCompatICS.alpha(view, value);
+        }
+
+        @Override
+        public void translationX(View view, float value) {
+            ViewPropertyAnimatorCompatICS.translationX(view, value);
+        }
+
+        @Override
+        public void translationY(View view, float value) {
+            ViewPropertyAnimatorCompatICS.translationY(view, value);
+        }
+
+        @Override
+        public long getDuration(View view) {
+            return ViewPropertyAnimatorCompatICS.getDuration(view);
+        }
+
+        @Override
+        public void setInterpolator(View view, Interpolator value) {
+            ViewPropertyAnimatorCompatICS.setInterpolator(view, value);
+        }
+
+        @Override
+        public void setStartDelay(View view, long value) {
+            ViewPropertyAnimatorCompatICS.setStartDelay(view, value);
+        }
+
+        @Override
+        public long getStartDelay(View view) {
+            return ViewPropertyAnimatorCompatICS.getStartDelay(view);
+        }
+
+        @Override
+        public void alphaBy(View view, float value) {
+            ViewPropertyAnimatorCompatICS.alphaBy(view, value);
+        }
+
+        @Override
+        public void rotation(View view, float value) {
+            ViewPropertyAnimatorCompatICS.rotation(view, value);
+        }
+
+        @Override
+        public void rotationBy(View view, float value) {
+            ViewPropertyAnimatorCompatICS.rotationBy(view, value);
+        }
+
+        @Override
+        public void rotationX(View view, float value) {
+            ViewPropertyAnimatorCompatICS.rotationX(view, value);
+        }
+
+        @Override
+        public void rotationXBy(View view, float value) {
+            ViewPropertyAnimatorCompatICS.rotationXBy(view, value);
+        }
+
+        @Override
+        public void rotationY(View view, float value) {
+            ViewPropertyAnimatorCompatICS.rotationY(view, value);
+        }
+
+        @Override
+        public void rotationYBy(View view, float value) {
+            ViewPropertyAnimatorCompatICS.rotationYBy(view, value);
+        }
+
+        @Override
+        public void scaleX(View view, float value) {
+            ViewPropertyAnimatorCompatICS.scaleX(view, value);
+        }
+
+        @Override
+        public void scaleXBy(View view, float value) {
+            ViewPropertyAnimatorCompatICS.scaleXBy(view, value);
+        }
+
+        @Override
+        public void scaleY(View view, float value) {
+            ViewPropertyAnimatorCompatICS.scaleY(view, value);
+        }
+
+        @Override
+        public void scaleYBy(View view, float value) {
+            ViewPropertyAnimatorCompatICS.scaleYBy(view, value);
+        }
+
+        @Override
+        public void cancel(View view) {
+            ViewPropertyAnimatorCompatICS.cancel(view);
+        }
+
+        @Override
+        public void x(View view, float value) {
+            ViewPropertyAnimatorCompatICS.x(view, value);
+        }
+
+        @Override
+        public void xBy(View view, float value) {
+            ViewPropertyAnimatorCompatICS.xBy(view, value);
+        }
+
+        @Override
+        public void y(View view, float value) {
+            ViewPropertyAnimatorCompatICS.y(view, value);
+        }
+
+        @Override
+        public void yBy(View view, float value) {
+            ViewPropertyAnimatorCompatICS.yBy(view, value);
+        }
+
+        @Override
+        public void translationXBy(View view, float value) {
+            ViewPropertyAnimatorCompatICS.translationXBy(view, value);
+        }
+
+        @Override
+        public void translationYBy(View view, float value) {
+            ViewPropertyAnimatorCompatICS.translationYBy(view, value);
+        }
+
+        @Override
+        public void start(View view) {
+            ViewPropertyAnimatorCompatICS.start(view);
+        }
+
+        @Override
+        public void setListener(View view, ViewPropertyAnimatorListener listener) {
+            ViewPropertyAnimatorCompatICS.setListener(view, listener);
+        }
+
+        @Override
+        public void withEndAction(View view, final Runnable runnable) {
+            setListener(view, new ViewPropertyAnimatorListener() {
+                @Override
+                public void onAnimationStart(View view) {
+                }
+
+                @Override
+                public void onAnimationEnd(View view) {
+                    runnable.run();
+                    setListener(view, null);
+                }
+
+                @Override
+                public void onAnimationCancel(View view) {
+                }
+            });
+        }
+
+        @Override
+        public void withStartAction(View view, final Runnable runnable) {
+            setListener(view, new ViewPropertyAnimatorListener() {
+                @Override
+                public void onAnimationStart(View view) {
+                    runnable.run();
+                    setListener(view, null);
+                }
+
+                @Override
+                public void onAnimationEnd(View view) {
+                }
+
+                @Override
+                public void onAnimationCancel(View view) {
+                }
+            });
+        }
+
+        @Override
+        public void withLayer(View view) {
+            final int currentLayerType = ViewCompat.getLayerType(view);
+            setListener(view, new ViewPropertyAnimatorListener() {
+                @Override
+                public void onAnimationStart(View view) {
+                    ViewCompat.setLayerType(view, ViewCompat.LAYER_TYPE_HARDWARE, null);
+                }
+                @Override
+                public void onAnimationEnd(View view) {
+                    ViewCompat.setLayerType(view, currentLayerType, null);
+                    setListener(view, null);
+                }
+
+                @Override
+                public void onAnimationCancel(View view) {
+                }
+            });
+        }
+    }
+
+    static class JBViewPropertyAnimatorCompatImpl extends ICSViewPropertyAnimatorCompatImpl {
+
+        @Override
+        public void withStartAction(View view, Runnable runnable) {
+            ViewPropertyAnimatorCompatJB.withStartAction(view, runnable);
+        }
+
+        @Override
+        public void withEndAction(View view, Runnable runnable) {
+            ViewPropertyAnimatorCompatJB.withEndAction(view, runnable);
+        }
+
+        @Override
+        public void withLayer(View view) {
+            ViewPropertyAnimatorCompatJB.withLayer(view);
+        }
+    }
+
+    static class JBMr2ViewPropertyAnimatorCompatImpl extends JBViewPropertyAnimatorCompatImpl {
+
+        @Override
+        public Interpolator getInterpolator(View view) {
+            return (Interpolator) ViewPropertyAnimatorCompatJellybeanMr2.getInterpolator(view);
+        }
+    }
+
+    static class KitKatViewPropertyAnimatorCompatImpl extends JBMr2ViewPropertyAnimatorCompatImpl {
+        @Override
+        public void setUpdateListener(View view, ViewPropertyAnimatorUpdateListener listener) {
+            ViewPropertyAnimatorCompatKK.setUpdateListener(view, listener);
+        }
+    }
+
+    static final ViewPropertyAnimatorCompatImpl IMPL;
+    static {
+        final int version = android.os.Build.VERSION.SDK_INT;
+        if (version >= 19) {
+            IMPL = new KitKatViewPropertyAnimatorCompatImpl();
+        } else if (version >= 18) {
+            IMPL = new JBMr2ViewPropertyAnimatorCompatImpl();
+        } else if (version >= 16) {
+            IMPL = new JBViewPropertyAnimatorCompatImpl();
+        } else if (version >= 14) {
+            IMPL = new ICSViewPropertyAnimatorCompatImpl();
+        } else {
+            IMPL = new BaseViewPropertyAnimatorCompatImpl();
+        }
+    }
+
+    /**
+     * Sets the duration for the underlying animator that animates the requested properties.
+     * By default, the animator uses the default value for ValueAnimator. Calling this method
+     * will cause the declared value to be used instead.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The length of ensuing property animations, in milliseconds. The value
+     * cannot be negative.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat setDuration(long value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.setDuration(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>alpha</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The value to be animated to.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat alpha(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.alpha(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>alpha</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat alphaBy(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.alphaBy(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>translationX</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The value to be animated to.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat translationX(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.translationX(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>translationY</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The value to be animated to.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat translationY(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.translationY(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * Specifies an action to take place when the next animation ends. The action is only
+     * run if the animation ends normally; if the ViewPropertyAnimator is canceled during
+     * that animation, the runnable will not run.
+     * This method, along with {@link #withStartAction(Runnable)}, is intended to help facilitate
+     * choreographing ViewPropertyAnimator animations with other animations or actions
+     * in the application.
+     *
+     * <p>For example, the following code animates a view to x=200 and then back to 0:</p>
+     * <pre>
+     *     Runnable endAction = new Runnable() {
+     *         public void run() {
+     *             view.animate().x(0);
+     *         }
+     *     };
+     *     view.animate().x(200).withEndAction(endAction);
+     * </pre>
+     *
+     * <p>Prior to API 14, this method will run the action immediately.</p>
+     *
+     * <p>For API 14 and 15, this method will run by setting
+     * a listener on the ViewPropertyAnimatorCompat object and running the action
+     * in that listener's {@link ViewPropertyAnimatorListener#onAnimationEnd(View)} method.</p>
+     *
+     * @param runnable The action to run when the next animation ends.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat withEndAction(Runnable runnable) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.withEndAction(view, runnable);
+        }
+        return this;
+    }
+
+    /**
+     * Returns the current duration of property animations. If the duration was set on this
+     * object, that value is returned. Otherwise, the default value of the underlying Animator
+     * is returned.
+     *
+     * <p>Prior to API 14, this method will return 0.</p>
+     *
+     * @see #setDuration(long)
+     * @return The duration of animations, in milliseconds.
+     */
+    public long getDuration() {
+        View view;
+        if ((view = mView.get()) != null) {
+            return IMPL.getDuration(view);
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Sets the interpolator for the underlying animator that animates the requested properties.
+     * By default, the animator uses the default interpolator for ValueAnimator. Calling this method
+     * will cause the declared object to be used instead.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The TimeInterpolator to be used for ensuing property animations.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat setInterpolator(Interpolator value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.setInterpolator(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * Returns the timing interpolator that this animation uses.
+     *
+     * <p>Prior to API 14, this method will return null.</p>
+     *
+     * @return The timing interpolator for this animation.
+     */
+    public Interpolator getInterpolator() {
+        View view;
+        if ((view = mView.get()) != null) {
+            return IMPL.getInterpolator(view);
+        }
+        else return null;
+    }
+
+    /**
+     * Sets the startDelay for the underlying animator that animates the requested properties.
+     * By default, the animator uses the default value for ValueAnimator. Calling this method
+     * will cause the declared value to be used instead.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The delay of ensuing property animations, in milliseconds. The value
+     * cannot be negative.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat setStartDelay(long value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.setStartDelay(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * Returns the current startDelay of property animations. If the startDelay was set on this
+     * object, that value is returned. Otherwise, the default value of the underlying Animator
+     * is returned.
+     *
+     * <p>Prior to API 14, this method will return 0.</p>
+     *
+     * @see #setStartDelay(long)
+     * @return The startDelay of animations, in milliseconds.
+     */
+    public long getStartDelay() {
+        View view;
+        if ((view = mView.get()) != null) {
+            return IMPL.getStartDelay(view);
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * This method will cause the View's <code>rotation</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The value to be animated to.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat rotation(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.rotation(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>rotation</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat rotationBy(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.rotationBy(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>rotationX</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The value to be animated to.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat rotationX(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.rotationX(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>rotationX</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat rotationXBy(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.rotationXBy(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>rotationY</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The value to be animated to.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat rotationY(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.rotationY(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>rotationY</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat rotationYBy(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.rotationYBy(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>scaleX</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The value to be animated to.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat scaleX(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.scaleX(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>scaleX</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat scaleXBy(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.scaleXBy(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>scaleY</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The value to be animated to.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat scaleY(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.scaleY(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>scaleY</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat scaleYBy(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.scaleYBy(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * Cancels all property animations that are currently running or pending.
+     */
+    public void cancel() {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.cancel(view);
+        }
+    }
+
+    /**
+     * This method will cause the View's <code>x</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The value to be animated to.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat x(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.x(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>x</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat xBy(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.xBy(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>y</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The value to be animated to.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat y(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.y(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>y</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat yBy(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.yBy(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>translationX</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat translationXBy(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.translationXBy(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * This method will cause the View's <code>translationY</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat translationYBy(float value) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.translationYBy(view, value);
+        }
+        return this;
+    }
+
+    /**
+     * Starts the currently pending property animations immediately. Calling <code>start()</code>
+     * is optional because all animations start automatically at the next opportunity. However,
+     * if the animations are needed to start immediately and synchronously (not at the time when
+     * the next event is processed by the hierarchy, which is when the animations would begin
+     * otherwise), then this method can be used.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     */
+    public void start() {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.start(view);
+        }
+    }
+
+    /**
+     * The View associated with this ViewPropertyAnimator will have its
+     * {@link ViewCompat#setLayerType(View, int, android.graphics.Paint) layer type} set to
+     * {@link ViewCompat#LAYER_TYPE_HARDWARE} for the duration of the next animation.
+     * As stated in the documentation for {@link ViewCompat#LAYER_TYPE_HARDWARE},
+     * the actual type of layer used internally depends on the runtime situation of the
+     * view. If the activity and this view are hardware-accelerated, then the layer will be
+     * accelerated as well. If the activity or the view is not accelerated, then the layer will
+     * effectively be the same as {@link ViewCompat#LAYER_TYPE_SOFTWARE}.
+     *
+     * <p>This state is not persistent, either on the View or on this ViewPropertyAnimator: the
+     * layer type of the View will be restored when the animation ends to what it was when this
+     * method was called, and this setting on ViewPropertyAnimator is only valid for the next
+     * animation. Note that calling this method and then independently setting the layer type of
+     * the View (by a direct call to
+     * {@link ViewCompat#setLayerType(View, int, android.graphics.Paint)}) will result in some
+     * inconsistency, including having the layer type restored to its pre-withLayer()
+     * value when the animation ends.</p>
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * <p>For API 14 and 15, this method will run by setting
+     * a listener on the ViewPropertyAnimatorCompat object, setting a hardware layer in
+     * the listener's {@link ViewPropertyAnimatorListener#onAnimationStart(View)} method,
+     * and then restoring the orignal layer type in the listener's
+     * {@link ViewPropertyAnimatorListener#onAnimationEnd(View)} method.</p>
+     *
+     * @see View#setLayerType(int, android.graphics.Paint)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat withLayer() {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.withLayer(view);
+        }
+        return this;
+    }
+
+    /**
+     * Specifies an action to take place when the next animation runs. If there is a
+     * {@link #setStartDelay(long) startDelay} set on this ViewPropertyAnimator, then the
+     * action will run after that startDelay expires, when the actual animation begins.
+     * This method, along with {@link #withEndAction(Runnable)}, is intended to help facilitate
+     * choreographing ViewPropertyAnimator animations with other animations or actions
+     * in the application.
+     *
+     * <p>Prior to API 14, this method will run the action immediately.</p>
+     *
+     * <p>For API 14 and 15, this method will run by setting
+     * a listener on the ViewPropertyAnimatorCompat object and running the action
+     * in that listener's {@link ViewPropertyAnimatorListener#onAnimationStart(View)} method.</p>
+     *
+     * @param runnable The action to run when the next animation starts.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat withStartAction(Runnable runnable) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.withStartAction(view, runnable);
+        }
+        return this;
+    }
+
+    /**
+     * Sets a listener for events in the underlying Animators that run the property
+     * animations.
+     *
+     * <p>Prior to API 14, this method will do nothing.</p>
+     *
+     * @param listener The listener to be called with AnimatorListener events. A value of
+     * <code>null</code> removes any existing listener.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat setListener(ViewPropertyAnimatorListener listener) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.setListener(view, listener);
+        }
+        return this;
+    }
+
+    /**
+     * Sets a listener for update events in the underlying Animator that runs
+     * the property animations.
+     *
+     * <p>Prior to API 19, this method will do nothing.</p>
+     *
+     * @param listener The listener to be called with update events. A value of
+     * <code>null</code> removes any existing listener.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimatorCompat setUpdateListener(
+            ViewPropertyAnimatorUpdateListener listener) {
+        View view;
+        if ((view = mView.get()) != null) {
+            IMPL.setUpdateListener(view, listener);
+        }
+        return this;
+    }
+}
diff --git a/v4/java/android/support/v4/view/ViewPropertyAnimatorListenerAdapter.java b/v4/java/android/support/v4/view/ViewPropertyAnimatorListenerAdapter.java
new file mode 100644
index 0000000..4f58912
--- /dev/null
+++ b/v4/java/android/support/v4/view/ViewPropertyAnimatorListenerAdapter.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.view.View;
+
+/**
+ * This adapter class provides empty implementations of the methods from
+ * {@link ViewPropertyAnimatorListener}. Any custom listener that cares only about a subset of
+ * the methods of this listener can simply subclass this adapter class instead of implementing
+ * the interface directly.
+ */
+public class ViewPropertyAnimatorListenerAdapter implements ViewPropertyAnimatorListener {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAnimationStart(View view) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAnimationEnd(View view) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAnimationCancel(View view) {
+    }
+}
diff --git a/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java b/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
index 43aab0a..9924b31 100644
--- a/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
+++ b/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
@@ -22,8 +22,6 @@
 import android.support.v4.accessibilityservice.AccessibilityServiceInfoCompat;
 import android.support.v4.view.ViewCompat;
 import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -34,6 +32,112 @@
  */
 public class AccessibilityNodeInfoCompat {
 
+    public static class AccessibilityActionCompat {
+        private final Object mAction;
+
+        private AccessibilityActionCompat(Object action) {
+            mAction = action;
+        }
+
+        /**
+         * Gets the id for this action.
+         *
+         * @return The action id.
+         */
+        public int getId() {
+            return AccessibilityNodeInfoCompatApi21.AccessibilityAction.getId(mAction);
+        }
+
+        /**
+         * Gets the label for this action. Its purpose is to describe the
+         * action to user.
+         *
+         * @return The label.
+         */
+        public CharSequence getLabel() {
+            return AccessibilityNodeInfoCompatApi21.AccessibilityAction.getLabel(mAction);
+        }
+    }
+
+    public static class CollectionInfoCompat {
+        private final Object mInfo;
+
+        private CollectionInfoCompat(Object info) {
+            mInfo = info;
+        }
+
+        public int getColumnCount() {
+            return AccessibilityNodeInfoCompatKitKat.CollectionInfo.getColumnCount(mInfo);
+        }
+
+        public int getRowCount() {
+            return AccessibilityNodeInfoCompatKitKat.CollectionInfo.getRowCount(mInfo);
+        }
+
+        public boolean isHierarchical() {
+            return AccessibilityNodeInfoCompatKitKat.CollectionInfo.isHierarchical(mInfo);
+        }
+    }
+
+    public static class CollectionItemInfoCompat {
+        private final Object mInfo;
+
+        private CollectionItemInfoCompat(Object info) {
+            mInfo = info;
+        }
+
+        public int getColumnIndex() {
+            return AccessibilityNodeInfoCompatKitKat.CollectionItemInfo.getColumnIndex(mInfo);
+        }
+
+        public int getColumnSpan() {
+            return AccessibilityNodeInfoCompatKitKat.CollectionItemInfo.getColumnSpan(mInfo);
+        }
+
+        public int getRowIndex() {
+            return AccessibilityNodeInfoCompatKitKat.CollectionItemInfo.getRowIndex(mInfo);
+        }
+
+        public int getRowSpan() {
+            return AccessibilityNodeInfoCompatKitKat.CollectionItemInfo.getRowSpan(mInfo);
+        }
+
+        public boolean isHeading() {
+            return AccessibilityNodeInfoCompatKitKat.CollectionItemInfo.isHeading(mInfo);
+        }
+    }
+
+    public static class RangeInfoCompat {
+        /** Range type: integer. */
+        public static final int RANGE_TYPE_INT = 0;
+        /** Range type: float. */
+        public static final int RANGE_TYPE_FLOAT = 1;
+        /** Range type: percent with values from zero to one.*/
+        public static final int RANGE_TYPE_PERCENT = 2;
+
+        private final Object mInfo;
+
+        private RangeInfoCompat(Object info) {
+            mInfo = info;
+        }
+
+        public float getCurrent() {
+            return AccessibilityNodeInfoCompatKitKat.RangeInfo.getCurrent(mInfo);
+        }
+
+        public float getMax() {
+            return AccessibilityNodeInfoCompatKitKat.RangeInfo.getMax(mInfo);
+        }
+
+        public float getMin() {
+            return AccessibilityNodeInfoCompatKitKat.RangeInfo.getMin(mInfo);
+        }
+
+        public int getType() {
+            return AccessibilityNodeInfoCompatKitKat.RangeInfo.getType(mInfo);
+        }
+    }
+
     static interface AccessibilityNodeInfoImpl {
         public Object obtain();
         public Object obtain(View source);
@@ -99,6 +203,11 @@
         public void setViewIdResourceName(Object info, String viewId);
         public int getLiveRegion(Object info);
         public void setLiveRegion(Object info, int mode);
+        public Object getCollectionInfo(Object info);
+        public Object getCollectionItemInfo(Object info);
+        public Object getRangeInfo(Object info);
+        public List<Object> getActionList(Object info);
+        public void addAction(Object info, int id, CharSequence label);
     }
 
     static class AccessibilityNodeInfoStubImpl implements AccessibilityNodeInfoImpl {
@@ -421,6 +530,31 @@
         public void setLiveRegion(Object info, int mode) {
             // No-op
         }
+
+        @Override
+        public Object getCollectionInfo(Object info) {
+            return null;
+        }
+
+        @Override
+        public Object getCollectionItemInfo(Object info) {
+            return null;
+        }
+
+        @Override
+        public Object getRangeInfo(Object info) {
+            return null;
+        }
+
+        @Override
+        public List<Object> getActionList(Object info) {
+            return null;
+        }
+
+        @Override
+        public void addAction(Object info, int id, CharSequence label) {
+        }
+
     }
 
     static class AccessibilityNodeInfoIcsImpl extends AccessibilityNodeInfoStubImpl {
@@ -658,6 +792,13 @@
         public void recycle(Object info) {
             AccessibilityNodeInfoCompatIcs.recycle(info);
         }
+
+        @Override
+        public void addAction(Object info, int id, CharSequence label) {
+            if (Integer.bitCount(id) == 1) {
+                addAction(info, id);
+            }
+        }
     }
 
     static class AccessibilityNodeInfoJellybeanImpl extends AccessibilityNodeInfoIcsImpl {
@@ -750,10 +891,39 @@
         public void setLiveRegion(Object info, int mode) {
             AccessibilityNodeInfoCompatKitKat.setLiveRegion(info, mode);
         }
+
+        @Override
+        public Object getCollectionInfo(Object info) {
+            return AccessibilityNodeInfoCompatKitKat.getCollectionInfo(info);
+        }
+
+        @Override
+        public Object getCollectionItemInfo(Object info) {
+            return AccessibilityNodeInfoCompatKitKat.getCollectionItemInfo(info);
+        }
+
+        @Override
+        public Object getRangeInfo(Object info) {
+            return AccessibilityNodeInfoCompatKitKat.getRangeInfo(info);
+        }
+    }
+
+    static class AccessibilityNodeInfoApi21Impl extends AccessibilityNodeInfoKitKatImpl {
+        @Override
+        public List<Object> getActionList(Object info) {
+            return AccessibilityNodeInfoCompatApi21.getActionList(info);
+        }
+
+        @Override
+        public void addAction(Object info, int id, CharSequence label) {
+            AccessibilityNodeInfoCompatApi21.addAction(info, id, label);
+        }
     }
 
     static {
-        if (Build.VERSION.SDK_INT >= 19) { // KitKat
+        if (Build.VERSION.CODENAME.equals("L")) { // b/15469684 filed to change to API 21
+            IMPL = new AccessibilityNodeInfoApi21Impl();
+        } else if (Build.VERSION.SDK_INT >= 19) { // KitKat
             IMPL = new AccessibilityNodeInfoKitKatImpl();
         } else if (Build.VERSION.SDK_INT >= 18) { // JellyBean MR2
             IMPL = new AccessibilityNodeInfoJellybeanMr2Impl();
@@ -956,6 +1126,22 @@
      */
     public static final int ACTION_SET_SELECTION = 0x00020000;
 
+    /**
+     * Action that sets the text of the node. Performing the action without argument, using <code>
+     * null</code> or empty {@link CharSequence} will clear the text. This action will also put the
+     * cursor at the end of text.
+     * <p>
+     * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE}<br>
+     * <strong>Example:</strong>
+     * <code><pre><p>
+     *   Bundle arguments = new Bundle();
+     *   arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
+     *       "android");
+     *   info.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
+     * </code></pre></p>
+     */
+    public static final int ACTION_SET_TEXT = 0x00200000;
+
     // Action arguments
 
     /**
@@ -1019,6 +1205,18 @@
     public static final String ACTION_ARGUMENT_SELECTION_END_INT =
             "ACTION_ARGUMENT_SELECTION_END_INT";
 
+    /**
+     * Argument for specifying the text content to set
+     * <p>
+     * <strong>Type:</strong> CharSequence<br>
+     * <strong>Actions:</strong> {@link #ACTION_SET_TEXT}
+     * </p>
+     *
+     * @see #ACTION_SET_TEXT
+     */
+    public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE =
+            "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE";
+
     // Focus types
 
     /**
@@ -1943,6 +2141,58 @@
         IMPL.setLiveRegion(mInfo, mode);
     }
 
+    /**
+     * Gets the collection info if the node is a collection. A collection
+     * child is always a collection item.
+     *
+     * @return The collection info.
+     */
+    public CollectionInfoCompat getCollectionInfo() {
+        Object info = IMPL.getCollectionInfo(mInfo);
+        if (info == null) return null;
+        return new CollectionInfoCompat(info);
+    }
+
+    /**
+     * Gets the collection item info if the node is a collection item. A collection
+     * item is always a child of a collection.
+     *
+     * @return The collection item info.
+     */
+    public CollectionItemInfoCompat getCollectionItemInfo() {
+        Object info = IMPL.getCollectionItemInfo(mInfo);
+        if (info == null) return null;
+        return new CollectionItemInfoCompat(info);
+    }
+
+    /**
+     * Gets the range info if this node is a range.
+     *
+     * @return The range.
+     */
+    public RangeInfoCompat getRangeInfo() {
+        Object info = IMPL.getRangeInfo(mInfo);
+        if (info == null) return null;
+        return new RangeInfoCompat(info);
+    }
+
+    /**
+     * Gets the actions that can be performed on the node.
+     *
+     * @return A list of AccessibilityActions.
+     */
+    public List<AccessibilityActionCompat> getActionList() {
+        List<AccessibilityActionCompat> result = new ArrayList<AccessibilityActionCompat>();
+        List<Object> actions = IMPL.getActionList(mInfo);
+        final int actionCount = actions.size();
+        for (int i = 0; i < actionCount; i++) {
+            Object action = actions.get(i);
+            result.add(new AccessibilityActionCompat(action));
+        }
+        return result;
+    }
+
+
     @Override
     public int hashCode() {
         return (mInfo == null) ? 0 : mInfo.hashCode();
diff --git a/v4/java/android/support/v4/widget/CircleImageView.java b/v4/java/android/support/v4/widget/CircleImageView.java
new file mode 100644
index 0000000..f7bcbb5
--- /dev/null
+++ b/v4/java/android/support/v4/widget/CircleImageView.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RadialGradient;
+import android.graphics.Shader;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.OvalShape;
+import android.support.v4.view.ViewCompat;
+import android.view.animation.Animation;
+import android.widget.ImageView;
+
+/**
+ * Private class created to work around issues with AnimationListeners being
+ * called before the animation is actually complete and support shadows on older platforms.
+ */
+class CircleImageView extends ImageView {
+
+    private static final int KEY_SHADOW_COLOR = 0x1E000000;
+    private static final int FILL_SHADOW_COLOR = 0x3D000000;
+    // PX
+    private static final float X_OFFSET = 0f;
+    private static final float Y_OFFSET = 1.75f;
+    private static final float SHADOW_RADIUS = 3.5f;
+    private static final int SHADOW_ELEVATION = 4;
+
+    private Animation.AnimationListener mListener;
+
+    public CircleImageView(Context context, int color, final float radius) {
+        super(context);
+        final float density = getContext().getResources().getDisplayMetrics().density;
+        final int diameter = (int) (radius * density * 2);
+        final int shadowRadius = (int) (density * SHADOW_RADIUS);
+        final int shadowYOffset = (int) (density * Y_OFFSET);
+        final int shadowXOffset = (int) (density * X_OFFSET);
+        ShapeDrawable circle;
+        if (android.os.Build.VERSION.CODENAME.equals("L")
+                || android.os.Build.VERSION.SDK_INT > 21) {
+            circle = new ShapeDrawable(new OvalShape());
+            ViewCompat.setElevation(this, SHADOW_ELEVATION * density);
+        } else {
+            OvalShape oval = new OvalShape() {
+                Paint shadowPaint = new Paint();
+                RadialGradient gradient;
+
+                @Override
+                public void draw(Canvas canvas, Paint paint) {
+                    if (gradient == null) {
+                        gradient = new RadialGradient(diameter/2, diameter/2, shadowRadius,
+                                new int[]{FILL_SHADOW_COLOR, 0x00000000},
+                                null, Shader.TileMode.CLAMP);
+                        shadowPaint.setShader(gradient);
+                    }
+                    canvas.drawCircle(diameter / 2, diameter / 2, (diameter / 2), shadowPaint);
+                    canvas.drawCircle(diameter / 2, diameter / 2, (diameter / 2 - shadowRadius),
+                            paint);
+                }
+            };
+            circle = new ShapeDrawable(oval);
+            ViewCompat.setLayerType(this, ViewCompat.LAYER_TYPE_SOFTWARE, circle.getPaint());
+            circle.getPaint().setShadowLayer(shadowRadius, shadowXOffset, shadowYOffset, KEY_SHADOW_COLOR);
+            final int padding = (int) (shadowRadius / 2);
+            // set padding so the inner image sits correctly within the shadow.
+            setPadding(padding, padding, padding, padding);
+        }
+        circle.getPaint().setColor(color);
+        setBackgroundDrawable(circle);
+    }
+
+    public void setAnimationListener(Animation.AnimationListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void onAnimationStart() {
+        super.onAnimationStart();
+        if (mListener != null) {
+            mListener.onAnimationStart(getAnimation());
+        }
+    }
+
+    @Override
+    public void onAnimationEnd() {
+        super.onAnimationEnd();
+        if (mListener != null) {
+            mListener.onAnimationEnd(getAnimation());
+        }
+    }
+
+    /**
+     * Update the background color of the circle image view.
+     */
+    public void setBackgroundColor(int colorRes) {
+        if (getBackground() instanceof ShapeDrawable) {
+            final Resources res = getResources();
+            ((ShapeDrawable) getBackground()).getPaint().setColor(res.getColor(colorRes));
+        }
+    }
+}
diff --git a/v4/java/android/support/v4/widget/DrawerLayout.java b/v4/java/android/support/v4/widget/DrawerLayout.java
index 92ef51d..d468f3b 100644
--- a/v4/java/android/support/v4/widget/DrawerLayout.java
+++ b/v4/java/android/support/v4/widget/DrawerLayout.java
@@ -27,6 +27,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
 import android.support.v4.view.AccessibilityDelegateCompat;
 import android.support.v4.view.GravityCompat;
 import android.support.v4.view.KeyEventCompat;
@@ -40,9 +43,12 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
 import android.view.ViewParent;
 import android.view.accessibility.AccessibilityEvent;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
@@ -64,15 +70,25 @@
  * stuttering; try to perform expensive operations during the {@link #STATE_IDLE} state.
  * {@link SimpleDrawerListener} offers default/no-op implementations of each callback method.</p>
  *
- * <p>As per the Android Design guide, any drawers positioned to the left/start should
+ * <p>As per the <a href="{@docRoot}design/patterns/navigation-drawer.html">Android Design
+ * guide</a>, any drawers positioned to the left/start should
  * always contain content for navigating around the application, whereas any drawers
  * positioned to the right/end should always contain actions to take on the current content.
  * This preserves the same navigation left, actions right structure present in the Action Bar
  * and elsewhere.</p>
+ *
+ * <p>For more information about how to use DrawerLayout, read <a
+ * href="{@docRoot}training/implementing-navigation/nav-drawer.html">Creating a Navigation
+ * Drawer</a>.</p>
  */
 public class DrawerLayout extends ViewGroup {
     private static final String TAG = "DrawerLayout";
 
+    /** @hide */
+    @IntDef({STATE_IDLE, STATE_DRAGGING, STATE_SETTLING})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface State {}
+
     /**
      * Indicates that any drawers are in an idle, settled state. No animation is in progress.
      */
@@ -88,6 +104,11 @@
      */
     public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING;
 
+    /** @hide */
+    @IntDef({LOCK_MODE_UNLOCKED, LOCK_MODE_LOCKED_CLOSED, LOCK_MODE_LOCKED_OPEN})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface LockMode {}
+
     /**
      * The drawer is unlocked.
      */
@@ -105,6 +126,12 @@
      */
     public static final int LOCK_MODE_LOCKED_OPEN = 2;
 
+    /** @hide */
+    @IntDef({Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface EdgeGravity {}
+
+
     private static final int MIN_DRAWER_MARGIN = 64; // dp
 
     private static final int DEFAULT_SCRIM_COLOR = 0x99000000;
@@ -132,6 +159,9 @@
             android.R.attr.layout_gravity
     };
 
+    private final ChildAccessibilityDelegate mChildAccessibilityDelegate =
+            new ChildAccessibilityDelegate();
+
     private int mMinDrawerMargin;
 
     private int mScrimColor = DEFAULT_SCRIM_COLOR;
@@ -193,7 +223,7 @@
          *
          * @param newState The new drawer motion state
          */
-        public void onDrawerStateChanged(int newState);
+        public void onDrawerStateChanged(@State int newState);
     }
 
     /**
@@ -249,6 +279,9 @@
         // So that we can catch the back button
         setFocusableInTouchMode(true);
 
+        ViewCompat.setImportantForAccessibility(this,
+                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+
         ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate());
         ViewGroupCompat.setMotionEventSplittingEnabled(this, false);
     }
@@ -260,7 +293,7 @@
      * @param shadowDrawable Shadow drawable to use at the edge of a drawer
      * @param gravity Which drawer the shadow should apply to
      */
-    public void setDrawerShadow(Drawable shadowDrawable, int gravity) {
+    public void setDrawerShadow(Drawable shadowDrawable, @EdgeGravity int gravity) {
         /*
          * TODO Someone someday might want to set more complex drawables here.
          * They're probably nuts, but we might want to consider registering callbacks,
@@ -286,7 +319,7 @@
      * @param resId Resource id of a shadow drawable to use at the edge of a drawer
      * @param gravity Which drawer the shadow should apply to
      */
-    public void setDrawerShadow(int resId, int gravity) {
+    public void setDrawerShadow(@DrawableRes int resId, @EdgeGravity int gravity) {
         setDrawerShadow(getResources().getDrawable(resId), gravity);
     }
 
@@ -323,7 +356,7 @@
      * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED},
      *                 {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}.
      */
-    public void setDrawerLockMode(int lockMode) {
+    public void setDrawerLockMode(@LockMode int lockMode) {
         setDrawerLockMode(lockMode, Gravity.LEFT);
         setDrawerLockMode(lockMode, Gravity.RIGHT);
     }
@@ -347,7 +380,7 @@
      * @see #LOCK_MODE_LOCKED_CLOSED
      * @see #LOCK_MODE_LOCKED_OPEN
      */
-    public void setDrawerLockMode(int lockMode, int edgeGravity) {
+    public void setDrawerLockMode(@LockMode int lockMode, @EdgeGravity int edgeGravity) {
         final int absGravity = GravityCompat.getAbsoluteGravity(edgeGravity,
                 ViewCompat.getLayoutDirection(this));
         if (absGravity == Gravity.LEFT) {
@@ -395,7 +428,7 @@
      * @see #LOCK_MODE_LOCKED_CLOSED
      * @see #LOCK_MODE_LOCKED_OPEN
      */
-    public void setDrawerLockMode(int lockMode, View drawerView) {
+    public void setDrawerLockMode(@LockMode int lockMode, View drawerView) {
         if (!isDrawerView(drawerView)) {
             throw new IllegalArgumentException("View " + drawerView + " is not a " +
                     "drawer with appropriate layout_gravity");
@@ -411,7 +444,8 @@
      * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or
      *         {@link #LOCK_MODE_LOCKED_OPEN}.
      */
-    public int getDrawerLockMode(int edgeGravity) {
+    @LockMode
+    public int getDrawerLockMode(@EdgeGravity int edgeGravity) {
         final int absGravity = GravityCompat.getAbsoluteGravity(
                 edgeGravity, ViewCompat.getLayoutDirection(this));
         if (absGravity == Gravity.LEFT) {
@@ -429,6 +463,7 @@
      * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or
      *         {@link #LOCK_MODE_LOCKED_OPEN}.
      */
+    @LockMode
     public int getDrawerLockMode(View drawerView) {
         final int absGravity = getDrawerViewAbsoluteGravity(drawerView);
         if (absGravity == Gravity.LEFT) {
@@ -449,7 +484,7 @@
      *            drawer to set the title for.
      * @param title The title for the drawer.
      */
-    public void setDrawerTitle(int edgeGravity, CharSequence title) {
+    public void setDrawerTitle(@EdgeGravity int edgeGravity, CharSequence title) {
         final int absGravity = GravityCompat.getAbsoluteGravity(
                 edgeGravity, ViewCompat.getLayoutDirection(this));
         if (absGravity == Gravity.LEFT) {
@@ -467,7 +502,8 @@
      * @return The title of the drawer, or null if none set.
      * @see #setDrawerTitle(int, CharSequence)
      */
-    public CharSequence getDrawerTitle(int edgeGravity) {
+    @Nullable
+    public CharSequence getDrawerTitle(@EdgeGravity int edgeGravity) {
         final int absGravity = GravityCompat.getAbsoluteGravity(
                 edgeGravity, ViewCompat.getLayoutDirection(this));
         if (absGravity == Gravity.LEFT) {
@@ -482,7 +518,7 @@
      * Resolve the shared state of all drawers from the component ViewDragHelpers.
      * Should be called whenever a ViewDragHelper's state changes.
      */
-    void updateDrawerState(int forGravity, int activeState, View activeDrawer) {
+    void updateDrawerState(int forGravity, @State int activeState, View activeDrawer) {
         final int leftState = mLeftDragger.getViewDragState();
         final int rightState = mRightDragger.getViewDragState();
 
@@ -521,6 +557,16 @@
                 mListener.onDrawerClosed(drawerView);
             }
 
+            // If no drawer is opened, all drawers are not shown
+            // for accessibility and the content is shown.
+            View content = getChildAt(0);
+            if (content != null) {
+                ViewCompat.setImportantForAccessibility(content,
+                        ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+            }
+            ViewCompat.setImportantForAccessibility(drawerView,
+                            ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+
             // Only send WINDOW_STATE_CHANGE if the host has window focus. This
             // may change if support for multiple foreground windows (e.g. IME)
             // improves.
@@ -540,7 +586,19 @@
             if (mListener != null) {
                 mListener.onDrawerOpened(drawerView);
             }
+
+            // If a drawer is opened, only it is shown for
+            // accessibility and the content is not shown.
+            View content = getChildAt(0);
+            if (content != null) {
+                ViewCompat.setImportantForAccessibility(content,
+                        ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+            }
+            ViewCompat.setImportantForAccessibility(drawerView,
+                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+
             sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+            drawerView.requestFocus();
         }
     }
 
@@ -627,7 +685,7 @@
      * @param gravity Absolute gravity value
      * @return LEFT or RIGHT as appropriate, or a hex string
      */
-    static String gravityToString(int gravity) {
+    static String gravityToString(@EdgeGravity int gravity) {
         if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
             return "LEFT";
         }
@@ -1093,7 +1151,7 @@
      * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
      *                GravityCompat.START or GravityCompat.END may also be used.
      */
-    public void openDrawer(int gravity) {
+    public void openDrawer(@EdgeGravity int gravity) {
         final View drawerView = findDrawerWithGravity(gravity);
         if (drawerView == null) {
             throw new IllegalArgumentException("No drawer view found with gravity " +
@@ -1133,7 +1191,7 @@
      * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
      *                GravityCompat.START or GravityCompat.END may also be used.
      */
-    public void closeDrawer(int gravity) {
+    public void closeDrawer(@EdgeGravity int gravity) {
         final View drawerView = findDrawerWithGravity(gravity);
         if (drawerView == null) {
             throw new IllegalArgumentException("No drawer view found with gravity " +
@@ -1168,7 +1226,7 @@
      * @param drawerGravity Gravity of the drawer to check
      * @return true if the given drawer view is in an open state
      */
-    public boolean isDrawerOpen(int drawerGravity) {
+    public boolean isDrawerOpen(@EdgeGravity int drawerGravity) {
         final View drawerView = findDrawerWithGravity(drawerGravity);
         if (drawerView != null) {
             return isDrawerOpen(drawerView);
@@ -1193,13 +1251,13 @@
 
     /**
      * Check if a given drawer view is currently visible on-screen. The drawer
-     * may be only peeking onto the screen, fully extended, or anywhere inbetween.
+     * may be only peeking onto the screen, fully extended, or anywhere in between.
      * If there is no drawer with the given gravity this method will return false.
      *
      * @param drawerGravity Gravity of the drawer to check
      * @return true if the given drawer is visible on-screen
      */
-    public boolean isDrawerVisible(int drawerGravity) {
+    public boolean isDrawerVisible(@EdgeGravity int drawerGravity) {
         final View drawerView = findDrawerWithGravity(drawerGravity);
         if (drawerView != null) {
             return isDrawerVisible(drawerView);
@@ -1336,6 +1394,35 @@
         return ss;
     }
 
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        // Until a drawer is open, it is hidden from accessibility.
+        if (index > 0 || (index < 0 && getChildCount() > 0)) {
+            ViewCompat.setImportantForAccessibility(child,
+                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+            // Also set a delegate to break the child-parent relation if the
+            // child is hidden. For details (see incluceChildForAccessibility).
+            ViewCompat.setAccessibilityDelegate(child, mChildAccessibilityDelegate);
+        } else  {
+            // Initially, the content is shown for accessibility.
+            ViewCompat.setImportantForAccessibility(child,
+                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+        }
+        super.addView(child, index, params);
+    }
+
+    private static boolean includeChildForAccessibilitiy(View child) {
+        // If the child is not important for accessibility we make
+        // sure this hides the entire subtree rooted at it as the
+        // IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDATS is not
+        // supported on older platforms but we want to hide the entire
+        // content and not opened drawers if a drawer is opened.
+        return ViewCompat.getImportantForAccessibility(child)
+                != ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                    && ViewCompat.getImportantForAccessibility(child)
+                != ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO;
+    }
+
     /**
      * State persisted across instances
      */
@@ -1633,31 +1720,8 @@
             final int childCount = v.getChildCount();
             for (int i = 0; i < childCount; i++) {
                 final View child = v.getChildAt(i);
-                if (filter(child)) {
-                    continue;
-                }
-
-                // Adding children that are marked as not important for
-                // accessibility will break the hierarchy, so we need to check
-                // that value and re-parent views if necessary.
-                final int importance = ViewCompat.getImportantForAccessibility(child);
-                switch (importance) {
-                    case ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS:
-                        // Always skip NO_HIDE views and their descendants.
-                        break;
-                    case ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO:
-                        // Re-parent children of NO view groups, skip NO views.
-                        if (child instanceof ViewGroup) {
-                            addChildrenForAccessibility(info, (ViewGroup) child);
-                        }
-                        break;
-                    case ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO:
-                        // Force AUTO views to YES and add them.
-                        ViewCompat.setImportantForAccessibility(
-                                child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
-                    case ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES:
-                        info.addChild(child);
-                        break;
+                if (includeChildForAccessibilitiy(child)) {
+                    info.addChild(child);
                 }
             }
         }
@@ -1665,17 +1729,12 @@
         @Override
         public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
                 AccessibilityEvent event) {
-            if (!filter(child)) {
+            if (includeChildForAccessibilitiy(child)) {
                 return super.onRequestSendAccessibilityEvent(host, child, event);
             }
             return false;
         }
 
-        public boolean filter(View child) {
-            final View openDrawer = findOpenDrawer();
-            return openDrawer != null && openDrawer != child;
-        }
-
         /**
          * This should really be in AccessibilityNodeInfoCompat, but there unfortunately
          * seem to be a few elements that are not easily cloneable using the underlying API.
@@ -1707,4 +1766,18 @@
             dest.addAction(src.getActions());
         }
     }
+
+    final class ChildAccessibilityDelegate extends AccessibilityDelegateCompat {
+        @Override
+        public void onInitializeAccessibilityNodeInfo(View child,
+                AccessibilityNodeInfoCompat info) {
+            super.onInitializeAccessibilityNodeInfo(child, info);
+            if (!includeChildForAccessibilitiy(child)) {
+                // If we are ignoring the sub-tree rooted at the child,
+                // break the connection to the rest of the node tree.
+                // For details refer to includeChildForAccessibilitiy.
+                info.setParent(null);
+            }
+        }
+    }
 }
diff --git a/v4/java/android/support/v4/widget/ExploreByTouchHelper.java b/v4/java/android/support/v4/widget/ExploreByTouchHelper.java
index b894399..7adbc6f 100644
--- a/v4/java/android/support/v4/widget/ExploreByTouchHelper.java
+++ b/v4/java/android/support/v4/widget/ExploreByTouchHelper.java
@@ -577,7 +577,8 @@
      * @param x The view-relative x coordinate
      * @param y The view-relative y coordinate
      * @return virtual view identifier for the logical item under
-     *         coordinates (x,y)
+     *         coordinates (x,y) or {@link View#NO_ID} if there is no item at
+     *         the given coordinates
      */
     protected abstract int getVirtualViewAt(float x, float y);
 
diff --git a/v4/java/android/support/v4/widget/ListViewAutoScrollHelper.java b/v4/java/android/support/v4/widget/ListViewAutoScrollHelper.java
index 0b3b30d..b23ca23 100644
--- a/v4/java/android/support/v4/widget/ListViewAutoScrollHelper.java
+++ b/v4/java/android/support/v4/widget/ListViewAutoScrollHelper.java
@@ -59,6 +59,10 @@
     public boolean canTargetScrollVertically(int direction) {
         final ListView target = mTarget;
         final int itemCount = target.getCount();
+        if (itemCount == 0) {
+            return false;
+        }
+
         final int childCount = target.getChildCount();
         final int firstPosition = target.getFirstVisiblePosition();
         final int lastPosition = firstPosition + childCount;
diff --git a/v4/java/android/support/v4/widget/MaterialProgressDrawable.java b/v4/java/android/support/v4/widget/MaterialProgressDrawable.java
new file mode 100644
index 0000000..f34c420
--- /dev/null
+++ b/v4/java/android/support/v4/widget/MaterialProgressDrawable.java
@@ -0,0 +1,717 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.widget;
+
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.Animation;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.Transformation;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Paint.Cap;
+import android.graphics.Paint.Style;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Animatable;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.util.DisplayMetrics;
+import android.view.View;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * Fancy progress indicator for Material theme.
+ */
+class MaterialProgressDrawable extends Drawable implements Animatable {
+    private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
+    private static final Interpolator END_CURVE_INTERPOLATOR = new EndCurveInterpolator();
+    private static final Interpolator START_CURVE_INTERPOLATOR = new StartCurveInterpolator();
+
+    @Retention(RetentionPolicy.CLASS)
+    @IntDef({LARGE, DEFAULT, SMALL })
+    public @interface ProgressDrawableSize {}
+    // Maps to ProgressBar.Large style
+    static final int LARGE = 0;
+    // Maps to ProgressBar default style
+    static final int DEFAULT = 1;
+    // Maps to ProgressBar.Small style
+    static final int SMALL = 2;
+
+    // Maps to ProgressBar default style
+    private static final int CIRCLE_DIAMETER = 40;
+    private static final int INNER_RADIUS = 10;
+    private static final int STROKE_WIDTH = 4;
+
+    // Maps to ProgressBar.Large style
+    private static final int CIRCLE_DIAMETER_LARGE = 76;
+    private static final float INNER_RADIUS_LARGE = 15.5f;
+    private static final float STROKE_WIDTH_LARGE = 6.3f;
+
+    // Maps to ProgressBar.Small style
+    private static final int CIRCLE_DIAMETER_SMALL = 16;
+    private static final float INNER_RADIUS_SMALL = 3.15f;
+    private static final float STROKE_WIDTH_SMALL = 1.3f;
+
+    private final int[] COLORS = new int[] {
+        Color.BLACK
+    };
+
+    /** The duration of a single progress spin in milliseconds. */
+    private static final int ANIMATION_DURATION = 1000 * 80 / 60;
+
+    /** The number of points in the progress "star". */
+    private static final float NUM_POINTS = 5f;
+    /** The list of animators operating on this drawable. */
+    private final ArrayList<Animation> mAnimators = new ArrayList<Animation>();
+
+    /** The indicator ring, used to manage animation state. */
+    private final Ring mRing;
+
+    /** Canvas rotation in degrees. */
+    private float mRotation;
+
+    /** Layout info for the arrowhead in dp */
+    private static final int ARROW_WIDTH = 10;
+    private static final int ARROW_HEIGHT = 5;
+    private static final float ARROW_OFFSET_ANGLE = 5;
+
+    private Resources mResources;
+    private int mColorIndex;
+    private View mParent;
+    private Animation mAnimation;
+    private float mRotationCount;
+    private int[] mColors;
+    private double mWidth;
+    private double mHeight;
+    private double mInnerRadius;
+    private double mStrokeWidth;
+    private Animation mFinishAnimation;
+
+    public MaterialProgressDrawable(Context context, View parent) {
+        mParent = parent;
+        mResources = context.getResources();
+
+        mRing = new Ring(mCallback);
+        mColors = COLORS;
+        mRing.setColors(mColors);
+
+        initialize(CIRCLE_DIAMETER, CIRCLE_DIAMETER, INNER_RADIUS, STROKE_WIDTH);
+        setupAnimators();
+    }
+
+    private void initialize(double progressCircleWidth, double progressCircleHeight,
+            double innerRadius, double strokeWidth) {
+        final Ring ring = mRing;
+        final DisplayMetrics metrics = mResources.getDisplayMetrics();
+        final float screenDensity = metrics.density;
+
+        mWidth = progressCircleWidth * screenDensity;
+        mHeight = progressCircleHeight * screenDensity;
+        mInnerRadius = innerRadius * screenDensity;
+        mStrokeWidth = strokeWidth * screenDensity;
+        ring.setStrokeWidth((float) mStrokeWidth);
+        ring.setInnerRadius(mInnerRadius);
+        ring.setDensity(screenDensity);
+        ring.setColorIndex(0);
+
+        final float minEdge = (float) Math.min(mWidth, mHeight);
+        if (mInnerRadius <= 0 || minEdge < 0) {
+            ring.setInsets((int) Math.ceil(mStrokeWidth / 2.0f));
+        } else {
+            float insets = (float) (minEdge / 2.0f - mInnerRadius);
+            ring.setInsets(insets);
+        }
+    }
+
+    /**
+     * Set the overall size for the progress spinner. This updates the radius
+     * and stroke width of the ring.
+     *
+     * @param size One of {@link MaterialProgressDrawable.LARGE},
+     *            {@link MaterialProgressDrawable.DEFAULT}, or
+     *            {@link MaterialProgressDrawable.SMALL}.
+     */
+    public void updateSizes(@ProgressDrawableSize int size) {
+        final DisplayMetrics metrics = mResources.getDisplayMetrics();
+        final float screenDensity = metrics.density;
+        int progressCircleWidth;
+        int progressCircleHeight;
+        float innerRadius;
+        float strokeWidth;
+
+        if (size == LARGE) {
+            progressCircleWidth = progressCircleHeight = CIRCLE_DIAMETER_LARGE;
+            innerRadius = INNER_RADIUS_LARGE;
+            strokeWidth = STROKE_WIDTH_LARGE;
+        } else if (size == SMALL) {
+            progressCircleWidth = progressCircleHeight = CIRCLE_DIAMETER_SMALL;
+            innerRadius = INNER_RADIUS_SMALL;
+            strokeWidth = STROKE_WIDTH_SMALL;
+        } else {
+            progressCircleWidth = progressCircleHeight = CIRCLE_DIAMETER;
+            innerRadius = INNER_RADIUS;
+            strokeWidth = STROKE_WIDTH;
+        }
+        mWidth = progressCircleWidth * screenDensity;
+        mHeight = progressCircleHeight * screenDensity;
+        mInnerRadius = innerRadius * screenDensity;
+        mStrokeWidth = strokeWidth * screenDensity;
+    }
+
+    /**
+     * @param show Set to true to display the arrowhead on the progress spinner.
+     */
+    public void showArrow(boolean show) {
+        mRing.setShowArrow(show);
+    }
+
+    /**
+     * @param scale Set the scale of the arrowhead for the spinner.
+     */
+    public void setArrowScale(float scale) {
+        mRing.setArrowScale(scale);
+    }
+
+    /**
+     * Set the start and end trim for the progress spinner arc.
+     *
+     * @param startAngle start angle
+     * @param endAngle end angle
+     */
+    public void setStartEndTrim(float startAngle, float endAngle) {
+        mRing.setStartTrim(startAngle);
+        mRing.setEndTrim(endAngle);
+    }
+
+    /**
+     * Set the amount of rotation to apply to the progress spinner.
+     *
+     * @param rotation Rotation in degrees
+     */
+    public void setProgressRotation(float rotation) {
+        mRing.setRotation(rotation);
+    }
+
+    /**
+     * Set the colors used in the progress animation from color resources.
+     * The first color will also be the color of the bar that grows in response
+     * to a user swipe gesture.
+     *
+     * @param colors
+     */
+    public void setColorSchemeColors(int... colors) {
+        mColors = colors;
+        mRing.setColors(mColors);
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return (int) mHeight;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return (int) mWidth;
+    }
+
+    @Override
+    public void draw(Canvas c) {
+        final Rect bounds = getBounds();
+        final int saveCount = c.save();
+        c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY());
+        mRing.draw(c, bounds);
+        c.restoreToCount(saveCount);
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mRing.setAlpha(alpha);
+    }
+
+    public int getAlpha() {
+        return mRing.getAlpha();
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mRing.setColorFilter(colorFilter);
+    }
+
+    @SuppressWarnings("unused")
+    private void setRotation(float rotation) {
+        mRotation = rotation;
+        invalidateSelf();
+    }
+
+    @SuppressWarnings("unused")
+    private float getRotation() {
+        return mRotation;
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    @Override
+    public boolean isRunning() {
+        final ArrayList<Animation> animators = mAnimators;
+        final int N = animators.size();
+        for (int i = 0; i < N; i++) {
+            final Animation animator = animators.get(i);
+            if (animator.hasStarted() && !animator.hasEnded()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void start() {
+        mAnimation.reset();
+        mRing.storeOriginals();
+        if (mRing.getStartingStartTrim() != 0) {
+            mParent.startAnimation(mFinishAnimation);
+        } else {
+            mColorIndex = 0;
+            mRing.setColorIndex(mColorIndex);
+            mRing.resetOriginals();
+            mParent.startAnimation(mAnimation);
+        }
+    }
+
+    @Override
+    public void stop() {
+        mParent.clearAnimation();
+        setRotation(0);
+        mColorIndex = 0;
+        mRing.setColorIndex(mColorIndex);
+        mRing.resetOriginals();
+    }
+
+    private void setupAnimators() {
+        final Ring ring = mRing;
+        final Animation finishRingAnimation = new Animation() {
+            public void applyTransformation(float interpolatedTime, Transformation t) {
+                // shrink back down and complete a full roation before starting other circles
+                float targetRotation = (float) (Math.floor(ring.getStartingRotation() / .75f) + 1f);
+                final float startTrim = ring.getStartingEndTrim()
+                        + (ring.getStartingStartTrim() - ring.getStartingEndTrim())
+                        * interpolatedTime;
+                ring.setEndTrim(startTrim);
+                final float rotation = ring.getStartingRotation()
+                        + ((targetRotation - ring.getStartingRotation()) * interpolatedTime);
+                ring.setRotation(rotation);
+                ring.setArrowScale(1 - interpolatedTime);
+            }
+        };
+        finishRingAnimation.setInterpolator(LINEAR_INTERPOLATOR);
+        finishRingAnimation.setDuration(ANIMATION_DURATION / 2);
+        finishRingAnimation.setAnimationListener(new Animation.AnimationListener() {
+
+            @Override
+            public void onAnimationStart(Animation animation) {
+            }
+
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                mColorIndex = (mColorIndex + 1) % (mColors.length);
+                ring.setColorIndex(mColorIndex);
+                ring.resetOriginals();
+                mParent.startAnimation(mAnimation);
+                ring.setShowArrow(false);
+            }
+
+            @Override
+            public void onAnimationRepeat(Animation animation) {
+            }
+        });
+        final Animation animation = new Animation() {
+            @Override
+            public void applyTransformation(float interpolatedTime, Transformation t) {
+                final float endTrim =
+                        0.75f * START_CURVE_INTERPOLATOR
+                                .getInterpolation(interpolatedTime);
+                ring.setEndTrim(endTrim);
+                final float startTrim = 0.75f * END_CURVE_INTERPOLATOR
+                                .getInterpolation(interpolatedTime);
+                ring.setStartTrim(startTrim);
+                final float rotation = 0.25f * interpolatedTime;
+                ring.setRotation(rotation);
+                float groupRotation = ((720.0f / NUM_POINTS) * interpolatedTime)
+                        + (720.0f * (mRotationCount / NUM_POINTS));
+                setRotation(groupRotation);
+            }
+        };
+        animation.setRepeatCount(Animation.INFINITE);
+        animation.setRepeatMode(Animation.RESTART);
+        animation.setInterpolator(LINEAR_INTERPOLATOR);
+        animation.setDuration(ANIMATION_DURATION);
+        animation.setAnimationListener(new Animation.AnimationListener() {
+
+            @Override
+            public void onAnimationStart(Animation animation) {
+                mRotationCount = 0;
+            }
+
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                // do nothing
+            }
+
+            @Override
+            public void onAnimationRepeat(Animation animation) {
+                mColorIndex = (mColorIndex + 1) % (mColors.length);
+                ring.setColorIndex(mColorIndex);
+                ring.resetOriginals();
+                mRotationCount = (mRotationCount + 1) % (NUM_POINTS);
+            }
+        });
+        mFinishAnimation = finishRingAnimation;
+        mAnimation = animation;
+    }
+
+    private final Callback mCallback = new Callback() {
+        @Override
+        public void invalidateDrawable(Drawable d) {
+            invalidateSelf();
+        }
+
+        @Override
+        public void scheduleDrawable(Drawable d, Runnable what, long when) {
+            scheduleSelf(what, when);
+        }
+
+        @Override
+        public void unscheduleDrawable(Drawable d, Runnable what) {
+            unscheduleSelf(what);
+        }
+    };
+
+    private static class Ring {
+        private final RectF mTempBounds = new RectF();
+        private final Paint mPaint = new Paint();
+        private final Paint mArrowPaint = new Paint();
+
+        private final Callback mCallback;
+
+        private float mStartTrim = 0.0f;
+        private float mEndTrim = 0.0f;
+        private float mRotation = 0.0f;
+        private float mStrokeWidth = 5.0f;
+        private float mStrokeInset = 2.5f;
+
+        private int[] mColors;
+        private int mColorIndex;
+        private float mStartingStartTrim;
+        private float mStartingEndTrim;
+        private float mStartingRotation;
+        private boolean mShowArrow;
+        private Path mArrow;
+        private float mArrowScale;
+        private double mRingInnerRadius;
+        private Path mArrowCopy;
+        private int mArrowWidth;
+        private int mArrowHeight;
+        private float mDensity;
+        private Matrix mArrowScaleMatrix;
+
+        public Ring(Callback callback) {
+            mCallback = callback;
+
+            mPaint.setStrokeCap(Cap.ROUND);
+            mPaint.setAntiAlias(true);
+            mPaint.setStyle(Style.STROKE);
+
+            mArrowPaint.setStrokeWidth(4);
+            mArrowPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+            mArrowPaint.setAntiAlias(true);
+        }
+
+        /**
+         * Draw the progress spinner
+         */
+        public void draw(Canvas c, Rect bounds) {
+            final RectF arcBounds = mTempBounds;
+            arcBounds.set(bounds);
+            arcBounds.inset(mStrokeInset, mStrokeInset);
+
+            final float startAngle = (mStartTrim + mRotation) * 360;
+            final float endAngle = (mEndTrim + mRotation) * 360;
+            float sweepAngle = endAngle - startAngle;
+
+            // Ensure the sweep angle isn't too small to draw.
+            final float diameter = Math.min(arcBounds.width(), arcBounds.height());
+            final float minAngle = (float) (360.0 / (diameter * Math.PI));
+            if (sweepAngle < minAngle && sweepAngle > -minAngle) {
+                sweepAngle = Math.signum(sweepAngle) * minAngle;
+            }
+            mPaint.setColor(mColors[mColorIndex]);
+            c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint);
+
+            if (mArrow == null) {
+                mArrowWidth = (int) (ARROW_WIDTH * mDensity);
+                mArrowHeight = (int) (ARROW_HEIGHT * mDensity);
+
+                // Adjust the position of the triangle so that it is inset as much as the arc, but
+                // also centered on the arc.
+                int inset = (int) (mStrokeInset/2 + (mArrowWidth / mStrokeWidth));
+                double rad = Math.toRadians(startAngle + sweepAngle);
+                float x = (float) (mRingInnerRadius * Math.cos(rad) + bounds.exactCenterX());
+                float y = (float) (mRingInnerRadius * Math.sin(rad) + bounds.exactCenterY());
+                Point a = new Point((int) (x - inset), (int) (y));
+                Point b = new Point((int) (x - inset) + mArrowWidth, (int) (y));
+                Point cPoint = new Point((int) (x - inset) + (mArrowWidth / 2),
+                        (int) (y) + mArrowHeight);
+
+                mArrow = new android.graphics.Path();
+                mArrow.setFillType(android.graphics.Path.FillType.EVEN_ODD);
+                mArrow.moveTo(a.x, a.y);
+                mArrow.lineTo(b.x, b.y);
+                mArrow.lineTo(cPoint.x, cPoint.y);
+                mArrow.lineTo(a.x, a.y);
+                mArrow.close();
+                mArrowCopy = new Path();
+            }
+
+            if (mShowArrow) {
+                // draw a triangle
+                if (mArrowScaleMatrix == null) {
+                    mArrowScaleMatrix = new Matrix();
+                }
+                Matrix scaleMatrix = mArrowScaleMatrix;
+                RectF arrowRect = mTempBounds;
+                mArrow.computeBounds(arrowRect, true);
+                scaleMatrix.setScale(mArrowScale, mArrowScale, arrowRect.centerX(),
+                        arrowRect.centerY());
+                mArrow.transform(scaleMatrix, mArrowCopy);
+                mArrowPaint.setColor(mColors[mColorIndex]);
+                // Offset the arrow slightly so that it aligns with the cap on the arc
+                c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(),
+                        bounds.exactCenterY());
+                c.drawPath(mArrowCopy, mArrowPaint);
+            }
+        }
+
+        /**
+         * Set the colors the progress spinner alternates between.
+         *
+         * @param colors Array of integers describing the colors. Must be non-<code>null</code>.
+         */
+        public void setColors(@NonNull int[] colors) {
+            mColors = colors;
+        }
+
+        /**
+         * @param index Index into the color array of the color to display in
+         *            the progress spinner.
+         */
+        public void setColorIndex(int index) {
+            mColorIndex = index;
+        }
+
+        public void setColorFilter(ColorFilter filter) {
+            mPaint.setColorFilter(filter);
+            invalidateSelf();
+        }
+
+        /**
+         * @param alpha Set the alpha of the progress spinner and associated arrowhead.
+         */
+        public void setAlpha(int alpha) {
+            final int oldAlpha = mPaint.getAlpha();
+            if (alpha != oldAlpha) {
+                mPaint.setAlpha(alpha);
+                invalidateSelf();
+            }
+        }
+
+        /**
+         * @return Current alpha of the progress spinner and arrowhead.
+         */
+        public int getAlpha() {
+            return mPaint.getAlpha();
+        }
+
+        /**
+         * @param strokeWidth Set the stroke width of the progress spinner in pixels.
+         */
+        public void setStrokeWidth(float strokeWidth) {
+            mStrokeWidth = strokeWidth;
+            mPaint.setStrokeWidth(strokeWidth);
+            invalidateSelf();
+        }
+
+        @SuppressWarnings("unused")
+        public float getStrokeWidth() {
+            return mStrokeWidth;
+        }
+
+        @SuppressWarnings("unused")
+        public void setStartTrim(float startTrim) {
+            mStartTrim = startTrim;
+            invalidateSelf();
+        }
+
+        @SuppressWarnings("unused")
+        public float getStartTrim() {
+            return mStartTrim;
+        }
+
+        public float getStartingStartTrim() {
+            return mStartingStartTrim;
+        }
+
+        public float getStartingEndTrim() {
+            return mStartingEndTrim;
+        }
+
+        @SuppressWarnings("unused")
+        public void setEndTrim(float endTrim) {
+            mEndTrim = endTrim;
+            invalidateSelf();
+        }
+
+        @SuppressWarnings("unused")
+        public float getEndTrim() {
+            return mEndTrim;
+        }
+
+        @SuppressWarnings("unused")
+        public void setRotation(float rotation) {
+            mRotation = rotation;
+            invalidateSelf();
+        }
+
+        @SuppressWarnings("unused")
+        public float getRotation() {
+            return mRotation;
+        }
+
+        public void setInsets(float insets) {
+            mStrokeInset = insets;
+        }
+
+        @SuppressWarnings("unused")
+        public float getInsets() {
+            return mStrokeInset;
+        }
+
+
+        /**
+         * @param screenDensity Logical screen density on which the progress
+         *            spinner is drawn.
+         */
+        public void setDensity(float screenDensity) {
+            mDensity = screenDensity;
+        }
+
+        /**
+         * @param innerRadius Inner radius in px of the circle the progress
+         *            spinner arc traces.
+         */
+        public void setInnerRadius(double innerRadius) {
+            mRingInnerRadius = innerRadius;
+        }
+
+        /**
+         * @param show Set to true to show the arrow head on the progress spinner.
+         */
+        public void setShowArrow(boolean show) {
+            if (mShowArrow != show) {
+                mShowArrow = show;
+                invalidateSelf();
+            }
+        }
+
+        /**
+         * @param scale Set the scale of the arrowhead for the spinner.
+         */
+        public void setArrowScale(float scale) {
+            if (scale != mArrowScale) {
+                mArrowScale = scale;
+                invalidateSelf();
+            }
+        }
+
+        /**
+         * @return The amount in degrees the progress spinner is currently rotated.
+         */
+        public float getStartingRotation() {
+            return mStartingRotation;
+        }
+
+        /**
+         * If the start / end trim are offset to begin with, store them so that
+         * animation starts from that offset.
+         */
+        public void storeOriginals() {
+            mStartingStartTrim = mStartTrim;
+            mStartingEndTrim = mEndTrim;
+            mStartingRotation = mRotation;
+        }
+
+        /**
+         * Reset the progress spinner to default rotation, start and end angles.
+         */
+        public void resetOriginals() {
+            mStartingStartTrim = 0;
+            mStartingEndTrim = 0;
+            mStartingRotation = 0;
+            setStartTrim(0);
+            setEndTrim(0);
+            setRotation(0);
+        }
+
+        private void invalidateSelf() {
+            mCallback.invalidateDrawable(null);
+        }
+    }
+
+    /**
+     * Squishes the interpolation curve into the second half of the animation.
+     */
+    private static class EndCurveInterpolator extends AccelerateDecelerateInterpolator {
+        @Override
+        public float getInterpolation(float input) {
+            return super.getInterpolation(Math.max(0, (input - 0.5f) * 2.0f));
+        }
+    }
+
+    /**
+     * Squishes the interpolation curve into the first half of the animation.
+     */
+    private static class StartCurveInterpolator extends AccelerateDecelerateInterpolator {
+        @Override
+        public float getInterpolation(float input) {
+            return super.getInterpolation(Math.min(1, input * 2.0f));
+        }
+    }
+}
diff --git a/v4/java/android/support/v4/widget/PopupWindowCompat.java b/v4/java/android/support/v4/widget/PopupWindowCompat.java
new file mode 100644
index 0000000..be96a73
--- /dev/null
+++ b/v4/java/android/support/v4/widget/PopupWindowCompat.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.widget;
+
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.widget.PopupWindow;
+
+/**
+ * Helper for accessing features in PopupWindow introduced after API level 4
+ * in a backwards compatible fashion.
+ */
+public class PopupWindowCompat {
+    /**
+     * Interface for the full API.
+     */
+    interface PopupWindowImpl {
+        public void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff,
+                int gravity);
+    }
+
+    /**
+     * Interface implementation that doesn't use anything above v4 APIs.
+     */
+    static class BasePopupWindowImpl implements PopupWindowImpl {
+        @Override
+        public void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff,
+                int gravity) {
+            popup.showAsDropDown(anchor, xoff, yoff);
+        }
+    }
+
+    /**
+     * Interface implementation for devices with at least KitKat APIs.
+     */
+    static class KitKatPopupWindowImpl extends BasePopupWindowImpl {
+        @Override
+        public void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff,
+                int gravity) {
+            PopupWindowCompatKitKat.showAsDropDown(popup, anchor, xoff, yoff, gravity);
+        }
+    }
+
+    /**
+     * Select the correct implementation to use for the current platform.
+     */
+    static final PopupWindowImpl IMPL;
+    static {
+        final int version = android.os.Build.VERSION.SDK_INT;
+        if (version >= 19) {
+            IMPL = new KitKatPopupWindowImpl();
+        } else {
+            IMPL = new BasePopupWindowImpl();
+        }
+    }
+
+    private PopupWindowCompat() {
+        // This class is not publicly instantiable.
+    }
+
+    /**
+     * <p>Display the content view in a popup window anchored to the bottom-left
+     * corner of the anchor view offset by the specified x and y coordinates.
+     * If there is not enough room on screen to show
+     * the popup in its entirety, this method tries to find a parent scroll
+     * view to scroll. If no parent scroll view can be scrolled, the bottom-left
+     * corner of the popup is pinned at the top left corner of the anchor view.</p>
+     * <p>If the view later scrolls to move <code>anchor</code> to a different
+     * location, the popup will be moved correspondingly.</p>
+     *
+     * @param popup the PopupWindow to show
+     * @param anchor the view on which to pin the popup window
+     * @param xoff A horizontal offset from the anchor in pixels
+     * @param yoff A vertical offset from the anchor in pixels
+     * @param gravity Alignment of the popup relative to the anchor
+     */
+    public static void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff,
+            int gravity) {
+        IMPL.showAsDropDown(popup, anchor, xoff, yoff, gravity);
+    }
+}
diff --git a/v4/java/android/support/v4/widget/ScrollerCompat.java b/v4/java/android/support/v4/widget/ScrollerCompat.java
index fec045a..afbf897 100644
--- a/v4/java/android/support/v4/widget/ScrollerCompat.java
+++ b/v4/java/android/support/v4/widget/ScrollerCompat.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.os.Build;
+import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.Scroller;
 
@@ -29,7 +30,10 @@
  * the APIs from Scroller or OverScroller.</p>
  */
 public class ScrollerCompat {
+    private static final String TAG = "ScrollerCompat";
+
     Object mScroller;
+    ScrollerCompatImpl mImpl;
 
     interface ScrollerCompatImpl {
         Object createScroller(Context context, Interpolator interpolator);
@@ -52,6 +56,8 @@
         int getFinalY(Object scroller);
     }
 
+    static final int CHASE_FRAME_TIME = 16; // ms per target frame
+
     static class ScrollerCompatImplBase implements ScrollerCompatImpl {
         @Override
         public Object createScroller(Context context, Interpolator interpolator) {
@@ -81,7 +87,8 @@
 
         @Override
         public boolean computeScrollOffset(Object scroller) {
-            return ((Scroller) scroller).computeScrollOffset();
+            final Scroller s = (Scroller) scroller;
+            return s.computeScrollOffset();
         }
 
         @Override
@@ -235,18 +242,6 @@
         }
     }
 
-    static final ScrollerCompatImpl IMPL;
-    static {
-        final int version = Build.VERSION.SDK_INT;
-        if (version >= 14) { // ICS
-            IMPL = new ScrollerCompatImplIcs();
-        } else if (version >= 9) { // Gingerbread
-            IMPL = new ScrollerCompatImplGingerbread();
-        } else {
-            IMPL = new ScrollerCompatImplBase();
-        }
-    }
-
     public static ScrollerCompat create(Context context) {
         return create(context, null);
     }
@@ -256,7 +251,23 @@
     }
 
     ScrollerCompat(Context context, Interpolator interpolator) {
-        mScroller = IMPL.createScroller(context, interpolator);
+        this(Build.VERSION.SDK_INT, context, interpolator);
+
+    }
+
+    /**
+     * Private constructer where API version can be provided.
+     * Useful for unit testing.
+     */
+    private ScrollerCompat(int apiVersion, Context context, Interpolator interpolator) {
+        if (apiVersion >= 14) { // ICS
+            mImpl = new ScrollerCompatImplIcs();
+        } else if (apiVersion>= 9) { // Gingerbread
+            mImpl = new ScrollerCompatImplGingerbread();
+        } else {
+            mImpl = new ScrollerCompatImplBase();
+        }
+        mScroller = mImpl.createScroller(context, interpolator);
     }
 
     /**
@@ -265,7 +276,7 @@
      * @return True if the scroller has finished scrolling, false otherwise.
      */
     public boolean isFinished() {
-        return IMPL.isFinished(mScroller);
+        return mImpl.isFinished(mScroller);
     }
 
     /**
@@ -274,7 +285,7 @@
      * @return The new X offset as an absolute distance from the origin.
      */
     public int getCurrX() {
-        return IMPL.getCurrX(mScroller);
+        return mImpl.getCurrX(mScroller);
     }
 
     /**
@@ -283,21 +294,21 @@
      * @return The new Y offset as an absolute distance from the origin.
      */
     public int getCurrY() {
-        return IMPL.getCurrY(mScroller);
+        return mImpl.getCurrY(mScroller);
     }
 
     /**
      * @return The final X position for the scroll in progress, if known.
      */
     public int getFinalX() {
-        return IMPL.getFinalX(mScroller);
+        return mImpl.getFinalX(mScroller);
     }
 
     /**
      * @return The final Y position for the scroll in progress, if known.
      */
     public int getFinalY() {
-        return IMPL.getFinalY(mScroller);
+        return mImpl.getFinalY(mScroller);
     }
 
     /**
@@ -311,7 +322,7 @@
      * negative.
      */
     public float getCurrVelocity() {
-        return IMPL.getCurrVelocity(mScroller);
+        return mImpl.getCurrVelocity(mScroller);
     }
 
     /**
@@ -320,7 +331,7 @@
      * new location.
      */
     public boolean computeScrollOffset() {
-        return IMPL.computeScrollOffset(mScroller);
+        return mImpl.computeScrollOffset(mScroller);
     }
 
     /**
@@ -338,7 +349,7 @@
      *        content up.
      */
     public void startScroll(int startX, int startY, int dx, int dy) {
-        IMPL.startScroll(mScroller, startX, startY, dx, dy);
+        mImpl.startScroll(mScroller, startX, startY, dx, dy);
     }
 
     /**
@@ -355,7 +366,7 @@
      * @param duration Duration of the scroll in milliseconds.
      */
     public void startScroll(int startX, int startY, int dx, int dy, int duration) {
-        IMPL.startScroll(mScroller, startX, startY, dx, dy, duration);
+        mImpl.startScroll(mScroller, startX, startY, dx, dy, duration);
     }
 
     /**
@@ -379,7 +390,7 @@
      */
     public void fling(int startX, int startY, int velocityX, int velocityY,
             int minX, int maxX, int minY, int maxY) {
-        IMPL.fling(mScroller, startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
+        mImpl.fling(mScroller, startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
     }
 
     /**
@@ -407,7 +418,7 @@
      */
     public void fling(int startX, int startY, int velocityX, int velocityY,
             int minX, int maxX, int minY, int maxY, int overX, int overY) {
-        IMPL.fling(mScroller, startX, startY, velocityX, velocityY,
+        mImpl.fling(mScroller, startX, startY, velocityX, velocityY,
                 minX, maxX, minY, maxY, overX, overY);
     }
 
@@ -416,7 +427,7 @@
      * position.
      */
     public void abortAnimation() {
-        IMPL.abortAnimation(mScroller);
+        mImpl.abortAnimation(mScroller);
     }
 
 
@@ -434,7 +445,7 @@
      *              desired distance from finalX. Absolute value - must be positive.
      */
     public void notifyHorizontalEdgeReached(int startX, int finalX, int overX) {
-        IMPL.notifyHorizontalEdgeReached(mScroller, startX, finalX, overX);
+        mImpl.notifyHorizontalEdgeReached(mScroller, startX, finalX, overX);
     }
 
     /**
@@ -451,7 +462,7 @@
      *              desired distance from finalY. Absolute value - must be positive.
      */
     public void notifyVerticalEdgeReached(int startY, int finalY, int overY) {
-        IMPL.notifyVerticalEdgeReached(mScroller, startY, finalY, overY);
+        mImpl.notifyVerticalEdgeReached(mScroller, startY, finalY, overY);
     }
 
     /**
@@ -468,6 +479,6 @@
      *         interpolating back to a valid value.
      */
     public boolean isOverScrolled() {
-        return IMPL.isOverScrolled(mScroller);
+        return mImpl.isOverScrolled(mScroller);
     }
 }
diff --git a/v4/java/android/support/v4/widget/SlidingPaneLayout.java b/v4/java/android/support/v4/widget/SlidingPaneLayout.java
index 1c23994..391ba99 100644
--- a/v4/java/android/support/v4/widget/SlidingPaneLayout.java
+++ b/v4/java/android/support/v4/widget/SlidingPaneLayout.java
@@ -29,6 +29,7 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.annotation.DrawableRes;
 import android.support.v4.view.AccessibilityDelegateCompat;
 import android.support.v4.view.MotionEventCompat;
 import android.support.v4.view.ViewCompat;
@@ -117,9 +118,14 @@
     private int mCoveredFadeColor;
 
     /**
-     * Drawable used to draw the shadow between panes.
+     * Drawable used to draw the shadow between panes by default.
      */
-    private Drawable mShadowDrawable;
+    private Drawable mShadowDrawableLeft;
+
+    /**
+     * Drawable used to draw the shadow between panes to support RTL (right to left language).
+     */
+    private Drawable mShadowDrawableRight;
 
     /**
      * The size of the overhang in pixels.
@@ -262,7 +268,6 @@
         ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
 
         mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback());
-        mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
         mDragHelper.setMinVelocity(MIN_FLING_VELOCITY * density);
     }
 
@@ -345,8 +350,11 @@
     }
 
     void updateObscuredViewsVisibility(View panel) {
-        final int leftBound = getPaddingLeft();
-        final int rightBound = getWidth() - getPaddingRight();
+        final boolean isLayoutRtl = isLayoutRtlSupport();
+        final int startBound = isLayoutRtl ? (getWidth() - getPaddingRight()) :
+            getPaddingLeft();
+        final int endBound = isLayoutRtl ? getPaddingLeft() :
+            (getWidth() - getPaddingRight());
         final int topBound = getPaddingTop();
         final int bottomBound = getHeight() - getPaddingBottom();
         final int left;
@@ -370,9 +378,11 @@
                 break;
             }
 
-            final int clampedChildLeft = Math.max(leftBound, child.getLeft());
+            final int clampedChildLeft = Math.max((isLayoutRtl ? endBound :
+                startBound), child.getLeft());
             final int clampedChildTop = Math.max(topBound, child.getTop());
-            final int clampedChildRight = Math.min(rightBound, child.getRight());
+            final int clampedChildRight = Math.min((isLayoutRtl ? startBound :
+                endBound), child.getRight());
             final int clampedChildBottom = Math.min(bottomBound, child.getBottom());
             final int vis;
             if (clampedChildLeft >= left && clampedChildTop >= top &&
@@ -641,14 +651,19 @@
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
-
+        final boolean isLayoutRtl = isLayoutRtlSupport();
+        if (isLayoutRtl) {
+            mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
+        } else {
+            mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
+        }
         final int width = r - l;
-        final int paddingLeft = getPaddingLeft();
-        final int paddingRight = getPaddingRight();
+        final int paddingStart = isLayoutRtl ? getPaddingRight() : getPaddingLeft();
+        final int paddingEnd = isLayoutRtl ? getPaddingLeft() : getPaddingRight();
         final int paddingTop = getPaddingTop();
 
         final int childCount = getChildCount();
-        int xStart = paddingLeft;
+        int xStart = paddingStart;
         int nextXStart = xStart;
 
         if (mFirstLayout) {
@@ -670,12 +685,13 @@
             if (lp.slideable) {
                 final int margin = lp.leftMargin + lp.rightMargin;
                 final int range = Math.min(nextXStart,
-                        width - paddingRight - mOverhangSize) - xStart - margin;
+                        width - paddingEnd - mOverhangSize) - xStart - margin;
                 mSlideRange = range;
-                lp.dimWhenOffset = xStart + lp.leftMargin + range + childWidth / 2 >
-                        width - paddingRight;
+                final int lpMargin = isLayoutRtl ? lp.rightMargin : lp.leftMargin;
+                lp.dimWhenOffset = xStart + lpMargin + range + childWidth / 2 >
+                        width - paddingEnd;
                 final int pos = (int) (range * mSlideOffset);
-                xStart += pos + lp.leftMargin;
+                xStart += pos + lpMargin;
                 mSlideOffset = (float) pos / mSlideRange;
             } else if (mCanSlide && mParallaxBy != 0) {
                 offset = (int) ((1 - mSlideOffset) * mParallaxBy);
@@ -684,8 +700,16 @@
                 xStart = nextXStart;
             }
 
-            final int childLeft = xStart - offset;
-            final int childRight = childLeft + childWidth;
+            final int childRight;
+            final int childLeft;
+            if (isLayoutRtl) {
+                childRight = width - xStart + offset;
+                childLeft = childRight - childWidth;
+            } else {
+                childLeft = xStart - offset;
+                childRight = childLeft + childWidth;
+            }
+
             final int childTop = paddingTop;
             final int childBottom = childTop + child.getMeasuredHeight();
             child.layout(childLeft, paddingTop, childRight, childBottom);
@@ -918,11 +942,17 @@
             mSlideOffset = 0;
             return;
         }
-
+        final boolean isLayoutRtl = isLayoutRtlSupport();
         final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
-        final int leftBound = getPaddingLeft() + lp.leftMargin;
 
-        mSlideOffset = (float) (newLeft - leftBound) / mSlideRange;
+        int childWidth = mSlideableView.getWidth();
+        final int newStart = isLayoutRtl ? getWidth() - newLeft - childWidth : newLeft;
+
+        final int paddingStart = isLayoutRtl ? getPaddingRight() : getPaddingLeft();
+        final int lpMargin = isLayoutRtl ? lp.rightMargin : lp.leftMargin;
+        final int startBound = paddingStart + lpMargin;
+
+        mSlideOffset = (float) (newStart - startBound) / mSlideRange;
 
         if (mParallaxBy != 0) {
             parallaxOtherViews(mSlideOffset);
@@ -968,7 +998,11 @@
         if (mCanSlide && !lp.slideable && mSlideableView != null) {
             // Clip against the slider; no sense drawing what will immediately be covered.
             canvas.getClipBounds(mTmpRect);
-            mTmpRect.right = Math.min(mTmpRect.right, mSlideableView.getLeft());
+            if (isLayoutRtlSupport()) {
+                mTmpRect.left = Math.max(mTmpRect.left, mSlideableView.getRight());
+            } else {
+                mTmpRect.right = Math.min(mTmpRect.right, mSlideableView.getLeft());
+            }
             canvas.clipRect(mTmpRect);
         }
 
@@ -1016,10 +1050,18 @@
             return false;
         }
 
+        final boolean isLayoutRtl = isLayoutRtlSupport();
         final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
 
-        final int leftBound = getPaddingLeft() + lp.leftMargin;
-        int x = (int) (leftBound + slideOffset * mSlideRange);
+        int x;
+        if (isLayoutRtl) {
+            int startBound = getPaddingRight() + lp.rightMargin;
+            int childWidth = mSlideableView.getWidth();
+            x = (int) (getWidth() - (startBound + slideOffset * mSlideRange + childWidth));
+        } else {
+            int startBound = getPaddingLeft() + lp.leftMargin;
+            x = (int) (startBound + slideOffset * mSlideRange);
+        }
 
         if (mDragHelper.smoothSlideViewTo(mSlideableView, x, mSlideableView.getTop())) {
             setAllChildrenVisible();
@@ -1042,13 +1084,35 @@
     }
 
     /**
+     * @deprecated Renamed to {@link #setShadowDrawableLeft(Drawable d)} to support LTR (left to
+     * right language) and {@link #setShadowDrawableRight(Drawable d)} to support RTL (right to left
+     * language) during opening/closing.
+     *
+     * @param d drawable to use as a shadow
+     */
+    @Deprecated
+    public void setShadowDrawable(Drawable d) {
+        setShadowDrawableLeft(d);
+    }
+
+    /**
      * Set a drawable to use as a shadow cast by the right pane onto the left pane
      * during opening/closing.
      *
      * @param d drawable to use as a shadow
      */
-    public void setShadowDrawable(Drawable d) {
-        mShadowDrawable = d;
+    public void setShadowDrawableLeft(Drawable d) {
+        mShadowDrawableLeft = d;
+    }
+
+    /**
+     * Set a drawable to use as a shadow cast by the left pane onto the right pane
+     * during opening/closing to support right to left language.
+     *
+     * @param d drawable to use as a shadow
+     */
+    public void setShadowDrawableRight(Drawable d) {
+        mShadowDrawableRight = d;
     }
 
     /**
@@ -1057,32 +1121,72 @@
      *
      * @param resId Resource ID of a drawable to use
      */
-    public void setShadowResource(int resId) {
+    @Deprecated
+    public void setShadowResource(@DrawableRes int resId) {
         setShadowDrawable(getResources().getDrawable(resId));
     }
 
+    /**
+     * Set a drawable to use as a shadow cast by the right pane onto the left pane
+     * during opening/closing.
+     *
+     * @param resId Resource ID of a drawable to use
+     */
+    public void setShadowResourceLeft(int resId) {
+        setShadowDrawableLeft(getResources().getDrawable(resId));
+    }
+
+    /**
+     * Set a drawable to use as a shadow cast by the left pane onto the right pane
+     * during opening/closing to support right to left language.
+     *
+     * @param resId Resource ID of a drawable to use
+     */
+    public void setShadowResourceRight(int resId) {
+        setShadowDrawableRight(getResources().getDrawable(resId));
+    }
+
+
     @Override
     public void draw(Canvas c) {
         super.draw(c);
+        final boolean isLayoutRtl = isLayoutRtlSupport();
+        Drawable shadowDrawable;
+        if (isLayoutRtl) {
+            shadowDrawable = mShadowDrawableRight;
+        } else {
+            shadowDrawable = mShadowDrawableLeft;
+        }
 
         final View shadowView = getChildCount() > 1 ? getChildAt(1) : null;
-        if (shadowView == null || mShadowDrawable == null) {
+        if (shadowView == null || shadowDrawable == null) {
             // No need to draw a shadow if we don't have one.
             return;
         }
 
-        final int shadowWidth = mShadowDrawable.getIntrinsicWidth();
-        final int right = shadowView.getLeft();
         final int top = shadowView.getTop();
         final int bottom = shadowView.getBottom();
-        final int left = right - shadowWidth;
-        mShadowDrawable.setBounds(left, top, right, bottom);
-        mShadowDrawable.draw(c);
+
+        final int shadowWidth = shadowDrawable.getIntrinsicWidth();
+        final int left;
+        final int right;
+        if (isLayoutRtlSupport()) {
+            left = shadowView.getRight();
+            right = left + shadowWidth;
+        } else {
+            right = shadowView.getLeft();
+            left = right - shadowWidth;
+        }
+
+        shadowDrawable.setBounds(left, top, right, bottom);
+        shadowDrawable.draw(c);
     }
 
     private void parallaxOtherViews(float slideOffset) {
+        final boolean isLayoutRtl = isLayoutRtlSupport();
         final LayoutParams slideLp = (LayoutParams) mSlideableView.getLayoutParams();
-        final boolean dimViews = slideLp.dimWhenOffset && slideLp.leftMargin <= 0;
+        final boolean dimViews = slideLp.dimWhenOffset &&
+                (isLayoutRtl ? slideLp.rightMargin : slideLp.leftMargin) <= 0;
         final int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             final View v = getChildAt(i);
@@ -1093,10 +1197,11 @@
             final int newOffset = (int) ((1 - slideOffset) * mParallaxBy);
             final int dx = oldOffset - newOffset;
 
-            v.offsetLeftAndRight(dx);
+            v.offsetLeftAndRight(isLayoutRtl ? -dx : dx);
 
             if (dimViews) {
-                dimChildView(v, 1 - mParallaxOffset, mCoveredFadeColor);
+                dimChildView(v, isLayoutRtl ? mParallaxOffset - 1 :
+                    1 - mParallaxOffset, mCoveredFadeColor);
             }
         }
     }
@@ -1132,7 +1237,7 @@
             }
         }
 
-        return checkV && ViewCompat.canScrollHorizontally(v, -dx);
+        return checkV && ViewCompat.canScrollHorizontally(v, (isLayoutRtlSupport() ? dx : -dx));
     }
 
     boolean isDimmed(View child) {
@@ -1228,9 +1333,20 @@
         @Override
         public void onViewReleased(View releasedChild, float xvel, float yvel) {
             final LayoutParams lp = (LayoutParams) releasedChild.getLayoutParams();
-            int left = getPaddingLeft() + lp.leftMargin;
-            if (xvel > 0 || (xvel == 0 && mSlideOffset > 0.5f)) {
-                left += mSlideRange;
+
+            int left;
+            if (isLayoutRtlSupport()) {
+                int startToRight =  getPaddingRight() + lp.rightMargin;
+                if (xvel < 0 || (xvel == 0 && mSlideOffset > 0.5f)) {
+                    startToRight += mSlideRange;
+                }
+                int childWidth = mSlideableView.getWidth();
+                left = getWidth() - startToRight - childWidth;
+            } else {
+                left = getPaddingLeft() + lp.leftMargin;
+                if (xvel > 0 || (xvel == 0 && mSlideOffset > 0.5f)) {
+                    left += mSlideRange;
+                }
             }
             mDragHelper.settleCapturedViewAt(left, releasedChild.getTop());
             invalidate();
@@ -1244,11 +1360,18 @@
         @Override
         public int clampViewPositionHorizontal(View child, int left, int dx) {
             final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
-            final int leftBound = getPaddingLeft() + lp.leftMargin;
-            final int rightBound = leftBound + mSlideRange;
 
-            final int newLeft = Math.min(Math.max(left, leftBound), rightBound);
-
+            final int newLeft;
+            if (isLayoutRtlSupport()) {
+                int startBound = getWidth() -
+                        (getPaddingRight() + lp.rightMargin + mSlideableView.getWidth());
+                int endBound =  startBound - mSlideRange;
+                newLeft = Math.max(Math.min(left, startBound), endBound);
+            } else {
+                int startBound = getPaddingLeft() + lp.leftMargin;
+                int endBound = startBound + mSlideRange;
+                newLeft = Math.min(Math.max(left, startBound), endBound);
+            }
             return newLeft;
         }
 
@@ -1514,4 +1637,8 @@
             mPostedRunnables.remove(this);
         }
     }
+
+    private boolean isLayoutRtlSupport() {
+        return ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
+    }
 }
diff --git a/v4/java/android/support/v4/widget/SwipeProgressBar.java b/v4/java/android/support/v4/widget/SwipeProgressBar.java
index b3b04cd..ba60d89 100644
--- a/v4/java/android/support/v4/widget/SwipeProgressBar.java
+++ b/v4/java/android/support/v4/widget/SwipeProgressBar.java
@@ -99,7 +99,8 @@
     void setTriggerPercentage(float triggerPercentage) {
         mTriggerPercentage = triggerPercentage;
         mStartTime = 0;
-        ViewCompat.postInvalidateOnAnimation(mParent);
+        ViewCompat.postInvalidateOnAnimation(
+                mParent, mBounds.left, mBounds.top, mBounds.right, mBounds.bottom);
     }
 
     /**
@@ -227,7 +228,8 @@
                 drawTrigger(canvas, cx, cy);
             }
             // Keep running until we finish out the last cycle.
-            ViewCompat.postInvalidateOnAnimation(mParent);
+            ViewCompat.postInvalidateOnAnimation(
+                    mParent, mBounds.left, mBounds.top, mBounds.right, mBounds.bottom);
         } else {
             // Otherwise if we're in the middle of a trigger, draw that.
             if (mTriggerPercentage > 0 && mTriggerPercentage <= 1.0) {
diff --git a/v4/java/android/support/v4/widget/SwipeRefreshLayout.java b/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
index ca68acd..7b24d57 100644
--- a/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
+++ b/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
@@ -19,22 +19,21 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.graphics.Canvas;
+import android.support.v4.view.MotionEventCompat;
 import android.support.v4.view.ViewCompat;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
-import android.view.animation.AccelerateInterpolator;
 import android.view.animation.Animation;
 import android.view.animation.Animation.AnimationListener;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Transformation;
 import android.widget.AbsListView;
 
-
 /**
  * The SwipeRefreshLayout should be used whenever the user can refresh the
  * contents of a view via a vertical swipe gesture. The activity that
@@ -45,123 +44,173 @@
  * initiate a refresh of its content. If the listener determines there should
  * not be a refresh, it must call setRefreshing(false) to cancel any visual
  * indication of a refresh. If an activity wishes to show just the progress
- * animation, it should call setRefreshing(true). To disable the gesture and progress
- * animation, call setEnabled(false) on the view.
- *
- * <p> This layout should be made the parent of the view that will be refreshed as a
+ * animation, it should call setRefreshing(true). To disable the gesture and
+ * progress animation, call setEnabled(false) on the view.
+ * <p>
+ * This layout should be made the parent of the view that will be refreshed as a
  * result of the gesture and can only support one direct child. This view will
  * also be made the target of the gesture and will be forced to match both the
  * width and the height supplied in this layout. The SwipeRefreshLayout does not
  * provide accessibility events; instead, a menu item must be provided to allow
- * refresh of the content wherever this gesture is used.</p>
+ * refresh of the content wherever this gesture is used.
+ * </p>
  */
 public class SwipeRefreshLayout extends ViewGroup {
-    private static final long RETURN_TO_ORIGINAL_POSITION_TIMEOUT = 300;
-    private static final float ACCELERATE_INTERPOLATION_FACTOR = 1.5f;
-    private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
-    private static final float PROGRESS_BAR_HEIGHT = 4;
-    private static final float MAX_SWIPE_DISTANCE_FACTOR = .6f;
-    private static final int REFRESH_TRIGGER_DISTANCE = 120;
+    // Maps to ProgressBar.Large style
+    public static final int LARGE = MaterialProgressDrawable.LARGE;
+    // Maps to ProgressBar default style
+    public static final int DEFAULT = MaterialProgressDrawable.DEFAULT;
+    // Maps to ProgressBar.Small style
+    public static final int SMALL = MaterialProgressDrawable.SMALL;
 
-    private SwipeProgressBar mProgressBar; //the thing that shows progress is going
-    private View mTarget; //the content that gets pulled down
-    private int mOriginalOffsetTop;
+    private static final String LOG_TAG = SwipeRefreshLayout.class.getSimpleName();
+
+    private static final int MAX_ALPHA = 255;
+
+    private static final int CIRCLE_DIAMETER = 40;
+    private static final int CIRCLE_DIAMETER_LARGE = 96;
+    private static final int CIRCLE_DIAMETER_SMALL = 16;
+
+    private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
+    private static final int INVALID_POINTER = -1;
+    private static final float DRAG_RATE = .5f;
+    private static final int MAX_OVERSWIPE = 80;
+
+    // Default background for the progress spinner
+    private static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA;
+    // Default offset in dips from the top of the view to where the progress spinner should stop
+    private static final int DEFAULT_CIRCLE_TARGET = 64;
+
+    private View mTarget; // the target of the gesture
     private OnRefreshListener mListener;
-    private MotionEvent mDownEvent;
-    private int mFrom;
     private boolean mRefreshing = false;
     private int mTouchSlop;
     private float mDistanceToTriggerSync = -1;
-    private float mPrevY;
     private int mMediumAnimationDuration;
-    private float mFromPercentage = 0;
-    private float mCurrPercentage = 0;
-    private int mProgressBarHeight;
+    private int mShortAnimationDuration;
     private int mCurrentTargetOffsetTop;
+
+    private float mInitialMotionY;
+    private boolean mIsBeingDragged;
+    private int mActivePointerId = INVALID_POINTER;
+    // Whether this item is scaled up rather than clipped
+    private boolean mScale;
+
     // Target is returning to its start offset because it was cancelled or a
     // refresh was triggered.
     private boolean mReturningToStart;
     private final DecelerateInterpolator mDecelerateInterpolator;
-    private final AccelerateInterpolator mAccelerateInterpolator;
     private static final int[] LAYOUT_ATTRS = new int[] {
         android.R.attr.enabled
     };
 
-    private final Animation mAnimateToStartPosition = new Animation() {
-        @Override
-        public void applyTransformation(float interpolatedTime, Transformation t) {
-            int targetTop = 0;
-            if (mFrom != mOriginalOffsetTop) {
-                targetTop = (mFrom + (int)((mOriginalOffsetTop - mFrom) * interpolatedTime));
-            }
-            int offset = targetTop - mTarget.getTop();
-            final int currentTop = mTarget.getTop();
-            if (offset + currentTop < 0) {
-                offset = 0 - currentTop;
-            }
-            setTargetOffsetTopAndBottom(offset);
-        }
-    };
+    private CircleImageView mCircleView;
 
-    private Animation mShrinkTrigger = new Animation() {
-        @Override
-        public void applyTransformation(float interpolatedTime, Transformation t) {
-            float percent = mFromPercentage + ((0 - mFromPercentage) * interpolatedTime);
-            mProgressBar.setTriggerPercentage(percent);
-        }
-    };
+    protected int mFrom;
 
-    private final AnimationListener mReturnToStartPositionListener = new BaseAnimationListener() {
+    protected int mOriginalOffsetTop;
+
+    private MaterialProgressDrawable mProgress;
+
+    private Animation mScaleAnimation;
+
+    private Animation mScaleDownAnimation;
+
+    private float mLastY;
+
+    private float mTargetCirclePosition;
+
+    private boolean mNotify;
+
+    private int mCircleWidth;
+
+    private int mCircleHeight;
+
+    private Animation.AnimationListener mRefreshListener = new Animation.AnimationListener() {
+        @Override
+        public void onAnimationStart(Animation animation) {
+        }
+
+        @Override
+        public void onAnimationRepeat(Animation animation) {
+        }
+
         @Override
         public void onAnimationEnd(Animation animation) {
-            // Once the target content has returned to its start position, reset
-            // the target offset to 0
-            mCurrentTargetOffsetTop = 0;
-        }
-    };
-
-    private final AnimationListener mShrinkAnimationListener = new BaseAnimationListener() {
-        @Override
-        public void onAnimationEnd(Animation animation) {
-            mCurrPercentage = 0;
-        }
-    };
-
-    private final Runnable mReturnToStartPosition = new Runnable() {
-
-        @Override
-        public void run() {
-            mReturningToStart = true;
-            animateOffsetToStartPosition(mCurrentTargetOffsetTop + getPaddingTop(),
-                    mReturnToStartPositionListener);
-        }
-
-    };
-
-    // Cancel the refresh gesture and animate everything back to its original state.
-    private final Runnable mCancel = new Runnable() {
-
-        @Override
-        public void run() {
-            mReturningToStart = true;
-            // Timeout fired since the user last moved their finger; animate the
-            // trigger to 0 and put the target back at its original position
-            if (mProgressBar != null) {
-                mFromPercentage = mCurrPercentage;
-                mShrinkTrigger.setDuration(mMediumAnimationDuration);
-                mShrinkTrigger.setAnimationListener(mShrinkAnimationListener);
-                mShrinkTrigger.reset();
-                mShrinkTrigger.setInterpolator(mDecelerateInterpolator);
-                startAnimation(mShrinkTrigger);
+            if (mRefreshing) {
+                mProgress.start();
+                if (mNotify) {
+                    if (mListener != null) {
+                        mListener.onRefresh();
+                    }
+                }
+            } else {
+                mProgress.stop();
+                mCircleView.setVisibility(View.GONE);
+                // Return the circle to its start position
+                if (mScale) {
+                    setAnimationProgress(0 /* animation complete and view is hidden */);
+                } else {
+                    setAnimationProgress(1 /* animation complete and view is showing */);
+                    setTargetOffsetTopAndBottom(-mCircleHeight - mCurrentTargetOffsetTop,
+                            true /* requires update */);
+                    mCircleView.setVisibility(View.VISIBLE);
+                }
             }
-            animateOffsetToStartPosition(mCurrentTargetOffsetTop + getPaddingTop(),
-                    mReturnToStartPositionListener);
+            mCurrentTargetOffsetTop = mCircleView.getTop();
         }
-
     };
 
+    private void setColorViewAlpha(int targetAlpha) {
+        mCircleView.getBackground().setAlpha(targetAlpha);
+        mProgress.setAlpha(targetAlpha);
+    }
+
+    /**
+     * The refresh indicator starting and resting position is always positioned
+     * near the top of the refreshing content. This position is a consistent
+     * location, but can be adjusted in either direction based on whether or not
+     * there is a toolbar or actionbar present.
+     *
+     * @param scale Set to true if there is no view at a higher z-order than
+     *            where the progress spinner is set to appear.
+     * @param start The offset in pixels from the top of this view at which the
+     *            progress spinner should appear.
+     * @param end The offset in pixels from the top of this view at which the
+     *            progress spinner should come to rest after a successful swipe
+     *            gesture.
+     */
+    public void setProgressViewOffset(boolean scale, int start, int end) {
+        mScale = scale;
+        mCircleView.setVisibility(View.GONE);
+        mOriginalOffsetTop = mCurrentTargetOffsetTop = start;
+        mDistanceToTriggerSync = (mTargetCirclePosition / DRAG_RATE);
+        mTargetCirclePosition = end;
+        mCircleView.invalidate();
+    }
+
+    /**
+     * One of SMALL, DEFAULT, or LARGE. DEFAULT is ProgressBar default styling.
+     */
+    public void setSize(int size) {
+        if (size != MaterialProgressDrawable.LARGE && size != MaterialProgressDrawable.DEFAULT
+                && size != MaterialProgressDrawable.SMALL) {
+            return;
+        }
+        final DisplayMetrics metrics = getResources().getDisplayMetrics();
+        if (size == MaterialProgressDrawable.LARGE) {
+            mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER_LARGE * metrics.density);
+        } else if (size == MaterialProgressDrawable.SMALL) {
+            mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER_SMALL * metrics.density);
+        } else {
+            mCircleHeight = mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density);
+        }
+        mProgress.updateSizes(size);
+    }
+
     /**
      * Simple constructor to use when creating a SwipeRefreshLayout from code.
+     *
      * @param context
      */
     public SwipeRefreshLayout(Context context) {
@@ -170,6 +219,7 @@
 
     /**
      * Constructor that is called when inflating SwipeRefreshLayout from XML.
+     *
      * @param context
      * @param attrs
      */
@@ -180,40 +230,42 @@
 
         mMediumAnimationDuration = getResources().getInteger(
                 android.R.integer.config_mediumAnimTime);
+        mShortAnimationDuration = getResources().getInteger(
+                android.R.integer.config_shortAnimTime);
 
         setWillNotDraw(false);
-        mProgressBar = new SwipeProgressBar(this);
-        final DisplayMetrics metrics = getResources().getDisplayMetrics();
-        mProgressBarHeight = (int) (metrics.density * PROGRESS_BAR_HEIGHT);
         mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR);
-        mAccelerateInterpolator = new AccelerateInterpolator(ACCELERATE_INTERPOLATION_FACTOR);
 
         final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
         setEnabled(a.getBoolean(0, true));
         a.recycle();
+
+        final DisplayMetrics metrics = getResources().getDisplayMetrics();
+        mCircleWidth = (int) (CIRCLE_DIAMETER * metrics.density);
+        mCircleHeight = (int) (CIRCLE_DIAMETER * metrics.density);
+
+        mTargetCirclePosition = DEFAULT_CIRCLE_TARGET * metrics.density;
+        // Since the circle moves at 1/2 rate of drag, and we want the circle to
+        // end at 64, and its starts -Diameter offscreen, it needs to move overall this
+        mDistanceToTriggerSync = (mTargetCirclePosition / DRAG_RATE)
+                + (mCircleWidth / DRAG_RATE);
+        createProgressView();
+        ViewCompat.setChildrenDrawingOrderEnabled(this, true);
     }
 
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        removeCallbacks(mCancel);
-        removeCallbacks(mReturnToStartPosition);
+    protected int getChildDrawingOrder (int childCount, int i) {
+        if (getChildAt(i).equals(mCircleView)) {
+            return childCount - 1;
+        }
+        return i;
     }
 
-    @Override
-    public void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        removeCallbacks(mReturnToStartPosition);
-        removeCallbacks(mCancel);
-    }
-
-    private void animateOffsetToStartPosition(int from, AnimationListener listener) {
-        mFrom = from;
-        mAnimateToStartPosition.reset();
-        mAnimateToStartPosition.setDuration(mMediumAnimationDuration);
-        mAnimateToStartPosition.setAnimationListener(listener);
-        mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator);
-        mTarget.startAnimation(mAnimateToStartPosition);
+    private void createProgressView() {
+        mCircleView = new CircleImageView(getContext(), CIRCLE_BG_LIGHT, CIRCLE_DIAMETER/2);
+        mProgress = new MaterialProgressDrawable(getContext(), this);
+        mCircleView.setImageDrawable(mProgress);
+        addView(mCircleView);
+        mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleWidth;
     }
 
     /**
@@ -224,17 +276,6 @@
         mListener = listener;
     }
 
-    private void setTriggerPercentage(float percent) {
-        if (percent == 0f) {
-            // No-op. A null trigger means it's uninitialized, and setting it to zero-percent
-            // means we're trying to reset state, so there's nothing to reset in this case.
-            mCurrPercentage = 0;
-            return;
-        }
-        mCurrPercentage = percent;
-        mProgressBar.setTriggerPercentage(percent);
-    }
-
     /**
      * Notify the widget that refresh state has changed. Do not call this when
      * refresh is triggered by a swipe gesture.
@@ -242,36 +283,118 @@
      * @param refreshing Whether or not the view should show refresh progress.
      */
     public void setRefreshing(boolean refreshing) {
+        if (refreshing && mRefreshing != refreshing) {
+            // scale and show
+            mRefreshing = refreshing;
+            setTargetOffsetTopAndBottom((int) (mTargetCirclePosition - mCircleView.getTop()),
+                    true /* requires update */);
+            mNotify = false;
+            startScaleUpAnimation(mRefreshListener);
+        } else {
+            setRefreshing(refreshing, false /* notify */);
+        }
+    }
+
+    private void startScaleUpAnimation(AnimationListener listener) {
+        mCircleView.setVisibility(View.VISIBLE);
+        mScaleAnimation = new Animation() {
+            @Override
+            public void applyTransformation(float interpolatedTime, Transformation t) {
+                setAnimationProgress(interpolatedTime);
+            }
+        };
+        mScaleAnimation.setDuration(mMediumAnimationDuration);
+        if (listener != null) {
+            mCircleView.setAnimationListener(listener);
+        }
+        mCircleView.clearAnimation();
+        mCircleView.startAnimation(mScaleAnimation);
+    }
+
+    /**
+     * Pre API 11, this does an alpha animation.
+     * @param progress
+     */
+    private void setAnimationProgress(float progress) {
+        if (android.os.Build.VERSION.SDK_INT < 11) {
+            setColorViewAlpha((int) (progress * MAX_ALPHA));
+        } else {
+            ViewCompat.setScaleX(mCircleView, progress);
+            ViewCompat.setScaleY(mCircleView, progress);
+        }
+    }
+
+    private void setRefreshing(boolean refreshing, final boolean notify) {
         if (mRefreshing != refreshing) {
+            mNotify = notify;
             ensureTarget();
-            mCurrPercentage = 0;
             mRefreshing = refreshing;
             if (mRefreshing) {
-                mProgressBar.start();
+                animateOffsetToCorrectPosition(mCurrentTargetOffsetTop, mRefreshListener);
             } else {
-                mProgressBar.stop();
+                startScaleDownAnimation(mRefreshListener);
             }
         }
     }
 
+    private void startScaleDownAnimation(Animation.AnimationListener listener) {
+        mScaleDownAnimation = new Animation() {
+            @Override
+            public void applyTransformation(float interpolatedTime, Transformation t) {
+                setAnimationProgress(1 - interpolatedTime);
+            }
+        };
+        mScaleDownAnimation.setDuration(mShortAnimationDuration);
+        if (listener != null) {
+            mCircleView.setAnimationListener(listener);
+        }
+        mCircleView.clearAnimation();
+        mCircleView.startAnimation(mScaleDownAnimation);
+    }
+
     /**
-     * Set the four colors used in the progress animation. The first color will
-     * also be the color of the bar that grows in response to a user swipe
-     * gesture.
+     * Set the background color of the progress spinner disc.
      *
-     * @param colorRes1 Color resource.
-     * @param colorRes2 Color resource.
-     * @param colorRes3 Color resource.
-     * @param colorRes4 Color resource.
+     * @param colorRes Resource id of the color.
      */
-    public void setColorScheme(int colorRes1, int colorRes2, int colorRes3, int colorRes4) {
-        ensureTarget();
+    public void setProgressBackgroundColor(int colorRes) {
+        mCircleView.setBackgroundColor(colorRes);
+    }
+
+    /**
+     * @deprecated Use {@link #setColorSchemeResources(int...)}
+     */
+    @Deprecated
+    public void setColorScheme(int... colors) {
+        setColorSchemeResources(colors);
+    }
+
+    /**
+     * Set the color resources used in the progress animation from color resources.
+     * The first color will also be the color of the bar that grows in response
+     * to a user swipe gesture.
+     *
+     * @param colorResIds
+     */
+    public void setColorSchemeResources(int... colorResIds) {
         final Resources res = getResources();
-        final int color1 = res.getColor(colorRes1);
-        final int color2 = res.getColor(colorRes2);
-        final int color3 = res.getColor(colorRes3);
-        final int color4 = res.getColor(colorRes4);
-        mProgressBar.setColorScheme(color1, color2, color3,color4);
+        int[] colorRes = new int[colorResIds.length];
+        for (int i = 0; i < colorResIds.length; i++) {
+            colorRes[i] = res.getColor(colorResIds[i]);
+        }
+        setColorSchemeColors(colorRes);
+    }
+
+    /**
+     * Set the colors used in the progress animation. The first
+     * color will also be the color of the bar that grows in response to a user
+     * swipe gesture.
+     *
+     * @param colors
+     */
+    public void setColorSchemeColors(int... colors) {
+        ensureTarget();
+        mProgress.setColorSchemeColors(colors);
     }
 
     /**
@@ -283,62 +406,66 @@
     }
 
     private void ensureTarget() {
-        // Don't bother getting the parent height if the parent hasn't been laid out yet.
+        // Don't bother getting the parent height if the parent hasn't been laid
+        // out yet.
         if (mTarget == null) {
-            if (getChildCount() > 1 && !isInEditMode()) {
-                throw new IllegalStateException(
-                        "SwipeRefreshLayout can host only one direct child");
-            }
-            mTarget = getChildAt(0);
-            mOriginalOffsetTop = mTarget.getTop() + getPaddingTop();
-        }
-        if (mDistanceToTriggerSync == -1) {
-            if (getParent() != null && ((View)getParent()).getHeight() > 0) {
-                final DisplayMetrics metrics = getResources().getDisplayMetrics();
-                mDistanceToTriggerSync = (int) Math.min(
-                        ((View) getParent()) .getHeight() * MAX_SWIPE_DISTANCE_FACTOR,
-                                REFRESH_TRIGGER_DISTANCE * metrics.density);
+            for (int i = 0; i < getChildCount(); i++) {
+                View child = getChildAt(i);
+                if (!child.equals(mCircleView)) {
+                    mTarget = child;
+                    break;
+                }
             }
         }
     }
 
-    @Override
-    public void draw(Canvas canvas) {
-        super.draw(canvas);
-        mProgressBar.draw(canvas);
+    /**
+     * Set the distance to trigger a sync in dips
+     *
+     * @param distance
+     */
+    public void setDistanceToTriggerSync(int distance) {
+        mDistanceToTriggerSync = distance;
     }
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        final int width =  getMeasuredWidth();
+        final int width = getMeasuredWidth();
         final int height = getMeasuredHeight();
-        mProgressBar.setBounds(0, 0, width, mProgressBarHeight);
         if (getChildCount() == 0) {
             return;
         }
-        final View child = getChildAt(0);
+        if (mTarget == null) {
+            ensureTarget();
+        }
+        if (mTarget == null) {
+            return;
+        }
+        final View child = mTarget;
         final int childLeft = getPaddingLeft();
-        final int childTop = mCurrentTargetOffsetTop + getPaddingTop();
+        final int childTop = getPaddingTop();
         final int childWidth = width - getPaddingLeft() - getPaddingRight();
         final int childHeight = height - getPaddingTop() - getPaddingBottom();
         child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
+        mCircleView.layout((width / 2 - mCircleWidth / 2), mCurrentTargetOffsetTop,
+                (width / 2 + mCircleWidth / 2), mCurrentTargetOffsetTop + mCircleHeight);
     }
 
     @Override
     public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        if (getChildCount() > 1 && !isInEditMode()) {
-            throw new IllegalStateException("SwipeRefreshLayout can host only one direct child");
+        if (mTarget == null) {
+            ensureTarget();
         }
-        if (getChildCount() > 0) {
-            getChildAt(0).measure(
-                    MeasureSpec.makeMeasureSpec(
-                            getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
-                            MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(
-                            getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
-                            MeasureSpec.EXACTLY));
+        if (mTarget == null) {
+            return;
         }
+        mTarget.measure(MeasureSpec.makeMeasureSpec(
+                getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
+                MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
+                getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY));
+        mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleWidth, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(mCircleHeight, MeasureSpec.EXACTLY));
     }
 
     /**
@@ -363,14 +490,57 @@
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         ensureTarget();
-        boolean handled = false;
-        if (mReturningToStart && ev.getAction() == MotionEvent.ACTION_DOWN) {
+
+        final int action = MotionEventCompat.getActionMasked(ev);
+
+        if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
             mReturningToStart = false;
         }
-        if (isEnabled() && !mReturningToStart && !canChildScrollUp()) {
-            handled = onTouchEvent(ev);
+
+        if (!isEnabled() || mReturningToStart || canChildScrollUp() || mRefreshing) {
+            // Fail fast if we're not in a state where a swipe is possible
+            return false;
         }
-        return !handled ? super.onInterceptTouchEvent(ev) : handled;
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mLastY = mInitialMotionY = ev.getY();
+                setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true);
+                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
+                mIsBeingDragged = false;
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                if (mActivePointerId == INVALID_POINTER) {
+                    Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
+                    return false;
+                }
+
+                final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
+                if (pointerIndex < 0) {
+                    Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
+                    return false;
+                }
+
+                final float y = MotionEventCompat.getY(ev, pointerIndex);
+                final float yDiff = y - mInitialMotionY;
+                if (yDiff > mTouchSlop) {
+                    mIsBeingDragged = true;
+                }
+                break;
+
+            case MotionEventCompat.ACTION_POINTER_UP:
+                onSecondaryPointerUp(ev);
+                break;
+
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                mIsBeingDragged = false;
+                mActivePointerId = INVALID_POINTER;
+                break;
+        }
+
+        return mIsBeingDragged;
     }
 
     @Override
@@ -379,86 +549,184 @@
     }
 
     @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        final int action = event.getAction();
-        boolean handled = false;
+    public boolean onTouchEvent(MotionEvent ev) {
+        final int action = MotionEventCompat.getActionMasked(ev);
+
+        if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
+            mReturningToStart = false;
+        }
+
+        if (!isEnabled() || mReturningToStart || canChildScrollUp()) {
+            // Fail fast if we're not in a state where a swipe is possible
+            return false;
+        }
+
         switch (action) {
             case MotionEvent.ACTION_DOWN:
-                mCurrPercentage = 0;
-                mDownEvent = MotionEvent.obtain(event);
-                mPrevY = mDownEvent.getY();
+                mLastY = mInitialMotionY = ev.getY();
+                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
+                mIsBeingDragged = false;
                 break;
+
             case MotionEvent.ACTION_MOVE:
-                if (mDownEvent != null && !mReturningToStart) {
-                    final float eventY = event.getY();
-                    float yDiff = eventY - mDownEvent.getY();
-                    if (yDiff > mTouchSlop) {
-                        // User velocity passed min velocity; trigger a refresh
-                        if (yDiff > mDistanceToTriggerSync) {
-                            // User movement passed distance; trigger a refresh
-                            startRefresh();
-                            handled = true;
-                            break;
+                final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
+                if (pointerIndex < 0) {
+                    Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
+                    return false;
+                }
+
+                final float y = MotionEventCompat.getY(ev, pointerIndex);
+                final float yDiff = y - mInitialMotionY;
+
+                if (!mIsBeingDragged && yDiff > mTouchSlop) {
+                    mIsBeingDragged = true;
+                }
+
+                if (mIsBeingDragged) {
+                    // User velocity passed min velocity; trigger a refresh
+                    if (yDiff > mDistanceToTriggerSync) {
+                        // Continue updating circle, but at a reduced rate
+                        float p = Math.max(0,
+                                Math.min((yDiff - mDistanceToTriggerSync), MAX_OVERSWIPE)
+                                        / MAX_OVERSWIPE / 2);
+                        double q = (p - Math.pow(p, 2));
+                        double extraDrag = (2 * MAX_OVERSWIPE * q);
+                        if (mTargetCirclePosition + extraDrag
+                                <= (mTargetCirclePosition + MAX_OVERSWIPE)) {
+                            setTargetOffsetTopAndBottom(
+                                    (int) (mTargetCirclePosition + extraDrag
+                                            - mCurrentTargetOffsetTop),
+                                    true /* requires update */);
+                        }
+                        if (mScale || mCurrentTargetOffsetTop > -mCircleHeight / 2) {
+                            // start showing the arrow
+                            float pullPercent = yDiff / mDistanceToTriggerSync;
+                            mProgress.setProgressRotation((float) (Math.PI / 4 * pullPercent));
+                            mProgress.setArrowScale(1f);
+                        }
+                    } else {
+                        if (mCircleView.getVisibility() != View.VISIBLE) {
+                            mCircleView.setVisibility(View.VISIBLE);
+                        }
+                        mProgress.showArrow(true);
+                        if (mScale) {
+                            setAnimationProgress(yDiff / mDistanceToTriggerSync);
                         } else {
-                            // Just track the user's movement
-                            setTriggerPercentage(
-                                    mAccelerateInterpolator.getInterpolation(
-                                            yDiff / mDistanceToTriggerSync));
-                            float offsetTop = yDiff;
-                            if (mPrevY > eventY) {
-                                offsetTop = yDiff - mTouchSlop;
-                            }
-                            updateContentOffsetTop((int) (offsetTop));
-                            if (mPrevY > eventY && (mTarget.getTop() < mTouchSlop)) {
-                                // If the user puts the view back at the top, we
-                                // don't need to. This shouldn't be considered
-                                // cancelling the gesture as the user can restart from the top.
-                                removeCallbacks(mCancel);
-                            } else {
-                                updatePositionTimeout();
-                            }
-                            mPrevY = event.getY();
-                            handled = true;
+                            setColorViewAlpha((int) (MAX_ALPHA));
+                        }
+                        // Just track the user's movement
+                        setTargetOffsetTopAndBottom((int) ((y - mLastY) * DRAG_RATE),
+                                true /* requires update */);
+                        if (mScale || mCurrentTargetOffsetTop > -mCircleHeight / 2) {
+                            // start showing the arrow
+                            float pullPercent = yDiff / mDistanceToTriggerSync;
+                            float startTrim = (float) (.2 * Math.min(.75f, (pullPercent)));
+                            mProgress.setStartEndTrim(startTrim,
+                                    Math.min(.75f, pullPercent));
+                            mProgress.setProgressRotation((float) (Math.PI / 4 * pullPercent));
+                            mProgress.setArrowScale(Math.min(1f, pullPercent));
                         }
                     }
                 }
+                mLastY = y;
                 break;
+
+            case MotionEventCompat.ACTION_POINTER_DOWN: {
+                final int index = MotionEventCompat.getActionIndex(ev);
+                mActivePointerId = MotionEventCompat.getPointerId(ev, index);
+                break;
+            }
+
+            case MotionEventCompat.ACTION_POINTER_UP:
+                onSecondaryPointerUp(ev);
+                break;
+
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                if (mDownEvent != null) {
-                    mDownEvent.recycle();
-                    mDownEvent = null;
+                mIsBeingDragged = false;
+                if (mCurrentTargetOffsetTop >= mTargetCirclePosition) {
+                    setRefreshing(true, true /* notify */);
+                } else {
+                    // cancel refresh
+                    mRefreshing = false;
+                    mProgress.setStartEndTrim(0f, 0f);
+                    animateOffsetToStartPosition(mCurrentTargetOffsetTop, null);
+                    mProgress.showArrow(false);
                 }
-                break;
+                mActivePointerId = INVALID_POINTER;
+                return false;
         }
-        return handled;
+
+        return true;
     }
 
-    private void startRefresh() {
-        removeCallbacks(mCancel);
-        mReturnToStartPosition.run();
-        setRefreshing(true);
-        mListener.onRefresh();
-    }
-
-    private void updateContentOffsetTop(int targetTop) {
-        final int currentTop = mTarget.getTop();
-        if (targetTop > mDistanceToTriggerSync) {
-            targetTop = (int) mDistanceToTriggerSync;
-        } else if (targetTop < 0) {
-            targetTop = 0;
+    private void animateOffsetToCorrectPosition(int from, AnimationListener listener) {
+        mFrom = from;
+        mAnimateToCorrectPosition.reset();
+        mAnimateToCorrectPosition.setDuration(mMediumAnimationDuration);
+        mAnimateToCorrectPosition.setInterpolator(mDecelerateInterpolator);
+        if (listener != null) {
+            mCircleView.setAnimationListener(listener);
         }
-        setTargetOffsetTopAndBottom(targetTop - currentTop);
+        mCircleView.clearAnimation();
+        mCircleView.startAnimation(mAnimateToCorrectPosition);
     }
 
-    private void setTargetOffsetTopAndBottom(int offset) {
-        mTarget.offsetTopAndBottom(offset);
-        mCurrentTargetOffsetTop = mTarget.getTop();
+    private void animateOffsetToStartPosition(int from, AnimationListener listener) {
+        mFrom = from;
+        mAnimateToStartPosition.reset();
+        mAnimateToStartPosition.setDuration(mMediumAnimationDuration);
+        mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator);
+        if (listener != null) {
+            mCircleView.setAnimationListener(listener);
+        }
+        mCircleView.clearAnimation();
+        mCircleView.startAnimation(mAnimateToStartPosition);
     }
 
-    private void updatePositionTimeout() {
-        removeCallbacks(mCancel);
-        postDelayed(mCancel, RETURN_TO_ORIGINAL_POSITION_TIMEOUT);
+    private final Animation mAnimateToCorrectPosition = new Animation() {
+        @Override
+        public void applyTransformation(float interpolatedTime, Transformation t) {
+            int targetTop = 0;
+            int endTarget = (int) (mTargetCirclePosition);
+            if (mFrom != endTarget) {
+                targetTop = (mFrom + (int) ((endTarget - mFrom) * interpolatedTime));
+            }
+            int offset = targetTop - mCircleView.getTop();
+            setTargetOffsetTopAndBottom(offset, false /* requires update */);
+        }
+    };
+
+    private final Animation mAnimateToStartPosition = new Animation() {
+        @Override
+        public void applyTransformation(float interpolatedTime, Transformation t) {
+            int targetTop = 0;
+            if (mFrom != mOriginalOffsetTop) {
+                targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime));
+            }
+            int offset = targetTop - mCircleView.getTop();
+            setTargetOffsetTopAndBottom(offset, false /* requires update */);
+        }
+    };
+
+    private void setTargetOffsetTopAndBottom(int offset, boolean requiresUpdate) {
+        mCircleView.bringToFront();
+        mCircleView.offsetTopAndBottom(offset);
+        mCurrentTargetOffsetTop = mCircleView.getTop();
+        if (requiresUpdate && android.os.Build.VERSION.SDK_INT <= 10) {
+            invalidate();
+        }
+    }
+
+    private void onSecondaryPointerUp(MotionEvent ev) {
+        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
+        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
+        if (pointerId == mActivePointerId) {
+            // This was our active pointer going up. Choose a new
+            // active pointer and adjust accordingly.
+            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
+        }
     }
 
     /**
@@ -468,22 +736,4 @@
     public interface OnRefreshListener {
         public void onRefresh();
     }
-
-    /**
-     * Simple AnimationListener to avoid having to implement unneeded methods in
-     * AnimationListeners.
-     */
-    private class BaseAnimationListener implements AnimationListener {
-        @Override
-        public void onAnimationStart(Animation animation) {
-        }
-
-        @Override
-        public void onAnimationEnd(Animation animation) {
-        }
-
-        @Override
-        public void onAnimationRepeat(Animation animation) {
-        }
-    }
-}
\ No newline at end of file
+}
diff --git a/v4/java/android/support/v4/widget/ViewDragHelper.java b/v4/java/android/support/v4/widget/ViewDragHelper.java
index 6b116f2..bfd6ad5 100644
--- a/v4/java/android/support/v4/widget/ViewDragHelper.java
+++ b/v4/java/android/support/v4/widget/ViewDragHelper.java
@@ -539,7 +539,14 @@
         mCapturedView = child;
         mActivePointerId = INVALID_POINTER;
 
-        return forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0);
+        boolean continueSliding = forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0);
+        if (!continueSliding && mDragState == STATE_IDLE && mCapturedView != null) {
+            // If we're in an IDLE state to begin with and aren't moving anywhere, we
+            // end up having a non-null capturedView with an IDLE dragState
+            mCapturedView = null;
+        }
+
+        return continueSliding;
     }
 
     /**
@@ -864,7 +871,7 @@
         if (mDragState != state) {
             mDragState = state;
             mCallback.onViewDragStateChanged(state);
-            if (state == STATE_IDLE) {
+            if (mDragState == STATE_IDLE) {
                 mCapturedView = null;
             }
         }
@@ -1004,15 +1011,38 @@
                     final float dx = x - mInitialMotionX[pointerId];
                     final float dy = y - mInitialMotionY[pointerId];
 
+                    final View toCapture = findTopChildUnder((int) x, (int) y);
+                    final boolean pastSlop = toCapture != null && checkTouchSlop(toCapture, dx, dy);
+                    if (pastSlop) {
+                        // check the callback's
+                        // getView[Horizontal|Vertical]DragRange methods to know
+                        // if you can move at all along an axis, then see if it
+                        // would clamp to the same value. If you can't move at
+                        // all in every dimension with a nonzero range, bail.
+                        final int oldLeft = toCapture.getLeft();
+                        final int targetLeft = oldLeft + (int) dx;
+                        final int newLeft = mCallback.clampViewPositionHorizontal(toCapture,
+                                targetLeft, (int) dx);
+                        final int oldTop = toCapture.getTop();
+                        final int targetTop = oldTop + (int) dy;
+                        final int newTop = mCallback.clampViewPositionVertical(toCapture, targetTop,
+                                (int) dy);
+                        final int horizontalDragRange = mCallback.getViewHorizontalDragRange(
+                                toCapture);
+                        final int verticalDragRange = mCallback.getViewVerticalDragRange(toCapture);
+                        if ((horizontalDragRange == 0 || horizontalDragRange > 0
+                                && newLeft == oldLeft) && (verticalDragRange == 0
+                                || verticalDragRange > 0 && newTop == oldTop)) {
+                            break;
+                        }
+                    }
                     reportNewEdgeDrags(dx, dy, pointerId);
                     if (mDragState == STATE_DRAGGING) {
                         // Callback might have started an edge drag
                         break;
                     }
 
-                    final View toCapture = findTopChildUnder((int) x, (int) y);
-                    if (toCapture != null && checkTouchSlop(toCapture, dx, dy) &&
-                            tryCaptureViewForDrag(toCapture, pointerId)) {
+                    if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) {
                         break;
                     }
                 }
@@ -1446,4 +1476,4 @@
 
         return result;
     }
-}
+}
\ No newline at end of file
diff --git a/v4/jellybean-mr1/android/support/v4/media/routing/MediaRouterJellybeanMr1.java b/v4/jellybean-mr1/android/support/v4/media/routing/MediaRouterJellybeanMr1.java
new file mode 100644
index 0000000..6e5cfd5
--- /dev/null
+++ b/v4/jellybean-mr1/android/support/v4/media/routing/MediaRouterJellybeanMr1.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media.routing;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.Build;
+import android.os.Handler;
+import android.util.Log;
+import android.view.Display;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+class MediaRouterJellybeanMr1 extends MediaRouterJellybean {
+    private static final String TAG = "MediaRouterJellybeanMr1";
+
+    public static Object createCallback(Callback callback) {
+        return new CallbackProxy<Callback>(callback);
+    }
+
+    public static final class RouteInfo {
+        public static boolean isEnabled(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).isEnabled();
+        }
+
+        public static Display getPresentationDisplay(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getPresentationDisplay();
+        }
+    }
+
+    public static interface Callback extends MediaRouterJellybean.Callback {
+        public void onRoutePresentationDisplayChanged(Object routeObj);
+    }
+
+    /**
+     * Workaround the fact that the version of MediaRouter.addCallback() that accepts a
+     * flag to perform an active scan does not exist in JB MR1 so we need to force
+     * wifi display scans directly through the DisplayManager.
+     * Do not use on JB MR2 and above.
+     */
+    public static final class ActiveScanWorkaround implements Runnable {
+        // Time between wifi display scans when actively scanning in milliseconds.
+        private static final int WIFI_DISPLAY_SCAN_INTERVAL = 15000;
+
+        private final DisplayManager mDisplayManager;
+        private final Handler mHandler;
+        private Method mScanWifiDisplaysMethod;
+
+        private boolean mActivelyScanningWifiDisplays;
+
+        public ActiveScanWorkaround(Context context, Handler handler) {
+            if (Build.VERSION.SDK_INT != 17) {
+                throw new UnsupportedOperationException();
+            }
+
+            mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
+            mHandler = handler;
+            try {
+                mScanWifiDisplaysMethod = DisplayManager.class.getMethod("scanWifiDisplays");
+            } catch (NoSuchMethodException ex) {
+            }
+        }
+
+        public void setActiveScanRouteTypes(int routeTypes) {
+            // On JB MR1, there is no API to scan wifi display routes.
+            // Instead we must make a direct call into the DisplayManager to scan
+            // wifi displays on this version but only when live video routes are requested.
+            // See also the JellybeanMr2Impl implementation of this method.
+            // This was fixed in JB MR2 by adding a new overload of addCallback() to
+            // enable active scanning on request.
+            if ((routeTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO) != 0) {
+                if (!mActivelyScanningWifiDisplays) {
+                    if (mScanWifiDisplaysMethod != null) {
+                        mActivelyScanningWifiDisplays = true;
+                        mHandler.post(this);
+                    } else {
+                        Log.w(TAG, "Cannot scan for wifi displays because the "
+                                + "DisplayManager.scanWifiDisplays() method is "
+                                + "not available on this device.");
+                    }
+                }
+            } else {
+                if (mActivelyScanningWifiDisplays) {
+                    mActivelyScanningWifiDisplays = false;
+                    mHandler.removeCallbacks(this);
+                }
+            }
+        }
+
+        @Override
+        public void run() {
+            if (mActivelyScanningWifiDisplays) {
+                try {
+                    mScanWifiDisplaysMethod.invoke(mDisplayManager);
+                } catch (IllegalAccessException ex) {
+                    Log.w(TAG, "Cannot scan for wifi displays.", ex);
+                } catch (InvocationTargetException ex) {
+                    Log.w(TAG, "Cannot scan for wifi displays.", ex);
+                }
+                mHandler.postDelayed(this, WIFI_DISPLAY_SCAN_INTERVAL);
+            }
+        }
+    }
+
+    /**
+     * Workaround the fact that the isConnecting() method does not exist in JB MR1.
+     * Do not use on JB MR2 and above.
+     */
+    public static final class IsConnectingWorkaround {
+        private Method mGetStatusCodeMethod;
+        private int mStatusConnecting;
+
+        public IsConnectingWorkaround() {
+            if (Build.VERSION.SDK_INT != 17) {
+                throw new UnsupportedOperationException();
+            }
+
+            try {
+                Field statusConnectingField =
+                        android.media.MediaRouter.RouteInfo.class.getField("STATUS_CONNECTING");
+                mStatusConnecting = statusConnectingField.getInt(null);
+                mGetStatusCodeMethod =
+                        android.media.MediaRouter.RouteInfo.class.getMethod("getStatusCode");
+            } catch (NoSuchFieldException ex) {
+            } catch (NoSuchMethodException ex) {
+            } catch (IllegalAccessException ex) {
+            }
+        }
+
+        public boolean isConnecting(Object routeObj) {
+            android.media.MediaRouter.RouteInfo route =
+                    (android.media.MediaRouter.RouteInfo)routeObj;
+
+            if (mGetStatusCodeMethod != null) {
+                try {
+                    int statusCode = (Integer)mGetStatusCodeMethod.invoke(route);
+                    return statusCode == mStatusConnecting;
+                } catch (IllegalAccessException ex) {
+                } catch (InvocationTargetException ex) {
+                }
+            }
+
+            // Assume not connecting.
+            return false;
+        }
+    }
+
+    static class CallbackProxy<T extends Callback>
+            extends MediaRouterJellybean.CallbackProxy<T> {
+        public CallbackProxy(T callback) {
+            super(callback);
+        }
+
+        @Override
+        public void onRoutePresentationDisplayChanged(android.media.MediaRouter router,
+                android.media.MediaRouter.RouteInfo route) {
+            mCallback.onRoutePresentationDisplayChanged(route);
+        }
+    }
+}
diff --git a/v4/jellybean-mr1/android/support/v4/view/ViewCompatJellybeanMr1.java b/v4/jellybean-mr1/android/support/v4/view/ViewCompatJellybeanMr1.java
index be7192d..6832b42 100644
--- a/v4/jellybean-mr1/android/support/v4/view/ViewCompatJellybeanMr1.java
+++ b/v4/jellybean-mr1/android/support/v4/view/ViewCompatJellybeanMr1.java
@@ -43,4 +43,20 @@
     public static void setLayoutDirection(View view, int layoutDirection) {
         view.setLayoutDirection(layoutDirection);
     }
+
+    public static int getPaddingStart(View view) {
+        return view.getPaddingStart();
+    }
+
+    public static int getPaddingEnd(View view) {
+        return view.getPaddingEnd();
+    }
+
+    public static void setPaddingRelative(View view, int start, int top, int end, int bottom) {
+        view.setPaddingRelative(start, top, end, bottom);
+    }
+
+    public static int getWindowSystemUiVisibility(View view) {
+        return view.getWindowSystemUiVisibility();
+    }
 }
diff --git a/v4/jellybean-mr2/android/support/v4/app/ActionBarDrawerToggleJellybeanMR2.java b/v4/jellybean-mr2/android/support/v4/app/ActionBarDrawerToggleJellybeanMR2.java
index a470a2d..5d98d88 100644
--- a/v4/jellybean-mr2/android/support/v4/app/ActionBarDrawerToggleJellybeanMR2.java
+++ b/v4/jellybean-mr2/android/support/v4/app/ActionBarDrawerToggleJellybeanMR2.java
@@ -20,6 +20,7 @@
 import android.R;
 import android.app.ActionBar;
 import android.app.Activity;
+import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.util.Log;
@@ -51,7 +52,16 @@
     }
 
     public static Drawable getThemeUpIndicator(Activity activity) {
-        final TypedArray a = activity.obtainStyledAttributes(THEME_ATTRS);
+        final ActionBar actionBar = activity.getActionBar();
+        final Context context;
+        if (actionBar != null) {
+            context = actionBar.getThemedContext();
+        } else {
+            context = activity;
+        }
+
+        final TypedArray a = context.obtainStyledAttributes(null, THEME_ATTRS,
+                R.attr.actionBarStyle, 0);
         final Drawable result = a.getDrawable(0);
         a.recycle();
         return result;
diff --git a/v4/jellybean-mr2/android/support/v4/graphics/BitmapCompatJellybeanMR2.java b/v4/jellybean-mr2/android/support/v4/graphics/BitmapCompatJellybeanMR2.java
new file mode 100644
index 0000000..21f0d49
--- /dev/null
+++ b/v4/jellybean-mr2/android/support/v4/graphics/BitmapCompatJellybeanMR2.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.graphics;
+
+import android.graphics.Bitmap;
+
+class BitmapCompatJellybeanMR2 {
+    public static boolean hasMipMap(Bitmap bitmap) {
+        return bitmap.hasMipMap();
+    }
+
+    public static void setHasMipMap(Bitmap bitmap, boolean hasMipMap) {
+        bitmap.setHasMipMap(hasMipMap);
+    }
+}
diff --git a/v4/jellybean-mr2/android/support/v4/media/routing/MediaRouterJellybeanMr2.java b/v4/jellybean-mr2/android/support/v4/media/routing/MediaRouterJellybeanMr2.java
new file mode 100644
index 0000000..92a1607
--- /dev/null
+++ b/v4/jellybean-mr2/android/support/v4/media/routing/MediaRouterJellybeanMr2.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media.routing;
+
+class MediaRouterJellybeanMr2 extends MediaRouterJellybeanMr1 {
+    public static Object getDefaultRoute(Object routerObj) {
+        return ((android.media.MediaRouter)routerObj).getDefaultRoute();
+    }
+
+    public static void addCallback(Object routerObj, int types, Object callbackObj, int flags) {
+        ((android.media.MediaRouter)routerObj).addCallback(types,
+                (android.media.MediaRouter.Callback)callbackObj, flags);
+    }
+
+    public static final class RouteInfo {
+        public static CharSequence getDescription(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getDescription();
+        }
+
+        public static boolean isConnecting(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).isConnecting();
+        }
+    }
+
+    public static final class UserRouteInfo {
+        public static void setDescription(Object routeObj, CharSequence description) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setDescription(description);
+        }
+    }
+}
diff --git a/v4/jellybean-mr2/android/support/v4/view/ViewPropertyAnimatorCompatJellybeanMr2.java b/v4/jellybean-mr2/android/support/v4/view/ViewPropertyAnimatorCompatJellybeanMr2.java
new file mode 100644
index 0000000..e9a29ee
--- /dev/null
+++ b/v4/jellybean-mr2/android/support/v4/view/ViewPropertyAnimatorCompatJellybeanMr2.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.view;
+
+import android.view.View;
+import android.view.animation.Interpolator;
+
+class ViewPropertyAnimatorCompatJellybeanMr2 {
+    public static Interpolator getInterpolator(View view) {
+        return (Interpolator) view.animate().getInterpolator();
+    }
+}
diff --git a/v4/jellybean/android/support/v4/app/BundleUtil.java b/v4/jellybean/android/support/v4/app/BundleUtil.java
new file mode 100644
index 0000000..fb6350d
--- /dev/null
+++ b/v4/jellybean/android/support/v4/app/BundleUtil.java
@@ -0,0 +1,27 @@
+package android.support.v4.app;
+
+import android.os.Bundle;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * @hide
+ */
+class BundleUtil {
+    /**
+     * Get an array of Bundle objects from a parcelable array field in a bundle.
+     * Update the bundle to have a typed array so fetches in the future don't need
+     * to do an array copy.
+     */
+    public static Bundle[] getBundleArrayFromBundle(Bundle bundle, String key) {
+        Parcelable[] array = bundle.getParcelableArray(key);
+        if (array instanceof Bundle[] || array == null) {
+            return (Bundle[]) array;
+        }
+        Bundle[] typedArray = Arrays.copyOf(array, array.length,
+                Bundle[].class);
+        bundle.putParcelableArray(key, typedArray);
+        return typedArray;
+    }
+}
diff --git a/v4/jellybean/android/support/v4/app/NotificationBuilderWithActions.java b/v4/jellybean/android/support/v4/app/NotificationBuilderWithActions.java
new file mode 100644
index 0000000..8e8d8ce
--- /dev/null
+++ b/v4/jellybean/android/support/v4/app/NotificationBuilderWithActions.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+/**
+ * Interface implemented by notification compat builders that support adding actions.
+ */
+interface NotificationBuilderWithActions {
+    public void addAction(NotificationCompatBase.Action action);
+}
diff --git a/v4/jellybean/android/support/v4/app/NotificationCompatJellybean.java b/v4/jellybean/android/support/v4/app/NotificationCompatJellybean.java
index 8fa7e98..6f54c4b 100644
--- a/v4/jellybean/android/support/v4/app/NotificationCompatJellybean.java
+++ b/v4/jellybean/android/support/v4/app/NotificationCompatJellybean.java
@@ -20,75 +20,166 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.SparseArray;
 import android.widget.RemoteViews;
+
+import java.lang.reflect.Field;
 import java.util.ArrayList;
+import java.util.List;
 
 class NotificationCompatJellybean {
-    private Notification.Builder b;
-    public NotificationCompatJellybean(Context context, Notification n,
-            CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
-            RemoteViews tickerView, int number,
-            PendingIntent contentIntent, PendingIntent fullScreenIntent, Bitmap largeIcon,
-            int mProgressMax, int mProgress, boolean mProgressIndeterminate,
-            boolean useChronometer, int priority, CharSequence subText) {
-        b = new Notification.Builder(context)
-            .setWhen(n.when)
-            .setSmallIcon(n.icon, n.iconLevel)
-            .setContent(n.contentView)
-            .setTicker(n.tickerText, tickerView)
-            .setSound(n.sound, n.audioStreamType)
-            .setVibrate(n.vibrate)
-            .setLights(n.ledARGB, n.ledOnMS, n.ledOffMS)
-            .setOngoing((n.flags & Notification.FLAG_ONGOING_EVENT) != 0)
-            .setOnlyAlertOnce((n.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)
-            .setAutoCancel((n.flags & Notification.FLAG_AUTO_CANCEL) != 0)
-            .setDefaults(n.defaults)
-            .setContentTitle(contentTitle)
-            .setContentText(contentText)
-            .setSubText(subText)
-            .setContentInfo(contentInfo)
-            .setContentIntent(contentIntent)
-            .setDeleteIntent(n.deleteIntent)
-            .setFullScreenIntent(fullScreenIntent,
-                    (n.flags & Notification.FLAG_HIGH_PRIORITY) != 0)
-            .setLargeIcon(largeIcon)
-            .setNumber(number)
-            .setUsesChronometer(useChronometer)
-            .setPriority(priority)
-            .setProgress(mProgressMax, mProgress, mProgressIndeterminate);
+    public static final String TAG = "NotificationCompat";
+
+    // Extras keys used for Jellybean SDK and above.
+    static final String EXTRA_LOCAL_ONLY = "android.support.localOnly";
+    static final String EXTRA_ACTION_EXTRAS = "android.support.actionExtras";
+    static final String EXTRA_REMOTE_INPUTS = "android.support.remoteInputs";
+    static final String EXTRA_GROUP_KEY = "android.support.groupKey";
+    static final String EXTRA_GROUP_SUMMARY = "android.support.isGroupSummary";
+    static final String EXTRA_SORT_KEY = "android.support.sortKey";
+    static final String EXTRA_USE_SIDE_CHANNEL = "android.support.useSideChannel";
+
+    // Bundle keys for storing action fields in a bundle
+    private static final String KEY_ICON = "icon";
+    private static final String KEY_TITLE = "title";
+    private static final String KEY_ACTION_INTENT = "actionIntent";
+    private static final String KEY_EXTRAS = "extras";
+    private static final String KEY_REMOTE_INPUTS = "remoteInputs";
+
+    private static final Object sExtrasLock = new Object();
+    private static Field sExtrasField;
+    private static boolean sExtrasFieldAccessFailed;
+
+    private static final Object sActionsLock = new Object();
+    private static Class<?> sActionClass;
+    private static Field sActionsField;
+    private static Field sActionIconField;
+    private static Field sActionTitleField;
+    private static Field sActionIntentField;
+    private static boolean sActionsAccessFailed;
+
+    public static class Builder implements NotificationBuilderWithBuilderAccessor,
+            NotificationBuilderWithActions {
+        private Notification.Builder b;
+        private final Bundle mExtras;
+        private List<Bundle> mActionExtrasList = new ArrayList<Bundle>();
+
+        public Builder(Context context, Notification n,
+                CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
+                RemoteViews tickerView, int number,
+                PendingIntent contentIntent, PendingIntent fullScreenIntent, Bitmap largeIcon,
+                int progressMax, int progress, boolean progressIndeterminate,
+                boolean useChronometer, int priority, CharSequence subText, boolean localOnly,
+                Bundle extras, String groupKey, boolean groupSummary, String sortKey) {
+            b = new Notification.Builder(context)
+                .setWhen(n.when)
+                .setSmallIcon(n.icon, n.iconLevel)
+                .setContent(n.contentView)
+                .setTicker(n.tickerText, tickerView)
+                .setSound(n.sound, n.audioStreamType)
+                .setVibrate(n.vibrate)
+                .setLights(n.ledARGB, n.ledOnMS, n.ledOffMS)
+                .setOngoing((n.flags & Notification.FLAG_ONGOING_EVENT) != 0)
+                .setOnlyAlertOnce((n.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)
+                .setAutoCancel((n.flags & Notification.FLAG_AUTO_CANCEL) != 0)
+                .setDefaults(n.defaults)
+                .setContentTitle(contentTitle)
+                .setContentText(contentText)
+                .setSubText(subText)
+                .setContentInfo(contentInfo)
+                .setContentIntent(contentIntent)
+                .setDeleteIntent(n.deleteIntent)
+                .setFullScreenIntent(fullScreenIntent,
+                        (n.flags & Notification.FLAG_HIGH_PRIORITY) != 0)
+                .setLargeIcon(largeIcon)
+                .setNumber(number)
+                .setUsesChronometer(useChronometer)
+                .setPriority(priority)
+                .setProgress(progressMax, progress, progressIndeterminate);
+            mExtras = new Bundle();
+            if (extras != null) {
+                mExtras.putAll(extras);
+            }
+            if (localOnly) {
+                mExtras.putBoolean(EXTRA_LOCAL_ONLY, true);
+            }
+            if (groupKey != null) {
+                mExtras.putString(EXTRA_GROUP_KEY, groupKey);
+                if (groupSummary) {
+                    mExtras.putBoolean(EXTRA_GROUP_SUMMARY, true);
+                } else {
+                    mExtras.putBoolean(EXTRA_USE_SIDE_CHANNEL, true);
+                }
+            }
+            if (sortKey != null) {
+                mExtras.putString(EXTRA_SORT_KEY, sortKey);
+            }
+        }
+
+        @Override
+        public void addAction(NotificationCompatBase.Action action) {
+            mActionExtrasList.add(writeActionAndGetExtras(b, action));
+        }
+
+        @Override
+        public Notification.Builder getBuilder() {
+            return b;
+        }
+
+        public Notification build() {
+            Notification notif = b.build();
+            // Merge in developer provided extras, but let the values already set
+            // for keys take precedence.
+            Bundle extras = getExtras(notif);
+            Bundle mergeBundle = new Bundle(mExtras);
+            for (String key : mExtras.keySet()) {
+                if (extras.containsKey(key)) {
+                    mergeBundle.remove(key);
+                }
+            }
+            extras.putAll(mergeBundle);
+            SparseArray<Bundle> actionExtrasMap = buildActionExtrasMap(mActionExtrasList);
+            if (actionExtrasMap != null) {
+                // Add the action extras sparse array if any action was added with extras.
+                getExtras(notif).putSparseParcelableArray(EXTRA_ACTION_EXTRAS, actionExtrasMap);
+            }
+            return notif;
+        }
     }
 
-    public void addAction(int icon, CharSequence title, PendingIntent intent) {
-        b.addAction(icon, title, intent);
-    }
-
-    public void addBigTextStyle(CharSequence bigContentTitle, boolean useSummary,
+    public static void addBigTextStyle(NotificationBuilderWithBuilderAccessor b,
+            CharSequence bigContentTitle, boolean useSummary,
             CharSequence summaryText, CharSequence bigText) {
-        Notification.BigTextStyle style = new Notification.BigTextStyle(b)
+        Notification.BigTextStyle style = new Notification.BigTextStyle(b.getBuilder())
             .setBigContentTitle(bigContentTitle)
             .bigText(bigText);
         if (useSummary) {
             style.setSummaryText(summaryText);
-         }
+        }
     }
 
-    public void addBigPictureStyle(CharSequence bigContentTitle, boolean useSummary,
+    public static void addBigPictureStyle(NotificationBuilderWithBuilderAccessor b,
+            CharSequence bigContentTitle, boolean useSummary,
             CharSequence summaryText, Bitmap bigPicture, Bitmap bigLargeIcon,
             boolean bigLargeIconSet) {
-       Notification.BigPictureStyle style = new Notification.BigPictureStyle(b)
-           .setBigContentTitle(bigContentTitle)
-           .bigPicture(bigPicture);
-       if (bigLargeIconSet) {
-           style.bigLargeIcon(bigLargeIcon);
-       }
+        Notification.BigPictureStyle style = new Notification.BigPictureStyle(b.getBuilder())
+            .setBigContentTitle(bigContentTitle)
+            .bigPicture(bigPicture);
+        if (bigLargeIconSet) {
+            style.bigLargeIcon(bigLargeIcon);
+        }
         if (useSummary) {
             style.setSummaryText(summaryText);
-         }
+        }
     }
 
-    public void addInboxStyle(CharSequence bigContentTitle, boolean useSummary,
+    public static void addInboxStyle(NotificationBuilderWithBuilderAccessor b,
+            CharSequence bigContentTitle, boolean useSummary,
             CharSequence summaryText, ArrayList<CharSequence> texts) {
-        Notification.InboxStyle style = new Notification.InboxStyle(b)
+        Notification.InboxStyle style = new Notification.InboxStyle(b.getBuilder())
             .setBigContentTitle(bigContentTitle);
         if (useSummary) {
             style.setSummaryText(summaryText);
@@ -98,7 +189,218 @@
         }
     }
 
-    public Notification build() {
-        return b.build();
+    /** Return an SparseArray for action extras or null if none was needed. */
+    public static SparseArray<Bundle> buildActionExtrasMap(List<Bundle> actionExtrasList) {
+        SparseArray<Bundle> actionExtrasMap = null;
+        for (int i = 0, count = actionExtrasList.size(); i < count; i++) {
+            Bundle actionExtras = actionExtrasList.get(i);
+            if (actionExtras != null) {
+                if (actionExtrasMap == null) {
+                    actionExtrasMap = new SparseArray<Bundle>();
+                }
+                actionExtrasMap.put(i, actionExtras);
+            }
+        }
+        return actionExtrasMap;
+    }
+
+    /**
+     * Get the extras Bundle from a notification using reflection. Extras were present in
+     * Jellybean notifications, but the field was private until KitKat.
+     */
+    public static Bundle getExtras(Notification notif) {
+        synchronized (sExtrasLock) {
+            if (sExtrasFieldAccessFailed) {
+                return null;
+            }
+            try {
+                if (sExtrasField == null) {
+                    Field extrasField = Notification.class.getDeclaredField("extras");
+                    if (!Bundle.class.isAssignableFrom(extrasField.getType())) {
+                        Log.e(TAG, "Notification.extras field is not of type Bundle");
+                        sExtrasFieldAccessFailed = true;
+                        return null;
+                    }
+                    extrasField.setAccessible(true);
+                    sExtrasField = extrasField;
+                }
+                Bundle extras = (Bundle) sExtrasField.get(notif);
+                if (extras == null) {
+                    extras = new Bundle();
+                    sExtrasField.set(notif, extras);
+                }
+                return extras;
+            } catch (IllegalAccessException e) {
+                Log.e(TAG, "Unable to access notification extras", e);
+            } catch (NoSuchFieldException e) {
+                Log.e(TAG, "Unable to access notification extras", e);
+            }
+            sExtrasFieldAccessFailed = true;
+            return null;
+        }
+    }
+
+    public static NotificationCompatBase.Action readAction(
+            NotificationCompatBase.Action.Factory factory,
+            RemoteInputCompatBase.RemoteInput.Factory remoteInputFactory, int icon,
+            CharSequence title, PendingIntent actionIntent, Bundle extras) {
+        RemoteInputCompatBase.RemoteInput[] remoteInputs = null;
+        if (extras != null) {
+            remoteInputs = RemoteInputCompatJellybean.fromBundleArray(
+                    BundleUtil.getBundleArrayFromBundle(extras, EXTRA_REMOTE_INPUTS),
+                    remoteInputFactory);
+        }
+        return factory.build(icon, title, actionIntent, extras, remoteInputs);
+    }
+
+    public static Bundle writeActionAndGetExtras(
+            Notification.Builder builder, NotificationCompatBase.Action action) {
+        builder.addAction(action.getIcon(), action.getTitle(), action.getActionIntent());
+        Bundle actionExtras = new Bundle(action.getExtras());
+        if (action.getRemoteInputs() != null) {
+            actionExtras.putParcelableArray(EXTRA_REMOTE_INPUTS,
+                    RemoteInputCompatJellybean.toBundleArray(action.getRemoteInputs()));
+        }
+        return actionExtras;
+    }
+
+    public static int getActionCount(Notification notif) {
+        synchronized (sActionsLock) {
+            Object[] actionObjects = getActionObjectsLocked(notif);
+            return actionObjects != null ? actionObjects.length : 0;
+        }
+    }
+
+    public static NotificationCompatBase.Action getAction(Notification notif, int actionIndex,
+            NotificationCompatBase.Action.Factory factory,
+            RemoteInputCompatBase.RemoteInput.Factory remoteInputFactory) {
+        synchronized (sActionsLock) {
+            try {
+                Object actionObject = getActionObjectsLocked(notif)[actionIndex];
+                Bundle actionExtras = null;
+                Bundle extras = getExtras(notif);
+                if (extras != null) {
+                    SparseArray<Bundle> actionExtrasMap = extras.getSparseParcelableArray(
+                            EXTRA_ACTION_EXTRAS);
+                    if (actionExtrasMap != null) {
+                        actionExtras = actionExtrasMap.get(actionIndex);
+                    }
+                }
+                return readAction(factory, remoteInputFactory,
+                        sActionIconField.getInt(actionObject),
+                        (CharSequence) sActionTitleField.get(actionObject),
+                        (PendingIntent) sActionIntentField.get(actionObject),
+                        actionExtras);
+            } catch (IllegalAccessException e) {
+                Log.e(TAG, "Unable to access notification actions", e);
+                sActionsAccessFailed = true;
+            }
+        }
+        return null;
+    }
+
+    private static Object[] getActionObjectsLocked(Notification notif) {
+        synchronized (sActionsLock) {
+            if (!ensureActionReflectionReadyLocked()) {
+                return null;
+            }
+            try {
+                return (Object[]) sActionsField.get(notif);
+            } catch (IllegalAccessException e) {
+                Log.e(TAG, "Unable to access notification actions", e);
+                sActionsAccessFailed = true;
+                return null;
+            }
+        }
+    }
+
+    private static boolean ensureActionReflectionReadyLocked() {
+        if (sActionsAccessFailed) {
+            return false;
+        }
+        try {
+            if (sActionsField == null) {
+                sActionClass = Class.forName("android.app.Notification$Action");
+                sActionIconField = sActionClass.getDeclaredField("icon");
+                sActionTitleField = sActionClass.getDeclaredField("title");
+                sActionIntentField = sActionClass.getDeclaredField("actionIntent");
+                sActionsField = Notification.class.getDeclaredField("actions");
+                sActionsField.setAccessible(true);
+            }
+        } catch (ClassNotFoundException e) {
+            Log.e(TAG, "Unable to access notification actions", e);
+            sActionsAccessFailed = true;
+        } catch (NoSuchFieldException e) {
+            Log.e(TAG, "Unable to access notification actions", e);
+            sActionsAccessFailed = true;
+        }
+        return !sActionsAccessFailed;
+    }
+
+    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++) {
+            actions[i] = getActionFromBundle((Bundle) parcelables.get(i),
+                    actionFactory, remoteInputFactory);
+        }
+        return actions;
+    }
+
+    private static NotificationCompatBase.Action getActionFromBundle(Bundle bundle,
+            NotificationCompatBase.Action.Factory actionFactory,
+            RemoteInputCompatBase.RemoteInput.Factory remoteInputFactory) {
+        return actionFactory.build(
+                bundle.getInt(KEY_ICON),
+                bundle.getCharSequence(KEY_TITLE),
+                bundle.<PendingIntent>getParcelable(KEY_ACTION_INTENT),
+                bundle.getBundle(KEY_EXTRAS),
+                RemoteInputCompatJellybean.fromBundleArray(
+                        BundleUtil.getBundleArrayFromBundle(bundle, KEY_REMOTE_INPUTS),
+                        remoteInputFactory));
+    }
+
+    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(getBundleForAction(action));
+        }
+        return parcelables;
+    }
+
+    private static Bundle getBundleForAction(NotificationCompatBase.Action action) {
+        Bundle bundle = new Bundle();
+        bundle.putInt(KEY_ICON, action.getIcon());
+        bundle.putCharSequence(KEY_TITLE, action.getTitle());
+        bundle.putParcelable(KEY_ACTION_INTENT, action.getActionIntent());
+        bundle.putBundle(KEY_EXTRAS, action.getExtras());
+        bundle.putParcelableArray(KEY_REMOTE_INPUTS, RemoteInputCompatJellybean.toBundleArray(
+                action.getRemoteInputs()));
+        return bundle;
+    }
+
+    public static boolean getLocalOnly(Notification notif) {
+        return getExtras(notif).getBoolean(EXTRA_LOCAL_ONLY);
+    }
+
+    public static String getGroup(Notification n) {
+        return getExtras(n).getString(EXTRA_GROUP_KEY);
+    }
+
+    public static boolean isGroupSummary(Notification n) {
+        return getExtras(n).getBoolean(EXTRA_GROUP_SUMMARY);
+    }
+
+    public static String getSortKey(Notification n) {
+        return getExtras(n).getString(EXTRA_SORT_KEY);
     }
 }
diff --git a/v4/jellybean/android/support/v4/app/RemoteInputCompatJellybean.java b/v4/jellybean/android/support/v4/app/RemoteInputCompatJellybean.java
new file mode 100644
index 0000000..36f5981
--- /dev/null
+++ b/v4/jellybean/android/support/v4/app/RemoteInputCompatJellybean.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Intent;
+import android.os.Bundle;
+
+class RemoteInputCompatJellybean {
+    /** Label used to denote the clip data type used for remote input transport */
+    public static final String RESULTS_CLIP_LABEL = "android.remoteinput.results";
+
+    /** Extra added to a clip data intent object to hold the results bundle. */
+    public static final String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData";
+
+    private static final String KEY_RESULT_KEY = "resultKey";
+    private static final String KEY_LABEL = "label";
+    private static final String KEY_CHOICES = "choices";
+    private static final String KEY_ALLOW_FREE_FORM_INPUT = "allowFreeFormInput";
+    private static final String KEY_EXTRAS = "extras";
+
+    static RemoteInputCompatBase.RemoteInput fromBundle(Bundle data,
+            RemoteInputCompatBase.RemoteInput.Factory factory) {
+        return factory.build(data.getString(KEY_RESULT_KEY),
+                data.getCharSequence(KEY_LABEL),
+                data.getCharSequenceArray(KEY_CHOICES),
+                data.getBoolean(KEY_ALLOW_FREE_FORM_INPUT),
+                data.getBundle(KEY_EXTRAS));
+    }
+
+    static Bundle toBundle(RemoteInputCompatBase.RemoteInput remoteInput) {
+        Bundle data = new Bundle();
+        data.putString(KEY_RESULT_KEY, remoteInput.getResultKey());
+        data.putCharSequence(KEY_LABEL, remoteInput.getLabel());
+        data.putCharSequenceArray(KEY_CHOICES, remoteInput.getChoices());
+        data.putBoolean(KEY_ALLOW_FREE_FORM_INPUT, remoteInput.getAllowFreeFormInput());
+        data.putBundle(KEY_EXTRAS, remoteInput.getExtras());
+        return data;
+    }
+
+    static RemoteInputCompatBase.RemoteInput[] fromBundleArray(Bundle[] bundles,
+            RemoteInputCompatBase.RemoteInput.Factory factory) {
+        if (bundles == null) {
+            return null;
+        }
+        RemoteInputCompatBase.RemoteInput[] remoteInputs = factory.newArray(bundles.length);
+        for (int i = 0; i < bundles.length; i++) {
+            remoteInputs[i] = fromBundle(bundles[i], factory);
+        }
+        return remoteInputs;
+    }
+
+    static Bundle[] toBundleArray(RemoteInputCompatBase.RemoteInput[] remoteInputs) {
+        if (remoteInputs == null) {
+            return null;
+        }
+        Bundle[] bundles = new Bundle[remoteInputs.length];
+        for (int i = 0; i < remoteInputs.length; i++) {
+            bundles[i] = toBundle(remoteInputs[i]);
+        }
+        return bundles;
+    }
+
+    static Bundle getResultsFromIntent(Intent intent) {
+        ClipData clipData = intent.getClipData();
+        if (clipData == null) {
+            return null;
+        }
+        ClipDescription clipDescription = clipData.getDescription();
+        if (!clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) {
+            return null;
+        }
+        if (clipDescription.getLabel().equals(RESULTS_CLIP_LABEL)) {
+            return clipData.getItemAt(0).getIntent().getExtras().getParcelable(EXTRA_RESULTS_DATA);
+        }
+        return null;
+    }
+
+    static void addResultsToIntent(RemoteInputCompatBase.RemoteInput[] remoteInputs, Intent intent,
+            Bundle results) {
+        Bundle resultsBundle = new Bundle();
+        for (RemoteInputCompatBase.RemoteInput remoteInput : remoteInputs) {
+            Object result = results.get(remoteInput.getResultKey());
+            if (result instanceof CharSequence) {
+                resultsBundle.putCharSequence(remoteInput.getResultKey(), (CharSequence) result);
+            }
+        }
+        Intent clipIntent = new Intent();
+        clipIntent.putExtra(EXTRA_RESULTS_DATA, resultsBundle);
+        intent.setClipData(ClipData.newIntent(RESULTS_CLIP_LABEL, clipIntent));
+    }
+}
diff --git a/v4/jellybean/android/support/v4/media/routing/MediaRouterJellybean.java b/v4/jellybean/android/support/v4/media/routing/MediaRouterJellybean.java
new file mode 100644
index 0000000..3cf6727
--- /dev/null
+++ b/v4/jellybean/android/support/v4/media/routing/MediaRouterJellybean.java
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media.routing;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+class MediaRouterJellybean {
+    private static final String TAG = "MediaRouterJellybean";
+
+    public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1;
+    public static final int ROUTE_TYPE_LIVE_VIDEO = 0x2;
+    public static final int ROUTE_TYPE_USER = 0x00800000;
+
+    public static final int ALL_ROUTE_TYPES =
+            MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO
+            | MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO
+            | MediaRouterJellybean.ROUTE_TYPE_USER;
+
+    public static Object getMediaRouter(Context context) {
+        return context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public static List getRoutes(Object routerObj) {
+        final android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
+        final int count = router.getRouteCount();
+        List out = new ArrayList(count);
+        for (int i = 0; i < count; i++) {
+            out.add(router.getRouteAt(i));
+        }
+        return out;
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public static List getCategories(Object routerObj) {
+        final android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
+        final int count = router.getCategoryCount();
+        List out = new ArrayList(count);
+        for (int i = 0; i < count; i++) {
+            out.add(router.getCategoryAt(i));
+        }
+        return out;
+    }
+
+    public static Object getSelectedRoute(Object routerObj, int type) {
+        return ((android.media.MediaRouter)routerObj).getSelectedRoute(type);
+    }
+
+    public static void selectRoute(Object routerObj, int types, Object routeObj) {
+        ((android.media.MediaRouter)routerObj).selectRoute(types,
+                (android.media.MediaRouter.RouteInfo)routeObj);
+    }
+
+    public static void addCallback(Object routerObj, int types, Object callbackObj) {
+        ((android.media.MediaRouter)routerObj).addCallback(types,
+                (android.media.MediaRouter.Callback)callbackObj);
+    }
+
+    public static void removeCallback(Object routerObj, Object callbackObj) {
+        ((android.media.MediaRouter)routerObj).removeCallback(
+                (android.media.MediaRouter.Callback)callbackObj);
+    }
+
+    public static Object createRouteCategory(Object routerObj,
+            String name, boolean isGroupable) {
+        return ((android.media.MediaRouter)routerObj).createRouteCategory(name, isGroupable);
+    }
+
+    public static Object createUserRoute(Object routerObj, Object categoryObj) {
+        return ((android.media.MediaRouter)routerObj).createUserRoute(
+                (android.media.MediaRouter.RouteCategory)categoryObj);
+    }
+
+    public static void addUserRoute(Object routerObj, Object routeObj) {
+        ((android.media.MediaRouter)routerObj).addUserRoute(
+                (android.media.MediaRouter.UserRouteInfo)routeObj);
+    }
+
+    public static void removeUserRoute(Object routerObj, Object routeObj) {
+        ((android.media.MediaRouter)routerObj).removeUserRoute(
+                (android.media.MediaRouter.UserRouteInfo)routeObj);
+    }
+
+    public static Object createCallback(Callback callback) {
+        return new CallbackProxy<Callback>(callback);
+    }
+
+    public static Object createVolumeCallback(VolumeCallback callback) {
+        return new VolumeCallbackProxy<VolumeCallback>(callback);
+    }
+
+    public static final class RouteInfo {
+        public static CharSequence getName(Object routeObj, Context context) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getName(context);
+        }
+
+        public static CharSequence getStatus(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getStatus();
+        }
+
+        public static int getSupportedTypes(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getSupportedTypes();
+        }
+
+        public static Object getCategory(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getCategory();
+        }
+
+        public static Drawable getIconDrawable(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getIconDrawable();
+        }
+
+        public static int getPlaybackType(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getPlaybackType();
+        }
+
+        public static int getPlaybackStream(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getPlaybackStream();
+        }
+
+        public static int getVolume(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getVolume();
+        }
+
+        public static int getVolumeMax(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getVolumeMax();
+        }
+
+        public static int getVolumeHandling(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getVolumeHandling();
+        }
+
+        public static Object getTag(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getTag();
+        }
+
+        public static void setTag(Object routeObj, Object tag) {
+            ((android.media.MediaRouter.RouteInfo)routeObj).setTag(tag);
+        }
+
+        public static void requestSetVolume(Object routeObj, int volume) {
+            ((android.media.MediaRouter.RouteInfo)routeObj).requestSetVolume(volume);
+        }
+
+        public static void requestUpdateVolume(Object routeObj, int direction) {
+            ((android.media.MediaRouter.RouteInfo)routeObj).requestUpdateVolume(direction);
+        }
+
+        public static Object getGroup(Object routeObj) {
+            return ((android.media.MediaRouter.RouteInfo)routeObj).getGroup();
+        }
+
+        public static boolean isGroup(Object routeObj) {
+            return routeObj instanceof android.media.MediaRouter.RouteGroup;
+        }
+    }
+
+    public static final class RouteGroup {
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        public static List getGroupedRoutes(Object groupObj) {
+            final android.media.MediaRouter.RouteGroup group =
+                    (android.media.MediaRouter.RouteGroup)groupObj;
+            final int count = group.getRouteCount();
+            List out = new ArrayList(count);
+            for (int i = 0; i < count; i++) {
+                out.add(group.getRouteAt(i));
+            }
+            return out;
+        }
+    }
+
+    public static final class UserRouteInfo {
+        public static void setName(Object routeObj, CharSequence name) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setName(name);
+        }
+
+        public static void setStatus(Object routeObj, CharSequence status) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setStatus(status);
+        }
+
+        public static void setIconDrawable(Object routeObj, Drawable icon) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setIconDrawable(icon);
+        }
+
+        public static void setPlaybackType(Object routeObj, int type) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setPlaybackType(type);
+        }
+
+        public static void setPlaybackStream(Object routeObj, int stream) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setPlaybackStream(stream);
+        }
+
+        public static void setVolume(Object routeObj, int volume) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolume(volume);
+        }
+
+        public static void setVolumeMax(Object routeObj, int volumeMax) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeMax(volumeMax);
+        }
+
+        public static void setVolumeHandling(Object routeObj, int volumeHandling) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeHandling(volumeHandling);
+        }
+
+        public static void setVolumeCallback(Object routeObj, Object volumeCallbackObj) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeCallback(
+                    (android.media.MediaRouter.VolumeCallback)volumeCallbackObj);
+        }
+
+        public static void setRemoteControlClient(Object routeObj, Object rccObj) {
+            ((android.media.MediaRouter.UserRouteInfo)routeObj).setRemoteControlClient(
+                    (android.media.RemoteControlClient)rccObj);
+        }
+    }
+
+    public static final class RouteCategory {
+        public static CharSequence getName(Object categoryObj, Context context) {
+            return ((android.media.MediaRouter.RouteCategory)categoryObj).getName(context);
+        }
+
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        public static List getRoutes(Object categoryObj) {
+            ArrayList out = new ArrayList();
+            ((android.media.MediaRouter.RouteCategory)categoryObj).getRoutes(out);
+            return out;
+        }
+
+        public static int getSupportedTypes(Object categoryObj) {
+            return ((android.media.MediaRouter.RouteCategory)categoryObj).getSupportedTypes();
+        }
+
+        public static boolean isGroupable(Object categoryObj) {
+            return ((android.media.MediaRouter.RouteCategory)categoryObj).isGroupable();
+        }
+    }
+
+    public static interface Callback {
+        public void onRouteSelected(int type, Object routeObj);
+        public void onRouteUnselected(int type, Object routeObj);
+        public void onRouteAdded(Object routeObj);
+        public void onRouteRemoved(Object routeObj);
+        public void onRouteChanged(Object routeObj);
+        public void onRouteGrouped(Object routeObj, Object groupObj, int index);
+        public void onRouteUngrouped(Object routeObj, Object groupObj);
+        public void onRouteVolumeChanged(Object routeObj);
+    }
+
+    public static interface VolumeCallback {
+        public void onVolumeSetRequest(Object routeObj, int volume);
+        public void onVolumeUpdateRequest(Object routeObj, int direction);
+    }
+
+    /**
+     * Workaround for limitations of selectRoute() on JB and JB MR1.
+     * Do not use on JB MR2 and above.
+     */
+    public static final class SelectRouteWorkaround {
+        private Method mSelectRouteIntMethod;
+
+        public SelectRouteWorkaround() {
+            if (Build.VERSION.SDK_INT < 16 || Build.VERSION.SDK_INT > 17) {
+                throw new UnsupportedOperationException();
+            }
+            try {
+                mSelectRouteIntMethod = android.media.MediaRouter.class.getMethod(
+                        "selectRouteInt", int.class, android.media.MediaRouter.RouteInfo.class);
+            } catch (NoSuchMethodException ex) {
+            }
+        }
+
+        public void selectRoute(Object routerObj, int types, Object routeObj) {
+            android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
+            android.media.MediaRouter.RouteInfo route =
+                    (android.media.MediaRouter.RouteInfo)routeObj;
+
+            int routeTypes = route.getSupportedTypes();
+            if ((routeTypes & ROUTE_TYPE_USER) == 0) {
+                // Handle non-user routes.
+                // On JB and JB MR1, the selectRoute() API only supports programmatically
+                // selecting user routes.  So instead we rely on the hidden selectRouteInt()
+                // method on these versions of the platform.
+                // This limitation was removed in JB MR2.
+                if (mSelectRouteIntMethod != null) {
+                    try {
+                        mSelectRouteIntMethod.invoke(router, types, route);
+                        return; // success!
+                    } catch (IllegalAccessException ex) {
+                        Log.w(TAG, "Cannot programmatically select non-user route.  "
+                                + "Media routing may not work.", ex);
+                    } catch (InvocationTargetException ex) {
+                        Log.w(TAG, "Cannot programmatically select non-user route.  "
+                                + "Media routing may not work.", ex);
+                    }
+                } else {
+                    Log.w(TAG, "Cannot programmatically select non-user route "
+                            + "because the platform is missing the selectRouteInt() "
+                            + "method.  Media routing may not work.");
+                }
+            }
+
+            // Default handling.
+            router.selectRoute(types, route);
+        }
+    }
+
+    /**
+     * Workaround the fact that the getDefaultRoute() method does not exist in JB and JB MR1.
+     * Do not use on JB MR2 and above.
+     */
+    public static final class GetDefaultRouteWorkaround {
+        private Method mGetSystemAudioRouteMethod;
+
+        public GetDefaultRouteWorkaround() {
+            if (Build.VERSION.SDK_INT < 16 || Build.VERSION.SDK_INT > 17) {
+                throw new UnsupportedOperationException();
+            }
+            try {
+                mGetSystemAudioRouteMethod =
+                        android.media.MediaRouter.class.getMethod("getSystemAudioRoute");
+            } catch (NoSuchMethodException ex) {
+            }
+        }
+
+        public Object getDefaultRoute(Object routerObj) {
+            android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
+
+            if (mGetSystemAudioRouteMethod != null) {
+                try {
+                    return mGetSystemAudioRouteMethod.invoke(router);
+                } catch (IllegalAccessException ex) {
+                } catch (InvocationTargetException ex) {
+                }
+            }
+
+            // Could not find the method or it does not work.
+            // Return the first route and hope for the best.
+            return router.getRouteAt(0);
+        }
+    }
+
+    static class CallbackProxy<T extends Callback>
+            extends android.media.MediaRouter.Callback {
+        protected final T mCallback;
+
+        public CallbackProxy(T callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void onRouteSelected(android.media.MediaRouter router,
+                int type, android.media.MediaRouter.RouteInfo route) {
+            mCallback.onRouteSelected(type, route);
+        }
+
+        @Override
+        public void onRouteUnselected(android.media.MediaRouter router,
+                int type, android.media.MediaRouter.RouteInfo route) {
+            mCallback.onRouteUnselected(type, route);
+        }
+
+        @Override
+        public void onRouteAdded(android.media.MediaRouter router,
+                android.media.MediaRouter.RouteInfo route) {
+            mCallback.onRouteAdded(route);
+        }
+
+        @Override
+        public void onRouteRemoved(android.media.MediaRouter router,
+                android.media.MediaRouter.RouteInfo route) {
+            mCallback.onRouteRemoved(route);
+        }
+
+        @Override
+        public void onRouteChanged(android.media.MediaRouter router,
+                android.media.MediaRouter.RouteInfo route) {
+            mCallback.onRouteChanged(route);
+        }
+
+        @Override
+        public void onRouteGrouped(android.media.MediaRouter router,
+                android.media.MediaRouter.RouteInfo route,
+                android.media.MediaRouter.RouteGroup group, int index) {
+            mCallback.onRouteGrouped(route, group, index);
+        }
+
+        @Override
+        public void onRouteUngrouped(android.media.MediaRouter router,
+                android.media.MediaRouter.RouteInfo route,
+                android.media.MediaRouter.RouteGroup group) {
+            mCallback.onRouteUngrouped(route, group);
+        }
+
+        @Override
+        public void onRouteVolumeChanged(android.media.MediaRouter router,
+                android.media.MediaRouter.RouteInfo route) {
+            mCallback.onRouteVolumeChanged(route);
+        }
+    }
+
+    static class VolumeCallbackProxy<T extends VolumeCallback>
+            extends android.media.MediaRouter.VolumeCallback {
+        protected final T mCallback;
+
+        public VolumeCallbackProxy(T callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void onVolumeSetRequest(android.media.MediaRouter.RouteInfo route,
+                int volume) {
+            mCallback.onVolumeSetRequest(route, volume);
+        }
+
+        @Override
+        public void onVolumeUpdateRequest(android.media.MediaRouter.RouteInfo route,
+                int direction) {
+            mCallback.onVolumeUpdateRequest(route, direction);
+        }
+    }
+}
diff --git a/v4/jellybean/android/support/v4/view/ViewCompatJB.java b/v4/jellybean/android/support/v4/view/ViewCompatJB.java
index 7456d28..8e90a16 100644
--- a/v4/jellybean/android/support/v4/view/ViewCompatJB.java
+++ b/v4/jellybean/android/support/v4/view/ViewCompatJB.java
@@ -69,4 +69,16 @@
     public static ViewParent getParentForAccessibility(View view) {
         return view.getParentForAccessibility();
     }
+
+    public static int getMinimumWidth(View view) {
+        return view.getMinimumWidth();
+    }
+
+    public static int getMinimumHeight(View view) {
+        return view.getMinimumHeight();
+    }
+
+    public static void requestApplyInsets(View view) {
+        view.requestFitSystemWindows();
+    }
 }
diff --git a/v4/jellybean/android/support/v4/view/ViewPropertyAnimatorCompatJB.java b/v4/jellybean/android/support/v4/view/ViewPropertyAnimatorCompatJB.java
new file mode 100644
index 0000000..d53bf2a
--- /dev/null
+++ b/v4/jellybean/android/support/v4/view/ViewPropertyAnimatorCompatJB.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.view;
+
+import android.view.View;
+
+class ViewPropertyAnimatorCompatJB {
+
+    public static void withStartAction(View view, Runnable runnable) {
+        view.animate().withStartAction(runnable);
+    }
+
+    public static void withEndAction(View view, Runnable runnable) {
+        view.animate().withEndAction(runnable);
+    }
+
+    public static void withLayer(View view) {
+        view.animate().withLayer();
+    }
+
+}
diff --git a/v4/kitkat/android/support/v4/app/ActivityManagerCompatKitKat.java b/v4/kitkat/android/support/v4/app/ActivityManagerCompatKitKat.java
new file mode 100644
index 0000000..3f889f6
--- /dev/null
+++ b/v4/kitkat/android/support/v4/app/ActivityManagerCompatKitKat.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.ActivityManager;
+
+class ActivityManagerCompatKitKat {
+    public static boolean isLowRamDevice(ActivityManager am) {
+        return am.isLowRamDevice();
+    }
+}
diff --git a/v4/kitkat/android/support/v4/app/NotificationCompatKitKat.java b/v4/kitkat/android/support/v4/app/NotificationCompatKitKat.java
new file mode 100644
index 0000000..0ef0927
--- /dev/null
+++ b/v4/kitkat/android/support/v4/app/NotificationCompatKitKat.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.util.SparseArray;
+import android.widget.RemoteViews;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class NotificationCompatKitKat {
+    public static class Builder implements NotificationBuilderWithBuilderAccessor,
+            NotificationBuilderWithActions {
+        private Notification.Builder b;
+        private Bundle mExtras;
+        private List<Bundle> mActionExtrasList = new ArrayList<Bundle>();
+
+        public Builder(Context context, Notification n,
+                CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
+                RemoteViews tickerView, int number,
+                PendingIntent contentIntent, PendingIntent fullScreenIntent, Bitmap largeIcon,
+                int progressMax, int progress, boolean progressIndeterminate,
+                boolean useChronometer, int priority, CharSequence subText, boolean localOnly,
+                ArrayList<String> people, Bundle extras, String groupKey, boolean groupSummary,
+                String sortKey) {
+            b = new Notification.Builder(context)
+                .setWhen(n.when)
+                .setSmallIcon(n.icon, n.iconLevel)
+                .setContent(n.contentView)
+                .setTicker(n.tickerText, tickerView)
+                .setSound(n.sound, n.audioStreamType)
+                .setVibrate(n.vibrate)
+                .setLights(n.ledARGB, n.ledOnMS, n.ledOffMS)
+                .setOngoing((n.flags & Notification.FLAG_ONGOING_EVENT) != 0)
+                .setOnlyAlertOnce((n.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)
+                .setAutoCancel((n.flags & Notification.FLAG_AUTO_CANCEL) != 0)
+                .setDefaults(n.defaults)
+                .setContentTitle(contentTitle)
+                .setContentText(contentText)
+                .setSubText(subText)
+                .setContentInfo(contentInfo)
+                .setContentIntent(contentIntent)
+                .setDeleteIntent(n.deleteIntent)
+                .setFullScreenIntent(fullScreenIntent,
+                        (n.flags & Notification.FLAG_HIGH_PRIORITY) != 0)
+                .setLargeIcon(largeIcon)
+                .setNumber(number)
+                .setUsesChronometer(useChronometer)
+                .setPriority(priority)
+                .setProgress(progressMax, progress, progressIndeterminate);
+            mExtras = new Bundle();
+            if (extras != null) {
+                mExtras.putAll(extras);
+            }
+            if (people != null && !people.isEmpty()) {
+                mExtras.putStringArray(Notification.EXTRA_PEOPLE,
+                        people.toArray(new String[people.size()]));
+            }
+            if (localOnly) {
+                mExtras.putBoolean(NotificationCompatJellybean.EXTRA_LOCAL_ONLY, true);
+            }
+            if (groupKey != null) {
+                mExtras.putString(NotificationCompatJellybean.EXTRA_GROUP_KEY, groupKey);
+                if (groupSummary) {
+                    mExtras.putBoolean(NotificationCompatJellybean.EXTRA_GROUP_SUMMARY, true);
+                } else {
+                    mExtras.putBoolean(NotificationCompatJellybean.EXTRA_USE_SIDE_CHANNEL, true);
+                }
+            }
+            if (sortKey != null) {
+                mExtras.putString(NotificationCompatJellybean.EXTRA_SORT_KEY, sortKey);
+            }
+        }
+
+        @Override
+        public void addAction(NotificationCompatBase.Action action) {
+            mActionExtrasList.add(NotificationCompatJellybean.writeActionAndGetExtras(b, action));
+        }
+
+        @Override
+        public Notification.Builder getBuilder() {
+            return b;
+        }
+
+        public Notification build() {
+            SparseArray<Bundle> actionExtrasMap = NotificationCompatJellybean.buildActionExtrasMap(
+                    mActionExtrasList);
+            if (actionExtrasMap != null) {
+                // Add the action extras sparse array if any action was added with extras.
+                mExtras.putSparseParcelableArray(
+                        NotificationCompatJellybean.EXTRA_ACTION_EXTRAS, actionExtrasMap);
+            }
+            b.setExtras(mExtras);
+            return b.build();
+        }
+    }
+
+    public static Bundle getExtras(Notification notif) {
+        return notif.extras;
+    }
+
+    public static int getActionCount(Notification notif) {
+        return notif.actions != null ? notif.actions.length : 0;
+    }
+
+    public static NotificationCompatBase.Action getAction(Notification notif,
+            int actionIndex, NotificationCompatBase.Action.Factory factory,
+            RemoteInputCompatBase.RemoteInput.Factory remoteInputFactory) {
+        Notification.Action action = notif.actions[actionIndex];
+        Bundle actionExtras = null;
+        SparseArray<Bundle> actionExtrasMap = notif.extras.getSparseParcelableArray(
+                NotificationCompatJellybean.EXTRA_ACTION_EXTRAS);
+        if (actionExtrasMap != null) {
+            actionExtras = actionExtrasMap.get(actionIndex);
+        }
+        return NotificationCompatJellybean.readAction(factory, remoteInputFactory,
+                action.icon, action.title, action.actionIntent, actionExtras);
+    }
+
+    public static boolean getLocalOnly(Notification notif) {
+        return notif.extras.getBoolean(NotificationCompatJellybean.EXTRA_LOCAL_ONLY);
+    }
+
+    public static String getGroup(Notification notif) {
+        return notif.extras.getString(NotificationCompatJellybean.EXTRA_GROUP_KEY);
+    }
+
+    public static boolean isGroupSummary(Notification notif) {
+        return notif.extras.getBoolean(NotificationCompatJellybean.EXTRA_GROUP_SUMMARY);
+    }
+
+    public static String getSortKey(Notification notif) {
+        return notif.extras.getString(NotificationCompatJellybean.EXTRA_SORT_KEY);
+    }
+}
diff --git a/v4/kitkat/android/support/v4/graphics/BitmapCompatKitKat.java b/v4/kitkat/android/support/v4/graphics/BitmapCompatKitKat.java
new file mode 100644
index 0000000..2d4a66e
--- /dev/null
+++ b/v4/kitkat/android/support/v4/graphics/BitmapCompatKitKat.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.graphics;
+
+import android.graphics.Bitmap;
+
+/**
+ * Implementation of BitmapCompat that can use KitKat APIs.
+ */
+class BitmapCompatKitKat {
+
+    static int getAllocationByteCount(Bitmap bitmap) {
+        return bitmap.getAllocationByteCount();
+    }
+
+}
diff --git a/v4/kitkat/android/support/v4/view/ViewPropertyAnimatorCompatKK.java b/v4/kitkat/android/support/v4/view/ViewPropertyAnimatorCompatKK.java
new file mode 100644
index 0000000..5439972
--- /dev/null
+++ b/v4/kitkat/android/support/v4/view/ViewPropertyAnimatorCompatKK.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.view;
+
+import android.animation.ValueAnimator;
+import android.view.View;
+
+class ViewPropertyAnimatorCompatKK {
+
+    public static void setUpdateListener(final View view,
+            final ViewPropertyAnimatorUpdateListener listener) {
+        view.animate().setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator valueAnimator) {
+                listener.onAnimationUpdate(view);
+            }
+        });
+    }
+
+}
diff --git a/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java b/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java
index 618a02d..4f77b8b 100644
--- a/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java
+++ b/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java
@@ -22,11 +22,77 @@
  * KitKat-specific AccessibilityNodeInfo API implementation.
  */
 class AccessibilityNodeInfoCompatKitKat {
-    public static int getLiveRegion(Object info) {
+    static int getLiveRegion(Object info) {
         return ((AccessibilityNodeInfo) info).getLiveRegion();
     }
 
-    public static void setLiveRegion(Object info, int mode) {
+    static void setLiveRegion(Object info, int mode) {
         ((AccessibilityNodeInfo) info).setLiveRegion(mode);
     }
+
+    static Object getCollectionInfo(Object info) {
+        return ((AccessibilityNodeInfo) info).getCollectionInfo();
+    }
+
+    static Object getCollectionItemInfo(Object info) {
+        return ((AccessibilityNodeInfo) info).getCollectionItemInfo();
+    }
+
+    static Object getRangeInfo(Object info) {
+        return ((AccessibilityNodeInfo) info).getRangeInfo();
+    }
+
+    static class CollectionInfo {
+        static int getColumnCount(Object info) {
+            return ((AccessibilityNodeInfo.CollectionInfo) info).getColumnCount();
+        }
+
+        static int getRowCount(Object info) {
+            return ((AccessibilityNodeInfo.CollectionInfo) info).getRowCount();
+        }
+
+        static boolean isHierarchical(Object info) {
+            return ((AccessibilityNodeInfo.CollectionInfo) info).isHierarchical();
+        }
+    }
+
+    static class CollectionItemInfo {
+        static int getColumnIndex(Object info) {
+            return ((AccessibilityNodeInfo.CollectionItemInfo) info).getColumnIndex();
+        }
+
+        static int getColumnSpan(Object info) {
+            return ((AccessibilityNodeInfo.CollectionItemInfo) info).getColumnSpan();
+        }
+
+        static int getRowIndex(Object info) {
+            return ((AccessibilityNodeInfo.CollectionItemInfo) info).getRowIndex();
+        }
+
+        static int getRowSpan(Object info) {
+            return ((AccessibilityNodeInfo.CollectionItemInfo) info).getRowSpan();
+        }
+
+        static boolean isHeading(Object info) {
+            return ((AccessibilityNodeInfo.CollectionItemInfo) info).isHeading();
+        }
+    }
+
+    static class RangeInfo {
+        static float getCurrent(Object info) {
+            return ((AccessibilityNodeInfo.RangeInfo) info).getCurrent();
+        }
+
+        static float getMax(Object info) {
+            return ((AccessibilityNodeInfo.RangeInfo) info).getMax();
+        }
+
+        static float getMin(Object info) {
+            return ((AccessibilityNodeInfo.RangeInfo) info).getMin();
+        }
+
+        static int getType(Object info) {
+            return ((AccessibilityNodeInfo.RangeInfo) info).getType();
+        }
+    }
 }
diff --git a/v4/kitkat/android/support/v4/widget/PopupWindowCompatKitKat.java b/v4/kitkat/android/support/v4/widget/PopupWindowCompatKitKat.java
new file mode 100644
index 0000000..4333f4a
--- /dev/null
+++ b/v4/kitkat/android/support/v4/widget/PopupWindowCompatKitKat.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.widget;
+
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.widget.ListPopupWindow;
+import android.widget.PopupWindow;
+
+/**
+ * Implementation of PopupWindow compatibility that can call KitKat APIs.
+ */
+class PopupWindowCompatKitKat {
+    public static void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff,
+            int gravity) {
+        popup.showAsDropDown(anchor, xoff, yoff, gravity);
+    }
+}
diff --git a/v4/tests/java/android/support/v4/widget/DonutScrollerCompatTest.java b/v4/tests/java/android/support/v4/widget/DonutScrollerCompatTest.java
new file mode 100644
index 0000000..dfa7e68
--- /dev/null
+++ b/v4/tests/java/android/support/v4/widget/DonutScrollerCompatTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.widget;
+
+import android.os.Build;
+
+public class DonutScrollerCompatTest extends ScrollerCompatTestBase {
+
+    public DonutScrollerCompatTest() {
+        super(Build.VERSION_CODES.DONUT);
+    }
+}
diff --git a/v4/tests/java/android/support/v4/widget/GingerbreadScrollerCompatTest.java b/v4/tests/java/android/support/v4/widget/GingerbreadScrollerCompatTest.java
new file mode 100644
index 0000000..d4420fe
--- /dev/null
+++ b/v4/tests/java/android/support/v4/widget/GingerbreadScrollerCompatTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.widget;
+
+import android.os.Build;
+
+public class GingerbreadScrollerCompatTest extends ScrollerCompatTestBase {
+
+    public GingerbreadScrollerCompatTest() {
+        super(9);
+    }
+}
diff --git a/v4/tests/java/android/support/v4/widget/IcsScrollerCompatTest.java b/v4/tests/java/android/support/v4/widget/IcsScrollerCompatTest.java
new file mode 100644
index 0000000..7d0e9a5
--- /dev/null
+++ b/v4/tests/java/android/support/v4/widget/IcsScrollerCompatTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.widget;
+
+import android.os.Build;
+
+public class IcsScrollerCompatTest extends ScrollerCompatTestBase {
+
+    public IcsScrollerCompatTest() {
+        super(14);
+    }
+}
diff --git a/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java b/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java
new file mode 100644
index 0000000..3efad25
--- /dev/null
+++ b/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.widget;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+
+abstract public class ScrollerCompatTestBase extends AndroidTestCase {
+
+    private static final boolean DEBUG = false;
+
+    private final String TAG;
+
+    private final int mApiLevel;
+
+    private ScrollerCompat mScroller;
+
+    public ScrollerCompatTestBase(int apiLevel) {
+        mApiLevel = apiLevel;
+        TAG = "ScrollerCompatTest api:" + apiLevel;
+    }
+
+    protected void createScroller(Interpolator interpolator)
+            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
+            InstantiationException {
+        Constructor<ScrollerCompat> constructor = ScrollerCompat.class
+                .getDeclaredConstructor(int.class, Context.class, Interpolator.class);
+        constructor.setAccessible(true);
+        mScroller = constructor.newInstance(mApiLevel, getContext(), interpolator);
+    }
+
+    public void testTargetReached() throws Throwable {
+        if (DEBUG) {
+            Log.d(TAG, "testing if target is reached");
+        }
+        createScroller(null);
+        mScroller.fling(0, 0, 0, 1000,
+                Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
+        int target = mScroller.getFinalY();
+        while (mScroller.computeScrollOffset()) {
+            Thread.sleep(100);
+        }
+        assertEquals("given enough time, scroller should reach target position", target,
+                mScroller.getCurrY());
+    }
+
+    public void testAbort() throws Throwable {
+        if (DEBUG) {
+            Log.d(TAG, "testing abort");
+        }
+        createScroller(null);
+        mScroller.fling(0, 0, 0, 10000,
+                Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
+        assertTrue("Scroller should have some offset", mScroller.computeScrollOffset());
+        mScroller.abortAnimation();
+        assertFalse("Scroller should clear offset after being aborted",
+                mScroller.computeScrollOffset());
+    }
+}
diff --git a/v7/Android.mk b/v7/Android.mk
new file mode 100644
index 0000000..14ff0aa
--- /dev/null
+++ b/v7/Android.mk
@@ -0,0 +1,16 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/v7/appcompat/Android.mk b/v7/appcompat/Android.mk
index 82e4816..a9ab60d 100644
--- a/v7/appcompat/Android.mk
+++ b/v7/appcompat/Android.mk
@@ -20,7 +20,7 @@
 # in their makefiles to include the resources in their package.
 include $(CLEAR_VARS)
 LOCAL_MODULE := android-support-v7-appcompat
-LOCAL_SDK_VERSION := 19
+LOCAL_SDK_VERSION := current
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_JAVA_LIBRARIES += android-support-v4
diff --git a/v7/appcompat/THEMES.txt b/v7/appcompat/THEMES.txt
new file mode 100644
index 0000000..aff5d64
--- /dev/null
+++ b/v7/appcompat/THEMES.txt
@@ -0,0 +1,65 @@
+==========================================
+
+       appcompat themes structure
+
+==========================================
+
+The themes structure in appcompat is complex since we
+need to deal with attributes and styles which are available
+at differing platform versions.
+
+The structure is as so:
+
+<- denote inherits from.
+
+---------------------
+Fourth level themes (Platform)
+---------------------
+These are the base themes and allow us to switch out the framework base
+theme automatically. These should setup the framework theme ready so
+that they are the theme is mostly the same on all platform versions.
+
+Example:
+values-v11/
+Platform.AppCompat    <-   android:Theme.Holo
+
+
+---------------------
+Third level
+---------------------
+Platform specific themes which inherit from the previous platform version. These allows us
+to build up the theme across platforms. These themes do most of the work.
+
+Example:
+(various)                         values/                        values-v21/
+Platform.AppCompat    <-    Base.V7.Theme.AppCompat   <-   Base.V21.Theme.AppCompat
+
+
+---------------------
+Second level
+---------------------
+There are the themes which are pointers to the correct third level theme.
+They can also be used to set attributes for that specific platform (and platforms up until the
+next declaration).
+
+Because of this, every time there is a third level theme set, there should be a second level
+theme pointing to it. This is so that devices do not use themes from newer and unavailable
+platforms.
+ 
+Names: Theme.Base.AppCompat, Theme.Base.AppCompat.Light and Theme.Base.AppCompat.Light.DarkActionBar
+
+Example:
+values-v21/
+Theme.V21.Base.AppCompat   <-  Theme.Base.AppCompat
+
+---------------------
+Top level themes
+---------------------
+These are the themes to be used directly by developers. These inherit from
+the second level relevant second level theme below.
+
+Names: Theme.AppCompat, Theme.AppCompat.Light and Theme.AppCompat.Light.DarkActionBar
+
+Example:
+values/
+Theme.AppCompat    <-    Theme.Base.AppCompat
diff --git a/v7/appcompat/build.gradle b/v7/appcompat/build.gradle
index d8aff0c..c1f91ec 100644
--- a/v7/appcompat/build.gradle
+++ b/v7/appcompat/build.gradle
@@ -7,8 +7,7 @@
 }
 
 android {
-    compileSdkVersion 19
-    buildToolsVersion "19.0.1"
+    compileSdkVersion 'current'
 
     sourceSets {
         main.manifest.srcFile 'AndroidManifest.xml'
@@ -28,4 +27,4 @@
         // TODO: fix errors and reenable.
         abortOnError false
     }
-}
\ No newline at end of file
+}
diff --git a/v7/appcompat/project.properties b/v7/appcompat/project.properties
index dfa4dd0..91d2b02 100644
--- a/v7/appcompat/project.properties
+++ b/v7/appcompat/project.properties
@@ -11,5 +11,5 @@
 #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
 
 # Project target.
-target=android-16
+target=android-19
 android.library=true
diff --git a/v7/appcompat/res/color/abc_background_cache_hint_selector_material_dark.xml b/v7/appcompat/res/color/abc_background_cache_hint_selector_material_dark.xml
new file mode 100644
index 0000000..e016076
--- /dev/null
+++ b/v7/appcompat/res/color/abc_background_cache_hint_selector_material_dark.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_accelerated="false" android:color="@color/background_material_dark" />
+    <item android:color="@android:color/transparent" />
+</selector>
diff --git a/v7/appcompat/res/color/abc_background_cache_hint_selector_material_light.xml b/v7/appcompat/res/color/abc_background_cache_hint_selector_material_light.xml
new file mode 100644
index 0000000..290faf1
--- /dev/null
+++ b/v7/appcompat/res/color/abc_background_cache_hint_selector_material_light.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_accelerated="false" android:color="@color/background_material_light" />
+    <item android:color="@android:color/transparent" />
+</selector>
diff --git a/v7/appcompat/res/color/abc_primary_text_disable_only_material_dark.xml b/v7/appcompat/res/color/abc_primary_text_disable_only_material_dark.xml
new file mode 100644
index 0000000..724c255
--- /dev/null
+++ b/v7/appcompat/res/color/abc_primary_text_disable_only_material_dark.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:color="@color/bright_foreground_disabled_material_dark"/>
+    <item android:color="@color/bright_foreground_material_dark"/>
+</selector>
diff --git a/v7/appcompat/res/color/abc_primary_text_disable_only_material_light.xml b/v7/appcompat/res/color/abc_primary_text_disable_only_material_light.xml
new file mode 100644
index 0000000..7395e68
--- /dev/null
+++ b/v7/appcompat/res/color/abc_primary_text_disable_only_material_light.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:color="@color/bright_foreground_disabled_material_light"/>
+    <item android:color="@color/bright_foreground_material_light"/>
+</selector>
diff --git a/v7/appcompat/res/color/abc_primary_text_material_dark.xml b/v7/appcompat/res/color/abc_primary_text_material_dark.xml
new file mode 100644
index 0000000..003a965
--- /dev/null
+++ b/v7/appcompat/res/color/abc_primary_text_material_dark.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:color="@color/primary_text_disabled_default_material_dark"/>
+    <item android:color="@color/primary_text_default_material_dark"/>
+</selector>
diff --git a/v7/appcompat/res/color/abc_primary_text_material_light.xml b/v7/appcompat/res/color/abc_primary_text_material_light.xml
new file mode 100644
index 0000000..9ee11eb
--- /dev/null
+++ b/v7/appcompat/res/color/abc_primary_text_material_light.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:color="@color/primary_text_disabled_default_material_dark"/>
+    <item android:color="@color/primary_text_default_material_light"/>
+</selector>
diff --git a/v7/appcompat/res/color/abc_search_url_text.xml b/v7/appcompat/res/color/abc_search_url_text.xml
new file mode 100644
index 0000000..0631d5d
--- /dev/null
+++ b/v7/appcompat/res/color/abc_search_url_text.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:color="@color/abc_search_url_text_pressed"/>
+    <item android:state_selected="true" android:color="@color/abc_search_url_text_selected"/>
+    <item android:color="@color/abc_search_url_text_normal"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_search_url_text_holo.xml b/v7/appcompat/res/color/abc_search_url_text_holo.xml
deleted file mode 100644
index c4579fa..0000000
--- a/v7/appcompat/res/color/abc_search_url_text_holo.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-  
-          http://www.apache.org/licenses/LICENSE-2.0
-  
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_pressed="true" android:color="@color/abc_search_url_text_pressed"/>
-    <item android:state_selected="true" android:color="@color/abc_search_url_text_selected"/>
-    <item android:color="@color/abc_search_url_text_normal"/> <!-- not selected -->
-</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ab_bottom_solid_dark_holo.9.png b/v7/appcompat/res/drawable-hdpi/abc_ab_bottom_solid_dark_holo.9.png
deleted file mode 100644
index 769463b..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ab_bottom_solid_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ab_bottom_solid_light_holo.9.png b/v7/appcompat/res/drawable-hdpi/abc_ab_bottom_solid_light_holo.9.png
deleted file mode 100644
index 7305047..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ab_bottom_solid_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ab_bottom_transparent_dark_holo.9.png b/v7/appcompat/res/drawable-hdpi/abc_ab_bottom_transparent_dark_holo.9.png
deleted file mode 100644
index 712a551..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ab_bottom_transparent_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ab_bottom_transparent_light_holo.9.png b/v7/appcompat/res/drawable-hdpi/abc_ab_bottom_transparent_light_holo.9.png
deleted file mode 100644
index bf3b943..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ab_bottom_transparent_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ab_solid_dark_holo.9.png b/v7/appcompat/res/drawable-hdpi/abc_ab_solid_dark_holo.9.png
deleted file mode 100644
index cbbaec5..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ab_solid_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ab_solid_light_holo.9.png b/v7/appcompat/res/drawable-hdpi/abc_ab_solid_light_holo.9.png
deleted file mode 100644
index af917e5..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ab_solid_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ab_stacked_solid_dark_holo.9.png b/v7/appcompat/res/drawable-hdpi/abc_ab_stacked_solid_dark_holo.9.png
deleted file mode 100644
index 0520e5a..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ab_stacked_solid_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ab_stacked_solid_light_holo.9.png b/v7/appcompat/res/drawable-hdpi/abc_ab_stacked_solid_light_holo.9.png
deleted file mode 100644
index e3e3f93..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ab_stacked_solid_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ab_stacked_transparent_dark_holo.9.png b/v7/appcompat/res/drawable-hdpi/abc_ab_stacked_transparent_dark_holo.9.png
deleted file mode 100644
index 1e39572..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ab_stacked_transparent_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ab_stacked_transparent_light_holo.9.png b/v7/appcompat/res/drawable-hdpi/abc_ab_stacked_transparent_light_holo.9.png
deleted file mode 100644
index a16db85..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ab_stacked_transparent_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ab_transparent_dark_holo.9.png b/v7/appcompat/res/drawable-hdpi/abc_ab_transparent_dark_holo.9.png
deleted file mode 100644
index 0eff695..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ab_transparent_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ab_transparent_light_holo.9.png b/v7/appcompat/res/drawable-hdpi/abc_ab_transparent_light_holo.9.png
deleted file mode 100644
index 219b170..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ab_transparent_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_cab_background_bottom_holo_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_cab_background_bottom_holo_dark.9.png
deleted file mode 100644
index 1d836f6..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_cab_background_bottom_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_cab_background_bottom_holo_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_cab_background_bottom_holo_light.9.png
deleted file mode 100644
index 5818666..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_cab_background_bottom_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_cab_background_top_holo_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_cab_background_top_holo_dark.9.png
deleted file mode 100644
index 564fb34..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_cab_background_top_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_cab_background_top_holo_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_cab_background_top_holo_light.9.png
deleted file mode 100644
index ae21b76..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_cab_background_top_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_holo_dark.png b/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_holo_dark.png
deleted file mode 100644
index 897a1c1..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_holo_light.png b/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_holo_light.png
deleted file mode 100644
index 0c89f71..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_material_dark.png b/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_material_dark.png
new file mode 100644
index 0000000..7645c50
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_material_light.png b/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_material_light.png
new file mode 100644
index 0000000..923cfa5
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png
new file mode 100644
index 0000000..f0910d8
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_clear_disabled.png b/v7/appcompat/res/drawable-hdpi/abc_ic_clear_disabled.png
deleted file mode 100644
index d97c342..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_clear_disabled.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_clear_material_dark.png b/v7/appcompat/res/drawable-hdpi/abc_ic_clear_material_dark.png
new file mode 100644
index 0000000..ba1d778
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_clear_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_clear_material_light.png b/v7/appcompat/res/drawable-hdpi/abc_ic_clear_material_light.png
new file mode 100644
index 0000000..3aa175e
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_clear_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png
new file mode 100644
index 0000000..3813563
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_clear_normal.png b/v7/appcompat/res/drawable-hdpi/abc_ic_clear_normal.png
deleted file mode 100644
index 33ad8d4..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_clear_normal.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_clear_search_api_disabled_holo_light.png b/v7/appcompat/res/drawable-hdpi/abc_ic_clear_search_api_disabled_holo_light.png
deleted file mode 100644
index 3edbd74..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_clear_search_api_disabled_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_clear_search_api_holo_light.png b/v7/appcompat/res/drawable-hdpi/abc_ic_clear_search_api_holo_light.png
deleted file mode 100644
index 90db01b..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_clear_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_holo_dark.png b/v7/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_holo_dark.png
deleted file mode 100644
index 83f36a9..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_holo_light.png b/v7/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_holo_light.png
deleted file mode 100644
index a3cc21e..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_material_dark.png b/v7/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_material_dark.png
new file mode 100644
index 0000000..a953640
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_material_light.png b/v7/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_material_light.png
new file mode 100644
index 0000000..56fff2c
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_mtrl_alpha.png
new file mode 100644
index 0000000..47263ea
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_commit_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_go.png b/v7/appcompat/res/drawable-hdpi/abc_ic_go.png
deleted file mode 100644
index 97b825e..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_go.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_holo_light.png b/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_holo_light.png
deleted file mode 100644
index 7e1ba2a..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_material_dark.png b/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_material_dark.png
new file mode 100644
index 0000000..4117e91
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_material_light.png b/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_material_light.png
new file mode 100644
index 0000000..342039f
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png
new file mode 100644
index 0000000..aa23c59
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_material_dark.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_material_dark.png
new file mode 100644
index 0000000..e5d56c2
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_material_light.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_material_light.png
new file mode 100644
index 0000000..3efd55e
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
new file mode 100644
index 0000000..1ba1295
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_normal_holo_dark.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_normal_holo_dark.png
deleted file mode 100644
index 2abc458..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_normal_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_normal_holo_light.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_normal_holo_light.png
deleted file mode 100644
index bb6aef1..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_normal_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_holo_dark.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_holo_dark.png
deleted file mode 100644
index 6f747c8..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_holo_light.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_holo_light.png
deleted file mode 100644
index 682b2fd..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_material_dark.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_material_dark.png
new file mode 100644
index 0000000..e9f570d
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_material_light.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_material_light.png
new file mode 100644
index 0000000..4e9f20a
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png
new file mode 100644
index 0000000..0eacedd
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_search.png b/v7/appcompat/res/drawable-hdpi/abc_ic_search.png
deleted file mode 100644
index bf8bd66..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_search.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_holo_light.png b/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_holo_light.png
deleted file mode 100644
index 72e207b..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_material_dark.png b/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_material_dark.png
new file mode 100644
index 0000000..b41ea33
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_material_light.png b/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_material_light.png
new file mode 100644
index 0000000..6e0cc95
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png
new file mode 100644
index 0000000..f7382d3
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search.png b/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search.png
deleted file mode 100644
index 66d14ae..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_holo_light.png b/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_holo_light.png
deleted file mode 100644
index 3481c98..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_material_dark.png b/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_material_dark.png
new file mode 100644
index 0000000..595f3bb
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_material_light.png b/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_material_light.png
new file mode 100644
index 0000000..d78cd90
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png
new file mode 100644
index 0000000..25b8935
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_list_pressed_holo_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_list_pressed_holo_dark.9.png
index 5654cd6..596accb 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_list_pressed_holo_dark.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_list_pressed_holo_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_list_pressed_holo_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_list_pressed_holo_light.9.png
index 5654cd6..2054530 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_list_pressed_holo_light.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_list_pressed_holo_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_popup_background_material_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_popup_background_material_dark.9.png
new file mode 100644
index 0000000..d725bde
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_popup_background_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_popup_background_material_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_popup_background_material_light.9.png
new file mode 100644
index 0000000..642ef90
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_popup_background_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_popup_background_mtrl_mult.9.png b/v7/appcompat/res/drawable-hdpi/abc_popup_background_mtrl_mult.9.png
new file mode 100644
index 0000000..385734e
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_popup_background_mtrl_mult.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_default_holo_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_default_holo_dark.9.png
deleted file mode 100644
index 88f8765..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_default_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_default_holo_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_default_holo_light.9.png
deleted file mode 100644
index fa68a13..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_default_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_disabled_holo_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_disabled_holo_dark.9.png
deleted file mode 100644
index 78c63cb..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_disabled_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_disabled_holo_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_disabled_holo_light.9.png
deleted file mode 100644
index e13a9f8..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_disabled_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_focused_holo_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_focused_holo_dark.9.png
deleted file mode 100644
index 26d2e16..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_focused_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_focused_holo_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_focused_holo_light.9.png
deleted file mode 100644
index 00ae92a..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_focused_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_pressed_holo_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_pressed_holo_dark.9.png
deleted file mode 100644
index dc20a8d..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_pressed_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_pressed_holo_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_pressed_holo_light.9.png
deleted file mode 100644
index 272a2a1..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_spinner_ab_pressed_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_spinner_checked_material_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_spinner_checked_material_dark.9.png
new file mode 100644
index 0000000..340e011
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_spinner_checked_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_spinner_checked_material_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_spinner_checked_material_light.9.png
new file mode 100644
index 0000000..3e3efe7
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_spinner_checked_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_spinner_default_material_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_spinner_default_material_dark.9.png
new file mode 100644
index 0000000..f7271bb
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_spinner_default_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_spinner_default_material_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_spinner_default_material_light.9.png
new file mode 100644
index 0000000..04fc9d8
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_spinner_default_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_spinner_mtrl_am_alpha.9.png
new file mode 100644
index 0000000..de7ac29
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_spinner_pressed_material_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_spinner_pressed_material_dark.9.png
new file mode 100644
index 0000000..340e011
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_spinner_pressed_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_spinner_pressed_material_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_spinner_pressed_material_light.9.png
new file mode 100644
index 0000000..3e3efe7
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_spinner_pressed_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_tab_indicator_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_tab_indicator_mtrl_alpha.9.png
new file mode 100644
index 0000000..21b2135
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_tab_indicator_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_tab_indicator_selected_material_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_tab_indicator_selected_material_dark.9.png
new file mode 100644
index 0000000..a952eb0
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_tab_indicator_selected_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_tab_indicator_selected_material_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_tab_indicator_selected_material_light.9.png
new file mode 100644
index 0000000..1660589
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_tab_indicator_selected_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_tab_selected_focused_holo.9.png b/v7/appcompat/res/drawable-hdpi/abc_tab_selected_focused_holo.9.png
deleted file mode 100644
index 673e3bf..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_tab_selected_focused_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_tab_selected_holo.9.png b/v7/appcompat/res/drawable-hdpi/abc_tab_selected_holo.9.png
deleted file mode 100644
index d57df98..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_tab_selected_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_tab_selected_pressed_holo.9.png b/v7/appcompat/res/drawable-hdpi/abc_tab_selected_pressed_holo.9.png
deleted file mode 100644
index 6278eef..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_tab_selected_pressed_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_tab_unselected_pressed_holo.9.png b/v7/appcompat/res/drawable-hdpi/abc_tab_unselected_pressed_holo.9.png
deleted file mode 100644
index aadc6f8..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_tab_unselected_pressed_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_activated_material_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_activated_material_dark.9.png
new file mode 100644
index 0000000..b772c13
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_textfield_activated_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_activated_material_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_activated_material_light.9.png
new file mode 100644
index 0000000..6352728
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_textfield_activated_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_activated_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_activated_mtrl_alpha.9.png
new file mode 100644
index 0000000..b9a81be
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_textfield_activated_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_default_disabled_material_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_default_disabled_material_dark.9.png
new file mode 100644
index 0000000..0bb0eb8
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_textfield_default_disabled_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_default_disabled_material_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_default_disabled_material_light.9.png
new file mode 100644
index 0000000..956ba08
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_textfield_default_disabled_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_default_material_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_default_material_dark.9.png
new file mode 100644
index 0000000..fc5cfcc
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_textfield_default_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_default_material_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_default_material_light.9.png
new file mode 100644
index 0000000..1d615f0
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_textfield_default_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_default_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_default_mtrl_alpha.9.png
new file mode 100644
index 0000000..3682629
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_textfield_default_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_activated_material_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_activated_material_dark.9.png
new file mode 100644
index 0000000..dc7cffc
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_activated_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_activated_material_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_activated_material_light.9.png
new file mode 100644
index 0000000..885636d
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_activated_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_activated_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_activated_mtrl_alpha.9.png
new file mode 100644
index 0000000..74d76d0
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_activated_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_default_holo_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_default_holo_dark.9.png
deleted file mode 100644
index 70c0e73..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_default_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_default_holo_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_default_holo_light.9.png
deleted file mode 100644
index 36e71d8..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_default_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_default_material_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_default_material_dark.9.png
new file mode 100644
index 0000000..699f3d4
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_default_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_default_material_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_default_material_light.9.png
new file mode 100644
index 0000000..a703c32
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_default_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_default_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_default_mtrl_alpha.9.png
new file mode 100644
index 0000000..8ac45b5
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_default_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_right_default_holo_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_right_default_holo_dark.9.png
deleted file mode 100644
index 4be4af5..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_right_default_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_right_default_holo_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_right_default_holo_light.9.png
deleted file mode 100644
index e72193f..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_right_default_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_right_selected_holo_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_right_selected_holo_dark.9.png
deleted file mode 100644
index 8f20b9d..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_right_selected_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_right_selected_holo_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_right_selected_holo_light.9.png
deleted file mode 100644
index 04f657e..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_right_selected_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_selected_holo_dark.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_selected_holo_dark.9.png
deleted file mode 100644
index 99309ef..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_selected_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_selected_holo_light.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_search_selected_holo_light.9.png
deleted file mode 100644
index 9bde7fb..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_textfield_search_selected_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ab_bottom_solid_dark_holo.9.png b/v7/appcompat/res/drawable-mdpi/abc_ab_bottom_solid_dark_holo.9.png
deleted file mode 100644
index b229367..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ab_bottom_solid_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ab_bottom_solid_light_holo.9.png b/v7/appcompat/res/drawable-mdpi/abc_ab_bottom_solid_light_holo.9.png
deleted file mode 100644
index 0706c8a..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ab_bottom_solid_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ab_bottom_transparent_dark_holo.9.png b/v7/appcompat/res/drawable-mdpi/abc_ab_bottom_transparent_dark_holo.9.png
deleted file mode 100644
index d814d02..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ab_bottom_transparent_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ab_bottom_transparent_light_holo.9.png b/v7/appcompat/res/drawable-mdpi/abc_ab_bottom_transparent_light_holo.9.png
deleted file mode 100644
index b139c8e..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ab_bottom_transparent_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ab_solid_dark_holo.9.png b/v7/appcompat/res/drawable-mdpi/abc_ab_solid_dark_holo.9.png
deleted file mode 100644
index 743d00b..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ab_solid_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ab_solid_light_holo.9.png b/v7/appcompat/res/drawable-mdpi/abc_ab_solid_light_holo.9.png
deleted file mode 100644
index 17c1fb9..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ab_solid_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ab_stacked_solid_dark_holo.9.png b/v7/appcompat/res/drawable-mdpi/abc_ab_stacked_solid_dark_holo.9.png
deleted file mode 100644
index 007a4b2..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ab_stacked_solid_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ab_stacked_solid_light_holo.9.png b/v7/appcompat/res/drawable-mdpi/abc_ab_stacked_solid_light_holo.9.png
deleted file mode 100644
index ad6e1a4..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ab_stacked_solid_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ab_stacked_transparent_dark_holo.9.png b/v7/appcompat/res/drawable-mdpi/abc_ab_stacked_transparent_dark_holo.9.png
deleted file mode 100644
index 0ad6c88..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ab_stacked_transparent_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ab_stacked_transparent_light_holo.9.png b/v7/appcompat/res/drawable-mdpi/abc_ab_stacked_transparent_light_holo.9.png
deleted file mode 100644
index 19b50ab..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ab_stacked_transparent_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ab_transparent_dark_holo.9.png b/v7/appcompat/res/drawable-mdpi/abc_ab_transparent_dark_holo.9.png
deleted file mode 100644
index ad980b1..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ab_transparent_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ab_transparent_light_holo.9.png b/v7/appcompat/res/drawable-mdpi/abc_ab_transparent_light_holo.9.png
deleted file mode 100644
index 60e6c52..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ab_transparent_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_cab_background_bottom_holo_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_cab_background_bottom_holo_dark.9.png
deleted file mode 100644
index d8f1c8b..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_cab_background_bottom_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_cab_background_bottom_holo_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_cab_background_bottom_holo_light.9.png
deleted file mode 100644
index 31e4989..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_cab_background_bottom_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_cab_background_top_holo_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_cab_background_top_holo_dark.9.png
deleted file mode 100644
index 7c2cbe5..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_cab_background_top_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_cab_background_top_holo_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_cab_background_top_holo_light.9.png
deleted file mode 100644
index 30cbdc1..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_cab_background_top_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_holo_dark.png b/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_holo_dark.png
deleted file mode 100644
index df2d3d1..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_holo_light.png b/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_holo_light.png
deleted file mode 100644
index b2aa9c2..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_material_dark.png b/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_material_dark.png
new file mode 100644
index 0000000..d55a6d6
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_material_light.png b/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_material_light.png
new file mode 100644
index 0000000..8693a6d
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png
new file mode 100644
index 0000000..e196bbe
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_clear_disabled.png b/v7/appcompat/res/drawable-mdpi/abc_ic_clear_disabled.png
deleted file mode 100644
index 79228ba..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_clear_disabled.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_clear_material_dark.png b/v7/appcompat/res/drawable-mdpi/abc_ic_clear_material_dark.png
new file mode 100644
index 0000000..c6b6a46
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_clear_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_clear_material_light.png b/v7/appcompat/res/drawable-mdpi/abc_ic_clear_material_light.png
new file mode 100644
index 0000000..5022d82
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_clear_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png
new file mode 100644
index 0000000..d43e4d1
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_clear_normal.png b/v7/appcompat/res/drawable-mdpi/abc_ic_clear_normal.png
deleted file mode 100644
index 86944a8..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_clear_normal.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_clear_search_api_disabled_holo_light.png b/v7/appcompat/res/drawable-mdpi/abc_ic_clear_search_api_disabled_holo_light.png
deleted file mode 100644
index c0bdf06..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_clear_search_api_disabled_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_clear_search_api_holo_light.png b/v7/appcompat/res/drawable-mdpi/abc_ic_clear_search_api_holo_light.png
deleted file mode 100644
index 15b86cb..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_clear_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_holo_dark.png b/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_holo_dark.png
deleted file mode 100644
index 844c99c..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_holo_light.png b/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_holo_light.png
deleted file mode 100644
index 86c170e..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_material_dark.png b/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_material_dark.png
new file mode 100644
index 0000000..d1d56da
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_material_light.png b/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_material_light.png
new file mode 100644
index 0000000..a6639b3
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_mtrl_alpha.png
new file mode 100644
index 0000000..42ac8ca
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_go.png b/v7/appcompat/res/drawable-mdpi/abc_ic_go.png
deleted file mode 100644
index bf19833..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_go.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_holo_light.png b/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_holo_light.png
deleted file mode 100644
index 8518498..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_material_dark.png b/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_material_dark.png
new file mode 100644
index 0000000..6b7fa77
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_material_light.png b/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_material_light.png
new file mode 100644
index 0000000..430b123
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png
new file mode 100644
index 0000000..b5f6176
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_material_dark.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_material_dark.png
new file mode 100644
index 0000000..ef7cf0f
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_material_light.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_material_light.png
new file mode 100644
index 0000000..099a619
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
new file mode 100644
index 0000000..8415096
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_normal_holo_dark.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_normal_holo_dark.png
deleted file mode 100644
index ba704b6..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_normal_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_normal_holo_light.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_normal_holo_light.png
deleted file mode 100644
index 01d6816..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_normal_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_holo_dark.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_holo_dark.png
deleted file mode 100644
index 6bf21e3..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_holo_light.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_holo_light.png
deleted file mode 100644
index 70fe31a..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_material_dark.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_material_dark.png
new file mode 100644
index 0000000..72f9d1c
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_material_light.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_material_light.png
new file mode 100644
index 0000000..a56c6d6
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png
new file mode 100644
index 0000000..e0d5ac4
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_search.png b/v7/appcompat/res/drawable-mdpi/abc_ic_search.png
deleted file mode 100644
index 4be72f1..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_search.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_holo_light.png b/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_holo_light.png
deleted file mode 100644
index f2e26f8..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_material_dark.png b/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_material_dark.png
new file mode 100644
index 0000000..a236eeb
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_material_light.png b/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_material_light.png
new file mode 100644
index 0000000..5da2dfc
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png
new file mode 100644
index 0000000..0fb57b2
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search.png b/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search.png
deleted file mode 100644
index 73c6be6..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_holo_light.png b/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_holo_light.png
deleted file mode 100644
index 71d838e..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_material_dark.png b/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_material_dark.png
new file mode 100644
index 0000000..edb13a0
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_material_light.png b/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_material_light.png
new file mode 100644
index 0000000..67cb856
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png
new file mode 100644
index 0000000..3f1eee3
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_list_pressed_holo_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_list_pressed_holo_dark.9.png
index 6e77525..fd0e8d7 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_list_pressed_holo_dark.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_list_pressed_holo_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_list_pressed_holo_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_list_pressed_holo_light.9.png
index 6e77525..061904c 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_list_pressed_holo_light.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_list_pressed_holo_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_popup_background_material_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_popup_background_material_dark.9.png
new file mode 100644
index 0000000..963fa0f
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_popup_background_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_popup_background_material_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_popup_background_material_light.9.png
new file mode 100644
index 0000000..373c6d5
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_popup_background_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_popup_background_mtrl_mult.9.png b/v7/appcompat/res/drawable-mdpi/abc_popup_background_mtrl_mult.9.png
new file mode 100644
index 0000000..e920499
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_popup_background_mtrl_mult.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_default_holo_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_default_holo_dark.9.png
deleted file mode 100644
index 8d75946..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_default_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_default_holo_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_default_holo_light.9.png
deleted file mode 100644
index 716560b..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_default_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_disabled_holo_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_disabled_holo_dark.9.png
deleted file mode 100644
index c3ba89c..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_disabled_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_disabled_holo_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_disabled_holo_light.9.png
deleted file mode 100644
index 67c5358..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_disabled_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_focused_holo_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_focused_holo_dark.9.png
deleted file mode 100644
index c015f43..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_focused_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_focused_holo_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_focused_holo_light.9.png
deleted file mode 100644
index 487edc2..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_focused_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_pressed_holo_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_pressed_holo_dark.9.png
deleted file mode 100644
index 2fa15e7..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_pressed_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_pressed_holo_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_pressed_holo_light.9.png
deleted file mode 100644
index a964b22..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_spinner_ab_pressed_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_spinner_checked_material_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_spinner_checked_material_dark.9.png
new file mode 100644
index 0000000..0716615
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_spinner_checked_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_spinner_checked_material_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_spinner_checked_material_light.9.png
new file mode 100644
index 0000000..07eb71b
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_spinner_checked_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_spinner_default_material_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_spinner_default_material_dark.9.png
new file mode 100644
index 0000000..a1e256d
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_spinner_default_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_spinner_default_material_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_spinner_default_material_light.9.png
new file mode 100644
index 0000000..943e00b
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_spinner_default_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_spinner_mtrl_am_alpha.9.png
new file mode 100644
index 0000000..bbf5928
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_spinner_pressed_material_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_spinner_pressed_material_dark.9.png
new file mode 100644
index 0000000..0716615
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_spinner_pressed_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_spinner_pressed_material_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_spinner_pressed_material_light.9.png
new file mode 100644
index 0000000..07eb71b
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_spinner_pressed_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_tab_indicator_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_tab_indicator_mtrl_alpha.9.png
new file mode 100644
index 0000000..b69529c
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_tab_indicator_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_tab_indicator_selected_material_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_tab_indicator_selected_material_dark.9.png
new file mode 100644
index 0000000..4365336
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_tab_indicator_selected_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_tab_indicator_selected_material_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_tab_indicator_selected_material_light.9.png
new file mode 100644
index 0000000..e446961
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_tab_indicator_selected_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_tab_selected_focused_holo.9.png b/v7/appcompat/res/drawable-mdpi/abc_tab_selected_focused_holo.9.png
deleted file mode 100644
index c9972e7..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_tab_selected_focused_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_tab_selected_holo.9.png b/v7/appcompat/res/drawable-mdpi/abc_tab_selected_holo.9.png
deleted file mode 100644
index 587337c..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_tab_selected_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_tab_selected_pressed_holo.9.png b/v7/appcompat/res/drawable-mdpi/abc_tab_selected_pressed_holo.9.png
deleted file mode 100644
index 155c4fc..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_tab_selected_pressed_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_tab_unselected_pressed_holo.9.png b/v7/appcompat/res/drawable-mdpi/abc_tab_unselected_pressed_holo.9.png
deleted file mode 100644
index b1223fe..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_tab_unselected_pressed_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_activated_material_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_activated_material_dark.9.png
new file mode 100644
index 0000000..c87257d
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_textfield_activated_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_activated_material_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_activated_material_light.9.png
new file mode 100644
index 0000000..1c1bb45
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_textfield_activated_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_activated_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_activated_mtrl_alpha.9.png
new file mode 100644
index 0000000..f3d06fe
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_textfield_activated_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_default_disabled_material_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_default_disabled_material_dark.9.png
new file mode 100644
index 0000000..f478c06
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_textfield_default_disabled_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_default_disabled_material_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_default_disabled_material_light.9.png
new file mode 100644
index 0000000..ba98413
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_textfield_default_disabled_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_default_material_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_default_material_dark.9.png
new file mode 100644
index 0000000..1dd8f8b
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_textfield_default_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_default_material_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_default_material_light.9.png
new file mode 100644
index 0000000..df7d3ef
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_textfield_default_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_default_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_default_mtrl_alpha.9.png
new file mode 100644
index 0000000..f0e7db8
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_textfield_default_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_activated_material_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_activated_material_dark.9.png
new file mode 100644
index 0000000..7b7e3db
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_activated_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_activated_material_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_activated_material_light.9.png
new file mode 100644
index 0000000..8779945
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_activated_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_activated_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_activated_mtrl_alpha.9.png
new file mode 100644
index 0000000..ef4ebc0
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_activated_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_default_holo_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_default_holo_dark.9.png
deleted file mode 100644
index 081657e..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_default_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_default_holo_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_default_holo_light.9.png
deleted file mode 100644
index 3f312b4..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_default_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_default_material_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_default_material_dark.9.png
new file mode 100644
index 0000000..df8f3d6
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_default_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_default_material_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_default_material_light.9.png
new file mode 100644
index 0000000..d8c6c4c
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_default_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_default_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_default_mtrl_alpha.9.png
new file mode 100644
index 0000000..9ddbcf5
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_default_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_right_default_holo_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_right_default_holo_dark.9.png
deleted file mode 100644
index b086fae..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_right_default_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_right_default_holo_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_right_default_holo_light.9.png
deleted file mode 100644
index 73c336a..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_right_default_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_right_selected_holo_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_right_selected_holo_dark.9.png
deleted file mode 100644
index 726e0ff..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_right_selected_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_right_selected_holo_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_right_selected_holo_light.9.png
deleted file mode 100644
index 726e0ff..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_right_selected_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_selected_holo_dark.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_selected_holo_dark.9.png
deleted file mode 100644
index 1767c16..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_selected_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_selected_holo_light.9.png b/v7/appcompat/res/drawable-mdpi/abc_textfield_search_selected_holo_light.9.png
deleted file mode 100644
index 1767c16..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_textfield_search_selected_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ab_bottom_solid_dark_holo.9.png b/v7/appcompat/res/drawable-xhdpi/abc_ab_bottom_solid_dark_holo.9.png
deleted file mode 100644
index 5753346..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ab_bottom_solid_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ab_bottom_solid_light_holo.9.png b/v7/appcompat/res/drawable-xhdpi/abc_ab_bottom_solid_light_holo.9.png
deleted file mode 100644
index 8155fe8..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ab_bottom_solid_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ab_bottom_transparent_dark_holo.9.png b/v7/appcompat/res/drawable-xhdpi/abc_ab_bottom_transparent_dark_holo.9.png
deleted file mode 100644
index 6cee9a1..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ab_bottom_transparent_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ab_bottom_transparent_light_holo.9.png b/v7/appcompat/res/drawable-xhdpi/abc_ab_bottom_transparent_light_holo.9.png
deleted file mode 100644
index fa4d76a..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ab_bottom_transparent_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ab_solid_dark_holo.9.png b/v7/appcompat/res/drawable-xhdpi/abc_ab_solid_dark_holo.9.png
deleted file mode 100644
index 6622cba..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ab_solid_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ab_solid_light_holo.9.png b/v7/appcompat/res/drawable-xhdpi/abc_ab_solid_light_holo.9.png
deleted file mode 100644
index c427297..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ab_solid_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ab_stacked_solid_dark_holo.9.png b/v7/appcompat/res/drawable-xhdpi/abc_ab_stacked_solid_dark_holo.9.png
deleted file mode 100644
index a0d9c1b..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ab_stacked_solid_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ab_stacked_solid_light_holo.9.png b/v7/appcompat/res/drawable-xhdpi/abc_ab_stacked_solid_light_holo.9.png
deleted file mode 100644
index d36f99f..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ab_stacked_solid_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ab_stacked_transparent_dark_holo.9.png b/v7/appcompat/res/drawable-xhdpi/abc_ab_stacked_transparent_dark_holo.9.png
deleted file mode 100644
index 5ad475d..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ab_stacked_transparent_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ab_stacked_transparent_light_holo.9.png b/v7/appcompat/res/drawable-xhdpi/abc_ab_stacked_transparent_light_holo.9.png
deleted file mode 100644
index 6ade5ee..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ab_stacked_transparent_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ab_transparent_dark_holo.9.png b/v7/appcompat/res/drawable-xhdpi/abc_ab_transparent_dark_holo.9.png
deleted file mode 100644
index 719b923..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ab_transparent_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ab_transparent_light_holo.9.png b/v7/appcompat/res/drawable-xhdpi/abc_ab_transparent_light_holo.9.png
deleted file mode 100644
index 6da264d..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ab_transparent_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_cab_background_bottom_holo_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_cab_background_bottom_holo_dark.9.png
deleted file mode 100644
index 0bd0980..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_cab_background_bottom_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_cab_background_bottom_holo_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_cab_background_bottom_holo_light.9.png
deleted file mode 100644
index 43ed26d..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_cab_background_bottom_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_cab_background_top_holo_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_cab_background_top_holo_dark.9.png
deleted file mode 100644
index 6b31579..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_cab_background_top_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_cab_background_top_holo_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_cab_background_top_holo_light.9.png
deleted file mode 100644
index df0121b..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_cab_background_top_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_holo_dark.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_holo_dark.png
deleted file mode 100644
index 8ded62f..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_holo_light.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_holo_light.png
deleted file mode 100644
index 517e9f7..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_material_dark.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_material_dark.png
new file mode 100644
index 0000000..9bd8f84
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_material_light.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_material_light.png
new file mode 100644
index 0000000..9eace4b
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png
new file mode 100644
index 0000000..4385b2b
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_disabled.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_disabled.png
deleted file mode 100644
index e35c5f0..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_disabled.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_material_dark.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_material_dark.png
new file mode 100644
index 0000000..f58cbc5
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_material_light.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_material_light.png
new file mode 100644
index 0000000..856a67b
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png
new file mode 100644
index 0000000..ddacb59
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_normal.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_normal.png
deleted file mode 100644
index f9dee98..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_normal.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_search_api_disabled_holo_light.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_search_api_disabled_holo_light.png
deleted file mode 100644
index 7fd7aeb..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_search_api_disabled_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_search_api_holo_light.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_search_api_holo_light.png
deleted file mode 100644
index 53cfbd3..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_holo_dark.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_holo_dark.png
deleted file mode 100644
index d8faf90..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_holo_light.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_holo_light.png
deleted file mode 100644
index e7c7280..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_material_dark.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_material_dark.png
new file mode 100644
index 0000000..e0cb811
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_material_light.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_material_light.png
new file mode 100644
index 0000000..cb89530
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_mtrl_alpha.png
new file mode 100644
index 0000000..c10a1b7
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_go.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_go.png
deleted file mode 100644
index 1e2dcfa..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_go.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_holo_light.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_holo_light.png
deleted file mode 100644
index f12eafc..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_material_dark.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_material_dark.png
new file mode 100644
index 0000000..8e3b640
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_material_light.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_material_light.png
new file mode 100644
index 0000000..4bb1016
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png
new file mode 100644
index 0000000..bd80981
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_material_dark.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_material_dark.png
new file mode 100644
index 0000000..1eaac52
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_material_light.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_material_light.png
new file mode 100644
index 0000000..e0a1fc2
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
new file mode 100644
index 0000000..f91b718
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_normal_holo_dark.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_normal_holo_dark.png
deleted file mode 100644
index a92fb1d..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_normal_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_normal_holo_light.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_normal_holo_light.png
deleted file mode 100644
index 930ca8d..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_normal_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_holo_dark.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_holo_dark.png
deleted file mode 100644
index 45a0f1d..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_holo_light.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_holo_light.png
deleted file mode 100644
index 528e554..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_material_dark.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_material_dark.png
new file mode 100644
index 0000000..7ea384a
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_material_light.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_material_light.png
new file mode 100644
index 0000000..5656c2c
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png
new file mode 100644
index 0000000..7accf52
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_search.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_search.png
deleted file mode 100644
index 998f91b..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_search.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_holo_light.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_holo_light.png
deleted file mode 100644
index a4cdf1c..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_material_dark.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_material_dark.png
new file mode 100644
index 0000000..f16fa70
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_material_light.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_material_light.png
new file mode 100644
index 0000000..3711444
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png
new file mode 100644
index 0000000..05cfab7
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search.png
deleted file mode 100644
index c625a36..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_holo_light.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_holo_light.png
deleted file mode 100644
index c332ba0..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_material_dark.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_material_dark.png
new file mode 100644
index 0000000..ffdbb1f
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_material_light.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_material_light.png
new file mode 100644
index 0000000..9ad5ec6
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png
new file mode 100644
index 0000000..c1c23d0
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_dark.9.png
index e4b3393..29037a0 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_dark.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_light.9.png
index e4b3393..f4af926 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_light.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_list_pressed_holo_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_popup_background_material_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_popup_background_material_dark.9.png
new file mode 100644
index 0000000..85ebbf4
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_popup_background_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_popup_background_material_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_popup_background_material_light.9.png
new file mode 100644
index 0000000..16b2a13
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_popup_background_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_popup_background_mtrl_mult.9.png b/v7/appcompat/res/drawable-xhdpi/abc_popup_background_mtrl_mult.9.png
new file mode 100644
index 0000000..a081ceb
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_popup_background_mtrl_mult.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_default_holo_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_default_holo_dark.9.png
deleted file mode 100644
index c43293d..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_default_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_default_holo_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_default_holo_light.9.png
deleted file mode 100644
index 3dc481e..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_default_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_disabled_holo_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_disabled_holo_dark.9.png
deleted file mode 100644
index 9a7b173..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_disabled_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_disabled_holo_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_disabled_holo_light.9.png
deleted file mode 100644
index 6888fdc..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_disabled_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_focused_holo_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_focused_holo_dark.9.png
deleted file mode 100644
index 9408b47..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_focused_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_focused_holo_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_focused_holo_light.9.png
deleted file mode 100644
index 1cb95d1..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_focused_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_pressed_holo_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_pressed_holo_dark.9.png
deleted file mode 100644
index a3c7711..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_pressed_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_pressed_holo_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_pressed_holo_light.9.png
deleted file mode 100644
index 2a21210..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_spinner_ab_pressed_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_spinner_checked_material_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_spinner_checked_material_dark.9.png
new file mode 100644
index 0000000..bf7e20a
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_spinner_checked_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_spinner_checked_material_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_spinner_checked_material_light.9.png
new file mode 100644
index 0000000..9e1e98b
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_spinner_checked_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_spinner_default_material_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_spinner_default_material_dark.9.png
new file mode 100644
index 0000000..a34ffc2
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_spinner_default_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_spinner_default_material_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_spinner_default_material_light.9.png
new file mode 100644
index 0000000..b22f434
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_spinner_default_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_spinner_mtrl_am_alpha.9.png
new file mode 100644
index 0000000..d4bd169
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_spinner_pressed_material_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_spinner_pressed_material_dark.9.png
new file mode 100644
index 0000000..bf7e20a
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_spinner_pressed_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_spinner_pressed_material_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_spinner_pressed_material_light.9.png
new file mode 100644
index 0000000..9e1e98b
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_spinner_pressed_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_tab_indicator_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_tab_indicator_mtrl_alpha.9.png
new file mode 100644
index 0000000..5610d8c
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_tab_indicator_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_tab_indicator_selected_material_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_tab_indicator_selected_material_dark.9.png
new file mode 100644
index 0000000..6a5bb6d
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_tab_indicator_selected_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_tab_indicator_selected_material_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_tab_indicator_selected_material_light.9.png
new file mode 100644
index 0000000..43b1c75
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_tab_indicator_selected_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_tab_selected_focused_holo.9.png b/v7/appcompat/res/drawable-xhdpi/abc_tab_selected_focused_holo.9.png
deleted file mode 100644
index 03cfb09..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_tab_selected_focused_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_tab_selected_holo.9.png b/v7/appcompat/res/drawable-xhdpi/abc_tab_selected_holo.9.png
deleted file mode 100644
index e4229f2..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_tab_selected_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_tab_selected_pressed_holo.9.png b/v7/appcompat/res/drawable-xhdpi/abc_tab_selected_pressed_holo.9.png
deleted file mode 100644
index e862cb1..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_tab_selected_pressed_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_tab_unselected_pressed_holo.9.png b/v7/appcompat/res/drawable-xhdpi/abc_tab_unselected_pressed_holo.9.png
deleted file mode 100644
index f1eb673..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_tab_unselected_pressed_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_activated_material_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_activated_material_dark.9.png
new file mode 100644
index 0000000..e3ee26e
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_textfield_activated_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_activated_material_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_activated_material_light.9.png
new file mode 100644
index 0000000..fb0df75
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_textfield_activated_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_activated_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_activated_mtrl_alpha.9.png
new file mode 100644
index 0000000..7174b67
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_textfield_activated_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_default_disabled_material_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_default_disabled_material_dark.9.png
new file mode 100644
index 0000000..1e702f9
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_textfield_default_disabled_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_default_disabled_material_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_default_disabled_material_light.9.png
new file mode 100644
index 0000000..e55f526
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_textfield_default_disabled_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_default_material_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_default_material_dark.9.png
new file mode 100644
index 0000000..fca5a1e
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_textfield_default_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_default_material_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_default_material_light.9.png
new file mode 100644
index 0000000..99a39a9
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_textfield_default_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_default_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_default_mtrl_alpha.9.png
new file mode 100644
index 0000000..46dad22
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_textfield_default_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_material_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_material_dark.9.png
new file mode 100644
index 0000000..03e79c1
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_material_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_material_light.9.png
new file mode 100644
index 0000000..1baa686
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_mtrl_alpha.9.png
new file mode 100644
index 0000000..1a2546f
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_activated_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_default_holo_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_default_holo_dark.9.png
deleted file mode 100644
index 8fdbbf3..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_default_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_default_holo_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_default_holo_light.9.png
deleted file mode 100644
index 4e9ae43..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_default_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_default_material_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_default_material_dark.9.png
new file mode 100644
index 0000000..b0ca72a
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_default_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_default_material_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_default_material_light.9.png
new file mode 100644
index 0000000..23c8bce
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_default_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_default_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_default_mtrl_alpha.9.png
new file mode 100644
index 0000000..500ec33
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_default_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_right_default_holo_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_right_default_holo_dark.9.png
deleted file mode 100644
index 98f4871..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_right_default_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_right_default_holo_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_right_default_holo_light.9.png
deleted file mode 100644
index 733373e..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_right_default_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_right_selected_holo_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_right_selected_holo_dark.9.png
deleted file mode 100644
index 0c6bb03..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_right_selected_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_right_selected_holo_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_right_selected_holo_light.9.png
deleted file mode 100644
index 0c6bb03..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_right_selected_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_selected_holo_dark.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_selected_holo_dark.9.png
deleted file mode 100644
index e5bfd8a..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_selected_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_selected_holo_light.9.png b/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_selected_holo_light.9.png
deleted file mode 100644
index 1743da6..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_textfield_search_selected_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ab_bottom_solid_dark_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_ab_bottom_solid_dark_holo.9.png
deleted file mode 100644
index ba6f005..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ab_bottom_solid_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ab_bottom_solid_light_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_ab_bottom_solid_light_holo.9.png
deleted file mode 100644
index 7c7eb77..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ab_bottom_solid_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ab_bottom_transparent_dark_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_ab_bottom_transparent_dark_holo.9.png
deleted file mode 100644
index 62aa5d6..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ab_bottom_transparent_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ab_bottom_transparent_light_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_ab_bottom_transparent_light_holo.9.png
deleted file mode 100644
index 136d8b6..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ab_bottom_transparent_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ab_solid_dark_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_ab_solid_dark_holo.9.png
deleted file mode 100644
index 580d122..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ab_solid_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ab_solid_light_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_ab_solid_light_holo.9.png
deleted file mode 100644
index 55d96e0..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ab_solid_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ab_stacked_solid_dark_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_ab_stacked_solid_dark_holo.9.png
deleted file mode 100644
index 1e8a9a9..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ab_stacked_solid_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ab_stacked_solid_light_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_ab_stacked_solid_light_holo.9.png
deleted file mode 100644
index 4bb233f..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ab_stacked_solid_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ab_stacked_transparent_dark_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_ab_stacked_transparent_dark_holo.9.png
deleted file mode 100644
index e1768ab..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ab_stacked_transparent_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ab_stacked_transparent_light_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_ab_stacked_transparent_light_holo.9.png
deleted file mode 100644
index 83fbbc4..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ab_stacked_transparent_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ab_transparent_dark_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_ab_transparent_dark_holo.9.png
deleted file mode 100644
index 9f0a2e7..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ab_transparent_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ab_transparent_light_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_ab_transparent_light_holo.9.png
deleted file mode 100644
index b959bd9..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ab_transparent_light_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_cab_background_bottom_holo_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_cab_background_bottom_holo_dark.9.png
deleted file mode 100644
index 087a6d6..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_cab_background_bottom_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_cab_background_bottom_holo_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_cab_background_bottom_holo_light.9.png
deleted file mode 100644
index 98d5d33..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_cab_background_bottom_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_cab_background_top_holo_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_cab_background_top_holo_dark.9.png
deleted file mode 100644
index 30db6bf..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_cab_background_top_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_cab_background_top_holo_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_cab_background_top_holo_light.9.png
deleted file mode 100644
index ced92af..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_cab_background_top_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_holo_dark.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_holo_dark.png
deleted file mode 100644
index 05cfc9f..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_holo_light.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_holo_light.png
deleted file mode 100644
index b3a6fb4..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_material_dark.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_material_dark.png
new file mode 100644
index 0000000..bd1352b
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_material_light.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_material_light.png
new file mode 100644
index 0000000..fee8fe4
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
new file mode 100644
index 0000000..ca15853
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_disabled.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_disabled.png
deleted file mode 100644
index 3c74adf..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_disabled.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_material_dark.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_material_dark.png
new file mode 100644
index 0000000..1442541
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_material_light.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_material_light.png
new file mode 100644
index 0000000..77923e4
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png
new file mode 100644
index 0000000..21ed914
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_normal.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_normal.png
deleted file mode 100644
index 1312732..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_normal.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_search_api_disabled_holo_light.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_search_api_disabled_holo_light.png
deleted file mode 100644
index d9eee29..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_search_api_disabled_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_search_api_holo_light.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_search_api_holo_light.png
deleted file mode 100644
index 681b981..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_holo_dark.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_holo_dark.png
deleted file mode 100644
index 33c81ce..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_holo_light.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_holo_light.png
deleted file mode 100644
index be3c224..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_material_dark.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_material_dark.png
new file mode 100644
index 0000000..0ca875c
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_material_light.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_material_light.png
new file mode 100644
index 0000000..d08771c
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_mtrl_alpha.png
new file mode 100644
index 0000000..fc1b8b4
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_go.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_go.png
deleted file mode 100644
index 622712b..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_go.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_holo_light.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_holo_light.png
deleted file mode 100644
index def0ac4..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_material_dark.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_material_dark.png
new file mode 100644
index 0000000..f2bd059
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_material_light.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_material_light.png
new file mode 100644
index 0000000..ab5c511
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png
new file mode 100644
index 0000000..8e1ab5b
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_material_dark.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_material_dark.png
new file mode 100644
index 0000000..13f63b9
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_material_light.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_material_light.png
new file mode 100644
index 0000000..a311454
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
new file mode 100644
index 0000000..ff1759b
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_normal_holo_dark.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_normal_holo_dark.png
deleted file mode 100644
index c1aa1c2..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_normal_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_normal_holo_light.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_normal_holo_light.png
deleted file mode 100644
index d856d2b..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_normal_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_holo_dark.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_holo_dark.png
deleted file mode 100644
index 22ddd92..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_holo_light.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_holo_light.png
deleted file mode 100644
index 8148e53..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_material_dark.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_material_dark.png
new file mode 100644
index 0000000..936e2b4
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_material_light.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_material_light.png
new file mode 100644
index 0000000..0d1874f
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png
new file mode 100644
index 0000000..66f7d16
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_search.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_search.png
deleted file mode 100644
index 08866a6..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_search.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_holo_light.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_holo_light.png
deleted file mode 100644
index 4ea3c9d..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_material_dark.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_material_dark.png
new file mode 100644
index 0000000..8d1dc91
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_material_light.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_material_light.png
new file mode 100644
index 0000000..7323f83
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png
new file mode 100644
index 0000000..6f60bd3
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search.png
deleted file mode 100644
index f8c50d9..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_holo_light.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_holo_light.png
deleted file mode 100644
index 0674795..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_material_dark.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_material_dark.png
new file mode 100644
index 0000000..22f59fd
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_material_dark.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_material_light.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_material_light.png
new file mode 100644
index 0000000..89613d8
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_material_light.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png
new file mode 100644
index 0000000..d95f1d0
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_list_divider_holo_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_list_divider_holo_dark.9.png
index 745e866..9678825 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_list_divider_holo_dark.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_list_divider_holo_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_list_divider_holo_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_list_divider_holo_light.9.png
index af30b86..c69a6a3 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_list_divider_holo_light.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_list_divider_holo_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_list_focused_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_list_focused_holo.9.png
index 147fc5d..76cad17 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_list_focused_holo.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_list_focused_holo.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_list_longpressed_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_list_longpressed_holo.9.png
index 2063d0a..8f436ea 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_list_longpressed_holo.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_list_longpressed_holo.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_dark.9.png
index 1399f66..d4952ea 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_dark.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_light.9.png
index 1399f66..1352a17 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_light.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_list_pressed_holo_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_popup_background_material_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_popup_background_material_dark.9.png
new file mode 100644
index 0000000..448e4a3
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_popup_background_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_popup_background_material_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_popup_background_material_light.9.png
new file mode 100644
index 0000000..96c5c63
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_popup_background_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_popup_background_mtrl_mult.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_popup_background_mtrl_mult.9.png
new file mode 100644
index 0000000..fb7d715
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_popup_background_mtrl_mult.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_default_holo_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_default_holo_dark.9.png
deleted file mode 100644
index d293589..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_default_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_default_holo_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_default_holo_light.9.png
deleted file mode 100644
index a43e9fe..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_default_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_disabled_holo_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_disabled_holo_dark.9.png
deleted file mode 100644
index f10f0bf..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_disabled_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_disabled_holo_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_disabled_holo_light.9.png
deleted file mode 100644
index 4f9a3a6..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_disabled_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_focused_holo_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_focused_holo_dark.9.png
deleted file mode 100644
index d67dcb3..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_focused_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_focused_holo_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_focused_holo_light.9.png
deleted file mode 100644
index 0271d6b..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_focused_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_pressed_holo_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_pressed_holo_dark.9.png
deleted file mode 100644
index 72a760a..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_pressed_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_pressed_holo_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_pressed_holo_light.9.png
deleted file mode 100644
index a129aab..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_ab_pressed_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_checked_material_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_checked_material_dark.9.png
new file mode 100644
index 0000000..fab49f1
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_checked_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_checked_material_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_checked_material_light.9.png
new file mode 100644
index 0000000..d12a353
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_checked_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_default_material_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_default_material_dark.9.png
new file mode 100644
index 0000000..f7c9d0e
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_default_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_default_material_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_default_material_light.9.png
new file mode 100644
index 0000000..a068d4c
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_default_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_mtrl_am_alpha.9.png
new file mode 100644
index 0000000..2e7bc12
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_pressed_material_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_pressed_material_dark.9.png
new file mode 100644
index 0000000..fab49f1
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_pressed_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_pressed_material_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_pressed_material_light.9.png
new file mode 100644
index 0000000..d12a353
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_pressed_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_tab_indicator_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_tab_indicator_mtrl_alpha.9.png
new file mode 100644
index 0000000..248f4f8
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_tab_indicator_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_tab_indicator_selected_material_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_tab_indicator_selected_material_dark.9.png
new file mode 100644
index 0000000..95ceaa3
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_tab_indicator_selected_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_tab_indicator_selected_material_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_tab_indicator_selected_material_light.9.png
new file mode 100644
index 0000000..5b7977c
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_tab_indicator_selected_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_tab_selected_focused_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_tab_selected_focused_holo.9.png
deleted file mode 100644
index cd15b0a..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_tab_selected_focused_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_tab_selected_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_tab_selected_holo.9.png
deleted file mode 100644
index 05c642a..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_tab_selected_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_tab_selected_pressed_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_tab_selected_pressed_holo.9.png
deleted file mode 100644
index f857a22..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_tab_selected_pressed_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_tab_unselected_pressed_holo.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_tab_unselected_pressed_holo.9.png
deleted file mode 100644
index bc856f90..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_tab_unselected_pressed_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_activated_material_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_activated_material_dark.9.png
new file mode 100644
index 0000000..46736da
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_activated_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_activated_material_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_activated_material_light.9.png
new file mode 100644
index 0000000..79644ee
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_activated_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_activated_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_activated_mtrl_alpha.9.png
new file mode 100644
index 0000000..661d5f0
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_activated_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_default_disabled_material_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_default_disabled_material_dark.9.png
new file mode 100644
index 0000000..ef27cb6
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_default_disabled_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_default_disabled_material_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_default_disabled_material_light.9.png
new file mode 100644
index 0000000..659f4e8
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_default_disabled_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_default_material_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_default_material_dark.9.png
new file mode 100644
index 0000000..c01de73
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_default_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_default_material_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_default_material_light.9.png
new file mode 100644
index 0000000..3a95c6f
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_default_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_default_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_default_mtrl_alpha.9.png
new file mode 100644
index 0000000..d7696c3
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_default_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_material_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_material_dark.9.png
new file mode 100644
index 0000000..5cd4e5a
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_material_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_material_light.9.png
new file mode 100644
index 0000000..ff42cbd
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_mtrl_alpha.9.png
new file mode 100644
index 0000000..cd5b00f
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_activated_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_holo_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_holo_dark.9.png
deleted file mode 100644
index 90932d6..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_holo_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_holo_light.9.png
deleted file mode 100644
index ae7b369..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_material_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_material_dark.9.png
new file mode 100644
index 0000000..4d3c30f
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_material_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_material_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_material_light.9.png
new file mode 100644
index 0000000..df7de06
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_material_light.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_mtrl_alpha.9.png
new file mode 100644
index 0000000..5ee867c
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_right_default_holo_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_right_default_holo_dark.9.png
deleted file mode 100644
index deba2d5..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_right_default_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_right_default_holo_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_right_default_holo_light.9.png
deleted file mode 100644
index ab26e8d..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_right_default_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_right_selected_holo_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_right_selected_holo_dark.9.png
deleted file mode 100644
index 3d5ebca..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_right_selected_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_right_selected_holo_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_right_selected_holo_light.9.png
deleted file mode 100644
index 3d5ebca..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_right_selected_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_selected_holo_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_selected_holo_dark.9.png
deleted file mode 100644
index c8c2e6e..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_selected_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_selected_holo_light.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_selected_holo_light.9.png
deleted file mode 100644
index ebb7c75..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_selected_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable/abc_edit_text_material_dark.xml b/v7/appcompat/res/drawable/abc_edit_text_material_dark.xml
new file mode 100644
index 0000000..d222d8e
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_edit_text_material_dark.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+       android:insetLeft="@dimen/abc_control_inset_material"
+       android:insetTop="@dimen/abc_control_inset_material"
+       android:insetBottom="@dimen/abc_control_inset_material"
+       android:insetRight="@dimen/abc_control_inset_material">
+
+    <selector>
+        <item android:state_enabled="true" android:state_focused="true" android:drawable="@drawable/abc_textfield_activated_material_dark"/>
+        <item android:state_enabled="true" android:state_activated="true" android:drawable="@drawable/abc_textfield_activated_material_dark"/>
+        <item android:state_enabled="true" android:drawable="@drawable/abc_textfield_default_material_dark"/>
+        <item android:drawable="@drawable/abc_textfield_default_disabled_material_dark"/>
+    </selector>
+
+</inset>
diff --git a/v7/appcompat/res/drawable/abc_edit_text_material_light.xml b/v7/appcompat/res/drawable/abc_edit_text_material_light.xml
new file mode 100644
index 0000000..e55990f
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_edit_text_material_light.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+       android:insetLeft="@dimen/abc_control_inset_material"
+       android:insetTop="@dimen/abc_control_inset_material"
+       android:insetBottom="@dimen/abc_control_inset_material"
+       android:insetRight="@dimen/abc_control_inset_material">
+
+    <selector>
+        <item android:state_enabled="true" android:state_focused="true" android:drawable="@drawable/abc_textfield_activated_material_light"/>
+        <item android:state_enabled="true" android:state_activated="true" android:drawable="@drawable/abc_textfield_activated_material_light"/>
+        <item android:state_enabled="true" android:drawable="@drawable/abc_textfield_default_material_light"/>
+        <item android:drawable="@drawable/abc_textfield_default_disabled_material_light"/>
+    </selector>
+
+</inset>
diff --git a/v7/appcompat/res/drawable/abc_ic_clear.xml b/v7/appcompat/res/drawable/abc_ic_clear.xml
deleted file mode 100644
index 802af6f..0000000
--- a/v7/appcompat/res/drawable/abc_ic_clear.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false"
-        android:drawable="@drawable/abc_ic_clear_disabled" />
-    <item
-         android:drawable="@drawable/abc_ic_clear_normal" />
-</selector>
diff --git a/v7/appcompat/res/drawable/abc_ic_clear_holo_light.xml b/v7/appcompat/res/drawable/abc_ic_clear_holo_light.xml
deleted file mode 100644
index 4b84efc..0000000
--- a/v7/appcompat/res/drawable/abc_ic_clear_holo_light.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false"
-        android:drawable="@drawable/abc_ic_clear_search_api_disabled_holo_light" />
-    <item
-         android:drawable="@drawable/abc_ic_clear_search_api_holo_light" />
-</selector>
diff --git a/v7/appcompat/res/drawable/abc_spinner_ab_holo_dark.xml b/v7/appcompat/res/drawable/abc_spinner_ab_holo_dark.xml
deleted file mode 100644
index 934b374..0000000
--- a/v7/appcompat/res/drawable/abc_spinner_ab_holo_dark.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false"
-          android:drawable="@drawable/abc_spinner_ab_disabled_holo_dark" />
-    <item android:state_pressed="true"
-          android:drawable="@drawable/abc_spinner_ab_pressed_holo_dark" />
-    <item android:state_pressed="false" android:state_focused="true"
-          android:drawable="@drawable/abc_spinner_ab_focused_holo_dark" />
-    <item android:drawable="@drawable/abc_spinner_ab_default_holo_dark" />
-</selector>
diff --git a/v7/appcompat/res/drawable/abc_spinner_ab_holo_light.xml b/v7/appcompat/res/drawable/abc_spinner_ab_holo_light.xml
deleted file mode 100644
index dd0245e..0000000
--- a/v7/appcompat/res/drawable/abc_spinner_ab_holo_light.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_enabled="false"
-          android:drawable="@drawable/abc_spinner_ab_disabled_holo_light" />
-    <item android:state_pressed="true"
-          android:drawable="@drawable/abc_spinner_ab_pressed_holo_light" />
-    <item android:state_pressed="false" android:state_focused="true"
-          android:drawable="@drawable/abc_spinner_ab_focused_holo_light" />
-    <item android:drawable="@drawable/abc_spinner_ab_default_holo_light" />
-</selector>
diff --git a/v7/appcompat/res/drawable/abc_spinner_background_material_dark.xml b/v7/appcompat/res/drawable/abc_spinner_background_material_dark.xml
new file mode 100644
index 0000000..e14b8fa
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_spinner_background_material_dark.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          android:autoMirrored="true">
+    <item android:state_checked="true" android:drawable="@drawable/abc_spinner_checked_material_dark"/>
+    <item android:state_pressed="true" android:drawable="@drawable/abc_spinner_pressed_material_dark"/>
+    <item android:drawable="@drawable/abc_spinner_default_material_dark"/>
+</selector>
diff --git a/v7/appcompat/res/drawable/abc_spinner_background_material_light.xml b/v7/appcompat/res/drawable/abc_spinner_background_material_light.xml
new file mode 100644
index 0000000..c31c0ac
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_spinner_background_material_light.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          android:autoMirrored="true">
+    <item android:state_checked="true" android:drawable="@drawable/abc_spinner_checked_material_light"/>
+    <item android:state_pressed="true" android:drawable="@drawable/abc_spinner_pressed_material_light"/>
+    <item android:drawable="@drawable/abc_spinner_default_material_light"/>
+</selector>
diff --git a/v7/appcompat/res/drawable/abc_tab_indicator_ab_holo.xml b/v7/appcompat/res/drawable/abc_tab_indicator_ab_holo.xml
deleted file mode 100644
index 3c828d8..0000000
--- a/v7/appcompat/res/drawable/abc_tab_indicator_ab_holo.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <!-- Non focused states -->
-    <item android:state_focused="false" android:state_selected="false" android:state_pressed="false" android:drawable="@android:color/transparent" />
-    <item android:state_focused="false" android:state_selected="true"  android:state_pressed="false" android:drawable="@drawable/abc_tab_selected_holo" />
-
-    <!-- Focused states -->
-    <item android:state_focused="true" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/abc_list_focused_holo" />
-    <item android:state_focused="true" android:state_selected="true"  android:state_pressed="false" android:drawable="@drawable/abc_tab_selected_focused_holo" />
-
-    <!-- Pressed -->
-    <!--    Non focused states -->
-    <item android:state_focused="false" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/abc_list_pressed_holo_dark" />
-    <item android:state_focused="false" android:state_selected="true"  android:state_pressed="true" android:drawable="@drawable/abc_tab_selected_pressed_holo" />
-
-    <!--    Focused states -->
-    <item android:state_focused="true" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/abc_tab_unselected_pressed_holo" />
-    <item android:state_focused="true" android:state_selected="true"  android:state_pressed="true" android:drawable="@drawable/abc_tab_selected_pressed_holo" />
-</selector>
diff --git a/v7/appcompat/res/drawable/abc_tab_indicator_material_dark.xml b/v7/appcompat/res/drawable/abc_tab_indicator_material_dark.xml
new file mode 100644
index 0000000..cb6b9c3
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_tab_indicator_material_dark.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:drawable="@drawable/abc_tab_indicator_selected_material_dark" />
+    <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/v7/appcompat/res/drawable/abc_tab_indicator_material_light.xml b/v7/appcompat/res/drawable/abc_tab_indicator_material_light.xml
new file mode 100644
index 0000000..77e61a4
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_tab_indicator_material_light.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="true" android:drawable="@drawable/abc_tab_indicator_selected_material_light" />
+    <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/v7/appcompat/res/drawable/abc_textfield_search_material_dark.xml b/v7/appcompat/res/drawable/abc_textfield_search_material_dark.xml
new file mode 100644
index 0000000..7455ce6
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_textfield_search_material_dark.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="true" android:state_focused="true" android:drawable="@drawable/abc_textfield_search_activated_material_dark"/>
+    <item android:state_enabled="true" android:state_activated="true" android:drawable="@drawable/abc_textfield_search_activated_material_dark"/>
+    <item android:state_enabled="true" android:drawable="@drawable/abc_textfield_search_default_material_dark"/>
+    <item android:drawable="@drawable/abc_textfield_search_default_material_dark"/>
+</selector>
diff --git a/v7/appcompat/res/drawable/abc_textfield_search_material_light.xml b/v7/appcompat/res/drawable/abc_textfield_search_material_light.xml
new file mode 100644
index 0000000..3bccb88
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_textfield_search_material_light.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="true" android:state_focused="true" android:drawable="@drawable/abc_textfield_search_activated_material_light"/>
+    <item android:state_enabled="true" android:state_activated="true" android:drawable="@drawable/abc_textfield_search_activated_material_light"/>
+    <item android:state_enabled="true" android:drawable="@drawable/abc_textfield_search_default_material_light"/>
+    <item android:drawable="@drawable/abc_textfield_search_default_material_light"/>
+</selector>
diff --git a/v7/appcompat/res/drawable/abc_textfield_searchview_holo_dark.xml b/v7/appcompat/res/drawable/abc_textfield_searchview_holo_dark.xml
deleted file mode 100644
index 6e9bc4c..0000000
--- a/v7/appcompat/res/drawable/abc_textfield_searchview_holo_dark.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true"
-        android:drawable="@drawable/abc_textfield_search_selected_holo_dark" />
-    <item android:drawable="@drawable/abc_textfield_search_default_holo_dark" />
-</selector>
-
diff --git a/v7/appcompat/res/drawable/abc_textfield_searchview_holo_light.xml b/v7/appcompat/res/drawable/abc_textfield_searchview_holo_light.xml
deleted file mode 100644
index a02e4b0..0000000
--- a/v7/appcompat/res/drawable/abc_textfield_searchview_holo_light.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true"
-        android:drawable="@drawable/abc_textfield_search_selected_holo_light" />
-    <item android:drawable="@drawable/abc_textfield_search_default_holo_light" />
-</selector>
-
diff --git a/v7/appcompat/res/drawable/abc_textfield_searchview_right_holo_dark.xml b/v7/appcompat/res/drawable/abc_textfield_searchview_right_holo_dark.xml
deleted file mode 100644
index b55a995..0000000
--- a/v7/appcompat/res/drawable/abc_textfield_searchview_right_holo_dark.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true"
-        android:drawable="@drawable/abc_textfield_search_right_selected_holo_dark" />
-    <item android:drawable="@drawable/abc_textfield_search_right_default_holo_dark" />
-</selector>
-
diff --git a/v7/appcompat/res/drawable/abc_textfield_searchview_right_holo_light.xml b/v7/appcompat/res/drawable/abc_textfield_searchview_right_holo_light.xml
deleted file mode 100644
index 7710232..0000000
--- a/v7/appcompat/res/drawable/abc_textfield_searchview_right_holo_light.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true"
-        android:drawable="@drawable/abc_textfield_search_right_selected_holo_light" />
-    <item android:drawable="@drawable/abc_textfield_search_right_default_holo_light" />
-</selector>
-
diff --git a/v7/appcompat/res/layout-large/abc_action_mode_close_item.xml b/v7/appcompat/res/layout-large/abc_action_mode_close_item.xml
new file mode 100644
index 0000000..f80504b
--- /dev/null
+++ b/v7/appcompat/res/layout-large/abc_action_mode_close_item.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/action_mode_close_button"
+              android:focusable="true"
+              android:clickable="true"
+              android:paddingStart="8dip"
+              style="?attr/actionModeCloseButtonStyle"
+              android:layout_width="wrap_content"
+              android:layout_height="match_parent"
+              android:layout_marginEnd="16dip">
+    <ImageView android:layout_width="48dip"
+               android:layout_height="wrap_content"
+               android:layout_gravity="center"
+               android:scaleType="center"
+               android:src="?attr/actionModeCloseDrawable" />
+    <TextView android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:layout_gravity="center"
+              android:layout_marginStart="4dip"
+              android:layout_marginEnd="16dip"
+              android:textAppearance="?android:attr/textAppearanceSmall"
+              android:textColor="?attr/actionMenuTextColor"
+              android:textSize="12sp"
+              android:textAllCaps="true"
+              android:text="@string/abc_action_mode_done" />
+</LinearLayout>
diff --git a/v7/appcompat/res/layout-v11/abc_action_bar_decor.xml b/v7/appcompat/res/layout-v11/abc_action_bar_decor.xml
deleted file mode 100644
index a212d56..0000000
--- a/v7/appcompat/res/layout-v11/abc_action_bar_decor.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<android.support.v7.internal.widget.NativeActionModeAwareLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/action_bar_root"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-    android:orientation="vertical"
-    android:fitsSystemWindows="true">
-
-    <include layout="@layout/abc_action_bar_decor_include" />
-
-</android.support.v7.internal.widget.NativeActionModeAwareLayout>
diff --git a/v7/appcompat/res/layout-v11/abc_screen_content_include.xml b/v7/appcompat/res/layout-v11/abc_screen_content_include.xml
new file mode 100644
index 0000000..757be1c
--- /dev/null
+++ b/v7/appcompat/res/layout-v11/abc_screen_content_include.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <android.support.v7.internal.widget.NativeActionModeAwareLayout
+            android:id="@id/action_bar_activity_content"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:foregroundGravity="fill_horizontal|top"
+            android:foreground="?android:attr/windowContentOverlay" />
+
+</merge>
diff --git a/v7/appcompat/res/layout-v11/abc_simple_decor.xml b/v7/appcompat/res/layout-v11/abc_simple_decor.xml
deleted file mode 100644
index 03a8f10..0000000
--- a/v7/appcompat/res/layout-v11/abc_simple_decor.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<android.support.v7.internal.widget.NativeActionModeAwareLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/action_bar_root"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:fitsSystemWindows="true">
-
-    <FrameLayout
-        android:id="@id/action_bar_activity_content"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:foreground="?android:attr/windowContentOverlay"/>
-
-</android.support.v7.internal.widget.NativeActionModeAwareLayout>
diff --git a/v7/appcompat/res/layout-v14/abc_activity_chooser_view.xml b/v7/appcompat/res/layout-v14/abc_activity_chooser_view.xml
deleted file mode 100644
index 4ce359b..0000000
--- a/v7/appcompat/res/layout-v14/abc_activity_chooser_view.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 2013, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/activity_chooser_view_content"
-    android:layout_width="wrap_content"
-    android:layout_height="match_parent"
-    android:layout_gravity="center"
-    style="?attr/activityChooserViewStyle">
-
-    <include layout="@layout/abc_activity_chooser_view_include" />
-
-</LinearLayout>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_action_bar_decor.xml b/v7/appcompat/res/layout/abc_action_bar_decor.xml
deleted file mode 100644
index 327e26f..0000000
--- a/v7/appcompat/res/layout/abc_action_bar_decor.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-    android:orientation="vertical"
-    android:fitsSystemWindows="true">
-
-    <include layout="@layout/abc_action_bar_decor_include" />
-
-</LinearLayout>
diff --git a/v7/appcompat/res/layout/abc_action_bar_decor_include.xml b/v7/appcompat/res/layout/abc_action_bar_decor_include.xml
deleted file mode 100644
index 3f2356b..0000000
--- a/v7/appcompat/res/layout/abc_action_bar_decor_include.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <android.support.v7.internal.widget.ActionBarContainer
-        android:id="@+id/action_bar_container"
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        style="?attr/actionBarStyle">
-
-        <android.support.v7.internal.widget.ActionBarView
-            android:id="@+id/action_bar"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            style="?attr/actionBarStyle" />
-
-        <android.support.v7.internal.widget.ActionBarContextView
-            android:id="@+id/action_context_bar"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            android:visibility="gone"
-            style="?attr/actionModeStyle" />
-    </android.support.v7.internal.widget.ActionBarContainer>
-
-    <FrameLayout
-        android:id="@id/action_bar_activity_content"
-        android:layout_width="fill_parent"
-        android:layout_height="0dip"
-        android:layout_weight="1"
-        android:foregroundGravity="fill_horizontal|top"
-        android:foreground="?android:attr/windowContentOverlay" />
-
-    <android.support.v7.internal.widget.ActionBarContainer
-        android:id="@+id/split_action_bar"
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        style="?attr/actionBarSplitStyle"
-        android:visibility="gone"
-        android:gravity="center" />
-
-</merge>
diff --git a/v7/appcompat/res/layout/abc_action_bar_decor_overlay.xml b/v7/appcompat/res/layout/abc_action_bar_decor_overlay.xml
deleted file mode 100644
index 5ea8fc1..0000000
--- a/v7/appcompat/res/layout/abc_action_bar_decor_overlay.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<android.support.v7.internal.widget.ActionBarOverlayLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/action_bar_overlay_layout"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent">
-    <FrameLayout android:id="@id/action_bar_activity_content"
-                 android:layout_width="fill_parent"
-                 android:layout_height="fill_parent"/>
-    <LinearLayout android:id="@+id/top_action_bar"
-                  android:layout_width="fill_parent"
-                  android:layout_height="wrap_content"
-                  android:layout_gravity="top">
-        <android.support.v7.internal.widget.ActionBarContainer android:id="@+id/action_bar_container"
-                                                             android:layout_width="fill_parent"
-                                                             android:layout_height="wrap_content"
-                                                             android:layout_alignParentTop="true"
-                                                             style="?attr/actionBarStyle"
-                                                             android:gravity="top">
-            <android.support.v7.internal.widget.ActionBarView
-                    android:id="@+id/action_bar"
-                    android:layout_width="fill_parent"
-                    android:layout_height="wrap_content"
-                    style="?attr/actionBarStyle"/>
-            <android.support.v7.internal.widget.ActionBarContextView
-                    android:id="@+id/action_context_bar"
-                    android:layout_width="fill_parent"
-                    android:layout_height="wrap_content"
-                    android:visibility="gone"
-                    style="?attr/actionModeStyle"/>
-        </android.support.v7.internal.widget.ActionBarContainer>
-        <ImageView android:src="?android:attr/windowContentOverlay"
-                   android:scaleType="fitXY"
-                   android:layout_width="fill_parent"
-                   android:layout_height="wrap_content"/>
-    </LinearLayout>
-    <android.support.v7.internal.widget.ActionBarContainer android:id="@+id/split_action_bar"
-                                                         android:layout_width="fill_parent"
-                                                         android:layout_height="wrap_content"
-                                                         android:layout_gravity="bottom"
-                                                         style="?attr/actionBarSplitStyle"
-                                                         android:visibility="gone"
-                                                         android:gravity="center"/>
-</android.support.v7.internal.widget.ActionBarOverlayLayout>
diff --git a/v7/appcompat/res/layout/abc_action_bar_home.xml b/v7/appcompat/res/layout/abc_action_bar_home.xml
index 7063c36..8a686b5 100644
--- a/v7/appcompat/res/layout/abc_action_bar_home.xml
+++ b/v7/appcompat/res/layout/abc_action_bar_home.xml
@@ -17,15 +17,15 @@
 <view xmlns:android="http://schemas.android.com/apk/res/android"
       class="android.support.v7.internal.widget.ActionBarView$HomeView"
       android:layout_width="wrap_content"
-      android:layout_height="fill_parent"
-      android:background="?attr/actionBarItemBackground">
-    <ImageView android:id="@+id/up"
+      android:layout_height="wrap_content"
+      android:layout_gravity="center_vertical|left">
+    <ImageView android:id="@id/up"
                android:src="?attr/homeAsUpIndicator"
                android:layout_gravity="center_vertical|left"
                android:visibility="gone"
-               android:layout_width="wrap_content"
-               android:layout_height="wrap_content"
-               android:layout_marginRight="-8dip"/>
+               android:layout_width="48dp"
+               android:layout_height="48dp"
+               android:scaleType="centerInside" />
     <ImageView android:id="@id/home"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
diff --git a/v7/appcompat/res/layout/abc_action_bar_tab.xml b/v7/appcompat/res/layout/abc_action_bar_tab.xml
deleted file mode 100644
index 06043a2..0000000
--- a/v7/appcompat/res/layout/abc_action_bar_tab.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-
-<view xmlns:android="http://schemas.android.com/apk/res/android"
-      class="android.support.v7.internal.widget.ScrollingTabContainerView$TabView"
-      android:layout_width="0dp"
-      android:layout_weight="1"
-      android:layout_height="wrap_content"
-      style="?attr/actionBarTabStyle"/>
diff --git a/v7/appcompat/res/layout/abc_action_bar_tabbar.xml b/v7/appcompat/res/layout/abc_action_bar_tabbar.xml
deleted file mode 100644
index 08f9c89..0000000
--- a/v7/appcompat/res/layout/abc_action_bar_tabbar.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
--->
-
-<android.support.v7.internal.widget.LinearLayoutICS
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:gravity="center"
-        style="?attr/actionBarTabBarStyle"/>
diff --git a/v7/appcompat/res/layout/abc_action_bar_title_item.xml b/v7/appcompat/res/layout/abc_action_bar_title_item.xml
index 0cb1237..7370130 100644
--- a/v7/appcompat/res/layout/abc_action_bar_title_item.xml
+++ b/v7/appcompat/res/layout/abc_action_bar_title_item.xml
@@ -17,34 +17,20 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
-              android:orientation="horizontal"
-              android:paddingRight="8dip"
-              android:background="?attr/actionBarItemBackground"
-              android:enabled="false">
-
-    <ImageView android:id="@+id/up"
-               android:src="?attr/homeAsUpIndicator"
-               android:layout_gravity="center_vertical|left"
-               android:visibility="gone"
-               android:layout_width="wrap_content"
-               android:layout_height="wrap_content"/>
-
-    <LinearLayout android:layout_width="wrap_content"
-                  android:layout_height="wrap_content"
-                  android:layout_gravity="center_vertical|left"
-                  android:orientation="vertical">
-        <TextView android:id="@+id/action_bar_title"
-                  android:layout_width="wrap_content"
-                  android:layout_height="wrap_content"
-                  android:singleLine="true"
-                  android:ellipsize="end"/>
-        <TextView android:id="@+id/action_bar_subtitle"
-                  android:layout_width="wrap_content"
-                  android:layout_height="wrap_content"
-                  android:layout_marginTop="@dimen/abc_action_bar_subtitle_top_margin"
-                  android:layout_marginBottom="@dimen/abc_action_bar_subtitle_bottom_margin"
-                  android:singleLine="true"
-                  android:ellipsize="end"
-                  android:visibility="gone"/>
-    </LinearLayout>
+              android:layout_gravity="center_vertical|left"
+              android:orientation="vertical"
+              android:paddingRight="8dp"
+              android:paddingEnd="8dp">
+    <TextView android:id="@+id/action_bar_title"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:singleLine="true"
+              android:ellipsize="end" />
+    <TextView android:id="@+id/action_bar_subtitle"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:layout_marginTop="@dimen/abc_action_bar_subtitle_top_margin"
+              android:singleLine="true"
+              android:ellipsize="end"
+              android:visibility="gone" />
 </LinearLayout>
diff --git a/v7/appcompat/res/layout/abc_action_bar_up_container.xml b/v7/appcompat/res/layout/abc_action_bar_up_container.xml
new file mode 100644
index 0000000..fb57b06
--- /dev/null
+++ b/v7/appcompat/res/layout/abc_action_bar_up_container.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="wrap_content"
+              android:layout_height="match_parent"
+              android:background="?attr/actionBarItemBackground"
+              android:gravity="center_vertical"
+              android:enabled="false">
+</LinearLayout>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_action_menu_layout.xml b/v7/appcompat/res/layout/abc_action_menu_layout.xml
index 37a02e6..4918d2f 100644
--- a/v7/appcompat/res/layout/abc_action_menu_layout.xml
+++ b/v7/appcompat/res/layout/abc_action_menu_layout.xml
@@ -4,9 +4,9 @@
      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.
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<android.support.v7.internal.view.menu.ActionMenuView
+<android.support.v7.widget.ActionMenuView
         xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:app="http://schemas.android.com/apk/res-auto"
         android:layout_width="wrap_content"
diff --git a/v7/appcompat/res/layout/abc_action_mode_bar.xml b/v7/appcompat/res/layout/abc_action_mode_bar.xml
index b81cd85..6af12ea 100644
--- a/v7/appcompat/res/layout/abc_action_mode_bar.xml
+++ b/v7/appcompat/res/layout/abc_action_mode_bar.xml
@@ -18,7 +18,7 @@
 -->
 <android.support.v7.internal.widget.ActionBarContextView
         xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="fill_parent"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:visibility="gone"
         style="?attr/actionModeStyle"/>
diff --git a/v7/appcompat/res/layout/abc_action_mode_close_item.xml b/v7/appcompat/res/layout/abc_action_mode_close_item.xml
index 2c76b8b..b847f92 100644
--- a/v7/appcompat/res/layout/abc_action_mode_close_item.xml
+++ b/v7/appcompat/res/layout/abc_action_mode_close_item.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
+<!-- Copyright (C) 2014 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -22,11 +22,13 @@
               android:contentDescription="@string/abc_action_mode_done"
               style="?attr/actionModeCloseButtonStyle"
               android:layout_width="wrap_content"
-              android:layout_height="fill_parent"
+              android:layout_height="match_parent"
               android:layout_marginRight="16dip">
-    <ImageView android:layout_width="wrap_content"
-               android:layout_height="wrap_content"
-               android:layout_gravity="center"
-               android:scaleType="fitCenter"
-               android:src="?attr/actionModeCloseDrawable"/>
+
+    <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:scaleType="fitCenter"
+            android:src="?attr/actionModeCloseDrawable"/>
 </LinearLayout>
diff --git a/v7/appcompat/res/layout/abc_activity_chooser_view.xml b/v7/appcompat/res/layout/abc_activity_chooser_view.xml
index eb42598..99c2395 100644
--- a/v7/appcompat/res/layout/abc_activity_chooser_view.xml
+++ b/v7/appcompat/res/layout/abc_activity_chooser_view.xml
@@ -16,7 +16,7 @@
 ** limitations under the License.
 */
 -->
-<android.support.v7.internal.widget.LinearLayoutICS
+<android.support.v7.widget.LinearLayoutCompat
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/activity_chooser_view_content"
     android:layout_width="wrap_content"
@@ -26,4 +26,4 @@
 
     <include layout="@layout/abc_activity_chooser_view_include" />
 
-</android.support.v7.internal.widget.LinearLayoutICS>
\ No newline at end of file
+</android.support.v7.widget.LinearLayoutCompat>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_list_menu_item_layout.xml b/v7/appcompat/res/layout/abc_list_menu_item_layout.xml
index d3be782..1cee43e 100644
--- a/v7/appcompat/res/layout/abc_list_menu_item_layout.xml
+++ b/v7/appcompat/res/layout/abc_list_menu_item_layout.xml
@@ -25,31 +25,33 @@
     <RelativeLayout
             android:layout_width="0dip"
             android:layout_weight="1"
-            android:layout_marginLeft="?attr/listPreferredItemPaddingLeft"
-            android:layout_marginRight="?attr/listPreferredItemPaddingRight"
             android:layout_height="wrap_content"
             android:layout_gravity="center_vertical"
+            android:layout_marginLeft="?attr/listPreferredItemPaddingLeft"
+            android:layout_marginRight="?attr/listPreferredItemPaddingRight"
             android:duplicateParentState="true">
 
         <TextView
-                android:id="@+id/title"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_alignParentTop="true"
-                android:textAppearance="?attr/textAppearanceListItemSmall"
-                android:singleLine="true"
-                android:duplicateParentState="true"
-                android:ellipsize="marquee"
-                android:fadingEdge="horizontal" />
+            android:id="@+id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:layout_alignParentLeft="true"
+            android:textAppearance="?attr/textAppearanceListItemSmall"
+            android:singleLine="true"
+            android:duplicateParentState="true"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal" />
 
         <TextView
-                android:id="@+id/shortcut"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_below="@id/title"
-                android:textAppearance="?android:attr/textAppearanceSmall"
-                android:singleLine="true"
-                android:duplicateParentState="true" />
+            android:id="@+id/shortcut"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/title"
+            android:layout_alignParentLeft="true"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:singleLine="true"
+            android:duplicateParentState="true" />
 
     </RelativeLayout>
 
diff --git a/v7/appcompat/res/layout/abc_popup_menu_item_layout.xml b/v7/appcompat/res/layout/abc_popup_menu_item_layout.xml
index 1e4e27d..6c68620 100644
--- a/v7/appcompat/res/layout/abc_popup_menu_item_layout.xml
+++ b/v7/appcompat/res/layout/abc_popup_menu_item_layout.xml
@@ -4,9 +4,9 @@
      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.
@@ -14,45 +14,44 @@
      limitations under the License.
 -->
 
-<android.support.v7.internal.view.menu.ListMenuItemView
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="fill_parent"
-        android:layout_height="?attr/dropdownListPreferredItemHeight"
-        android:minWidth="196dip"
-        android:paddingRight="16dip">
+<android.support.v7.internal.view.menu.ListMenuItemView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="?attr/dropdownListPreferredItemHeight"
+    android:minWidth="196dip"
+    android:paddingRight="16dip">
 
     <!-- Icon will be inserted here. -->
 
     <!-- The title and summary have some gap between them, and this 'group' should be centered vertically. -->
     <RelativeLayout
-            android:layout_width="0dip"
-            android:layout_weight="1"
+        android:layout_width="0dip"
+        android:layout_weight="1"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_marginLeft="16dip"
+        android:duplicateParentState="true">
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical"
-            android:layout_marginLeft="16dip"
-            android:duplicateParentState="true">
+            android:layout_alignParentTop="true"
+            android:layout_alignParentLeft="true"
+            android:textAppearance="?attr/textAppearanceLargePopupMenu"
+            android:singleLine="true"
+            android:duplicateParentState="true"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"/>
 
         <TextView
-                android:id="@+id/title"
-                android:layout_width="fill_parent"
-                android:layout_height="wrap_content"
-                android:layout_alignParentTop="true"
-                android:layout_alignParentLeft="true"
-                android:textAppearance="?attr/textAppearanceLargePopupMenu"
-                android:singleLine="true"
-                android:duplicateParentState="true"
-                android:ellipsize="marquee"
-                android:fadingEdge="horizontal"/>
-
-        <TextView
-                android:id="@+id/shortcut"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_below="@id/title"
-                android:layout_alignParentLeft="true"
-                android:textAppearance="?attr/textAppearanceSmallPopupMenu"
-                android:singleLine="true"
-                android:duplicateParentState="true"/>
+            android:id="@+id/shortcut"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/title"
+            android:layout_alignParentLeft="true"
+            android:textAppearance="?attr/textAppearanceSmallPopupMenu"
+            android:singleLine="true"
+            android:duplicateParentState="true"/>
 
     </RelativeLayout>
 
diff --git a/v7/appcompat/res/layout/abc_screen_content_include.xml b/v7/appcompat/res/layout/abc_screen_content_include.xml
new file mode 100644
index 0000000..57d1d31
--- /dev/null
+++ b/v7/appcompat/res/layout/abc_screen_content_include.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <android.support.v7.internal.widget.ContentFrameLayout
+            android:id="@id/action_bar_activity_content"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+
+</merge>
diff --git a/v7/appcompat/res/layout/abc_screen_simple.xml b/v7/appcompat/res/layout/abc_screen_simple.xml
new file mode 100644
index 0000000..9d5c32d
--- /dev/null
+++ b/v7/appcompat/res/layout/abc_screen_simple.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/action_bar_root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:fitsSystemWindows="true">
+
+    <ViewStub
+        android:id="@+id/action_mode_bar_stub"
+        android:inflatedId="@+id/action_mode_bar"
+        android:layout="@layout/abc_action_mode_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+    <include layout="@layout/abc_screen_content_include" />
+
+</LinearLayout>
diff --git a/v7/appcompat/res/layout/abc_screen_simple_overlay_action_mode.xml b/v7/appcompat/res/layout/abc_screen_simple_overlay_action_mode.xml
new file mode 100644
index 0000000..fe4c533
--- /dev/null
+++ b/v7/appcompat/res/layout/abc_screen_simple_overlay_action_mode.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+This is an optimized layout for a screen, with the minimum set of features
+enabled.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:id="@+id/action_bar_root"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"
+             android:fitsSystemWindows="true">
+
+    <include layout="@layout/abc_screen_content_include" />
+
+    <ViewStub
+            android:id="@+id/action_mode_bar_stub"
+            android:inflatedId="@+id/action_mode_bar"
+            android:layout="@layout/abc_action_mode_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+</FrameLayout>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_screen_toolbar.xml b/v7/appcompat/res/layout/abc_screen_toolbar.xml
new file mode 100644
index 0000000..ef4c8d7
--- /dev/null
+++ b/v7/appcompat/res/layout/abc_screen_toolbar.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!--
+    This is an optimized layout for a screen with a toolbar enabled.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:id="@+id/action_bar_root"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"
+             android:orientation="vertical"
+             android:fitsSystemWindows="true">
+
+    <!-- @layout/abc_screen_toolbar_include will be inflated and merged in code -->
+
+</LinearLayout>
diff --git a/v7/appcompat/res/layout/abc_screen_toolbar_include.xml b/v7/appcompat/res/layout/abc_screen_toolbar_include.xml
new file mode 100644
index 0000000..b2c97bc
--- /dev/null
+++ b/v7/appcompat/res/layout/abc_screen_toolbar_include.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <android.support.v7.internal.widget.ActionBarOverlayLayout
+            xmlns:android="http://schemas.android.com/apk/res/android"
+            android:id="@+id/decor_content_parent"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+        <include layout="@layout/abc_screen_content_include" />
+
+        <android.support.v7.internal.widget.ActionBarContainer
+                android:id="@+id/action_bar_container"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignParentTop="true"
+                style="?attr/actionBarStyle"
+                android:touchscreenBlocksFocus="true"
+                android:gravity="top">
+
+            <android.support.v7.widget.Toolbar
+                    android:id="@+id/action_bar"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    style="?attr/toolbarStyle"/>
+
+            <android.support.v7.internal.widget.ActionBarContextView
+                    android:id="@+id/action_context_bar"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:visibility="gone"
+                    style="?attr/actionModeStyle"/>
+
+        </android.support.v7.internal.widget.ActionBarContainer>
+
+    </android.support.v7.internal.widget.ActionBarOverlayLayout>
+
+</merge>
diff --git a/v7/appcompat/res/layout/abc_search_dropdown_item_icons_2line.xml b/v7/appcompat/res/layout/abc_search_dropdown_item_icons_2line.xml
index ffc3aed..30ba774 100644
--- a/v7/appcompat/res/layout/abc_search_dropdown_item_icons_2line.xml
+++ b/v7/appcompat/res/layout/abc_search_dropdown_item_icons_2line.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2014 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,14 +15,15 @@
  * 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:paddingStart="@dimen/abc_dropdownitem_text_padding_left"
+                android:paddingEnd="4dip"
                 android:paddingLeft="@dimen/abc_dropdownitem_text_padding_left"
                 android:paddingRight="4dip"
                 android:layout_width="match_parent"
-                android:layout_height="?attr/searchResultListItemHeight" >
+                android:layout_height="58dip" >
 
     <!-- Icons come first in the layout, since their placement doesn't depend on
          the placement of the text views. -->
@@ -30,6 +31,7 @@
                android:layout_width="@dimen/abc_dropdownitem_icon_width"
                android:layout_height="48dip"
                android:scaleType="centerInside"
+               android:layout_alignParentStart="true"
                android:layout_alignParentLeft="true"
                android:layout_alignParentTop="true"
                android:layout_alignParentBottom="true"
@@ -39,18 +41,19 @@
                android:layout_width="48dip"
                android:layout_height="48dip"
                android:scaleType="centerInside"
+               android:layout_alignParentEnd="true"
                android:layout_alignParentRight="true"
                android:layout_alignParentTop="true"
                android:layout_alignParentBottom="true"
-               android:src="?attr/searchViewEditQuery"
-               android:background="?attr/searchViewEditQueryBackground"
+               android:background="?attr/selectableItemBackground"
                android:visibility="gone" />
 
-    <ImageView android:id="@android:id/icon2"
+    <ImageView android:id="@id/android:icon2"
                android:layout_width="48dip"
                android:layout_height="48dip"
                android:scaleType="centerInside"
                android:layout_alignWithParentIfMissing="true"
+               android:layout_toStartOf="@id/edit_query"
                android:layout_toLeftOf="@id/edit_query"
                android:layout_alignParentTop="true"
                android:layout_alignParentBottom="true"
@@ -67,6 +70,8 @@
               android:layout_height="29dip"
               android:paddingBottom="4dip"
               android:gravity="top"
+              android:layout_toEndOf="@android:id/icon1"
+              android:layout_toStartOf="@android:id/icon2"
               android:layout_toRightOf="@android:id/icon1"
               android:layout_toLeftOf="@android:id/icon2"
               android:layout_alignWithParentIfMissing="true"
@@ -82,8 +87,10 @@
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:layout_centerVertical="true"
+              android:layout_toEndOf="@android:id/icon1"
+              android:layout_toStartOf="@android:id/icon2"
               android:layout_toRightOf="@android:id/icon1"
               android:layout_toLeftOf="@android:id/icon2"
               android:layout_above="@android:id/text2" />
 
-</RelativeLayout>
\ No newline at end of file
+</RelativeLayout>
diff --git a/v7/appcompat/res/layout/abc_search_view.xml b/v7/appcompat/res/layout/abc_search_view.xml
index da8adb8..46e2ece 100644
--- a/v7/appcompat/res/layout/abc_search_view.xml
+++ b/v7/appcompat/res/layout/abc_search_view.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2014 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,8 +22,7 @@
         android:id="@+id/search_bar"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:orientation="horizontal"
-        >
+        android:orientation="horizontal">
 
     <!-- This is actually used for the badge icon *or* the badge label (or neither) -->
     <TextView
@@ -35,8 +34,7 @@
             android:drawablePadding="0dip"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:textColor="?android:attr/textColorPrimary"
-            android:visibility="gone"
-            />
+            android:visibility="gone" />
 
     <ImageView
             android:id="@+id/search_button"
@@ -44,10 +42,8 @@
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
             android:layout_gravity="center_vertical"
-            android:src="?attr/searchViewSearchIcon"
             android:focusable="true"
-            android:contentDescription="@string/abc_searchview_description_search"
-            />
+            android:contentDescription="@string/abc_searchview_description_search" />
 
     <LinearLayout
             android:id="@+id/search_edit_frame"
@@ -59,18 +55,18 @@
             android:layout_marginBottom="4dip"
             android:layout_marginLeft="8dip"
             android:layout_marginRight="8dip"
-            android:orientation="horizontal">
+            android:orientation="horizontal"
+            android:layoutDirection="locale">
 
         <ImageView
                 android:id="@+id/search_mag_icon"
                 android:layout_width="@dimen/abc_dropdownitem_icon_width"
                 android:layout_height="wrap_content"
                 android:scaleType="centerInside"
+                android:layout_marginStart="@dimen/abc_dropdownitem_text_padding_left"
                 android:layout_marginLeft="@dimen/abc_dropdownitem_text_padding_left"
                 android:layout_gravity="center_vertical"
-                android:src="?attr/searchViewSearchIcon"
-                android:visibility="gone"
-                />
+                android:visibility="gone" />
 
         <!-- Inner layout contains the app icon, button(s) and EditText -->
         <LinearLayout
@@ -79,11 +75,9 @@
                 android:layout_height="wrap_content"
                 android:layout_weight="1"
                 android:layout_gravity="center_vertical"
-                android:orientation="horizontal"
-                android:background="?attr/searchViewTextField">
+                android:orientation="horizontal">
 
             <view class="android.support.v7.widget.SearchView$SearchAutoComplete"
-                  style="?attr/searchViewAutoCompleteTextView"
                   android:id="@+id/search_src_text"
                   android:layout_height="36dip"
                   android:layout_width="0dp"
@@ -100,9 +94,7 @@
                   android:dropDownHeight="wrap_content"
                   android:dropDownAnchor="@id/search_edit_frame"
                   android:dropDownVerticalOffset="0dip"
-                  android:dropDownHorizontalOffset="0dip"
-                  android:contentDescription="@string/abc_searchview_description_query"
-                    />
+                  android:dropDownHorizontalOffset="0dip" />
 
             <ImageView
                     android:id="@+id/search_close_btn"
@@ -112,10 +104,8 @@
                     android:paddingRight="8dip"
                     android:layout_gravity="center_vertical"
                     android:background="?attr/selectableItemBackground"
-                    android:src="?attr/searchViewCloseIcon"
                     android:focusable="true"
-                    android:contentDescription="@string/abc_searchview_description_clear"
-                    />
+                    android:contentDescription="@string/abc_searchview_description_clear" />
 
         </LinearLayout>
 
@@ -123,37 +113,31 @@
                 android:id="@+id/submit_area"
                 android:orientation="horizontal"
                 android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:background="?attr/searchViewTextFieldRight">
+                android:layout_height="match_parent">
 
             <ImageView
                     android:id="@+id/search_go_btn"
                     android:layout_width="wrap_content"
                     android:layout_height="match_parent"
                     android:layout_gravity="center_vertical"
-                    android:paddingLeft="16dip"
-                    android:paddingRight="16dip"
+                    android:paddingStart="16dip"
+                    android:paddingEnd="16dip"
                     android:background="?attr/selectableItemBackground"
-                    android:src="?attr/searchViewGoIcon"
                     android:visibility="gone"
                     android:focusable="true"
-                    android:contentDescription="@string/abc_searchview_description_submit"
-                    />
+                    android:contentDescription="@string/abc_searchview_description_submit" />
 
             <ImageView
                     android:id="@+id/search_voice_btn"
                     android:layout_width="wrap_content"
                     android:layout_height="match_parent"
                     android:layout_gravity="center_vertical"
-                    android:paddingLeft="16dip"
-                    android:paddingRight="16dip"
-                    android:src="?attr/searchViewVoiceIcon"
+                    android:paddingStart="16dip"
+                    android:paddingEnd="16dip"
                     android:background="?attr/selectableItemBackground"
                     android:visibility="gone"
                     android:focusable="true"
-                    android:contentDescription="@string/abc_searchview_description_voice"
-                    />
+                    android:contentDescription="@string/abc_searchview_description_voice" />
         </LinearLayout>
     </LinearLayout>
-
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/v7/appcompat/res/layout/abc_simple_decor.xml b/v7/appcompat/res/layout/abc_simple_decor.xml
deleted file mode 100644
index 78316e8..0000000
--- a/v7/appcompat/res/layout/abc_simple_decor.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/action_bar_root"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:fitsSystemWindows="true">
-
-    <FrameLayout
-        android:id="@id/action_bar_activity_content"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:foreground="?android:attr/windowContentOverlay"/>
-
-</LinearLayout>
diff --git a/v7/appcompat/res/layout/abc_simple_dropdown_hint.xml b/v7/appcompat/res/layout/abc_simple_dropdown_hint.xml
new file mode 100644
index 0000000..ad8bc39
--- /dev/null
+++ b/v7/appcompat/res/layout/abc_simple_dropdown_hint.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+          android:id="@android:id/text1"
+          android:textAppearance="?android:attr/dropDownHintAppearance"
+          android:singleLine="true"
+          android:layout_marginStart="3dip"
+          android:layout_marginTop="3dip"
+          android:layout_marginEnd="3dip"
+          android:layout_marginBottom="3dip"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content" />
\ No newline at end of file
diff --git a/v7/appcompat/res/values-af/strings.xml b/v7/appcompat/res/values-af/strings.xml
new file mode 100644
index 0000000..f7348c4
--- /dev/null
+++ b/v7/appcompat/res/values-af/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Klaar"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigeer tuis"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigeer op"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Nog opsies"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Soek"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Soeknavraag"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Vee navraag uit"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Dien navraag in"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Stemsoektog"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Kies \'n program"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Sien alles"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Deel met %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Deel met"</string>
+</resources>
diff --git a/v7/appcompat/res/values-am/strings.xml b/v7/appcompat/res/values-am/strings.xml
new file mode 100644
index 0000000..e849b31
--- /dev/null
+++ b/v7/appcompat/res/values-am/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"ተከናውኗል"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"ወደ መነሻ ይዳስሱ"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"ወደ ላይ ይዳስሱ"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ተጨማሪ አማራጮች"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"ፍለጋ"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"የፍለጋ ጥያቄ"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"መጠይቅ አጽዳ"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"መጠይቅ ያስረክቡ"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"የድምፅ ፍለጋ"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"መተግበሪያ ይምረጡ"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"ሁሉንም ይመልከቱ"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"ከ%s ጋር ያጋሩ"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"ከሚከተለው ጋር ያጋሩ"</string>
+</resources>
diff --git a/v7/appcompat/res/values-ar/strings.xml b/v7/appcompat/res/values-ar/strings.xml
new file mode 100644
index 0000000..93dde04
--- /dev/null
+++ b/v7/appcompat/res/values-ar/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"تم"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"التنقل إلى الشاشة الرئيسية"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"التنقل إلى أعلى"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"خيارات إضافية"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"بحث"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"طلب البحث"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"محو طلب البحث"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"إرسال طلب البحث"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"البحث الصوتي"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"اختيار تطبيق"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"عرض الكل"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"‏مشاركة مع %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"مشاركة مع"</string>
+</resources>
diff --git a/v7/appcompat/res/values-bg/strings.xml b/v7/appcompat/res/values-bg/strings.xml
new file mode 100644
index 0000000..de3bde8
--- /dev/null
+++ b/v7/appcompat/res/values-bg/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Готово"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Придвижване към „Начало“"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Придвижване нагоре"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Още опции"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Търсене"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Заявка за търсене"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Изчистване на заявката"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Изпращане на заявката"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Гласово търсене"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Изберете приложение"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Вижте всички"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Споделяне със: %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Споделяне със:"</string>
+</resources>
diff --git a/v7/appcompat/res/values-bn-rBD/strings.xml b/v7/appcompat/res/values-bn-rBD/strings.xml
new file mode 100644
index 0000000..393240f
--- /dev/null
+++ b/v7/appcompat/res/values-bn-rBD/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"সম্পন্ন হয়েছে"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"হোম এ নেভিগেট করুন"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"উপরের দিকে নেভিগেট করুন"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"আরো বিকল্প"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"অনুসন্ধান করুন"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"ক্যোয়ারী অনুসন্ধান করুন"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"ক্যোয়ারী সাফ করুন"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"ক্যোয়ারী জমা দিন"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"ভয়েস অনুসন্ধান"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"একটি অ্যাপ্লিকেশান চয়ন করুন"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"সবগুলো দেখুন"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s এর সাথে ভাগ করুন"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"এর সাথে ভাগ করুন"</string>
+</resources>
diff --git a/v7/appcompat/res/values-ca/strings.xml b/v7/appcompat/res/values-ca/strings.xml
new file mode 100644
index 0000000..bfd4cb0
--- /dev/null
+++ b/v7/appcompat/res/values-ca/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Fet"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navega a la pàgina d\'inici"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navega cap a dalt"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Més opcions"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Cerca"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Consulta de cerca"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Esborra la consulta"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Envia la consulta"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Cerca per veu"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Selecciona una aplicació"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Mostra\'ls tots"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Comparteix amb %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Comparteix amb"</string>
+</resources>
diff --git a/v7/appcompat/res/values-cs/strings.xml b/v7/appcompat/res/values-cs/strings.xml
new file mode 100644
index 0000000..1465fdc
--- /dev/null
+++ b/v7/appcompat/res/values-cs/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Hotovo"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Přejít na plochu"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Přejít nahoru"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Více možností"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Hledat"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Vyhledávací dotaz"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Smazat dotaz"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Odeslat dotaz"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Hlasové vyhledávání"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Vybrat aplikaci"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Zobrazit vše"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Sdílet pomocí %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Sdílet pomocí"</string>
+</resources>
diff --git a/v7/appcompat/res/values-da/strings.xml b/v7/appcompat/res/values-da/strings.xml
new file mode 100644
index 0000000..b178513
--- /dev/null
+++ b/v7/appcompat/res/values-da/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Luk"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Naviger hjem"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Naviger op"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Flere muligheder"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Søg"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Søgeforespørgsel"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Ryd forespørgslen"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Indsend forespørgslen"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Stemmesøgning"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Vælg en app"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Se alle"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Del med %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Del med"</string>
+</resources>
diff --git a/v7/appcompat/res/values-de/strings.xml b/v7/appcompat/res/values-de/strings.xml
new file mode 100644
index 0000000..6da4b71
--- /dev/null
+++ b/v7/appcompat/res/values-de/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Fertig"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Zur Startseite"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Nach oben"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Weitere Optionen"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Suchen"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Suchanfrage"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Suchanfrage löschen"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Suchanfrage senden"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Sprachsuche"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"App auswählen"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Alle ansehen"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Freigeben für %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Freigeben für"</string>
+</resources>
diff --git a/v7/appcompat/res/values-el/strings.xml b/v7/appcompat/res/values-el/strings.xml
new file mode 100644
index 0000000..4c0e286
--- /dev/null
+++ b/v7/appcompat/res/values-el/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Τέλος"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Πλοήγηση στην αρχική σελίδα"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Πλοήγηση προς τα επάνω"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Περισσότερες επιλογές"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Αναζήτηση"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Ερώτημα αναζήτησης"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Διαγραφή ερωτήματος"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Υποβολή ερωτήματος"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Φωνητική αναζήτηση"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Επιλέξτε κάποια εφαρμογή"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Προβολή όλων"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Κοινή χρήση με %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Κοινή χρήση με"</string>
+</resources>
diff --git a/v7/appcompat/res/values-en-rGB/strings.xml b/v7/appcompat/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..3ec0b0e
--- /dev/null
+++ b/v7/appcompat/res/values-en-rGB/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Finished"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigate home"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigate up"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"More options"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Search"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Search query"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Clear query"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Submit query"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Voice search"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Choose an app"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"See all"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Share with %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Share with"</string>
+</resources>
diff --git a/v7/appcompat/res/values-en-rIN/strings.xml b/v7/appcompat/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..3ec0b0e
--- /dev/null
+++ b/v7/appcompat/res/values-en-rIN/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Finished"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigate home"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigate up"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"More options"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Search"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Search query"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Clear query"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Submit query"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Voice search"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Choose an app"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"See all"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Share with %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Share with"</string>
+</resources>
diff --git a/v7/appcompat/res/values-es-rUS/strings.xml b/v7/appcompat/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..6ab7942
--- /dev/null
+++ b/v7/appcompat/res/values-es-rUS/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Listo"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navegar a la página principal"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navegar hacia arriba"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Más opciones"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Búsqueda"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Consulta de búsqueda"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Eliminar la consulta"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Enviar consulta"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Búsqueda por voz"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Elige una aplicación."</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Ver todo"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Compartir con %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Compartir con"</string>
+</resources>
diff --git a/v7/appcompat/res/values-es/strings.xml b/v7/appcompat/res/values-es/strings.xml
new file mode 100644
index 0000000..ed15b35
--- /dev/null
+++ b/v7/appcompat/res/values-es/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Listo"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Ir a la pantalla de inicio"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Desplazarse hacia arriba"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Más opciones"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Buscar"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Consulta"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Borrar consulta"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Enviar consulta"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Búsqueda por voz"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Seleccionar una aplicación"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Ver todo"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Compartir con %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Compartir con"</string>
+</resources>
diff --git a/v7/appcompat/res/values-et-rEE/strings.xml b/v7/appcompat/res/values-et-rEE/strings.xml
new file mode 100644
index 0000000..2ae925d
--- /dev/null
+++ b/v7/appcompat/res/values-et-rEE/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Valmis"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigeerimine avaekraanile"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigeerimine üles"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Rohkem valikuid"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Otsing"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Otsingupäring"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Päringu tühistamine"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Päringu esitamine"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Häälotsing"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Valige rakendus"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Kuva kõik"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Jagamine kasutajaga %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Jagamine:"</string>
+</resources>
diff --git a/v7/appcompat/res/values-eu-rES/strings.xml b/v7/appcompat/res/values-eu-rES/strings.xml
new file mode 100644
index 0000000..ee6ac4d
--- /dev/null
+++ b/v7/appcompat/res/values-eu-rES/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Eginda"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Joan orri nagusira"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Joan gora"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Aukera gehiago"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Bilatu"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Bilaketa-kontsulta"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Garbitu kontsulta"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Bidali kontsulta"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Ahots bidezko bilaketa"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Aukeratu aplikazio bat"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Ikusi guztiak"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Partekatu %s erabiltzailearekin"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Partekatu hauekin"</string>
+</resources>
diff --git a/v7/appcompat/res/values-fa/strings.xml b/v7/appcompat/res/values-fa/strings.xml
new file mode 100644
index 0000000..8e10e92
--- /dev/null
+++ b/v7/appcompat/res/values-fa/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"انجام شد"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"پیمایش به صفحه اصلی"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"پیمایش به بالا"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"گزینه‌های بیشتر"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"جستجو"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"عبارت جستجو"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"پاک کردن عبارت جستجو"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"ارسال عبارت جستجو"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"جستجوی شفاهی"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"انتخاب برنامه"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"مشاهده همه"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"‏اشتراک‌گذاری با %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"اشتراک‌گذاری با"</string>
+</resources>
diff --git a/v7/appcompat/res/values-fi/strings.xml b/v7/appcompat/res/values-fi/strings.xml
new file mode 100644
index 0000000..6755cea
--- /dev/null
+++ b/v7/appcompat/res/values-fi/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Valmis"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Siirry etusivulle"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Siirry ylös"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Lisää"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Haku"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Hakulauseke"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Tyhjennä kysely"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Lähetä kysely"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Puhehaku"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Valitse sovellus"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Näytä kaikki"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Jakaminen: %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Jakaminen:"</string>
+</resources>
diff --git a/v7/appcompat/res/values-fr-rCA/strings.xml b/v7/appcompat/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..417705a
--- /dev/null
+++ b/v7/appcompat/res/values-fr-rCA/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Terminé"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Revenir à l\'accueil"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Revenir en haut de la page"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Plus d\'options"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Rechercher"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Requête de recherche"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Effacer la requête"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Envoyer la requête"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Recherche vocale"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Sélectionnez une application"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Voir toutes les chaînes"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Partager avec %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Partager avec"</string>
+</resources>
diff --git a/v7/appcompat/res/values-fr/strings.xml b/v7/appcompat/res/values-fr/strings.xml
new file mode 100644
index 0000000..27b8f38
--- /dev/null
+++ b/v7/appcompat/res/values-fr/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"OK"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Revenir à l\'accueil"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Revenir en haut de la page"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Plus d\'options"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Rechercher"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Requête de recherche"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Effacer la requête"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Envoyer la requête"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Recherche vocale"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Sélectionner une application"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Tout afficher"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Partager avec %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Partager avec"</string>
+</resources>
diff --git a/v7/appcompat/res/values-gl-rES/strings.xml b/v7/appcompat/res/values-gl-rES/strings.xml
new file mode 100644
index 0000000..1d0d50d
--- /dev/null
+++ b/v7/appcompat/res/values-gl-rES/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Feito"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Ir á páxina de inicio"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Desprazarse cara arriba"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Máis opcións"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Buscar"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Consulta de busca"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Borrar consulta"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Enviar consulta"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Busca de voz"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Escoller unha aplicación"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Ver todas"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Compartir con %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Compartir con"</string>
+</resources>
diff --git a/v7/appcompat/res/values-hi/strings.xml b/v7/appcompat/res/values-hi/strings.xml
new file mode 100644
index 0000000..b236ebb
--- /dev/null
+++ b/v7/appcompat/res/values-hi/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"पूर्ण"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"मुखपृष्ठ पर नेविगेट करें"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"ऊपर नेविगेट करें"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"अधिक विकल्प"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"खोजें"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"खोज क्वेरी"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"क्‍वेरी साफ़ करें"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"क्वेरी सबमिट करें"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"ध्वनि खोज"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"कोई एप्‍लिकेशन चुनें"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"सभी देखें"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s के साथ साझा करें"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"इसके द्वारा साझा करें"</string>
+</resources>
diff --git a/v7/appcompat/res/values-hr/strings.xml b/v7/appcompat/res/values-hr/strings.xml
new file mode 100644
index 0000000..680e39f
--- /dev/null
+++ b/v7/appcompat/res/values-hr/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Gotovo"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Idi na početnu"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Idi gore"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Dodatne opcije"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Pretraživanje"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Upit za pretraživanje"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Izbriši upit"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Pošalji upit"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Glasovno pretraživanje"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Odabir aplikacije"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Prikaži sve"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Dijeljenje sa: %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Dijeljenje sa"</string>
+</resources>
diff --git a/v7/appcompat/res/values-hu/strings.xml b/v7/appcompat/res/values-hu/strings.xml
new file mode 100644
index 0000000..52dafb0
--- /dev/null
+++ b/v7/appcompat/res/values-hu/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Kész"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Ugrás a főoldalra"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Felfelé mozgatás"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"További lehetőségek"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Keresés"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Keresési lekérdezés"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Lekérdezés törlése"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Lekérdezés küldése"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Hangalapú keresés"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Válasszon ki egy alkalmazást"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Összes megtekintése"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Megosztás a következővel: %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Megosztás a következővel:"</string>
+</resources>
diff --git a/v7/appcompat/res/values-hy-rAM/strings.xml b/v7/appcompat/res/values-hy-rAM/strings.xml
new file mode 100644
index 0000000..6c0ee27
--- /dev/null
+++ b/v7/appcompat/res/values-hy-rAM/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Կատարված է"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Ուղղվել տուն"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Ուղղվել վերև"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Այլ ընտրանքներ"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Որոնել"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Որոնման հարցում"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Մաքրել հարցումը"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Ուղարկել հարցումը"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Ձայնային որոնում"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Ընտրել ծրագիր"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Տեսնել բոլորը"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Տարածել ըստ %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Տարածել"</string>
+</resources>
diff --git a/v7/appcompat/res/values-in/strings.xml b/v7/appcompat/res/values-in/strings.xml
new file mode 100644
index 0000000..9481e83
--- /dev/null
+++ b/v7/appcompat/res/values-in/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Selesai"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigasi ke beranda"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigasi naik"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Opsi lain"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Telusuri"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Kueri penelusuran"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Hapus kueri"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Kirim kueri"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Penelusuran suara"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Pilih aplikasi"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Lihat semua"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Bagikan dengan %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Bagikan dengan"</string>
+</resources>
diff --git a/v7/appcompat/res/values-is-rIS/strings.xml b/v7/appcompat/res/values-is-rIS/strings.xml
new file mode 100644
index 0000000..8cac570
--- /dev/null
+++ b/v7/appcompat/res/values-is-rIS/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Lokið"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Fara heim"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Fara upp"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Fleiri valkostir"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Leita"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Leitarfyrirspurn"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Hreinsa fyrirspurn"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Senda fyrirspurn"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Raddleit"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Veldu forrit"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Sjá allt"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Deila með %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Deila með"</string>
+</resources>
diff --git a/v7/appcompat/res/values-it/strings.xml b/v7/appcompat/res/values-it/strings.xml
new file mode 100644
index 0000000..a8b0f2c
--- /dev/null
+++ b/v7/appcompat/res/values-it/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Fine"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Vai alla home page"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Vai in alto"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Altre opzioni"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Cerca"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Query di ricerca"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Cancella query"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Invia query"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Ricerca vocale"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Scegli un\'applicazione"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Visualizza tutte"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Condividi con %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Condividi con"</string>
+</resources>
diff --git a/v7/appcompat/res/values-iw/strings.xml b/v7/appcompat/res/values-iw/strings.xml
new file mode 100644
index 0000000..1af07df
--- /dev/null
+++ b/v7/appcompat/res/values-iw/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"בוצע"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"נווט לדף הבית"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"נווט למעלה"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"עוד אפשרויות"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"חפש"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"שאילתת חיפוש"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"מחק שאילתה"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"שלח שאילתה"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"חיפוש קולי"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"בחר אפליקציה"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"ראה הכול"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"‏שתף עם %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"שתף עם"</string>
+</resources>
diff --git a/v7/appcompat/res/values-ja/strings.xml b/v7/appcompat/res/values-ja/strings.xml
new file mode 100644
index 0000000..659358a
--- /dev/null
+++ b/v7/appcompat/res/values-ja/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"完了"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"ホームへ移動"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"上へ移動"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"その他のオプション"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"検索"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"検索キーワード"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"検索キーワードを削除"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"検索キーワードを送信"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"音声検索"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"アプリの選択"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"すべて表示"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%sと共有"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"共有"</string>
+</resources>
diff --git a/v7/appcompat/res/values-ka-rGE/strings.xml b/v7/appcompat/res/values-ka-rGE/strings.xml
new file mode 100644
index 0000000..0c430b1
--- /dev/null
+++ b/v7/appcompat/res/values-ka-rGE/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"დასრულდა"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"მთავარზე ნავიგაცია"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"ზემოთ ნავიგაცია"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"მეტი ვარიანტები"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"ძიება"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"ძიების მოთხოვნა"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"მოთხოვნის გასუფთავება"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"მოთხოვნის გადაგზავნა"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"ხმოვანი ძიება"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"აპის არჩევა"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"ყველას ნახვა"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s-თან გაზიარება"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"გაზიარება:"</string>
+</resources>
diff --git a/v7/appcompat/res/values-kk-rKZ/strings.xml b/v7/appcompat/res/values-kk-rKZ/strings.xml
new file mode 100644
index 0000000..d3ad9e8
--- /dev/null
+++ b/v7/appcompat/res/values-kk-rKZ/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Орындалды"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Негізгі бетте қозғалу"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Жоғары қозғалу"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Басқа опциялар"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Іздеу"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Сұрақты іздеу"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Сұрақты жою"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Сұрақты жіберу"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Дауыс арқылы іздеу"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Қолданбаны таңдау"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Барлығын көру"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s бөлісу"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Бөлісу"</string>
+</resources>
diff --git a/v7/appcompat/res/values-km-rKH/strings.xml b/v7/appcompat/res/values-km-rKH/strings.xml
new file mode 100644
index 0000000..df78372
--- /dev/null
+++ b/v7/appcompat/res/values-km-rKH/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"រួចរាល់"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"រកមើល​ទៅ​ដើម"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"រកមើល​ឡើងលើ"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ជម្រើស​ច្រើន​ទៀត"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"ស្វែងរក"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"ស្វែងរក​សំណួរ"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"សម្អាត​សំណួរ"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"ដាក់​​​ស្នើ​សំណួរ"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"ការស្វែងរក​សំឡេង"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"ជ្រើស​កម្មវិធី​​"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"មើល​ទាំងអស់"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"ចែករំលែក​ជាមួយ %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"ចែករំលែក​ជាមួយ"</string>
+</resources>
diff --git a/v7/appcompat/res/values-kn-rIN/strings.xml b/v7/appcompat/res/values-kn-rIN/strings.xml
new file mode 100644
index 0000000..6af7a39
--- /dev/null
+++ b/v7/appcompat/res/values-kn-rIN/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"ಮುಗಿದಿದೆ"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"ಮುಖಪುಟವನ್ನು ನ್ಯಾವಿಗೇಟ್ ಮಾಡಿ"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"ಮೇಲಕ್ಕೆ ನ್ಯಾವಿಗೇಟ್ ಮಾಡಿ"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ಇನ್ನಷ್ಟು ಆಯ್ಕೆಗಳು"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"ಹುಡುಕು"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"ಪ್ರಶ್ನೆಯನ್ನು ಹುಡುಕಿ"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"ಪ್ರಶ್ನೆಯನ್ನು ತೆರವುಗೊಳಿಸು"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"ಪ್ರಶ್ನೆಯನ್ನು ಸಲ್ಲಿಸು"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"ಧ್ವನಿ ಹುಡುಕಾಟ"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"ಒಂದು ಅಪ್ಲಿಕೇಶನ್ ಆಯ್ಕೆಮಾಡಿ"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"ಎಲ್ಲವನ್ನೂ ನೋಡಿ"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s ಜೊತೆಗೆ ಹಂಚಿಕೊಳ್ಳಿ"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"ಇವರೊಂದಿಗೆ ಹಂಚಿಕೊಳ್ಳಿ"</string>
+</resources>
diff --git a/v7/appcompat/res/values-ko/strings.xml b/v7/appcompat/res/values-ko/strings.xml
new file mode 100644
index 0000000..d331975
--- /dev/null
+++ b/v7/appcompat/res/values-ko/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"완료"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"홈 탐색"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"위로 탐색"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"옵션 더보기"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"검색"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"검색어"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"검색어 삭제"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"검색어 보내기"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"음성 검색"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"앱 선택"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"전체 보기"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s와(과) 공유"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"공유 대상"</string>
+</resources>
diff --git a/v7/appcompat/res/values-ky-rKG/strings.xml b/v7/appcompat/res/values-ky-rKG/strings.xml
new file mode 100644
index 0000000..52abd9f
--- /dev/null
+++ b/v7/appcompat/res/values-ky-rKG/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Даяр"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Үйгө багыттоо"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Жогору"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Көбүрөөк мүмкүнчүлүктөр"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Издөө"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Издөө талаптары"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Талаптарды тазалоо"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Талап жөнөтүү"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Үн аркылуу издөө"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Колдонмо тандоо"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Бардыгын көрүү"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s аркылуу бөлүшүү"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Бөлүшүү"</string>
+</resources>
diff --git a/v7/appcompat/res/values-land/bools.xml b/v7/appcompat/res/values-land/bools.xml
index cdadd69..7d1a1af 100644
--- a/v7/appcompat/res/values-land/bools.xml
+++ b/v7/appcompat/res/values-land/bools.xml
@@ -15,6 +15,5 @@
 -->
 
 <resources>
-    <bool name="abc_split_action_bar_is_narrow">false</bool>
     <bool name="abc_action_bar_embed_tabs_pre_jb">true</bool>
 </resources>
diff --git a/v7/appcompat/res/values-land/dimens.xml b/v7/appcompat/res/values-land/dimens.xml
index 9aaf587..276476d 100644
--- a/v7/appcompat/res/values-land/dimens.xml
+++ b/v7/appcompat/res/values-land/dimens.xml
@@ -15,8 +15,6 @@
 -->
 
 <resources>
-    <!-- Default height of an action bar. -->
-    <dimen name="abc_action_bar_default_height">40dip</dimen>
     <!-- Vertical padding around action bar icons. -->
     <dimen name="abc_action_bar_icon_vertical_padding">4dip</dimen>
     <!-- Text size for action bar titles -->
@@ -29,4 +27,8 @@
     <dimen name="abc_action_bar_subtitle_bottom_margin">4dip</dimen>
     <!-- Size of the indeterminate Progress Bar -->
     <dimen name="abc_action_bar_progress_bar_size">32dp</dimen>
+
+    <!-- Default height of an action bar. -->
+    <dimen name="action_bar_default_height_material">48dp</dimen>
+
 </resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-large-v14/themes_base.xml b/v7/appcompat/res/values-large-v14/themes_base.xml
deleted file mode 100644
index d898069..0000000
--- a/v7/appcompat/res/values-large-v14/themes_base.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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="Theme.Base.AppCompat.DialogWhenLarge"
-           parent="Theme.Base.AppCompat.DialogWhenLarge.Base" />
-
-    <style name="Theme.Base.AppCompat.Light.DialogWhenLarge"
-           parent="Theme.Base.AppCompat.Light.DialogWhenLarge.Base" />
-
-</resources>
diff --git a/v7/appcompat/res/values-large/bools.xml b/v7/appcompat/res/values-large/bools.xml
index cdadd69..7d1a1af 100644
--- a/v7/appcompat/res/values-large/bools.xml
+++ b/v7/appcompat/res/values-large/bools.xml
@@ -15,6 +15,5 @@
 -->
 
 <resources>
-    <bool name="abc_split_action_bar_is_narrow">false</bool>
     <bool name="abc_action_bar_embed_tabs_pre_jb">true</bool>
 </resources>
diff --git a/v7/appcompat/res/values-large/themes_base.xml b/v7/appcompat/res/values-large/themes_base.xml
index a19dcf0..aafef5f 100644
--- a/v7/appcompat/res/values-large/themes_base.xml
+++ b/v7/appcompat/res/values-large/themes_base.xml
@@ -16,10 +16,10 @@
 
 <resources>
 
-    <style name="Theme.Base.AppCompat.DialogWhenLarge"
-           parent="Theme.Base.AppCompat.Dialog.FixedSize" />
+    <style name="Base.Theme.AppCompat.DialogWhenLarge"
+           parent="Base.Theme.AppCompat.Dialog.FixedSize" />
 
-    <style name="Theme.Base.AppCompat.Light.DialogWhenLarge"
-           parent="Theme.Base.AppCompat.Dialog.Light.FixedSize" />
+    <style name="Base.Theme.AppCompat.Light.DialogWhenLarge"
+           parent="Base.Theme.AppCompat.Light.Dialog.FixedSize" />
 
 </resources>
diff --git a/v7/appcompat/res/values-lo-rLA/strings.xml b/v7/appcompat/res/values-lo-rLA/strings.xml
new file mode 100644
index 0000000..7eb42ea
--- /dev/null
+++ b/v7/appcompat/res/values-lo-rLA/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"ແລ້ວໆ"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"ກັບໄປໜ້າຫຼັກ"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"ຂຶ້ນເທິງ"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ໂຕເລືອກອື່ນ"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"ຊອກຫາ"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"ຊອກຫາ"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"ລຶບຂໍ້ຄວາມຊອກຫາ"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"ສົ່ງການຊອກຫາ"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"ຊອກຫາດ້ວຍສຽງ"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"ເລືອກແອັບຯ"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"ເບິ່ງທັງຫມົດ"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"ແບ່ງ​ປັນ​ກັບ​ %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"ແບ່ງປັນກັບ"</string>
+</resources>
diff --git a/v7/appcompat/res/values-lt/strings.xml b/v7/appcompat/res/values-lt/strings.xml
new file mode 100644
index 0000000..c4738a7
--- /dev/null
+++ b/v7/appcompat/res/values-lt/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Atlikta"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Eiti į pagrindinį puslapį"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Eiti į viršų"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Daugiau parinkčių"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Paieška"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Paieškos užklausa"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Išvalyti užklausą"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Pateikti užklausą"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Paieška balsu"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Pasirinkti programą"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Peržiūrėti viską"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Bendrinti naudojant „%s“"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Bendrinti naudojant"</string>
+</resources>
diff --git a/v7/appcompat/res/values-lv/strings.xml b/v7/appcompat/res/values-lv/strings.xml
new file mode 100644
index 0000000..c33858a
--- /dev/null
+++ b/v7/appcompat/res/values-lv/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Gatavs"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Pārvietoties uz sākuma ekrānu"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Pārvietoties augšup"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Vairāk opciju"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Meklēt"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Meklēšanas vaicājums"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Notīrīt vaicājumu"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Iesniegt vaicājumu"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Meklēšana ar balsi"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Izvēlieties lietotni"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Skatīt visu"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Kopīgot ar %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Kopīgot ar:"</string>
+</resources>
diff --git a/v7/appcompat/res/values-mk-rMK/strings.xml b/v7/appcompat/res/values-mk-rMK/strings.xml
new file mode 100644
index 0000000..632728a
--- /dev/null
+++ b/v7/appcompat/res/values-mk-rMK/strings.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Готово"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Движи се кон дома"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Движи се нагоре"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Повеќе опции"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Пребарај"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Пребарај барање"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Исчисти барање"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Поднеси барање"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Гласовно пребарување"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Избери апликација"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Види ги сите"</string>
+    <!-- String.format failed for translation -->
+    <!-- no translation found for abc_shareactionprovider_share_with_application (7165123711973476752) -->
+    <skip />
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Сподели со"</string>
+</resources>
diff --git a/v7/appcompat/res/values-ml-rIN/strings.xml b/v7/appcompat/res/values-ml-rIN/strings.xml
new file mode 100644
index 0000000..2fb368e
--- /dev/null
+++ b/v7/appcompat/res/values-ml-rIN/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"പൂർത്തിയാക്കി"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"ഹോമിലേക്ക് നാവിഗേറ്റുചെയ്യുക"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"മുകളിലേക്ക് നാവിഗേറ്റുചെയ്യുക"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"കൂടുതല്‍ ഓപ്‌ഷനുകള്‍"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"തിരയൽ"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"തിരയൽ അന്വേഷണം"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"അന്വേഷണം മായ്‌ക്കുക"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"അന്വേഷണം സമർപ്പിക്കുക"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"വോയ്‌സ് തിരയൽ"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"ഒരു അപ്ലിക്കേഷൻ തിരഞ്ഞെടുക്കുക"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"എല്ലാം കാണുക"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s എന്നതുമായി പങ്കിടുക"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"ഇവരുമായി പങ്കിടുക"</string>
+</resources>
diff --git a/v7/appcompat/res/values-mn-rMN/strings.xml b/v7/appcompat/res/values-mn-rMN/strings.xml
new file mode 100644
index 0000000..203e959
--- /dev/null
+++ b/v7/appcompat/res/values-mn-rMN/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Дууссан"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Нүүр хуудас руу шилжих"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Дээш шилжих"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Нэмэлт сонголтууд"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Хайх"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Хайх асуулга"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Асуулгыг цэвэрлэх"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Асуулгыг илгээх"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Дуут хайлт"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Апп сонгох"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Бүгдийг харах"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s-тай хуваалцах"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Хуваалцах"</string>
+</resources>
diff --git a/v7/appcompat/res/values-mr-rIN/strings.xml b/v7/appcompat/res/values-mr-rIN/strings.xml
new file mode 100644
index 0000000..41271d4
--- /dev/null
+++ b/v7/appcompat/res/values-mr-rIN/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"पूर्ण झाले"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"मुख्‍यपृष्‍ठ नेव्‍हिगेट करा"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"वर नेव्‍हिगेट करा"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"अधिक पर्याय"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"शोध"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"शोध क्वेरी"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"क्‍वेरी स्‍पष्‍ट करा"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"क्वेरी सबमिट करा"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"व्हॉइस शोध"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"एक अ‍ॅप निवडा"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"सर्व पहा"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s सह सामायिक करा"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"यांच्यासह सामायिक करा"</string>
+</resources>
diff --git a/v7/appcompat/res/values-ms-rMY/strings.xml b/v7/appcompat/res/values-ms-rMY/strings.xml
new file mode 100644
index 0000000..b174068
--- /dev/null
+++ b/v7/appcompat/res/values-ms-rMY/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Selesai"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigasi skrin utama"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigasi ke atas"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Lagi pilihan"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Cari"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Pertanyaan carian"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Kosongkan pertanyaan"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Serah pertanyaan"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Carian suara"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Pilih apl"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Lihat semua"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Kongsi dengan %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Kongsi dengan"</string>
+</resources>
diff --git a/v7/appcompat/res/values-my-rMM/strings.xml b/v7/appcompat/res/values-my-rMM/strings.xml
new file mode 100644
index 0000000..d487f50
--- /dev/null
+++ b/v7/appcompat/res/values-my-rMM/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"ပြီးဆုံးပါပြီ"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"မူလနေရာကို သွားရန်"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"အပေါ်သို့သွားရန်"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ပိုမိုရွေးချယ်စရာများ"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"ရှာဖွေရန်"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"ရှာစရာ အချက်အလက်နေရာ"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"ရှာစရာ အချက်အလက်များ ရှင်းလင်းရန်"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"ရှာဖွေစရာ အချက်အလက်ကို အတည်ပြုရန်"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"အသံဖြင့် ရှာဖွေခြင်း"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"အပလီကေးရှင်း တစ်ခုခုကို ရွေးချယ်ပါ"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"အားလုံးကို ကြည့်ရန်"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s ကို မျှဝေပါရန်"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"မျှဝေဖို့ ရွေးပါ"</string>
+</resources>
diff --git a/v7/appcompat/res/values-nb/strings.xml b/v7/appcompat/res/values-nb/strings.xml
new file mode 100644
index 0000000..6630acf
--- /dev/null
+++ b/v7/appcompat/res/values-nb/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Ferdig"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Gå til startsiden"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Gå opp"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Flere alternativer"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Søk"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Søkeord"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Slett søket"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Utfør søket"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Talesøk"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Velg en app"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Se alle"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Del med %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Del med"</string>
+</resources>
diff --git a/v7/appcompat/res/values-ne-rNP/strings.xml b/v7/appcompat/res/values-ne-rNP/strings.xml
new file mode 100644
index 0000000..69d10dc
--- /dev/null
+++ b/v7/appcompat/res/values-ne-rNP/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"सम्पन्न भयो"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"गृह खोज्नुहोस्"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"माथि खोज्नुहोस्"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"थप विकल्पहरू"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"खोज्नुहोस्"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"जिज्ञासाको खोज गर्नुहोस्"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"प्रश्‍न हटाउनुहोस्"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"जिज्ञासा पेस गर्नुहोस्"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"भ्वाइस खोजी"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"एउटा अनुप्रयोग छान्नुहोस्"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"सबै हेर्नुहोस्"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s सँग साझेदारी गर्नुहोस्"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"साझेदारी गर्नुहोस्..."</string>
+</resources>
diff --git a/v7/appcompat/res/values-nl/strings.xml b/v7/appcompat/res/values-nl/strings.xml
new file mode 100644
index 0000000..1375f9e
--- /dev/null
+++ b/v7/appcompat/res/values-nl/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Gereed"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigeren naar startpositie"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Omhoog navigeren"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Meer opties"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Zoeken"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Zoekopdracht"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Zoekopdracht wissen"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Zoekopdracht verzenden"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Gesproken zoekopdracht"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Een app selecteren"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Alles weergeven"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Delen met %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Delen met"</string>
+</resources>
diff --git a/v7/appcompat/res/values-pl/strings.xml b/v7/appcompat/res/values-pl/strings.xml
new file mode 100644
index 0000000..aa1ba79
--- /dev/null
+++ b/v7/appcompat/res/values-pl/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Gotowe"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Przejdź do strony głównej"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Przejdź wyżej"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Więcej opcji"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Szukaj"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Wyszukiwane hasło"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Wyczyść zapytanie"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Wyślij zapytanie"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Wyszukiwanie głosowe"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Wybierz aplikację"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Zobacz wszystkie"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Udostępnij dla %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Udostępnij dla"</string>
+</resources>
diff --git a/v7/appcompat/res/values-port/bools.xml b/v7/appcompat/res/values-port/bools.xml
new file mode 100644
index 0000000..25053be
--- /dev/null
+++ b/v7/appcompat/res/values-port/bools.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+
+    <bool name="abc_action_bar_embed_tabs">false</bool>
+
+</resources>
diff --git a/v7/appcompat/res/values-pt-rPT/strings.xml b/v7/appcompat/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..0d63f5f
--- /dev/null
+++ b/v7/appcompat/res/values-pt-rPT/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Concluído"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navegar para a página inicial"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navegar para cima"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Mais opções"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Pesquisar"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Consulta de pesquisa"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Limpar consulta"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Enviar consulta"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Pesquisa por voz"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Escolher uma aplicação"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Ver tudo"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Partilhar com %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Partilhar com"</string>
+</resources>
diff --git a/v7/appcompat/res/values-pt/strings.xml b/v7/appcompat/res/values-pt/strings.xml
new file mode 100644
index 0000000..88b09ea
--- /dev/null
+++ b/v7/appcompat/res/values-pt/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Concluído"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navegar para a página inicial"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navegar para cima"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Mais opções"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Pesquisar"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Consulta de pesquisa"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Limpar consulta"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Enviar consulta"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Pesquisa por voz"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Selecione um aplicativo"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Ver tudo"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Compartilhar com %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Compartilhar com"</string>
+</resources>
diff --git a/v7/appcompat/res/values-ro/strings.xml b/v7/appcompat/res/values-ro/strings.xml
new file mode 100644
index 0000000..36a7b31
--- /dev/null
+++ b/v7/appcompat/res/values-ro/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Terminat"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Navigați la ecranul de pornire"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigați în sus"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Mai multe opțiuni"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Căutați"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Interogare de căutare"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Ștergeți interogarea"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Trimiteți interogarea"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Căutare vocală"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Alegeți o aplicaţie"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Afișați-le pe toate"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Trimiteți la %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Trimiteți la"</string>
+</resources>
diff --git a/v7/appcompat/res/values-ru/strings.xml b/v7/appcompat/res/values-ru/strings.xml
new file mode 100644
index 0000000..5c22e5e
--- /dev/null
+++ b/v7/appcompat/res/values-ru/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Готово"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Перейти на главный экран"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Перейти вверх"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Другие параметры"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Поиск"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Поисковый запрос"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Удалить запрос"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Отправить запрос"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Голосовой поиск"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Выбрать приложение"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Показать все"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Открыть доступ пользователю %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Открыть доступ"</string>
+</resources>
diff --git a/v7/appcompat/res/values-si-rLK/strings.xml b/v7/appcompat/res/values-si-rLK/strings.xml
new file mode 100644
index 0000000..a6809c3
--- /dev/null
+++ b/v7/appcompat/res/values-si-rLK/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"අවසාන වූ"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"ගෙදරට සංචාලනය කරන්න"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"ඉහලට සංචාලනය කරන්න"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"තවත් විකල්ප"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"සෙවීම"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"සෙවුම් විමසුම"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"විමසුම හිස් කරන්න"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"විමසුම යොමු කරන්න"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"හඬ සෙවීම"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"යෙදුමක් තෝරන්න"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"සියල්ල බලන්න"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s සමඟ බෙදාගන්න"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"සමඟ බෙදාගන්න"</string>
+</resources>
diff --git a/v7/appcompat/res/values-sk/strings.xml b/v7/appcompat/res/values-sk/strings.xml
new file mode 100644
index 0000000..253f3e5
--- /dev/null
+++ b/v7/appcompat/res/values-sk/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Hotovo"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Prejsť na plochu"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Prejsť hore"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Ďalšie možnosti"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Hľadať"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Vyhľadávací dopyt"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Vymazať dopyt"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Odoslať dopyt"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Hlasové vyhľadávanie"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Zvoľte aplikáciu"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Zobraziť všetko"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Zdieľať pomocou %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Zdieľať pomocou"</string>
+</resources>
diff --git a/v7/appcompat/res/values-sl/strings.xml b/v7/appcompat/res/values-sl/strings.xml
new file mode 100644
index 0000000..8e3e23e
--- /dev/null
+++ b/v7/appcompat/res/values-sl/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Končano"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Krmarjenje domov"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Krmarjenje navzgor"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Več možnosti"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Iskanje"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Iskalna poizvedba"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Izbris poizvedbe"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Pošiljanje poizvedbe"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Glasovno iskanje"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Izbira aplikacije"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Pokaži vse"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Deljenje z:"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Deljenje z"</string>
+</resources>
diff --git a/v7/appcompat/res/values-sr/strings.xml b/v7/appcompat/res/values-sr/strings.xml
new file mode 100644
index 0000000..213c939
--- /dev/null
+++ b/v7/appcompat/res/values-sr/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Готово"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Одлазак на Почетну"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Кретање нагоре"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Још опција"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Претрага"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Упит за претрагу"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Брисање упита"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Слање упита"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Гласовна претрага"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Избор апликације"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Прикажи све"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Дели са апликацијом %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Дели са"</string>
+</resources>
diff --git a/v7/appcompat/res/values-sv/strings.xml b/v7/appcompat/res/values-sv/strings.xml
new file mode 100644
index 0000000..49c7a5d
--- /dev/null
+++ b/v7/appcompat/res/values-sv/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Klart"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Visa startsidan"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Navigera uppåt"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Fler alternativ"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Sök"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Sökfråga"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Ta bort frågan"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Skicka fråga"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Röstsökning"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Välj en app"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Visa alla"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Dela med %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Dela med"</string>
+</resources>
diff --git a/v7/appcompat/res/values-sw/strings.xml b/v7/appcompat/res/values-sw/strings.xml
new file mode 100644
index 0000000..6455ba5
--- /dev/null
+++ b/v7/appcompat/res/values-sw/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Nimemaliza"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Nenda mwanzo"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Nenda juu"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Chaguo zaidi"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Tafuta"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Hoja ya utafutaji"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Futa hoja"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Wasilisha hoja"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Tafuta kwa kutamka"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Chagua programu"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Angalia zote"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Shiriki na %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Shiriki na:"</string>
+</resources>
diff --git a/v7/appcompat/res/values-sw600dp/dimens.xml b/v7/appcompat/res/values-sw600dp/dimens.xml
index 94fe525..06ab4ee 100644
--- a/v7/appcompat/res/values-sw600dp/dimens.xml
+++ b/v7/appcompat/res/values-sw600dp/dimens.xml
@@ -19,8 +19,6 @@
          an action bar/action mode. This will be used to determine how many
          showAsAction="ifRoom" items can fit. "always" items can override this. -->
     <integer name="abc_max_action_buttons">5</integer>
-    <!-- Default height of an action bar. -->
-    <dimen name="abc_action_bar_default_height">56dip</dimen>
     <!-- Vertical padding around action bar icons. -->
     <dimen name="abc_action_bar_icon_vertical_padding">4dip</dimen>
     <!-- Text size for action bar titles -->
diff --git a/v7/appcompat/res/values-ta-rIN/strings.xml b/v7/appcompat/res/values-ta-rIN/strings.xml
new file mode 100644
index 0000000..4d7d94e
--- /dev/null
+++ b/v7/appcompat/res/values-ta-rIN/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"முடிந்தது"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"முகப்பிற்கு வழிசெலுத்து"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"மேலே வழிசெலுத்து"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"மேலும் விருப்பங்கள்"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"தேடு"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"தேடல் வினவல்"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"வினவலை அழி"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"வினவலைச் சமர்ப்பி"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"குரல் தேடல்"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"பயன்பாட்டைத் தேர்வுசெய்க"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"எல்லாம் காட்டு"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s உடன் பகிர்"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"இதனுடன் பகிர்"</string>
+</resources>
diff --git a/v7/appcompat/res/values-te-rIN/strings.xml b/v7/appcompat/res/values-te-rIN/strings.xml
new file mode 100644
index 0000000..f6b1775
--- /dev/null
+++ b/v7/appcompat/res/values-te-rIN/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"పూర్తయింది"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"హోమ్‌కు నావిగేట్ చేయండి"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"పైకి నావిగేట్ చేయండి"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"మరిన్ని ఎంపికలు"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"శోధించు"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"ప్రశ్న శోధించండి"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"ప్రశ్నను క్లియర్ చేయి"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"ప్రశ్నని సమర్పించు"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"వాయిస్ శోధన"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"అనువర్తనాన్ని ఎంచుకోండి"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"అన్నీ చూడండి"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%sతో భాగస్వామ్యం చేయి"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"వీరితో భాగస్వామ్యం చేయి"</string>
+</resources>
diff --git a/v7/appcompat/res/values-th/strings.xml b/v7/appcompat/res/values-th/strings.xml
new file mode 100644
index 0000000..275dc57
--- /dev/null
+++ b/v7/appcompat/res/values-th/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"เสร็จสิ้น"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"นำทางไปหน้าแรก"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"นำทางขึ้น"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"ตัวเลือกอื่น"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"ค้นหา"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"ข้อความค้นหา"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"ล้างข้อความค้นหา"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"ส่งข้อความค้นหา"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"ค้นหาด้วยเสียง"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"เลือกแอป"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"ดูทั้งหมด"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"แชร์กับ %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"แชร์กับ"</string>
+</resources>
diff --git a/v7/appcompat/res/values-tl/strings.xml b/v7/appcompat/res/values-tl/strings.xml
new file mode 100644
index 0000000..e0705d6
--- /dev/null
+++ b/v7/appcompat/res/values-tl/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Tapos na"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Mag-navigate patungo sa home"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Mag-navigate pataas"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Higit pang mga opsyon"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Maghanap"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Query sa paghahanap"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"I-clear ang query"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Isumite ang query"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Paghahanap gamit ang boses"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Pumili ng isang app"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Tingnan lahat"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Ibahagi sa/kay %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Ibahagi sa/kay"</string>
+</resources>
diff --git a/v7/appcompat/res/values-tr/strings.xml b/v7/appcompat/res/values-tr/strings.xml
new file mode 100644
index 0000000..61cb966
--- /dev/null
+++ b/v7/appcompat/res/values-tr/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Tamamlandı"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Ana ekrana git"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Yukarı git"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Diğer seçenekler"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Ara"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Arama sorgusu"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Sorguyu temizle"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Sorguyu gönder"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Sesli arama"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Bir uygulama seçin"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Tümünü göster"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s ile paylaş"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Şununla paylaş"</string>
+</resources>
diff --git a/v7/appcompat/res/values-uk/strings.xml b/v7/appcompat/res/values-uk/strings.xml
new file mode 100644
index 0000000..f670140
--- /dev/null
+++ b/v7/appcompat/res/values-uk/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Готово"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Перейти на головний"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Перейти вгору"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Інші опції"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Пошук"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Пошуковий запит"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Очистити запит"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Надіслати запит"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Голосовий пошук"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Вибрати програму"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Переглянути всі"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Надіслати через %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Надіслати через"</string>
+</resources>
diff --git a/v7/appcompat/res/values-ur-rPK/strings.xml b/v7/appcompat/res/values-ur-rPK/strings.xml
new file mode 100644
index 0000000..f209747
--- /dev/null
+++ b/v7/appcompat/res/values-ur-rPK/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"ہو گیا"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"ہوم پر نیویگیٹ کریں"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"اوپر نیویگیٹ کریں"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"مزید اختیارات"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"تلاش کریں"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"استفسار تلاش کریں"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"استفسار صاف کریں"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"استفسار جمع کرائیں"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"صوتی تلاش"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"ایک ایپ منتخب کریں"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"سبھی دیکھیں"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"‏%s کے ساتھ اشتراک کریں"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"اشتراک کریں مع"</string>
+</resources>
diff --git a/v7/appcompat/res/values-uz-rUZ/strings.xml b/v7/appcompat/res/values-uz-rUZ/strings.xml
new file mode 100644
index 0000000..84d9541
--- /dev/null
+++ b/v7/appcompat/res/values-uz-rUZ/strings.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Tayyor"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Boshiga o‘tish"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Yuqoriga o‘tish"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Qo‘shimcha sozlamalar"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Izlash"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"So‘rovni izlash"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"So‘rovni tozalash"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"So‘rov yaratish"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Ovozli qidiruv"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Dastur tanlang"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Barchasini ko‘rish"</string>
+    <!-- String.format failed for translation -->
+    <!-- no translation found for abc_shareactionprovider_share_with_application (7165123711973476752) -->
+    <skip />
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Bo‘lishish:"</string>
+</resources>
diff --git a/v7/appcompat/res/values-v11/styles_base.xml b/v7/appcompat/res/values-v11/styles_base.xml
index 2cee503..6583424 100644
--- a/v7/appcompat/res/values-v11/styles_base.xml
+++ b/v7/appcompat/res/values-v11/styles_base.xml
@@ -23,21 +23,21 @@
 
     <!-- Progress Bar -->
 
-    <style name="Widget.AppCompat.Base.ProgressBar.Horizontal"
+    <style name="Base.Widget.AppCompat.ProgressBar.Horizontal"
            parent="android:Widget.Holo.ProgressBar.Horizontal">
     </style>
 
-    <style name="Widget.AppCompat.Base.ProgressBar"
+    <style name="Base.Widget.AppCompat.ProgressBar"
            parent="android:Widget.Holo.ProgressBar">
     </style>
 
     <!-- AutoCompleteTextView styles (for SearchView) -->
 
-    <style name="Widget.AppCompat.Base.AutoCompleteTextView"
+    <style name="Base.Widget.AppCompat.AutoCompleteTextView"
            parent="android:Widget.Holo.AutoCompleteTextView">
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.AutoCompleteTextView"
+    <style name="Base.Widget.AppCompat.Light.AutoCompleteTextView"
            parent="android:Widget.Holo.Light.AutoCompleteTextView">
     </style>
 
diff --git a/v7/appcompat/res/values-v11/styles_base_text.xml b/v7/appcompat/res/values-v11/styles_base_text.xml
new file mode 100644
index 0000000..d7118c0
--- /dev/null
+++ b/v7/appcompat/res/values-v11/styles_base_text.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+
+    <!-- Deprecated text styles -->
+
+    <style name="Base.TextAppearance.AppCompat.Inverse">
+        <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+        <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
+        <item name="android:textColorHighlight">?android:attr/textColorHighlightInverse</item>
+        <item name="android:textColorLink">?android:attr/textColorLinkInverse</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Large.Inverse">
+        <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+        <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
+        <item name="android:textColorHighlight">?android:attr/textColorHighlightInverse</item>
+        <item name="android:textColorLink">?android:attr/textColorLinkInverse</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Medium.Inverse">
+        <item name="android:textColor">?android:attr/textColorSecondaryInverse</item>
+        <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
+        <item name="android:textColorHighlight">?android:attr/textColorHighlightInverse</item>
+        <item name="android:textColorLink">?android:attr/textColorLinkInverse</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Small.Inverse">
+        <item name="android:textColor">?android:attr/textColorTertiaryInverse</item>
+        <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
+        <item name="android:textColorHighlight">?android:attr/textColorHighlightInverse</item>
+        <item name="android:textColorLink">?android:attr/textColorLinkInverse</item>
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-v11/themes_base.xml b/v7/appcompat/res/values-v11/themes_base.xml
index ddd9d59..8c99cbf 100644
--- a/v7/appcompat/res/values-v11/themes_base.xml
+++ b/v7/appcompat/res/values-v11/themes_base.xml
@@ -16,78 +16,266 @@
 
 <resources>
 
-    <!-- Themes in the "Theme.Base" family vary based on the current platform
-          version to provide the correct basis on each device. You probably don't
-          want to use them directly in your apps.
-
-          Themes in the "Theme.AppCompat" family are meant to be extended or used
-          directly by apps.
-
-          This is the values-v11/ file that only declares the Base themes for
-          Honeycomb+. You probably want to edit values/themes.xml instead. -->
+    <!--
+        Theme in the "Platform.AppCompat" family are designed to be aliases for the default
+        theme on a given platform version and should set up the default theme ready for adding our
+        unbundled Action Bar.
+    -->
     <eat-comment/>
-
-    <!-- Base platform-dependent theme  -->
-    <style name="Theme.Base" parent="android:Theme.Holo">
+    <style name="Platform.AppCompat" parent="android:Theme.Holo">
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowActionBar">false</item>
 
-        <!--
-            A native Action Mode could be displayed (for text selection, etc) so we need to ensure
-            that it is positioned correctly, so we request windowActionModeOverlay so that it
-            displays over the compat Action Bar.
-        -->
-        <item name="android:windowActionModeOverlay">true</item>
+        <!-- Window colors -->
+        <item name="android:colorForeground">@color/bright_foreground_material_dark</item>
+        <item name="android:colorForegroundInverse">@color/bright_foreground_material_light</item>
+        <item name="android:colorBackground">@color/background_material_dark</item>
+        <item name="android:colorBackgroundCacheHint">@color/abc_background_cache_hint_selector_material_dark</item>
+        <item name="android:disabledAlpha">0.5</item>
+        <item name="android:backgroundDimAmount">0.6</item>
+        <item name="android:windowBackground">@color/background_material_dark</item>
 
-        <!-- Attributes populated from the framework to be read by apps -->
-        <item name="buttonBarStyle">?android:attr/buttonBarStyle</item>
-        <item name="buttonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
-        <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
+        <!-- Text colors -->
+        <item name="android:textColorPrimary">@color/abc_primary_text_material_dark</item>
+        <item name="android:textColorPrimaryInverse">@color/abc_primary_text_material_light</item>
+        <item name="android:textColorPrimaryDisableOnly">@color/abc_primary_text_disable_only_material_dark</item>
+        <item name="android:textColorSecondary">@color/secondary_text_material_dark</item>
+        <item name="android:textColorSecondaryInverse">@color/secondary_text_material_light</item>
+        <item name="android:textColorTertiary">@color/tertiary_text_material_dark</item>
+        <item name="android:textColorTertiaryInverse">@color/tertiary_text_material_light</item>
+        <item name="android:textColorHint">@color/hint_foreground_material_dark</item>
+        <item name="android:textColorHintInverse">@color/hint_foreground_material_light</item>
+        <item name="android:textColorHighlight">@color/highlighted_text_material_dark</item>
+        <item name="android:textColorLink">@color/material_teal_500</item>
+
+        <!-- Text styles -->
+        <item name="android:textAppearance">@style/TextAppearance.AppCompat</item>
+        <item name="android:textAppearanceInverse">@style/TextAppearance.AppCompat.Inverse</item>
+        <item name="android:textAppearanceLarge">@style/TextAppearance.AppCompat.Large</item>
+        <item name="android:textAppearanceLargeInverse">@style/TextAppearance.AppCompat.Large.Inverse</item>
+        <item name="android:textAppearanceMedium">@style/TextAppearance.AppCompat.Medium</item>
+        <item name="android:textAppearanceMediumInverse">@style/TextAppearance.AppCompat.Medium.Inverse</item>
+        <item name="android:textAppearanceSmall">@style/TextAppearance.AppCompat.Small</item>
+        <item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
     </style>
 
-    <!-- Base platform-dependent theme providing a light-themed activity. -->
-    <style name="Theme.Base.Light" parent="android:Theme.Holo.Light">
+    <style name="Platform.AppCompat.Light" parent="android:Theme.Holo.Light">
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowActionBar">false</item>
 
-        <!--
-            A native Action Mode could be displayed (for text selection, etc) so we need to ensure
-            that it is positioned correctly, so we request windowActionModeOverlay so that it
-            displays over the compat Action Bar.
-        -->
-        <item name="android:windowActionModeOverlay">true</item>
+        <!-- Window colors -->
+        <item name="android:colorForeground">@color/bright_foreground_material_light</item>
+        <item name="android:colorForegroundInverse">@color/bright_foreground_material_dark</item>
+        <item name="android:colorBackground">@color/background_material_light</item>
+        <item name="android:colorBackgroundCacheHint">@color/abc_background_cache_hint_selector_material_light</item>
+        <item name="android:disabledAlpha">0.5</item>
+        <item name="android:backgroundDimAmount">0.6</item>
+        <item name="android:windowBackground">@color/background_material_light</item>
 
-        <!-- Attributes populated from the framework to be read by apps -->
-        <item name="buttonBarStyle">?android:attr/buttonBarStyle</item>
-        <item name="buttonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
-        <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
+        <!-- Text colors -->
+        <item name="android:textColorPrimary">@color/abc_primary_text_material_light</item>
+        <item name="android:textColorPrimaryInverse">@color/abc_primary_text_material_dark</item>
+        <item name="android:textColorSecondary">@color/secondary_text_material_light</item>
+        <item name="android:textColorSecondaryInverse">@color/secondary_text_material_dark</item>
+        <item name="android:textColorTertiary">@color/tertiary_text_material_light</item>
+        <item name="android:textColorTertiaryInverse">@color/tertiary_text_material_dark</item>
+        <item name="android:textColorPrimaryDisableOnly">@color/abc_primary_text_disable_only_material_light</item>
+        <item name="android:textColorPrimaryInverseDisableOnly">@color/abc_primary_text_disable_only_material_dark</item>
+        <item name="android:textColorHint">@color/hint_foreground_material_light</item>
+        <item name="android:textColorHintInverse">@color/hint_foreground_material_dark</item>
+        <item name="android:textColorHighlight">@color/highlighted_text_material_light</item>
+        <item name="android:textColorLink">@color/material_teal_500</item>
+
+        <!-- Text styles -->
+        <item name="android:textAppearance">@style/TextAppearance.AppCompat</item>
+        <item name="android:textAppearanceInverse">@style/TextAppearance.AppCompat.Inverse</item>
+        <item name="android:textAppearanceLarge">@style/TextAppearance.AppCompat.Large</item>
+        <item name="android:textAppearanceLargeInverse">@style/TextAppearance.AppCompat.Large.Inverse</item>
+        <item name="android:textAppearanceMedium">@style/TextAppearance.AppCompat.Medium</item>
+        <item name="android:textAppearanceMediumInverse">@style/TextAppearance.AppCompat.Medium.Inverse</item>
+        <item name="android:textAppearanceSmall">@style/TextAppearance.AppCompat.Small</item>
+        <item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
     </style>
 
-    <style name="Theme.Base.AppCompat.Dialog.FixedSize" parent="android:Theme.Holo.Dialog">
-        <item name="windowFixedWidthMajor">@dimen/dialog_fixed_width_major</item>
-        <item name="windowFixedWidthMinor">@dimen/dialog_fixed_width_minor</item>
-        <item name="windowFixedHeightMajor">@dimen/dialog_fixed_height_major</item>
-        <item name="windowFixedHeightMinor">@dimen/dialog_fixed_height_minor</item>
-        <item name="windowActionBar">false</item>
+    <style name="Platform.AppCompat.Dialog" parent="android:Theme.Holo.Dialog">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowActionBar">false</item>
 
-        <!-- Attributes populated from the framework to be read by apps -->
-        <item name="buttonBarStyle">?android:attr/buttonBarStyle</item>
-        <item name="buttonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
-        <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
+        <!-- Window colors -->
+        <item name="android:colorForeground">@color/bright_foreground_material_dark</item>
+        <item name="android:colorForegroundInverse">@color/bright_foreground_material_light</item>
+        <item name="android:colorBackground">@color/background_material_dark</item>
+        <item name="android:colorBackgroundCacheHint">@color/abc_background_cache_hint_selector_material_dark</item>
+        <item name="android:disabledAlpha">0.5</item>
+        <item name="android:backgroundDimAmount">0.6</item>
+        <item name="android:windowBackground">@color/background_material_dark</item>
+
+        <!-- Text colors -->
+        <item name="android:textColorPrimary">@color/abc_primary_text_material_dark</item>
+        <item name="android:textColorPrimaryInverse">@color/abc_primary_text_material_light</item>
+        <item name="android:textColorPrimaryDisableOnly">@color/abc_primary_text_disable_only_material_dark</item>
+        <item name="android:textColorSecondary">@color/secondary_text_material_dark</item>
+        <item name="android:textColorSecondaryInverse">@color/secondary_text_material_light</item>
+        <item name="android:textColorTertiary">@color/tertiary_text_material_dark</item>
+        <item name="android:textColorTertiaryInverse">@color/tertiary_text_material_light</item>
+        <item name="android:textColorHint">@color/hint_foreground_material_dark</item>
+        <item name="android:textColorHintInverse">@color/hint_foreground_material_light</item>
+        <item name="android:textColorHighlight">@color/highlighted_text_material_dark</item>
+        <item name="android:textColorLink">@color/material_teal_500</item>
+
+        <!-- Text styles -->
+        <item name="android:textAppearance">@style/TextAppearance.AppCompat</item>
+        <item name="android:textAppearanceInverse">@style/TextAppearance.AppCompat.Inverse</item>
+        <item name="android:textAppearanceLarge">@style/TextAppearance.AppCompat.Large</item>
+        <item name="android:textAppearanceLargeInverse">@style/TextAppearance.AppCompat.Large.Inverse</item>
+        <item name="android:textAppearanceMedium">@style/TextAppearance.AppCompat.Medium</item>
+        <item name="android:textAppearanceMediumInverse">@style/TextAppearance.AppCompat.Medium.Inverse</item>
+        <item name="android:textAppearanceSmall">@style/TextAppearance.AppCompat.Small</item>
+        <item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
     </style>
 
-    <style name="Theme.Base.AppCompat.Dialog.Light.FixedSize"
-           parent="android:Theme.Holo.Light.Dialog">
-        <item name="windowFixedWidthMajor">@dimen/dialog_fixed_width_major</item>
-        <item name="windowFixedWidthMinor">@dimen/dialog_fixed_width_minor</item>
-        <item name="windowFixedHeightMajor">@dimen/dialog_fixed_height_major</item>
-        <item name="windowFixedHeightMinor">@dimen/dialog_fixed_height_minor</item>
-        <item name="windowActionBar">false</item>
+    <style name="Platform.AppCompat.Light.Dialog" parent="android:Theme.Holo.Light.Dialog">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowActionBar">false</item>
 
-        <!-- Attributes populated from the framework to be read by apps -->
-        <item name="buttonBarStyle">?android:attr/buttonBarStyle</item>
-        <item name="buttonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
-        <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
+        <!-- Window colors -->
+        <item name="android:colorForeground">@color/bright_foreground_material_light</item>
+        <item name="android:colorForegroundInverse">@color/bright_foreground_material_dark</item>
+        <item name="android:colorBackground">@color/background_material_light</item>
+        <item name="android:colorBackgroundCacheHint">@color/abc_background_cache_hint_selector_material_light</item>
+        <item name="android:disabledAlpha">0.5</item>
+        <item name="android:backgroundDimAmount">0.6</item>
+        <item name="android:windowBackground">@color/background_material_light</item>
+
+        <!-- Text colors -->
+        <item name="android:textColorPrimary">@color/abc_primary_text_material_light</item>
+        <item name="android:textColorPrimaryInverse">@color/abc_primary_text_material_dark</item>
+        <item name="android:textColorSecondary">@color/secondary_text_material_light</item>
+        <item name="android:textColorSecondaryInverse">@color/secondary_text_material_dark</item>
+        <item name="android:textColorTertiary">@color/tertiary_text_material_light</item>
+        <item name="android:textColorTertiaryInverse">@color/tertiary_text_material_dark</item>
+        <item name="android:textColorPrimaryDisableOnly">@color/abc_primary_text_disable_only_material_light</item>
+        <item name="android:textColorPrimaryInverseDisableOnly">@color/abc_primary_text_disable_only_material_dark</item>
+        <item name="android:textColorHint">@color/hint_foreground_material_light</item>
+        <item name="android:textColorHintInverse">@color/hint_foreground_material_dark</item>
+        <item name="android:textColorHighlight">@color/highlighted_text_material_light</item>
+        <item name="android:textColorLink">@color/material_teal_500</item>
+
+        <!-- Text styles -->
+        <item name="android:textAppearance">@style/TextAppearance.AppCompat</item>
+        <item name="android:textAppearanceInverse">@style/TextAppearance.AppCompat.Inverse</item>
+        <item name="android:textAppearanceLarge">@style/TextAppearance.AppCompat.Large</item>
+        <item name="android:textAppearanceLargeInverse">@style/TextAppearance.AppCompat.Large.Inverse</item>
+        <item name="android:textAppearanceMedium">@style/TextAppearance.AppCompat.Medium</item>
+        <item name="android:textAppearanceMediumInverse">@style/TextAppearance.AppCompat.Medium.Inverse</item>
+        <item name="android:textAppearanceSmall">@style/TextAppearance.AppCompat.Small</item>
+        <item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
     </style>
 
+
+    <style name="Base.V11.Theme.AppCompat.Dialog" parent="Base.V7.Theme.AppCompat.Dialog" />
+
+    <style name="Base.V11.Theme.AppCompat.Light.Dialog" parent="Platform.AppCompat.Light.Dialog">
+        <item name="windowActionBar">true</item>
+        <item name="windowActionBarOverlay">false</item>
+        <item name="isLightTheme">true</item>
+
+        <item name="buttonBarStyle">@android:style/ButtonBar</item>
+        <item name="buttonBarButtonStyle">@android:style/Widget.Button</item>
+        <item name="selectableItemBackground">@drawable/abc_item_background_holo_light</item>
+        <item name="selectableItemBackgroundBorderless">?attr/selectableItemBackground</item>
+        <item name="homeAsUpIndicator">@drawable/abc_ic_ab_back_material_light</item>
+
+        <item name="dividerVertical">@drawable/abc_list_divider_holo_light</item>
+        <item name="dividerHorizontal">@drawable/abc_list_divider_holo_light</item>
+
+        <!-- Action Bar Styles -->
+        <item name="actionBarTabStyle">@style/Widget.AppCompat.Light.ActionBar.TabView</item>
+        <item name="actionBarTabBarStyle">@style/Widget.AppCompat.Light.ActionBar.TabBar</item>
+        <item name="actionBarTabTextStyle">@style/Widget.AppCompat.Light.ActionBar.TabText</item>
+        <item name="actionButtonStyle">@style/Widget.AppCompat.Light.ActionButton</item>
+        <item name="actionOverflowButtonStyle">@style/Widget.AppCompat.Light.ActionButton.Overflow</item>
+        <item name="actionOverflowMenuStyle">@style/Widget.AppCompat.Light.PopupMenu.Overflow</item>
+        <item name="actionBarStyle">@style/Widget.AppCompat.Light.ActionBar.Solid</item>
+        <item name="actionBarSplitStyle">?attr/actionBarStyle</item>
+        <item name="actionBarWidgetTheme">@null</item>
+        <item name="actionBarTheme">@style/ThemeOverlay.AppCompat.ActionBar</item>
+        <item name="actionBarSize">@dimen/abc_action_bar_default_height_material</item>
+        <item name="actionBarDivider">?attr/dividerVertical</item>
+        <item name="actionBarItemBackground">?attr/selectableItemBackgroundBorderless</item>
+        <item name="actionMenuTextAppearance">@style/TextAppearance.AppCompat.Widget.ActionBar.Menu</item>
+        <item name="actionMenuTextColor">?android:attr/textColorPrimaryDisableOnly</item>
+
+        <!-- Action Mode -->
+        <item name="actionModeStyle">@style/Widget.AppCompat.ActionMode</item>
+        <item name="actionModeBackground">?attr/colorPrimaryDark</item>
+        <item name="actionModeSplitBackground">?attr/colorPrimaryDark</item>
+        <item name="actionModeCloseDrawable">@drawable/abc_ic_cab_done_holo_light</item>
+        <item name="actionModeCloseButtonStyle">@style/Widget.AppCompat.Light.ActionButton.CloseMode</item>
+
+        <!-- Dropdown Spinner Attributes -->
+        <item name="actionDropDownStyle">@style/Widget.AppCompat.Light.Spinner.DropDown.ActionBar</item>
+
+        <!-- Panel attributes -->
+        <item name="panelMenuListWidth">@dimen/abc_panel_menu_list_width</item>
+        <item name="panelMenuListTheme">@style/Theme.AppCompat.CompactMenu</item>
+        <item name="android:panelBackground">@drawable/abc_menu_hardkey_panel_holo_light</item>
+        <item name="listChoiceBackgroundIndicator">@drawable/abc_list_selector_holo_light</item>
+
+        <!-- List attributes -->
+        <item name="textAppearanceListItem">@style/TextAppearance.AppCompat.Subhead</item>
+        <item name="textAppearanceListItemSmall">@style/TextAppearance.AppCompat.Subhead</item>
+        <item name="listPreferredItemHeight">64dp</item>
+        <item name="listPreferredItemHeightSmall">48dp</item>
+        <item name="listPreferredItemHeightLarge">80dp</item>
+        <item name="listPreferredItemPaddingLeft">16dip</item>
+        <item name="listPreferredItemPaddingRight">16dip</item>
+
+        <!-- Required for use of support_simple_spinner_dropdown_item.xml -->
+        <item name="spinnerDropDownItemStyle">@style/Widget.AppCompat.Light.DropDownItem.Spinner</item>
+        <item name="dropdownListPreferredItemHeight">?attr/listPreferredItemHeightSmall</item>
+
+        <!-- Popup Menu styles -->
+        <item name="popupMenuStyle">@style/Widget.AppCompat.Light.PopupMenu</item>
+        <item name="textAppearanceLargePopupMenu">@style/TextAppearance.AppCompat.Light.Widget.PopupMenu.Large</item>
+        <item name="textAppearanceSmallPopupMenu">@style/TextAppearance.AppCompat.Light.Widget.PopupMenu.Small</item>
+        <item name="listPopupWindowStyle">@style/Widget.AppCompat.Light.ListPopupWindow</item>
+        <item name="dropDownListViewStyle">@style/Widget.AppCompat.Light.ListView.DropDown</item>
+
+        <!-- SearchView attributes -->
+        <item name="searchViewStyle">@style/Widget.AppCompat.Light.SearchView</item>
+        <item name="android:dropDownItemStyle">@style/Widget.AppCompat.Light.DropDownItem.Spinner</item>
+        <item name="textColorSearchUrl">@color/abc_search_url_text</item>
+        <item name="textAppearanceSearchResultTitle">@style/TextAppearance.AppCompat.Light.SearchResult.Title</item>
+        <item name="textAppearanceSearchResultSubtitle">@style/TextAppearance.AppCompat.Light.SearchResult.Subtitle</item>
+
+        <item name="actionModeShareDrawable">@drawable/abc_ic_menu_share_material_light</item>
+
+        <!-- ShareActionProvider attributes -->
+        <item name="activityChooserViewStyle">@style/Widget.AppCompat.Light.ActivityChooserView</item>
+
+        <!-- Toolbar styles -->
+        <item name="toolbarStyle">@style/Widget.AppCompat.Toolbar</item>
+        <item name="toolbarNavigationButtonStyle">@style/Widget.AppCompat.Toolbar.Button.Navigation</item>
+
+        <item name="android:editTextStyle">@style/Widget.AppCompat.EditText</item>
+        <item name="editTextBackground">@drawable/abc_edit_text_material_light</item>
+        <item name="editTextColor">?android:attr/textColorPrimary</item>
+
+        <!-- Color palette -->
+        <item name="colorPrimaryDark">@color/material_blue_grey_600</item>
+        <item name="colorPrimary">@color/material_blue_grey_400</item>
+        <item name="colorAccent">@color/material_light_blue_A200</item>
+
+        <item name="colorControlNormal">?android:attr/textColorSecondary</item>
+        <item name="colorControlActivated">?attr/colorAccent</item>
+
+        <item name="colorControlHighlight">@color/ripple_material_light</item>
+        <!-- TODO: <item name="colorButtonNormal">@color/btn_default_material_light</item>-->
+    </style>
+
+    <style name="Base.Theme.AppCompat.Dialog" parent="Base.V11.Theme.AppCompat.Dialog" />
+
+    <style name="Base.Theme.AppCompat.Light.Dialog" parent="Base.V11.Theme.AppCompat.Light.Dialog" />
+
 </resources>
diff --git a/v7/appcompat/res/values-v14/styles_base.xml b/v7/appcompat/res/values-v14/styles_base.xml
deleted file mode 100644
index 4c64aee..0000000
--- a/v7/appcompat/res/values-v14/styles_base.xml
+++ /dev/null
@@ -1,228 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 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>
-
-    <!-- Like in themes_base.xml, the namespace "*.AppCompat.Base" is used to
-     define base styles for the platform version. The "*.AppCompat"
-     variants are for direct use or use as parent styles by the app. -->
-    <eat-comment/>
-
-    <style name="Widget.AppCompat.Base.ActionBar"
-           parent="android:Widget.Holo.ActionBar">
-    </style>
-
-    <style name="Widget.AppCompat.Light.Base.ActionBar"
-           parent="android:Widget.Holo.Light.ActionBar">
-    </style>
-
-    <style name="Widget.AppCompat.Base.ActionBar.Solid"
-           parent="android:Widget.Holo.ActionBar.Solid">
-    </style>
-
-    <style name="Widget.AppCompat.Light.Base.ActionBar.Solid"
-           parent="android:Widget.Holo.Light.ActionBar.Solid">
-    </style>
-
-    <style name="Widget.AppCompat.Light.Base.ActionBar.Solid.Inverse"
-           parent="android:Widget.Holo.Light.ActionBar.Solid.Inverse">
-    </style>
-
-    <style name="Widget.AppCompat.Base.ActionBar.TabBar"
-           parent="android:Widget.Holo.ActionBar.TabBar">
-    </style>
-
-    <style name="Widget.AppCompat.Light.Base.ActionBar.TabBar"
-           parent="android:Widget.Holo.Light.ActionBar.TabBar">
-    </style>
-
-    <style name="Widget.AppCompat.Light.Base.ActionBar.TabBar.Inverse"
-           parent="android:Widget.Holo.Light.ActionBar.TabBar.Inverse">
-    </style>
-
-    <style name="Widget.AppCompat.Base.ActionBar.TabView"
-           parent="android:Widget.Holo.ActionBar.TabView">
-    </style>
-
-    <style name="Widget.AppCompat.Light.Base.ActionBar.TabView"
-           parent="android:Widget.Holo.Light.ActionBar.TabView">
-    </style>
-
-    <style name="Widget.AppCompat.Light.Base.ActionBar.TabView.Inverse"
-           parent="android:Widget.Holo.Light.ActionBar.TabView.Inverse">
-    </style>
-
-    <style name="Widget.AppCompat.Base.ActionBar.TabText"
-           parent="android:Widget.Holo.ActionBar.TabText">
-    </style>
-
-    <style name="Widget.AppCompat.Light.Base.ActionBar.TabText"
-           parent="android:Widget.Holo.Light.ActionBar.TabText">
-    </style>
-
-    <style name="Widget.AppCompat.Light.Base.ActionBar.TabText.Inverse"
-           parent="android:Widget.Holo.Light.ActionBar.TabText.Inverse">
-    </style>
-
-    <style name="Widget.AppCompat.Light.Base.ActionMode.Inverse"
-           parent="android:Widget.Holo.Light.ActionMode.Inverse">
-    </style>
-
-    <style name="TextAppearance.AppCompat.Widget.Base.ActionBar.Menu"
-           parent="android:TextAppearance.Holo.Widget.ActionBar.Menu">
-    </style>
-
-    <style name="TextAppearance.AppCompat.Widget.Base.ActionBar.Title"
-           parent="android:TextAppearance.Holo.Widget.ActionBar.Title">
-    </style>
-
-    <style name="TextAppearance.AppCompat.Widget.Base.ActionBar.Subtitle"
-           parent="android:TextAppearance.Holo.Widget.ActionBar.Subtitle">
-    </style>
-
-    <style name="TextAppearance.AppCompat.Widget.Base.ActionBar.Title.Inverse"
-           parent="android:TextAppearance.Holo.Widget.ActionBar.Title.Inverse">
-    </style>
-
-    <style name="TextAppearance.AppCompat.Widget.Base.ActionBar.Subtitle.Inverse"
-           parent="android:TextAppearance.Holo.Widget.ActionBar.Subtitle.Inverse">
-    </style>
-
-    <style name="TextAppearance.AppCompat.Widget.Base.ActionMode.Title"
-           parent="android:TextAppearance.Holo.Widget.ActionMode.Title">
-    </style>
-
-    <style name="TextAppearance.AppCompat.Widget.Base.ActionMode.Subtitle"
-           parent="android:TextAppearance.Holo.Widget.ActionMode.Subtitle">
-    </style>
-
-    <style name="TextAppearance.AppCompat.Widget.Base.ActionMode.Title.Inverse"
-           parent="android:TextAppearance.Holo.Widget.ActionMode.Title.Inverse">
-    </style>
-
-    <style name="TextAppearance.AppCompat.Widget.Base.ActionMode.Subtitle.Inverse"
-           parent="android:TextAppearance.Holo.Widget.ActionMode.Subtitle.Inverse">
-    </style>
-
-    <!-- Action Button Styles -->
-
-    <style name="Widget.AppCompat.Base.ActionButton" parent="android:Widget.Holo.ActionButton">
-    </style>
-
-    <style name="Widget.AppCompat.Light.Base.ActionButton"
-           parent="android:Widget.Holo.Light.ActionButton">
-    </style>
-
-    <style name="Widget.AppCompat.Base.ActionButton.CloseMode"
-           parent="android:Widget.Holo.ActionButton.CloseMode">
-    </style>
-
-    <style name="Widget.AppCompat.Light.Base.ActionButton.CloseMode"
-           parent="android:Widget.Holo.Light.ActionButton.CloseMode">
-    </style>
-
-    <style name="Widget.AppCompat.Base.ActionButton.Overflow"
-           parent="android:Widget.Holo.ActionButton.Overflow">
-    </style>
-
-    <style name="Widget.AppCompat.Light.Base.ActionButton.Overflow"
-           parent="android:Widget.Holo.Light.ActionButton.Overflow">
-    </style>
-
-    <!-- Spinner Widgets -->
-
-    <style name="Widget.AppCompat.Base.ListView.DropDown"
-           parent="android:Widget.Holo.ListView.DropDown"/>
-
-    <style name="Widget.AppCompat.Light.Base.ListView.DropDown"
-           parent="android:Widget.Holo.ListView.DropDown"/>
-
-    <style name="Widget.AppCompat.Base.DropDownItem.Spinner"
-           parent="android:Widget.Holo.DropDownItem.Spinner"/>
-
-    <style name="Widget.AppCompat.Light.Base.DropDownItem.Spinner"
-           parent="android:Widget.Holo.Light.DropDownItem.Spinner"/>
-
-    <style name="Widget.AppCompat.Base.Spinner" parent="android:Widget.Holo.Spinner" />
-
-    <style name="Widget.AppCompat.Light.Base.Spinner" parent="android:Widget.Holo.Light.Spinner"/>
-
-    <style name="Widget.AppCompat.Base.ListView.Menu" parent="android:Widget.ListView.Menu" />
-
-    <!-- Popup Menu -->
-
-    <style name="Widget.AppCompat.Base.ListPopupWindow"
-           parent="android:Widget.Holo.ListPopupWindow">
-    </style>
-
-    <style name="Widget.AppCompat.Light.Base.ListPopupWindow"
-           parent="android:Widget.Holo.Light.ListPopupWindow">
-    </style>
-
-    <style name="Widget.AppCompat.Base.PopupMenu" parent="android:Widget.Holo.PopupMenu">
-    </style>
-
-    <style name="Widget.AppCompat.Light.Base.PopupMenu"
-        parent="android:Widget.Holo.Light.PopupMenu">
-    </style>
-
-    <style name="TextAppearance.AppCompat.Base.Widget.PopupMenu.Large"
-        parent="android:TextAppearance.Holo.Widget.PopupMenu.Large">
-    </style>
-
-    <style name="TextAppearance.AppCompat.Base.Widget.PopupMenu.Small"
-        parent="android:TextAppearance.Holo.Widget.PopupMenu.Small">
-    </style>
-
-    <style name="TextAppearance.AppCompat.Light.Base.Widget.PopupMenu.Large"
-        parent="android:TextAppearance.Holo.Widget.PopupMenu.Large">
-    </style>
-
-    <style name="TextAppearance.AppCompat.Light.Base.Widget.PopupMenu.Small"
-        parent="android:TextAppearance.Holo.Widget.PopupMenu.Small">
-    </style>
-
-    <!-- Search View result styles -->
-
-    <style name="TextAppearance.AppCompat.Base.SearchResult.Title"
-           parent="@android:TextAppearance.Holo.SearchResult.Title">
-    </style>
-
-    <style name="TextAppearance.AppCompat.Base.SearchResult.Subtitle"
-            parent="@android:TextAppearance.Holo.SearchResult.Subtitle">
-    </style>
-
-    <!--
-        TextAppearance.Holo.Light.SearchResult.* are private so we extend from the default
-        versions instead (which are exactly the same).
-    -->
-    <style name="TextAppearance.AppCompat.Light.Base.SearchResult.Title"
-           parent="@android:TextAppearance.Holo.SearchResult.Title">
-    </style>
-
-    <style name="TextAppearance.AppCompat.Light.Base.SearchResult.Subtitle"
-           parent="@android:TextAppearance.Holo.SearchResult.Subtitle">
-    </style>
-
-    <style name="Widget.AppCompat.Base.ActivityChooserView" parent="">
-        <item name="android:gravity">center</item>
-        <item name="android:background">@drawable/abc_ab_share_pack_holo_dark</item>
-        <item name="android:divider">?attr/dividerVertical</item>
-        <item name="android:showDividers">middle</item>
-        <item name="android:dividerPadding">6dip</item>
-    </style>
-
-</resources>
diff --git a/v7/appcompat/res/values-v14/themes_base.xml b/v7/appcompat/res/values-v14/themes_base.xml
deleted file mode 100644
index 3b30074..0000000
--- a/v7/appcompat/res/values-v14/themes_base.xml
+++ /dev/null
@@ -1,186 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 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>
-
-    <!-- Themes in the "Theme.Base" family vary based on the current platform
-          version to provide the correct basis on each device. You probably don't
-          want to use them directly in your apps.
-
-          Themes in the "Theme.AppCompat" family are meant to be extended or used
-          directly by apps.
-
-          This is the values-v14/ file that only declares the Base themes for
-          Ice Cream Sandwich+. You probably want to edit values/themes.xml instead. -->
-    <eat-comment/>
-
-    <!-- Base platform-dependent theme providing an action bar in a dark-themed activity. -->
-    <style name="Theme.Base.AppCompat" parent="android:Theme.Holo">
-        <!-- Copy system flag values for our use -->
-        <item name="windowActionBar">?android:attr/windowActionBar</item>
-        <item name="actionBarSize">?android:attr/actionBarSize</item>
-        <item name="actionBarItemBackground">?android:attr/actionBarItemBackground</item>
-        <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
-        <item name="actionButtonStyle">?android:attr/actionButtonStyle</item>
-        <item name="dividerVertical">?android:attr/dividerVertical</item>
-        <item name="dividerHorizontal">?android:attr/dividerHorizontal</item>
-        <item name="actionBarWidgetTheme">@null</item>
-        <item name="android:actionBarWidgetTheme">?attr/actionBarWidgetTheme</item>
-
-        <!-- Required for use of support_simple_spinner_dropdown_item.xml -->
-        <item name="listPreferredItemHeight">?android:attr/listPreferredItemHeight</item>
-        <item name="listPreferredItemHeightSmall">?android:attr/listPreferredItemHeightSmall</item>
-        <item name="listPreferredItemHeightLarge">?android:attr/listPreferredItemHeightLarge</item>
-        <item name="listPreferredItemPaddingLeft">?android:attr/listPreferredItemPaddingLeft</item>
-        <item name="listPreferredItemPaddingRight">?android:attr/listPreferredItemPaddingRight
-        </item>
-
-        <!-- Redirecting to the platform-dependet textAppearanceListItem -->
-        <item name="textAppearanceListItem">?android:attr/textAppearanceListItem</item>
-        <item name="textAppearanceListItemSmall">?android:attr/textAppearanceListItemSmall</item>
-
-        <!-- Attributes populated from the framework to be read by apps -->
-        <item name="buttonBarStyle">?android:attr/buttonBarStyle</item>
-        <item name="buttonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
-    </style>
-
-    <!-- Base platform-dependent theme providing an action bar in a light-themed activity. -->
-    <style name="Theme.Base.AppCompat.Light" parent="android:Theme.Holo.Light">
-        <!-- Copy system flag values for our use -->
-        <item name="windowActionBar">?android:attr/windowActionBar</item>
-        <item name="actionBarSize">?android:attr/actionBarSize</item>
-        <item name="actionBarItemBackground">?android:attr/actionBarItemBackground</item>
-        <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
-        <item name="actionButtonStyle">?android:attr/actionButtonStyle</item>
-        <item name="dividerVertical">?android:attr/dividerVertical</item>
-        <item name="dividerHorizontal">?android:attr/dividerHorizontal</item>
-        <item name="actionBarWidgetTheme">@null</item>
-        <item name="android:actionBarWidgetTheme">?attr/actionBarWidgetTheme</item>
-
-        <!-- Required for use of support_simple_spinner_dropdown_item.xml -->
-        <item name="listPreferredItemHeight">?android:attr/listPreferredItemHeight</item>
-        <item name="listPreferredItemHeightSmall">?android:attr/listPreferredItemHeightSmall</item>
-        <item name="listPreferredItemHeightLarge">?android:attr/listPreferredItemHeightLarge</item>
-        <item name="listPreferredItemPaddingLeft">?android:attr/listPreferredItemPaddingLeft</item>
-        <item name="listPreferredItemPaddingRight">?android:attr/listPreferredItemPaddingRight
-        </item>
-
-        <!-- Redirecting to the platform-dependet textAppearanceListItem -->
-        <item name="textAppearanceListItem">?android:attr/textAppearanceListItem</item>
-        <item name="textAppearanceListItemSmall">?android:attr/textAppearanceListItemSmall</item>
-
-        <!-- Attributes populated from the framework to be read by apps -->
-        <item name="buttonBarStyle">?android:attr/buttonBarStyle</item>
-        <item name="buttonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
-    </style>
-
-    <!-- Base platform-dependent theme providing a dark action bar in a light-themed activity. -->
-    <style name="Theme.Base.AppCompat.Light.DarkActionBar"
-           parent="android:Theme.Holo.Light.DarkActionBar">
-        <!-- Copy system flag values for our use -->
-        <item name="windowActionBar">?android:attr/windowActionBar</item>
-        <item name="actionBarSize">?android:attr/actionBarSize</item>
-        <item name="actionBarItemBackground">?android:attr/actionBarItemBackground</item>
-        <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
-        <item name="actionButtonStyle">?android:attr/actionButtonStyle</item>
-        <item name="dividerVertical">?android:attr/dividerVertical</item>
-        <item name="dividerHorizontal">?android:attr/dividerHorizontal</item>
-        <item name="actionBarWidgetTheme">@style/Theme.AppCompat</item>
-        <item name="android:actionBarWidgetTheme">?attr/actionBarWidgetTheme</item>
-
-        <!-- Required for use of support_simple_spinner_dropdown_item.xml -->
-        <item name="listPreferredItemHeight">?android:attr/listPreferredItemHeight</item>
-        <item name="listPreferredItemHeightSmall">?android:attr/listPreferredItemHeightSmall</item>
-        <item name="listPreferredItemHeightLarge">?android:attr/listPreferredItemHeightLarge</item>
-        <item name="listPreferredItemPaddingLeft">?android:attr/listPreferredItemPaddingLeft</item>
-        <item name="listPreferredItemPaddingRight">?android:attr/listPreferredItemPaddingRight
-        </item>
-
-        <!-- Redirecting to the platform-dependet textAppearanceListItem -->
-        <item name="textAppearanceListItem">?android:attr/textAppearanceListItem</item>
-        <item name="textAppearanceListItemSmall">?android:attr/textAppearanceListItemSmall</item>
-
-        <!-- Attributes populated from the framework to be read by apps -->
-        <item name="buttonBarStyle">?android:attr/buttonBarStyle</item>
-        <item name="buttonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
-    </style>
-
-    <style name="Theme.Base.AppCompat.DialogWhenLarge"
-           parent="Theme.Base.AppCompat.DialogWhenLarge.Base" />
-
-    <style name="Theme.Base.AppCompat.Light.DialogWhenLarge"
-           parent="Theme.Base.AppCompat.Light.DialogWhenLarge.Base" />
-
-    <!--
-        As we have defined the theme in values-large (for compat) and values-large takes precedence
-        over values-v14, we need to reset back to the Holo parent in values-large-v14. As the themes
-        in values-v14 & values-large-v14 are exactly the same, these "double base" themes can be
-        inherited from in both values-v14 and values-large-v14.
-    -->
-
-    <style name="Theme.Base.AppCompat.DialogWhenLarge.Base"
-           parent="android:Theme.Holo.DialogWhenLarge">
-        <!-- Copy system flag values for our use -->
-        <item name="windowActionBar">?android:attr/windowActionBar</item>
-        <item name="actionBarSize">?android:attr/actionBarSize</item>
-        <item name="actionBarItemBackground">?android:attr/actionBarItemBackground</item>
-        <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
-        <item name="actionButtonStyle">?android:attr/actionButtonStyle</item>
-        <item name="dividerVertical">?android:attr/dividerVertical</item>
-        <item name="dividerHorizontal">?android:attr/dividerHorizontal</item>
-        <item name="actionBarWidgetTheme">@null</item>
-        <item name="android:actionBarWidgetTheme">?attr/actionBarWidgetTheme</item>
-
-        <!-- Required for use of support_simple_spinner_dropdown_item.xml -->
-        <item name="listPreferredItemHeight">?android:attr/listPreferredItemHeight</item>
-        <item name="listPreferredItemHeightSmall">?android:attr/listPreferredItemHeightSmall</item>
-        <item name="listPreferredItemHeightLarge">?android:attr/listPreferredItemHeightLarge</item>
-        <item name="listPreferredItemPaddingLeft">?android:attr/listPreferredItemPaddingLeft</item>
-        <item name="listPreferredItemPaddingRight">?android:attr/listPreferredItemPaddingRight
-        </item>
-
-        <!-- Attributes populated from the framework to be read by apps -->
-        <item name="buttonBarStyle">?android:attr/buttonBarStyle</item>
-        <item name="buttonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
-    </style>
-
-    <style name="Theme.Base.AppCompat.Light.DialogWhenLarge.Base"
-           parent="android:Theme.Holo.Light.DialogWhenLarge">
-        <!-- Copy system flag values for our use -->
-        <item name="windowActionBar">?android:attr/windowActionBar</item>
-        <item name="actionBarSize">?android:attr/actionBarSize</item>
-        <item name="actionBarItemBackground">?android:attr/actionBarItemBackground</item>
-        <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
-        <item name="actionButtonStyle">?android:attr/actionButtonStyle</item>
-        <item name="dividerVertical">?android:attr/dividerVertical</item>
-        <item name="dividerHorizontal">?android:attr/dividerHorizontal</item>
-        <item name="actionBarWidgetTheme">@null</item>
-        <item name="android:actionBarWidgetTheme">?attr/actionBarWidgetTheme</item>
-
-        <!-- Required for use of support_simple_spinner_dropdown_item.xml -->
-        <item name="listPreferredItemHeight">?android:attr/listPreferredItemHeight</item>
-        <item name="listPreferredItemHeightSmall">?android:attr/listPreferredItemHeightSmall</item>
-        <item name="listPreferredItemHeightLarge">?android:attr/listPreferredItemHeightLarge</item>
-        <item name="listPreferredItemPaddingLeft">?android:attr/listPreferredItemPaddingLeft</item>
-        <item name="listPreferredItemPaddingRight">?android:attr/listPreferredItemPaddingRight
-        </item>
-
-        <!-- Attributes populated from the framework to be read by apps -->
-        <item name="buttonBarStyle">?android:attr/buttonBarStyle</item>
-        <item name="buttonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
-    </style>
-
-</resources>
diff --git a/v7/appcompat/res/values-v21/styles_base.xml b/v7/appcompat/res/values-v21/styles_base.xml
new file mode 100644
index 0000000..bf78ff3
--- /dev/null
+++ b/v7/appcompat/res/values-v21/styles_base.xml
@@ -0,0 +1,236 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+
+    <!-- Like in themes_base.xml, the namespace "*.AppCompat.Base" is used to
+     define base styles for the platform version. The "*.AppCompat"
+     variants are for direct use or use as parent styles by the app. -->
+    <eat-comment/>
+
+    <style name="Base.Widget.AppCompat.ActionBar.TabView"
+           parent="android:Widget.Material.ActionBar.TabView">
+    </style>
+
+    <style name="Base.Widget.AppCompat.Light.ActionBar.TabView"
+           parent="android:Widget.Material.Light.ActionBar.TabView">
+    </style>
+
+    <style name="Base.Widget.AppCompat.ActionBar.TabText"
+           parent="android:Widget.Material.ActionBar.TabText">
+    </style>
+
+    <style name="Base.Widget.AppCompat.Light.ActionBar.TabText"
+           parent="android:Widget.Material.Light.ActionBar.TabText">
+    </style>
+
+    <style name="Base.Widget.AppCompat.Light.ActionBar.TabText.Inverse"
+           parent="android:Widget.Material.Light.ActionBar.TabText">
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Widget.ActionBar.Menu"
+           parent="android:TextAppearance.Material.Widget.ActionBar.Menu">
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Widget.ActionBar.Title"
+           parent="android:TextAppearance.Material.Widget.ActionBar.Title">
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Widget.ActionBar.Subtitle"
+           parent="android:TextAppearance.Material.Widget.ActionBar.Subtitle">
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse"
+           parent="android:TextAppearance.Material.Widget.ActionBar.Title.Inverse">
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Widget.ActionBar.Subtitle.Inverse"
+           parent="android:TextAppearance.Material.Widget.ActionBar.Subtitle.Inverse">
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Widget.ActionMode.Title"
+           parent="android:TextAppearance.Material.Widget.ActionMode.Title">
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Widget.ActionMode.Subtitle"
+           parent="android:TextAppearance.Material.Widget.ActionMode.Subtitle">
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Widget.ActionMode.Title.Inverse"
+           parent="android:TextAppearance.Material.Widget.ActionMode.Title.Inverse">
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Widget.ActionMode.Subtitle.Inverse"
+           parent="android:TextAppearance.Material.Widget.ActionMode.Subtitle.Inverse">
+    </style>
+
+    <!-- Action Button Styles -->
+
+    <style name="Base.Widget.AppCompat.ActionButton"
+           parent="android:Widget.Material.ActionButton">
+    </style>
+
+    <style name="Base.Widget.AppCompat.Light.ActionButton"
+           parent="android:Widget.Material.Light.ActionButton">
+    </style>
+
+    <style name="Base.Widget.AppCompat.ActionButton.CloseMode"
+           parent="android:Widget.Material.ActionButton.CloseMode">
+    </style>
+
+    <style name="Base.Widget.AppCompat.Light.ActionButton.CloseMode"
+           parent="android:Widget.Material.Light.ActionButton.CloseMode">
+    </style>
+
+    <style name="Base.Widget.AppCompat.ActionButton.Overflow"
+           parent="android:Widget.Material.ActionButton.Overflow">
+    </style>
+
+    <style name="Base.Widget.AppCompat.Light.ActionButton.Overflow"
+           parent="android:Widget.Material.Light.ActionButton.Overflow">
+    </style>
+
+    <!--
+        Widget.AppCompat.Toolbar style is purposely ommitted. This is because the support
+        Toolbar implementation is used on ALL platforms and relies on the unbundled attrs.
+        The supporting Toolbar styles below only use basic attrs so work fine.
+    -->
+
+    <style name="Base.Widget.AppCompat.Toolbar.Button.Navigation"
+           parent="android:Widget.Material.Toolbar.Button.Navigation">
+    </style>
+
+    <style name="Base.TextAppearance.Widget.AppCompat.Toolbar.Title"
+           parent="android:TextAppearance.Material.Widget.ActionBar.Title">
+    </style>
+
+    <style name="Base.TextAppearance.Widget.AppCompat.Toolbar.Subtitle"
+           parent="android:TextAppearance.Material.Widget.ActionBar.Subtitle">
+    </style>
+
+    <!-- Spinner Widgets -->
+
+    <style name="Base.Widget.AppCompat.ListView.DropDown"
+           parent="android:Widget.Material.ListView.DropDown"/>
+
+    <style name="Base.Widget.AppCompat.Light.ListView.DropDown"
+           parent="android:Widget.Material.ListView.DropDown"/>
+
+    <style name="Base.Widget.AppCompat.DropDownItem.Spinner"
+           parent="android:Widget.Material.DropDownItem.Spinner"/>
+
+    <style name="Base.Widget.AppCompat.Light.DropDownItem.Spinner"
+           parent="android:Widget.Material.Light.DropDownItem.Spinner"/>
+
+    <style name="Base.Widget.AppCompat.Spinner" parent="android:Widget.Material.Spinner">
+        <item name="spinnerMode">dropdown</item>
+        <item name="disableChildrenWhenDisabled">true</item>
+        <item name="popupPromptView">@layout/abc_simple_dropdown_hint</item>
+    </style>
+
+    <style name="Base.Widget.AppCompat.Light.Spinner" parent="android:Widget.Material.Light.Spinner">
+        <item name="spinnerMode">dropdown</item>
+        <item name="disableChildrenWhenDisabled">true</item>
+        <item name="popupPromptView">@layout/abc_simple_dropdown_hint</item>
+    </style>
+
+    <style name="Base.Widget.AppCompat.ListView.Menu"
+           parent="android:Widget.ListView.Menu" />
+
+    <!-- Popup Menu -->
+
+    <style name="Base.Widget.AppCompat.ListPopupWindow"
+           parent="android:Widget.Material.ListPopupWindow">
+    </style>
+
+    <style name="Base.Widget.AppCompat.Light.ListPopupWindow"
+           parent="android:Widget.Material.Light.ListPopupWindow">
+    </style>
+
+    <style name="Base.Widget.AppCompat.PopupMenu" parent="android:Widget.Material.PopupMenu">
+    </style>
+
+    <style name="Base.Widget.AppCompat.Light.PopupMenu"
+        parent="android:Widget.Material.Light.PopupMenu">
+    </style>
+
+    <style name="Base.Widget.AppCompat.PopupMenu.Overflow">
+        <item name="android:overlapAnchor">true</item>
+    </style>
+
+    <style name="Base.Widget.AppCompat.Light.PopupMenu.Overflow">
+        <item name="android:overlapAnchor">true</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Large"
+        parent="android:TextAppearance.Material.Widget.PopupMenu.Large">
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Small"
+        parent="android:TextAppearance.Material.Widget.PopupMenu.Small">
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Large"
+        parent="android:TextAppearance.Material.Widget.PopupMenu.Large">
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Small"
+        parent="android:TextAppearance.Material.Widget.PopupMenu.Small">
+    </style>
+
+    <!-- Search View result styles -->
+
+    <style name="Base.TextAppearance.AppCompat.SearchResult.Title"
+           parent="@android:TextAppearance.Material.SearchResult.Title">
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.SearchResult.Subtitle"
+           parent="@android:TextAppearance.Material.SearchResult.Subtitle">
+    </style>
+
+    <!-- Progress Bar -->
+
+    <style name="Base.Widget.AppCompat.ProgressBar.Horizontal"
+           parent="android:Widget.Material.ProgressBar.Horizontal">
+    </style>
+
+    <style name="Base.Widget.AppCompat.ProgressBar"
+           parent="android:Widget.Material.ProgressBar">
+    </style>
+
+    <!--
+        TextAppearance.Material.Light.SearchResult.* are private so we extend from the default
+        versions instead (which are exactly the same).
+    -->
+    <style name="Base.TextAppearance.AppCompat.Light.SearchResult.Title"
+           parent="Base.TextAppearance.AppCompat.SearchResult.Title">
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Light.SearchResult.Subtitle"
+           parent="Base.TextAppearance.AppCompat.SearchResult.Subtitle">
+    </style>
+
+    <!-- TODO. Needs updating for Material -->
+    <style name="Base.Widget.AppCompat.ActivityChooserView" parent="">
+        <item name="android:gravity">center</item>
+        <item name="android:background">@drawable/abc_ab_share_pack_holo_dark</item>
+        <item name="android:divider">?attr/dividerVertical</item>
+        <item name="android:showDividers">middle</item>
+        <item name="android:dividerPadding">6dip</item>
+    </style>
+
+</resources>
diff --git a/v7/appcompat/res/values-v21/styles_base_text.xml b/v7/appcompat/res/values-v21/styles_base_text.xml
new file mode 100644
index 0000000..86f3c03
--- /dev/null
+++ b/v7/appcompat/res/values-v21/styles_base_text.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+
+    <style name="Base.TextAppearance.AppCompat" parent="android:TextAppearance.Material" />
+
+    <style name="Base.TextAppearance.AppCompat.Display4" parent="android:TextAppearance.Material.Display4" />
+
+    <style name="Base.TextAppearance.AppCompat.Display3" parent="android:TextAppearance.Material.Display3" />
+
+    <style name="Base.TextAppearance.AppCompat.Display2" parent="android:TextAppearance.Material.Display2" />
+
+    <style name="Base.TextAppearance.AppCompat.Display1" parent="android:TextAppearance.Material.Display1" />
+
+    <style name="Base.TextAppearance.AppCompat.Headline" parent="android:TextAppearance.Material.Headline" />
+
+    <style name="Base.TextAppearance.AppCompat.Title" parent="android:TextAppearance.Material.Title" />
+
+    <style name="Base.TextAppearance.AppCompat.Subhead" parent="android:TextAppearance.Material.Subhead" />
+
+    <style name="Base.TextAppearance.AppCompat.Body2" parent="android:TextAppearance.Material.Body2" />
+
+    <style name="Base.TextAppearance.AppCompat.Body1" parent="android:TextAppearance.Material.Body1" />
+
+    <style name="Base.TextAppearance.AppCompat.Caption" parent="android:TextAppearance.Material.Caption" />
+
+    <style name="Base.TextAppearance.AppCompat.Menu" parent="android:TextAppearance.Material.Menu" />
+
+    <!-- Now deprecated styles -->
+
+    <style name="Base.TextAppearance.AppCompat.Inverse" parent="android:TextAppearance.Material.Inverse" />
+
+    <style name="Base.TextAppearance.AppCompat.Large" parent="android:TextAppearance.Material.Large" />
+
+    <style name="Base.TextAppearance.AppCompat.Large.Inverse" parent="android:TextAppearance.Material.Large.Inverse" />
+
+    <style name="Base.TextAppearance.AppCompat.Medium" parent="android:TextAppearance.Material.Medium" />
+
+    <style name="Base.TextAppearance.AppCompat.Medium.Inverse" parent="android:TextAppearance.Material.Medium.Inverse" />
+
+    <style name="Base.TextAppearance.AppCompat.Small" parent="android:TextAppearance.Material.Small" />
+
+    <style name="Base.TextAppearance.AppCompat.Small.Inverse" parent="android:TextAppearance.Material.Small.Inverse" />
+
+</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-v21/themes_base.xml b/v7/appcompat/res/values-v21/themes_base.xml
new file mode 100644
index 0000000..fd28ac8
--- /dev/null
+++ b/v7/appcompat/res/values-v21/themes_base.xml
@@ -0,0 +1,212 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+
+    <!--
+        Theme in the "Platform.AppCompat" family are designed to be aliases for the default
+        theme on a given platform version and should set up the default theme ready for adding our
+        unbundled Action Bar.
+    -->
+    <eat-comment/>
+    <style name="Platform.AppCompat" parent="android:Theme.Material">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowActionBar">false</item>
+    </style>
+
+    <style name="Platform.AppCompat.Light" parent="android:Theme.Material.Light">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowActionBar">false</item>
+    </style>
+
+    <style name="Platform.AppCompat.Dialog" parent="android:Theme.Material.Dialog">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowActionBar">false</item>
+    </style>
+
+    <style name="Platform.AppCompat.Light.Dialog" parent="android:Theme.Material.Light.Dialog">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowActionBar">false</item>
+    </style>
+
+    <style name="Base.Theme.AppCompat" parent="Base.V21.Theme.AppCompat" />
+
+    <style name="Base.Theme.AppCompat.Light" parent="Base.V21.Theme.AppCompat.Light" />
+
+    <style name="Base.V21.Theme.AppCompat" parent="Base.V7.Theme.AppCompat">
+        <!-- Action Bar styling attributes -->
+        <item name="actionBarSize">?android:attr/actionBarSize</item>
+        <item name="actionBarDivider">?android:attr/actionBarDivider</item>
+        <item name="actionBarItemBackground">?android:attr/actionBarItemBackground</item>
+        <item name="actionButtonStyle">?android:attr/actionButtonStyle</item>
+        <item name="actionMenuTextColor">?android:attr/actionMenuTextColor</item>
+        <item name="actionMenuTextAppearance">?android:attr/actionMenuTextAppearance</item>
+        <item name="actionOverflowButtonStyle">?android:attr/actionOverflowButtonStyle</item>
+        <item name="homeAsUpIndicator">?android:attr/homeAsUpIndicator</item>
+
+        <!-- For PopupMenu -->
+        <item name="listPreferredItemHeightSmall">?android:attr/listPreferredItemHeightSmall</item>
+        <item name="textAppearanceLargePopupMenu">?android:attr/textAppearanceLargePopupMenu</item>
+        <item name="textAppearanceSmallPopupMenu">?android:attr/textAppearanceSmallPopupMenu</item>
+
+        <!-- General view attributes -->
+        <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
+        <item name="selectableItemBackgroundBorderless">?android:attr/selectableItemBackgroundBorderless</item>
+        <item name="dividerHorizontal">?android:attr/dividerHorizontal</item>
+        <item name="dividerVertical">?android:attr/dividerVertical</item>
+        <item name="editTextBackground">?android:attr/editTextBackground</item>
+        <item name="editTextColor">?android:attr/editTextColor</item>
+
+        <!-- Copy our color theme attributes to the framework -->
+        <item name="android:colorPrimary">?attr/colorPrimary</item>
+        <item name="android:colorPrimaryDark">?attr/colorPrimaryDark</item>
+        <item name="android:colorAccent">?attr/colorAccent</item>
+        <item name="android:colorControlNormal">?attr/colorControlNormal</item>
+        <item name="android:colorControlActivated">?attr/colorControlActivated</item>
+        <item name="android:colorControlHighlight">?attr/colorControlHighlight</item>
+        <!-- TODO: <item name="android:colorButtonNormal">?attr/colorButtonNormal</item>-->
+
+
+    </style>
+
+    <style name="Base.V21.Theme.AppCompat.Light" parent="Base.V7.Theme.AppCompat.Light">
+        <!-- Action Bar styling attributes -->
+        <item name="actionBarSize">?android:attr/actionBarSize</item>
+        <item name="actionBarDivider">?android:attr/actionBarDivider</item>
+        <item name="actionBarItemBackground">?android:attr/actionBarItemBackground</item>
+        <item name="actionButtonStyle">?android:attr/actionButtonStyle</item>
+        <item name="actionMenuTextColor">?android:attr/actionMenuTextColor</item>
+        <item name="actionMenuTextAppearance">?android:attr/actionMenuTextAppearance</item>
+        <item name="actionOverflowButtonStyle">?android:attr/actionOverflowButtonStyle</item>
+        <item name="homeAsUpIndicator">?android:attr/homeAsUpIndicator</item>
+
+        <!-- For PopupMenu -->
+        <item name="listPreferredItemHeightSmall">?android:attr/listPreferredItemHeightSmall</item>
+        <item name="textAppearanceLargePopupMenu">?android:attr/textAppearanceLargePopupMenu</item>
+        <item name="textAppearanceSmallPopupMenu">?android:attr/textAppearanceSmallPopupMenu</item>
+
+        <!-- General view attributes -->
+        <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
+        <item name="selectableItemBackgroundBorderless">?android:attr/selectableItemBackgroundBorderless</item>
+        <item name="dividerHorizontal">?android:attr/dividerHorizontal</item>
+        <item name="dividerVertical">?android:attr/dividerVertical</item>
+        <item name="editTextBackground">?android:attr/editTextBackground</item>
+        <item name="editTextColor">?android:attr/editTextColor</item>
+
+        <!-- Copy our color theme attributes to the framework -->
+        <item name="android:colorPrimary">?attr/colorPrimary</item>
+        <item name="android:colorPrimaryDark">?attr/colorPrimaryDark</item>
+        <item name="android:colorAccent">?attr/colorAccent</item>
+        <item name="android:colorControlNormal">?attr/colorControlNormal</item>
+        <item name="android:colorControlActivated">?attr/colorControlActivated</item>
+        <item name="android:colorControlHighlight">?attr/colorControlHighlight</item>
+        <!-- TODO: <item name="android:colorButtonNormal">?attr/colorButtonNormal</item>-->
+    </style>
+
+    <style name="Base.V21.Theme.AppCompat.Dialog" parent="Base.V11.Theme.AppCompat.Dialog">
+        <!-- Action Bar styling attributes -->
+        <item name="actionBarSize">?android:attr/actionBarSize</item>
+        <item name="actionBarDivider">?android:attr/actionBarDivider</item>
+        <item name="actionBarItemBackground">?android:attr/actionBarItemBackground</item>
+        <item name="actionButtonStyle">?android:attr/actionButtonStyle</item>
+        <item name="actionMenuTextColor">?android:attr/actionMenuTextColor</item>
+        <item name="actionMenuTextAppearance">?android:attr/actionMenuTextAppearance</item>
+        <item name="actionOverflowButtonStyle">?android:attr/actionOverflowButtonStyle</item>
+        <item name="homeAsUpIndicator">?android:attr/homeAsUpIndicator</item>
+
+        <!-- For PopupMenu -->
+        <item name="listPreferredItemHeightSmall">?android:attr/listPreferredItemHeightSmall</item>
+        <item name="textAppearanceLargePopupMenu">?android:attr/textAppearanceLargePopupMenu</item>
+        <item name="textAppearanceSmallPopupMenu">?android:attr/textAppearanceSmallPopupMenu</item>
+
+        <!-- General view attributes -->
+        <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
+        <item name="selectableItemBackgroundBorderless">?android:attr/selectableItemBackgroundBorderless</item>
+        <item name="dividerHorizontal">?android:attr/dividerHorizontal</item>
+        <item name="dividerVertical">?android:attr/dividerVertical</item>
+        <item name="editTextBackground">?android:attr/editTextBackground</item>
+        <item name="editTextColor">?android:attr/editTextColor</item>
+
+        <!-- Copy our color theme attributes to the framework -->
+        <item name="android:colorPrimary">?attr/colorPrimary</item>
+        <item name="android:colorPrimaryDark">?attr/colorPrimaryDark</item>
+        <item name="android:colorAccent">?attr/colorAccent</item>
+        <item name="android:colorControlNormal">?attr/colorControlNormal</item>
+        <item name="android:colorControlActivated">?attr/colorControlActivated</item>
+        <item name="android:colorControlHighlight">?attr/colorControlHighlight</item>
+        <!-- TODO: <item name="android:colorButtonNormal">?attr/colorButtonNormal</item>-->
+    </style>
+
+    <style name="Base.V21.Theme.AppCompat.Light.Dialog" parent="Base.V11.Theme.AppCompat.Light.Dialog">
+        <!-- Action Bar styling attributes -->
+        <item name="actionBarSize">?android:attr/actionBarSize</item>
+        <item name="actionBarDivider">?android:attr/actionBarDivider</item>
+        <item name="actionBarItemBackground">?android:attr/actionBarItemBackground</item>
+        <item name="actionButtonStyle">?android:attr/actionButtonStyle</item>
+        <item name="actionMenuTextColor">?android:attr/actionMenuTextColor</item>
+        <item name="actionMenuTextAppearance">?android:attr/actionMenuTextAppearance</item>
+        <item name="actionOverflowButtonStyle">?android:attr/actionOverflowButtonStyle</item>
+        <item name="homeAsUpIndicator">?android:attr/homeAsUpIndicator</item>
+
+        <!-- For PopupMenu -->
+        <item name="listPreferredItemHeightSmall">?android:attr/listPreferredItemHeightSmall</item>
+        <item name="textAppearanceLargePopupMenu">?android:attr/textAppearanceLargePopupMenu</item>
+        <item name="textAppearanceSmallPopupMenu">?android:attr/textAppearanceSmallPopupMenu</item>
+
+        <!-- General view attributes -->
+        <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
+        <item name="selectableItemBackgroundBorderless">?android:attr/selectableItemBackgroundBorderless</item>
+        <item name="dividerHorizontal">?android:attr/dividerHorizontal</item>
+        <item name="dividerVertical">?android:attr/dividerVertical</item>
+        <item name="editTextBackground">?android:attr/editTextBackground</item>
+        <item name="editTextColor">?android:attr/editTextColor</item>
+
+        <!-- Copy our color theme attributes to the framework -->
+        <item name="android:colorPrimary">?attr/colorPrimary</item>
+        <item name="android:colorPrimaryDark">?attr/colorPrimaryDark</item>
+        <item name="android:colorAccent">?attr/colorAccent</item>
+        <item name="android:colorControlNormal">?attr/colorControlNormal</item>
+        <item name="android:colorControlActivated">?attr/colorControlActivated</item>
+        <item name="android:colorControlHighlight">?attr/colorControlHighlight</item>
+        <!-- TODO: <item name="android:colorButtonNormal">?attr/colorButtonNormal</item>-->
+    </style>
+
+    <style name="Base.Theme.AppCompat.Dialog" parent="Base.V21.Theme.AppCompat.Dialog" />
+
+    <style name="Base.Theme.AppCompat.Light.Dialog" parent="Base.V21.Theme.AppCompat.Light.Dialog" />
+
+    <style name="Base.ThemeOverlay.AppCompat" parent="android:ThemeOverlay.Material">
+        <!-- TODO: Need to check if we need to modify anything here -->
+    </style>
+
+    <style name="Base.ThemeOverlay.AppCompat.Dark" parent="android:ThemeOverlay.Material.Dark">
+        <!-- TODO: Need to check if we need to modify anything here -->
+    </style>
+
+    <style name="Base.ThemeOverlay.AppCompat.Light" parent="android:ThemeOverlay.Material.Light">
+        <!-- TODO: Need to check if we need to modify anything here -->
+    </style>
+
+    <style name="Base.ThemeOverlay.AppCompat.ActionBar" parent="android:ThemeOverlay.Material.ActionBar">
+        <!-- TODO: Need to check if we need to modify anything here -->
+    </style>
+
+    <style name="Base.ThemeOverlay.AppCompat.Dark.ActionBar" parent="android:ThemeOverlay.Material.Dark.ActionBar">
+        <!-- TODO: Need to check if we need to modify anything here -->
+    </style>
+
+</resources>
diff --git a/v7/appcompat/res/values-vi/strings.xml b/v7/appcompat/res/values-vi/strings.xml
new file mode 100644
index 0000000..0840f73
--- /dev/null
+++ b/v7/appcompat/res/values-vi/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Xong"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Điều hướng về trang chủ"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Điều hướng lên trên"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Thêm tùy chọn"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Tìm kiếm"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Tìm kiếm truy vấn"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Xóa truy vấn"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Gửi truy vấn"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Tìm kiếm bằng giọng nói"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Chọn một ứng dụng"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Xem tất cả"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Chia sẻ với %s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Chia sẻ với"</string>
+</resources>
diff --git a/v7/appcompat/res/values-w480dp/bools.xml b/v7/appcompat/res/values-w480dp/bools.xml
index 6e6a3df..470f89b 100644
--- a/v7/appcompat/res/values-w480dp/bools.xml
+++ b/v7/appcompat/res/values-w480dp/bools.xml
@@ -15,5 +15,4 @@
 -->
 <resources>
     <bool name="abc_action_bar_embed_tabs_pre_jb">true</bool>
-    <bool name="abc_split_action_bar_is_narrow">false</bool>
 </resources>
diff --git a/v7/appcompat/res/values-w600dp/dimens.xml b/v7/appcompat/res/values-w600dp/dimens.xml
index 5bbc34d..5a0a8b4 100644
--- a/v7/appcompat/res/values-w600dp/dimens.xml
+++ b/v7/appcompat/res/values-w600dp/dimens.xml
@@ -20,8 +20,6 @@
          showAsAction="ifRoom" items can fit. "always" items can override this. -->
     <integer name="abc_max_action_buttons">5</integer>
 
-    <!-- Default height of an action bar. -->
-    <dimen name="abc_action_bar_default_height">56dip</dimen>
     <!-- Vertical padding around action bar icons. -->
     <dimen name="abc_action_bar_icon_vertical_padding">4dip</dimen>
     <!-- Text size for action bar titles -->
diff --git a/v7/appcompat/res/values-xlarge/dimens.xml b/v7/appcompat/res/values-xlarge/dimens.xml
index 4172442..106f155 100644
--- a/v7/appcompat/res/values-xlarge/dimens.xml
+++ b/v7/appcompat/res/values-xlarge/dimens.xml
@@ -21,8 +21,6 @@
          showAsAction="ifRoom" items can fit. "always" items can override this. -->
     <integer name="abc_max_action_buttons">5</integer>
 
-    <!-- Default height of an action bar. -->
-    <dimen name="abc_action_bar_default_height">56dip</dimen>
     <!-- Vertical padding around action bar icons. -->
     <dimen name="abc_action_bar_icon_vertical_padding">4dip</dimen>
     <!-- Text size for action bar titles -->
diff --git a/v7/appcompat/res/values-zh-rCN/strings.xml b/v7/appcompat/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..87b36b0
--- /dev/null
+++ b/v7/appcompat/res/values-zh-rCN/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"完成"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"转到主屏幕"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"转到上一层级"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"更多选项"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"搜索"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"搜索查询"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"清除查询"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"提交查询"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"语音搜索"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"选择应用"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"查看全部"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"通过%s分享"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"分享方式"</string>
+</resources>
diff --git a/v7/appcompat/res/values-zh-rHK/strings.xml b/v7/appcompat/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..f6a367d
--- /dev/null
+++ b/v7/appcompat/res/values-zh-rHK/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"完成"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"瀏覽主頁"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"向上瀏覽"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"更多選項"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"搜尋"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"搜尋查詢"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"清除查詢"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"提交查詢"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"語音搜尋"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"選擇應用程式"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"顯示全部"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"與「%s」分享"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"分享對象"</string>
+</resources>
diff --git a/v7/appcompat/res/values-zh-rTW/strings.xml b/v7/appcompat/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..c804ccf
--- /dev/null
+++ b/v7/appcompat/res/values-zh-rTW/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"完成"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"瀏覽首頁"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"向上瀏覽"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"更多選項"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"搜尋"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"搜尋查詢"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"清除查詢"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"提交查詢"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"語音搜尋"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"選擇應用程式"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"查看全部"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"與「%s」分享"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"選擇分享對象"</string>
+</resources>
diff --git a/v7/appcompat/res/values-zu/strings.xml b/v7/appcompat/res/values-zu/strings.xml
new file mode 100644
index 0000000..92eac7e
--- /dev/null
+++ b/v7/appcompat/res/values-zu/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2012 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="abc_action_mode_done" msgid="4076576682505996667">"Kwenziwe"</string>
+    <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Zulazulela ekhaya"</string>
+    <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Zulazulela phezulu"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Izinketho eziningi"</string>
+    <string name="abc_searchview_description_search" msgid="8264924765203268293">"Sesha"</string>
+    <string name="abc_searchview_description_query" msgid="2550479030709304392">"Umbuzo wosesho"</string>
+    <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Sula inkinga"</string>
+    <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Hambisa umbuzo"</string>
+    <string name="abc_searchview_description_voice" msgid="893419373245838918">"Ukusesha ngezwi"</string>
+    <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Khetha uhlelo lokusebenza"</string>
+    <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Buka konke"</string>
+    <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Yabelana no-%s"</string>
+    <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Yabelana no-"</string>
+</resources>
diff --git a/v7/appcompat/res/values/attrs.xml b/v7/appcompat/res/values/attrs.xml
index e0c8465..aabd8e7 100644
--- a/v7/appcompat/res/values/attrs.xml
+++ b/v7/appcompat/res/values/attrs.xml
@@ -24,14 +24,32 @@
          read by the app. -->
     <eat-comment/>
 
-    <!-- These attributes are meant to be specified and customized by the app.
-         The system will read and apply them as needed. These attributes control
-         properties of the activity window, such as whether an action bar should
-         be present and whether it should overlay content. -->
-    <declare-styleable name="ActionBarWindow">
-        <attr name="windowActionBar" format="boolean"/>
-        <attr name="windowActionBarOverlay" format="boolean"/>
-        <attr name="windowSplitActionBar" format="boolean" />
+
+    <attr name="title" format="string"/>
+    <attr name="height" format="dimension"/>
+    <!-- Specifies whether the theme is light, otherwise it is dark. -->
+    <attr name="isLightTheme" format="boolean" />
+
+    <!-- These are the standard attributes that make up a complete theme. -->
+    <declare-styleable name="Theme">
+
+        <!-- ============= -->
+        <!-- Window styles -->
+        <!-- ============= -->
+        <eat-comment />
+
+        <!-- Flag indicating whether this window should have an Action Bar
+             in place of the usual title bar. -->
+        <attr name="windowActionBar" format="boolean" />
+
+        <!-- Flag indicating whether this window's Action Bar should overlay
+             application content. Does nothing if the window would not
+             have an Action Bar. -->
+        <attr name="windowActionBarOverlay" format="boolean" />
+
+        <!-- Flag indicating whether action modes should overlay window content
+             when there is not reserved space for their UI (such as an Action Bar). -->
+        <attr name="windowActionModeOverlay" format="boolean" />
 
         <!-- A fixed width for the window along the major axis of the screen,
              that is, when in landscape. Can be either an absolute dimension
@@ -51,104 +69,236 @@
              or a fraction of the screen size in that dimension. -->
         <attr name="windowFixedHeightMajor" format="dimension|fraction" />
 
-    </declare-styleable>
+        <attr name="android:windowIsFloating" />
 
-    <!-- ============================================ -->
+        <!-- =================== -->
+        <!-- Action bar styles   -->
+        <!-- =================== -->
+        <eat-comment />
+        <!-- Default style for tabs within an action bar -->
+        <attr name="actionBarTabStyle" format="reference" />
+        <attr name="actionBarTabBarStyle" format="reference" />
+        <attr name="actionBarTabTextStyle" format="reference" />
+        <attr name="actionOverflowButtonStyle" format="reference" />
+        <attr name="actionOverflowMenuStyle" format="reference" />
+        <!-- Reference to a theme that should be used to inflate popups
+             shown by widgets in the action bar. -->
+        <attr name="actionBarPopupTheme" format="reference" />
+        <!-- Reference to a style for the Action Bar -->
+        <attr name="actionBarStyle" format="reference" />
+        <!-- Reference to a style for the split Action Bar. This style
+             controls the split component that holds the menu/action
+             buttons. actionBarStyle is still used for the primary
+             bar. -->
+        <attr name="actionBarSplitStyle" format="reference" />
+        <!-- Reference to a theme that should be used to inflate the
+             action bar. This will be inherited by any widget inflated
+             into the action bar. -->
+        <attr name="actionBarTheme" format="reference" />
+        <!-- Reference to a theme that should be used to inflate widgets
+             and layouts destined for the action bar. Most of the time
+             this will be a reference to the current theme, but when
+             the action bar has a significantly different contrast
+             profile than the rest of the activity the difference
+             can become important. If this is set to @null the current
+             theme will be used.-->
+        <attr name="actionBarWidgetTheme" format="reference" />
+        <!-- Size of the Action Bar, including the contextual
+             bar used to present Action Modes. -->
+        <attr name="actionBarSize" format="dimension" >
+            <enum name="wrap_content" value="0" />
+        </attr>
+        <!-- Custom divider drawable to use for elements in the action bar. -->
+        <attr name="actionBarDivider" format="reference" />
+        <!-- Custom item state list drawable background for action bar items. -->
+        <attr name="actionBarItemBackground" format="reference" />
+        <!-- TextAppearance style that will be applied to text that
+             appears within action menu items. -->
+        <attr name="actionMenuTextAppearance" format="reference" />
+        <!-- Color for text that appears within action menu items. -->
+        <!-- Color for text that appears within action menu items. -->
+        <attr name="actionMenuTextColor" format="color|reference"/>
 
-    <!-- Action bar appearance and styling attributes.
-         These attributes are meant to be specified and customized by the
-         app. The system will read and apply them as needed. -->
-    <eat-comment/>
 
-    <!-- Default style for tabs within an action bar -->
-    <attr name="actionBarTabStyle" format="reference"/>
-    <attr name="actionBarTabBarStyle" format="reference"/>
-    <attr name="actionBarTabTextStyle" format="reference"/>
-    <attr name="actionOverflowButtonStyle" format="reference"/>
-    <!-- Reference to a style for the Action Bar -->
-    <attr name="actionBarStyle" format="reference"/>
-    <!-- Reference to a theme that should be used to inflate widgets
-         and layouts destined for the action bar. Most of the time
-         this will be a reference to the current theme, but when
-         the action bar has a significantly different contrast
-         profile than the rest of the activity the difference
-         can become important. If this is set to @null the current
-         theme will be used.-->
-    <attr name="actionBarSplitStyle" format="reference"/>
-    <!-- Reference to a theme that should be used to inflate widgets
-         and layouts destined for the action bar. Most of the time
-         this will be a reference to the current theme, but when
-         the action bar has a significantly different contrast
-         profile than the rest of the activity the difference
-         can become important. If this is set to @null the current
-         theme will be used.-->
-    <attr name="actionBarWidgetTheme" format="reference"/>
-    <!-- Size of the Action Bar, including the contextual
-         bar used to present Action Modes. -->
-    <attr name="actionBarSize" format="dimension"/>
-    <!-- Custom divider drawable to use for elements in the action bar. -->
-    <attr name="actionBarDivider" format="reference"/>
-    <!-- Custom item state list drawable background for action bar items. -->
-    <attr name="actionBarItemBackground" format="reference"/>
-    <!-- TextAppearance style that will be applied to text that
-         appears within action menu items. -->
-    <attr name="actionMenuTextAppearance" format="reference"/>
-    <!-- Color for text that appears within action menu items. -->
-    <attr name="actionMenuTextColor" format="color|reference"/>
-    <!-- Specifies a drawable to use for the 'home as up' indicator. -->
-    <attr name="homeAsUpIndicator" format="reference"/>
-    <!-- Text color, typeface, size, and style for the text inside of a popup menu. -->
-    <attr name="textAppearanceLargePopupMenu" format="reference"/>
-    <!-- Text color, typeface, size, and style for small text inside of a popup menu. -->
-    <attr name="textAppearanceSmallPopupMenu" format="reference"/>
-    <!-- Default action button style. -->
-    <attr name="actionButtonStyle" format="reference"/>
+        <!-- =================== -->
+        <!-- Action mode styles  -->
+        <!-- =================== -->
+        <eat-comment/>
+        <attr name="actionModeStyle" format="reference"/>
+        <attr name="actionModeCloseButtonStyle" format="reference"/>
+        <!-- Background drawable to use for action mode UI -->
+        <attr name="actionModeBackground" format="reference"/>
+        <!-- Background drawable to use for action mode UI in the lower split bar -->
+        <attr name="actionModeSplitBackground" format="reference"/>
+        <!-- Drawable to use for the close action mode button -->
+        <attr name="actionModeCloseDrawable" format="reference"/>
+        <!-- Drawable to use for the Cut action button in Contextual Action Bar -->
+        <attr name="actionModeCutDrawable" format="reference"/>
+        <!-- Drawable to use for the Copy action button in Contextual Action Bar -->
+        <attr name="actionModeCopyDrawable" format="reference"/>
+        <!-- Drawable to use for the Paste action button in Contextual Action Bar -->
+        <attr name="actionModePasteDrawable" format="reference"/>
+        <!-- Drawable to use for the Select all action button in Contextual Action Bar -->
+        <attr name="actionModeSelectAllDrawable" format="reference"/>
+        <!-- Drawable to use for the Share action button in WebView selection action modes -->
+        <attr name="actionModeShareDrawable" format="reference"/>
+        <!-- Drawable to use for the Find action button in WebView selection action modes -->
+        <attr name="actionModeFindDrawable" format="reference"/>
+        <!-- Drawable to use for the Web Search action button in WebView selection action modes -->
+        <attr name="actionModeWebSearchDrawable" format="reference"/>
 
-    <!-- ============================================ -->
+        <!-- PopupWindow style to use for action modes when showing as a window overlay. -->
+        <attr name="actionModePopupWindowStyle" format="reference"/>
 
-    <!-- Assorted theme compatibility attributes.
-         These attributes are meant to be set by the system and read by apps
-         for use in layouts or other style declarations. -->
-    <eat-comment/>
-    <!-- A style that may be applied to horizontal LinearLayouts
+
+        <!-- =================== -->
+        <!-- Text styles -->
+        <!-- =================== -->
+        <eat-comment />
+        <!-- Text color, typeface, size, and style for the text inside of a popup menu. -->
+        <attr name="textAppearanceLargePopupMenu" format="reference"/>
+        <!-- Text color, typeface, size, and style for small text inside of a popup menu. -->
+        <attr name="textAppearanceSmallPopupMenu" format="reference"/>
+
+
+        <!-- =================== -->
+        <!-- Other widget styles -->
+        <!-- =================== -->
+        <eat-comment />
+
+        <!-- Default ActionBar dropdown style. -->
+        <attr name="actionDropDownStyle" format="reference"/>
+        <!-- The preferred item height for dropdown lists. -->
+        <attr name="dropdownListPreferredItemHeight" format="dimension"/>
+
+        <!-- Default Spinner style. -->
+        <attr name="spinnerStyle" format="reference" />
+        <!-- Default Spinner style. -->
+        <attr name="spinnerDropDownItemStyle" format="reference" />
+        <!-- Specifies a drawable to use for the 'home as up' indicator. -->
+        <attr name="homeAsUpIndicator" format="reference"/>
+
+        <!-- Default action button style. -->
+        <attr name="actionButtonStyle" format="reference"/>
+
+        <!-- A style that may be applied to horizontal LinearLayouts
          to form a button bar. -->
-    <attr name="buttonBarStyle" format="reference"/>
-    <!-- A style that may be applied to Buttons placed within a
-         LinearLayout with the style buttonBarStyle to form a button bar. -->
-    <attr name="buttonBarButtonStyle" format="reference"/>
-    <!-- A style that may be applied to buttons or other selectable items
-         that should react to pressed and focus states, but that do not
-         have a clear visual border along the edges. -->
-    <attr name="selectableItemBackground" format="reference"/>
-    <!-- A drawable that may be used as a vertical divider between visual elements. -->
-    <attr name="dividerVertical" format="reference"/>
-    <!-- A drawable that may be used as a horizontal divider between visual elements. -->
-    <attr name="dividerHorizontal" format="reference"/>
+        <attr name="buttonBarStyle" format="reference"/>
+        <!-- A style that may be applied to Buttons placed within a
+             LinearLayout with the style buttonBarStyle to form a button bar. -->
+        <attr name="buttonBarButtonStyle" format="reference"/>
+        <!-- A style that may be applied to buttons or other selectable items
+             that should react to pressed and focus states, but that do not
+             have a clear visual border along the edges. -->
+        <attr name="selectableItemBackground" format="reference"/>
+        <!-- Background drawable for borderless standalone items that need focus/pressed states. -->
+        <attr name="selectableItemBackgroundBorderless" format="reference" />
+        <!-- A drawable that may be used as a vertical divider between visual elements. -->
+        <attr name="dividerVertical" format="reference"/>
+        <!-- A drawable that may be used as a horizontal divider between visual elements. -->
+        <attr name="dividerHorizontal" format="reference"/>
+        <!-- Default ActivityChooserView style. -->
+        <attr name="activityChooserViewStyle" format="reference" />
 
-    <!-- The preferred list item height. -->
-    <attr name="listPreferredItemHeight" format="dimension"/>
-    <!-- A smaller, sleeker list item height. -->
-    <attr name="listPreferredItemHeightSmall" format="dimension"/>
-    <!-- A larger, more robust list item height. -->
-    <attr name="listPreferredItemHeightLarge" format="dimension"/>
+        <!-- Default Toolbar style. -->
+        <attr name="toolbarStyle" format="reference" />
+        <!-- Default Toolar NavigationButtonStyle -->
+        <attr name="toolbarNavigationButtonStyle" format="reference" />
 
-    <!-- The preferred padding along the left edge of list items. -->
-    <attr name="listPreferredItemPaddingLeft" format="dimension"/>
-    <!-- The preferred padding along the right edge of list items. -->
-    <attr name="listPreferredItemPaddingRight" format="dimension"/>
+        <!-- Default PopupMenu style. -->
+        <attr name="popupMenuStyle" format="reference"/>
+        <!-- Default PopupWindow style. -->
+        <attr name="popupWindowStyle" format="reference" />
 
-    <!-- ListPopupWindow comaptibility -->
-    <attr name="dropDownListViewStyle" format="reference"/>
-    <attr name="listPopupWindowStyle" format="reference"/>
+        <!-- EditText text foreground color. -->
+        <attr name="editTextColor" format="reference|color" />
+        <!-- EditText background drawable. -->
+        <attr name="editTextBackground" format="reference" />
+
+        <!-- ============================ -->
+        <!-- SearchView styles and assets -->
+        <!-- ============================ -->
+        <eat-comment />
+        <!-- Text color, typeface, size, and style for system search result title. Defaults to primary inverse text color. -->
+        <attr name="textAppearanceSearchResultTitle" format="reference" />
+        <!-- Text color, typeface, size, and style for system search result subtitle. Defaults to primary inverse text color. -->
+        <attr name="textAppearanceSearchResultSubtitle" format="reference" />
+        <!-- Text color for urls in search suggestions, used by things like global search -->
+        <attr name="textColorSearchUrl" format="reference|color" />
+        <!-- Style for the search query widget. -->
+        <attr name="searchViewStyle" format="reference" />
+
+        <!-- =========== -->
+        <!-- List styles -->
+        <!-- =========== -->
+        <eat-comment />
+
+        <!-- The preferred list item height. -->
+        <attr name="listPreferredItemHeight" format="dimension"/>
+        <!-- A smaller, sleeker list item height. -->
+        <attr name="listPreferredItemHeightSmall" format="dimension"/>
+        <!-- A larger, more robust list item height. -->
+        <attr name="listPreferredItemHeightLarge" format="dimension"/>
+
+        <!-- The preferred padding along the left edge of list items. -->
+        <attr name="listPreferredItemPaddingLeft" format="dimension"/>
+        <!-- The preferred padding along the right edge of list items. -->
+        <attr name="listPreferredItemPaddingRight" format="dimension"/>
+
+        <!-- ListPopupWindow compatibility -->
+        <attr name="dropDownListViewStyle" format="reference"/>
+        <attr name="listPopupWindowStyle" format="reference"/>
+
+        <!-- The preferred TextAppearance for the primary text of list items. -->
+        <attr name="textAppearanceListItem" format="reference"/>
+        <!-- The preferred TextAppearance for the primary text of small list items. -->
+        <attr name="textAppearanceListItemSmall" format="reference"/>
 
 
-    <!-- The preferred TextAppearance for the primary text of list items. -->
-    <attr name="textAppearanceListItem" format="reference"/>
-    <!-- The preferred TextAppearance for the primary text of small list items. -->
-    <attr name="textAppearanceListItemSmall" format="reference"/>
+        <!-- ============ -->
+        <!-- Panel styles -->
+        <!-- ============ -->
+        <eat-comment />
 
-    <attr name="title" format="string"/>
-    <attr name="height" format="dimension"/>
+        <!-- Default Panel Menu width. -->
+        <attr name="panelMenuListWidth" format="dimension" />
+
+        <!-- Default Panel Menu style. -->
+        <attr name="panelMenuListTheme" format="reference" />
+
+        <!-- Drawable used as a background for selected list items. -->
+        <attr name="listChoiceBackgroundIndicator" format="reference" />
+
+        <!-- ============= -->
+        <!-- Color palette -->
+        <!-- ============= -->
+        <eat-comment />
+
+        <!-- The primary branding color for the app. By default, this is the color applied to the
+             action bar background. -->
+        <attr name="colorPrimary" format="color" />
+
+        <!-- Dark variant of the primary branding color. By default, this is the color applied to
+             the status bar (via statusBarColor) and navigation bar (via navigationBarColor). -->
+        <attr name="colorPrimaryDark" format="color" />
+
+        <!-- Bright complement to the primary branding color. By default, this is the color applied
+             to framework controls (via colorControlActivated). -->
+        <attr name="colorAccent" format="color" />
+
+        <!-- The color applied to framework controls in their normal state. -->
+        <attr name="colorControlNormal" format="color" />
+
+        <!-- The color applied to framework controls in their activated (ex. checked) state. -->
+        <attr name="colorControlActivated" format="color" />
+
+        <!-- The color applied to framework control highlights (ex. ripples, list selectors). -->
+        <attr name="colorControlHighlight" format="color" />
+
+        <!-- The color applied to framework buttons in their normal state. -->
+        <attr name="colorButtonNormal" format="color" />
+
+    </declare-styleable>
 
 
     <!-- ============================================ -->
@@ -172,6 +322,7 @@
         </attr>
         <!-- Options affecting how the action bar is displayed. -->
         <attr name="displayOptions">
+            <flag name="none" value="0" />
             <flag name="useLogo" value="0x1"/>
             <flag name="showHome" value="0x2"/>
             <flag name="homeAsUp" value="0x4"/>
@@ -211,9 +362,30 @@
         <attr name="indeterminateProgressStyle" format="reference"/>
         <!-- Specifies the horizontal padding on either end for an embedded progress bar. -->
         <attr name="progressBarPadding" format="dimension"/>
+        <!-- Up navigation glyph -->
+        <attr name="homeAsUpIndicator" />
         <!-- Specifies padding that should be applied to the left and right sides of
              system-provided items in the bar. -->
         <attr name="itemPadding" format="dimension"/>
+        <!-- Set true to hide the action bar on a vertical nested scroll of content. -->
+        <attr name="hideOnContentScroll" format="boolean"/>
+        <!-- Minimum inset for content views within a bar. Navigation buttons and
+             menu views are excepted. Only valid for some themes and configurations. -->
+        <attr name="contentInsetStart" format="dimension"/>
+        <!-- Minimum inset for content views within a bar. Navigation buttons and
+             menu views are excepted. Only valid for some themes and configurations. -->
+        <attr name="contentInsetEnd" format="dimension"/>
+        <!-- Minimum inset for content views within a bar. Navigation buttons and
+             menu views are excepted. Only valid for some themes and configurations. -->
+        <attr name="contentInsetLeft" format="dimension"/>
+        <!-- Minimum inset for content views within a bar. Navigation buttons and
+             menu views are excepted. Only valid for some themes and configurations. -->
+        <attr name="contentInsetRight" format="dimension"/>
+        <!-- Elevation for the action bar itself -->
+        <attr name="elevation" format="dimension" />
+        <!-- Reference to a theme that should be used to inflate popups
+             shown by widgets in the action bar. -->
+        <attr name="popupTheme" format="reference" />
     </declare-styleable>
 
     <!-- Valid LayoutParams for views placed in the action bar as custom views. -->
@@ -253,62 +425,6 @@
         <attr name="android:focusable" />
     </declare-styleable>
 
-    <!-- =================== -->
-    <!-- Action mode styles  -->
-    <!-- =================== -->
-    <eat-comment/>
-    <attr name="actionModeStyle" format="reference"/>
-    <attr name="actionModeCloseButtonStyle" format="reference"/>
-    <!-- Background drawable to use for action mode UI -->
-    <attr name="actionModeBackground" format="reference"/>
-    <!-- Background drawable to use for action mode UI in the lower split bar -->
-    <attr name="actionModeSplitBackground" format="reference"/>
-    <!-- Drawable to use for the close action mode button -->
-    <attr name="actionModeCloseDrawable" format="reference"/>
-
-    <!-- Drawable to use for the Cut action button in Contextual Action Bar -->
-    <attr name="actionModeCutDrawable" format="reference"/>
-    <!-- Drawable to use for the Copy action button in Contextual Action Bar -->
-    <attr name="actionModeCopyDrawable" format="reference"/>
-    <!-- Drawable to use for the Paste action button in Contextual Action Bar -->
-    <attr name="actionModePasteDrawable" format="reference"/>
-    <!-- Drawable to use for the Select all action button in Contextual Action Bar -->
-    <attr name="actionModeSelectAllDrawable" format="reference"/>
-    <!-- Drawable to use for the Share action button in WebView selection action modes -->
-    <attr name="actionModeShareDrawable" format="reference"/>
-    <!-- Drawable to use for the Find action button in WebView selection action modes -->
-    <attr name="actionModeFindDrawable" format="reference"/>
-    <!-- Drawable to use for the Web Search action button in WebView selection action modes -->
-    <attr name="actionModeWebSearchDrawable" format="reference"/>
-
-    <!-- PopupWindow style to use for action modes when showing as a window overlay. -->
-    <attr name="actionModePopupWindowStyle" format="reference"/>
-
-    <!-- These are the standard attributes that make up a complete theme. -->
-    <declare-styleable name="Theme">
-        <!-- Default ActionBar dropdown style. -->
-        <attr name="actionDropDownStyle" format="reference"/>
-        <!-- The preferred item height for dropdown lists. -->
-        <attr name="dropdownListPreferredItemHeight" format="dimension"/>
-        <!-- Default PopupMenu style. -->
-        <attr name="popupMenuStyle" format="reference"/>
-
-        <!-- ============ -->
-        <!-- Panel styles -->
-        <!-- ============ -->
-        <eat-comment />
-
-        <!-- Default Panel Menu width. -->
-        <attr name="panelMenuListWidth" format="dimension" />
-
-        <!-- Default Panel Menu style. -->
-        <attr name="panelMenuListTheme" format="reference" />
-
-        <!-- Drawable used as a background for selected list items. -->
-        <attr name="listChoiceBackgroundIndicator" format="reference" />
-
-    </declare-styleable>
-
     <declare-styleable name="MenuView">
         <!-- Default appearance of menu item text. -->
         <attr name="android:itemTextAppearance"/>
@@ -481,33 +597,12 @@
         <attr name="disableChildrenWhenDisabled" format="boolean" />
     </declare-styleable>
 
-    <declare-styleable name="LinearLayoutICS">
-        <!-- Drawable to use as a vertical divider between buttons. -->
-        <attr name="divider" />
-        <!-- Setting for which dividers to show. -->
-        <attr name="showDividers">
-            <flag name="none" value="0" />
-            <flag name="beginning" value="1" />
-            <flag name="middle" value="2" />
-            <flag name="end" value="4" />
-        </attr>
-        <!-- Size of padding on either end of a divider. -->
-        <attr name="dividerPadding" format="dimension" />
-    </declare-styleable>
-
-    <!-- Default Spinner style. -->
-    <attr name="spinnerStyle" format="reference" />
-
-    <!-- Default Spinner style. -->
-    <attr name="spinnerDropDownItemStyle" format="reference" />
-
-    <!-- Specifies whether the theme is light, otherwise it is dark. -->
-    <attr name="isLightTheme" format="boolean" />
-
     <declare-styleable name="SearchView">
+        <!-- The layout to use for the search view. -->
+        <attr name="layout" format="reference" />
         <!-- The default state of the SearchView. If true, it will be iconified when not in
              use and expanded when clicked. -->
-        <attr name="iconifiedByDefault" format="boolean"/>
+        <attr name="iconifiedByDefault" format="boolean" />
         <!-- An optional maximum width of the SearchView. -->
         <attr name="android:maxWidth" />
         <!-- An optional query hint string to be displayed in the empty query field. -->
@@ -516,41 +611,24 @@
         <attr name="android:imeOptions" />
         <!-- The input type to set on the query text field. -->
         <attr name="android:inputType" />
+        <!-- Close button icon -->
+        <attr name="closeIcon" format="reference" />
+        <!-- Go button icon -->
+        <attr name="goIcon" format="reference" />
+        <!-- Search icon -->
+        <attr name="searchIcon" format="reference" />
+        <!-- Voice button icon -->
+        <attr name="voiceIcon" format="reference" />
+        <!-- Commit icon shown in the query suggestion row -->
+        <attr name="commitIcon" format="reference" />
+        <!-- Layout for query suggestion rows -->
+        <attr name="suggestionRowLayout" format="reference" />
+        <!-- Background for the section containing the search query -->
+        <attr name="queryBackground" format="reference" />
+        <!-- Background for the section containing the action (e.g. voice search) -->
+        <attr name="submitBackground" format="reference" />
     </declare-styleable>
 
-    <!-- ============================ -->
-    <!-- SearchView styles and assets -->
-    <!-- ============================ -->
-    <eat-comment />
-    <!-- SearchView dropdown background -->
-    <attr name="searchDropdownBackground" format="reference" />
-    <!-- SearchView close button icon -->
-    <attr name="searchViewCloseIcon" format="reference" />
-    <!-- SearchView Go button icon -->
-    <attr name="searchViewGoIcon" format="reference" />
-    <!-- SearchView Search icon -->
-    <attr name="searchViewSearchIcon" format="reference" />
-    <!-- SearchView Voice button icon -->
-    <attr name="searchViewVoiceIcon" format="reference" />
-    <!-- SearchView query refinement icon -->
-    <attr name="searchViewEditQuery" format="reference" />
-    <!-- SearchView query refinement icon background -->
-    <attr name="searchViewEditQueryBackground" format="reference" />
-    <!-- SearchView text field background for the left section -->
-    <attr name="searchViewTextField" format="reference" />
-    <!-- SearchView text field background for the right section -->
-    <attr name="searchViewTextFieldRight" format="reference" />
-    <!-- The list item height for search results. @hide -->
-    <attr name="searchResultListItemHeight" format="dimension" />
-    <!-- Text color, typeface, size, and style for system search result title. Defaults to primary inverse text color. -->
-    <attr name="textAppearanceSearchResultTitle" format="reference" />
-    <!-- Text color, typeface, size, and style for system search result subtitle. Defaults to primary inverse text color. -->
-    <attr name="textAppearanceSearchResultSubtitle" format="reference" />
-    <!-- Text color for urls in search suggestions, used by things like global search -->
-    <attr name="textColorSearchUrl" format="reference|color" />
-    <!-- SearchView AutoCompleteTextView style -->
-    <attr name="searchViewAutoCompleteTextView" format="reference" />
-
     <!-- Attrbitutes for a ActivityChooserView. -->
     <declare-styleable name="ActivityChooserView">
         <!-- The maximal number of items initially shown in the activity list. -->
@@ -564,12 +642,90 @@
         <attr name="expandActivityOverflowButtonDrawable" format="reference" />
     </declare-styleable>
 
-    <!-- Default ActivityChooserView style. -->
-    <attr name="activityChooserViewStyle" format="reference" />
-
     <declare-styleable name="CompatTextView">
         <!-- Present the text in ALL CAPS. This may use a small-caps form when available. -->
         <attr name="textAllCaps" format="reference|boolean" />
     </declare-styleable>
 
+    <declare-styleable name="LinearLayoutCompat">
+        <!-- Should the layout be a column or a row?  Use "horizontal"
+             for a row, "vertical" for a column.  The default is
+             horizontal. -->
+        <attr name="android:orientation" />
+        <attr name="android:gravity" />
+        <!-- When set to false, prevents the layout from aligning its children's
+             baselines. This attribute is particularly useful when the children
+             use different values for gravity. The default value is true. -->
+        <attr name="android:baselineAligned" />
+        <!-- When a linear layout is part of another layout that is baseline
+          aligned, it can specify which of its children to baseline align to
+          (that is, which child TextView).-->
+        <attr name="android:baselineAlignedChildIndex" />
+        <!-- Defines the maximum weight sum. If unspecified, the sum is computed
+             by adding the layout_weight of all of the children. This can be
+             used for instance to give a single child 50% of the total available
+             space by giving it a layout_weight of 0.5 and setting the weightSum
+             to 1.0. -->
+        <attr name="android:weightSum" />
+        <!-- When set to true, all children with a weight will be considered having
+             the minimum size of the largest child. If false, all children are
+             measured normally. -->
+        <attr name="measureWithLargestChild" format="boolean" />
+        <!-- Drawable to use as a vertical divider between buttons. -->
+        <attr name="divider" />
+        <!-- Setting for which dividers to show. -->
+        <attr name="showDividers">
+            <flag name="none" value="0" />
+            <flag name="beginning" value="1" />
+            <flag name="middle" value="2" />
+            <flag name="end" value="4" />
+        </attr>
+        <!-- Size of padding on either end of a divider. -->
+        <attr name="dividerPadding" format="dimension" />
+    </declare-styleable>
+
+    <declare-styleable name="LinearLayoutCompat_Layout">
+        <attr name="android:layout_width" />
+        <attr name="android:layout_height" />
+        <attr name="android:layout_weight" />
+        <attr name="android:layout_gravity" />
+    </declare-styleable>
+
+    <declare-styleable name="Toolbar">
+        <attr name="titleTextAppearance" format="reference" />
+        <attr name="subtitleTextAppearance" format="reference" />
+        <attr name="title" />
+        <attr name="subtitle" />
+        <attr name="android:gravity" />
+        <attr name="titleMargins" format="dimension" />
+        <attr name="titleMarginStart" format="dimension" />
+        <attr name="titleMarginEnd" format="dimension" />
+        <attr name="titleMarginTop" format="dimension" />
+        <attr name="titleMarginBottom" format="dimension" />
+        <attr name="contentInsetStart" />
+        <attr name="contentInsetEnd" />
+        <attr name="contentInsetLeft" />
+        <attr name="contentInsetRight" />
+        <attr name="maxButtonHeight" format="dimension" />
+
+        <!-- Use Theme toolbarNavigationButtonStyle instead -->
+        <!-- <attr name="navigationButtonStyle" format="reference" /> -->
+
+        <attr name="buttonGravity">
+            <!-- Push object to the top of its container, not changing its size. -->
+            <flag name="top" value="0x30" />
+            <!-- Push object to the bottom of its container, not changing its size. -->
+            <flag name="bottom" value="0x50" />
+        </attr>
+        <attr name="collapseIcon" format="reference" />
+        <!-- Reference to a theme that should be used to inflate popups
+             shown by widgets in the toolbar. -->
+        <attr name="popupTheme" />
+    </declare-styleable>
+
+    <declare-styleable name="PopupWindowBackgroundState">
+        <!-- State identifier indicating the popup will be above the anchor. -->
+        <attr name="state_above_anchor" format="boolean" />
+    </declare-styleable>
+
 </resources>
diff --git a/v7/appcompat/res/values/bools.xml b/v7/appcompat/res/values/bools.xml
index 004d947..79a5035 100644
--- a/v7/appcompat/res/values/bools.xml
+++ b/v7/appcompat/res/values/bools.xml
@@ -15,9 +15,10 @@
 -->
 
 <resources>
+
+    <bool name="abc_action_bar_embed_tabs">true</bool>
     <bool name="abc_action_bar_embed_tabs_pre_jb">false</bool>
     <bool name="abc_action_bar_expanded_action_views_exclusive">true</bool>
-    <bool name="abc_split_action_bar_is_narrow">true</bool>
 
     <bool name="abc_config_showMenuShortcutsWhenKeyboardPresent">false</bool>
 </resources>
diff --git a/v7/appcompat/res/values/colors_material.xml b/v7/appcompat/res/values/colors_material.xml
new file mode 100644
index 0000000..110093d
--- /dev/null
+++ b/v7/appcompat/res/values/colors_material.xml
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Colors specific to Quantum themes. -->
+<resources>
+    <color name="background_material_dark">#ff212121</color>
+    <color name="background_material_light">#fffafafa</color>
+
+    <color name="ripple_material_light">#20444444</color>
+    <color name="ripple_material_dark">#20ffffff</color>
+
+    <color name="button_material_dark">#ff5a595b</color>
+    <color name="button_material_light">#ffd6d7d7</color>
+
+    <color name="bright_foreground_material_dark">@android:color/white</color>
+    <color name="bright_foreground_material_light">@android:color/black</color>
+    <!-- White 50% -->
+    <color name="bright_foreground_disabled_material_dark">#80ffffff</color>
+    <!-- Black 50% -->
+    <color name="bright_foreground_disabled_material_light">#80000000</color>
+    <color name="bright_foreground_inverse_material_dark">@color/bright_foreground_material_light</color>
+    <color name="bright_foreground_inverse_material_light">@color/bright_foreground_material_dark</color>
+
+    <color name="dim_foreground_material_dark">#ffbebebe</color>
+    <color name="dim_foreground_material_light">#ff323232</color>
+    <color name="dim_foreground_disabled_material_dark">#80bebebe</color>
+    <color name="dim_foreground_disabled_material_light">#80323232</color>
+
+    <color name="hint_foreground_material_dark">@color/bright_foreground_disabled_material_dark</color>
+    <color name="hint_foreground_material_light">@color/bright_foreground_disabled_material_light</color>
+    <!-- TODO: This is 40% alpha on the default accent color. -->
+    <color name="highlighted_text_material_dark">#6640c4ff</color>
+    <!-- TODO: This is 40% alpha on the default accent color. -->
+    <color name="highlighted_text_material_light">#6640c4ff</color>
+
+    <!-- Text & foreground colors -->
+    <eat-comment />
+
+    <!-- Black 87% -->
+    <color name="primary_text_default_material_light">#de000000</color>
+    <!-- Black 43% -->
+    <color name="primary_text_disabled_default_material_light">#6e000000</color>
+    <!-- Black 54% -->
+    <color name="secondary_text_material_light">#8a000000</color>
+    <!-- Black 54% (TODO: same as secondary?) -->
+    <color name="tertiary_text_material_light">#8a000000</color>
+
+    <!-- White 87% -->
+    <color name="primary_text_default_material_dark">#deffffff</color>
+    <!-- White 43% -->
+    <color name="primary_text_disabled_default_material_dark">#6effffff</color>
+    <!-- White 38% -->
+    <color name="secondary_text_material_dark">#61ffffff</color>
+    <!-- White 38% (TODO: same as secondary?) -->
+    <color name="tertiary_text_material_dark">#61ffffff</color>
+
+    <!-- Primary & accent colors -->
+    <eat-comment />
+
+    <color name="material_red_100">#fff4c7c3</color>
+    <color name="material_red_300">#ffe67c73</color>
+    <color name="material_red_500">#ffdb4437</color>
+    <color name="material_red_700">#ffc53929</color>
+    <color name="material_red_A200">#ffff5252</color>
+    <color name="material_red_A400">#ffff1744</color>
+
+    <color name="material_blue_100">#ffc6dafc</color>
+    <color name="material_blue_300">#ff7baaf7</color>
+    <color name="material_blue_500">#ff4285f4</color>
+    <color name="material_blue_700">#ff3367d6</color>
+    <color name="material_blue_A200">#ff448aff</color>
+    <color name="material_blue_A400">#ff2979ff</color>
+
+    <color name="material_light_blue_A200">#ff40c4ff</color>
+
+    <color name="material_teal_100">#ffb2ebf2</color>
+    <color name="material_teal_300">#ff4dd0e1</color>
+    <color name="material_teal_500">#ff00bcd4</color>
+    <color name="material_teal_700">#ff0097a7</color>
+    <color name="material_teal_A200">#ff18ffff</color>
+    <color name="material_teal_A400">#ff00e5ff</color>
+
+    <color name="material_deep_teal_A200">#ff80cbc4</color>
+    <color name="material_deep_teal_A500">#ff009688</color>
+
+    <color name="material_green_100">#ffb7e1cd</color>
+    <color name="material_green_300">#ff57bb8a</color>
+    <color name="material_green_500">#ff0f9d58</color>
+    <color name="material_green_700">#ff0b8043</color>
+    <color name="material_green_A200">#ff69f0ae</color>
+    <color name="material_green_A400">#ff00e676</color>
+
+    <color name="material_lime_100">#fff0f4c3</color>
+    <color name="material_lime_300">#ffdce775</color>
+    <color name="material_lime_500">#ffcddc39</color>
+    <color name="material_lime_700">#ffafb42b</color>
+    <color name="material_lime_A200">#ffeeff41</color>
+    <color name="material_lime_A400">#ffc6ff00</color>
+
+    <color name="material_yellow_100">#fffce8b2</color>
+    <color name="material_yellow_300">#fff7cb4d</color>
+    <color name="material_yellow_500">#fff4b400</color>
+    <color name="material_yellow_700">#fff09300</color>
+    <color name="material_yellow_A200">#ffffcd40</color>
+    <color name="material_yellow_A400">#ffffbc00</color>
+
+    <color name="material_orange_100">#ffffe0b2</color>
+    <color name="material_orange_300">#ffffb74d</color>
+    <color name="material_orange_500">#ffff9800</color>
+    <color name="material_orange_700">#fff57c00</color>
+    <color name="material_orange_A200">#ffffab40</color>
+    <color name="material_orange_A400">#ffff9100</color>
+
+    <color name="material_deep_orange_100">#fff4c7c3</color>
+    <color name="material_deep_orange_300">#ffe67c73</color>
+    <color name="material_deep_orange_500">#ffff5722</color>
+    <color name="material_deep_orange_700">#ffc53929</color>
+    <color name="material_deep_orange_A200">#ffff5252</color>
+    <color name="material_deep_orange_A400">#ffff1744</color>
+
+    <!-- Neutral colors -->
+    <eat-comment />
+
+    <color name="material_grey_50">#fffafafa</color>
+    <color name="material_grey_100">#fff5f5f5</color>
+    <color name="material_grey_300">#ffeeeeee</color>
+    <color name="material_grey_500">#ffa3a3a3</color>
+    <color name="material_grey_600">#ff757575</color>
+    <color name="material_grey_700">#ff717171</color>
+    <color name="material_grey_900">#ff212121</color>
+
+    <color name="material_blue_grey_50">#ffeceff1</color>
+    <color name="material_blue_grey_100">#ffcfd8dc</color>
+    <color name="material_blue_grey_300">#ff90a4ae</color>
+    <color name="material_blue_grey_400">#ff78909c</color>
+    <color name="material_blue_grey_500">#ff607d8b</color>
+    <color name="material_blue_grey_600">#ff546e7a</color>
+    <color name="material_blue_grey_700">#ff455a64</color>
+    <color name="material_blue_grey_800">#ff37474f</color>
+    <!-- Primary color used by Settings -->
+    <color name="material_blue_grey_900">#ff263238</color>
+    <!-- Primary dark color used by Settings -->
+    <color name="material_blue_grey_950">#ff21272b</color>
+
+    <color name="material_brown_100">#ffd7ccc8</color>
+    <color name="material_brown_300">#ffa1887f</color>
+    <color name="material_brown_500">#ff795548</color>
+    <color name="material_brown_700">#ff5d4037</color>
+
+</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values/config.xml b/v7/appcompat/res/values/config.xml
index 8c027d1..0f8b7dc 100644
--- a/v7/appcompat/res/values/config.xml
+++ b/v7/appcompat/res/values/config.xml
@@ -31,4 +31,9 @@
          Defaults to true. If this is not appropriate for specific locales
          it should be disabled in that locale's resources. -->
     <bool name="abc_config_actionMenuItemAllCaps">true</bool>
+
+    <!-- The duration (in milliseconds) of the activity open/close and fragment open/close animations. -->
+    <integer name="abc_config_activityShortDur">150</integer>
+    <integer name="abc_config_activityDefaultDur">220</integer>
+
 </resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values/dimens.xml b/v7/appcompat/res/values/dimens.xml
index c2cf3a3..75a84bd 100644
--- a/v7/appcompat/res/values/dimens.xml
+++ b/v7/appcompat/res/values/dimens.xml
@@ -26,8 +26,6 @@
          a few are present. -->
     <dimen name="abc_action_bar_stacked_tab_max_width">180dp</dimen>
 
-    <!-- Default height of an action bar. -->
-    <dimen name="abc_action_bar_default_height">48dip</dimen>
     <!-- Vertical padding around action bar icons. -->
     <dimen name="abc_action_bar_icon_vertical_padding">8dip</dimen>
     <!-- Text size for action bar titles -->
@@ -72,4 +70,9 @@
          (the screen is in landscape). This may be either a fraction or a dimension.-->
     <item type="dimen" name="dialog_fixed_height_minor">100%</item>
 
+    <!-- Default insets (outer padding) around controls -->
+    <dimen name="abc_control_inset_material">4dp</dimen>
+    <!-- Default inner padding within controls -->
+    <dimen name="abc_control_padding_material">4dp</dimen>
+
 </resources>
diff --git a/v7/appcompat/res/values/dimens_material.xml b/v7/appcompat/res/values/dimens_material.xml
new file mode 100644
index 0000000..c604a5e
--- /dev/null
+++ b/v7/appcompat/res/values/dimens_material.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+
+    <!-- Default height of an action bar. -->
+    <dimen name="abc_action_bar_default_height_material">56dp</dimen>
+
+    <dimen name="abc_action_button_min_width_material">48dp</dimen>
+    <dimen name="abc_action_button_min_height_material">48dp</dimen>
+    <dimen name="abc_action_overflow_min_width_material">36dp</dimen>
+
+    <dimen name="text_size_display_4_material">112sp</dimen>
+    <dimen name="text_size_display_3_material">56sp</dimen>
+    <dimen name="text_size_display_2_material">45sp</dimen>
+    <dimen name="text_size_display_1_material">34sp</dimen>
+    <dimen name="text_size_headline_material">24sp</dimen>
+    <dimen name="text_size_title_material">20sp</dimen>
+    <dimen name="text_size_subhead_material">16sp</dimen>
+    <dimen name="text_size_body_2_material">14sp</dimen>
+    <dimen name="text_size_body_1_material">14sp</dimen>
+    <dimen name="text_size_caption_material">12sp</dimen>
+    <dimen name="text_size_menu_material">14sp</dimen>
+    <dimen name="text_size_button_material">14sp</dimen>
+
+    <dimen name="text_size_large_material">22sp</dimen>
+    <dimen name="text_size_medium_material">18sp</dimen>
+    <dimen name="text_size_small_material">14sp</dimen>
+
+    <!-- Text size for action bar titles -->
+    <dimen name="abc_action_bar_title_text_size_material">20sp</dimen>
+    <!-- Text size for action bar subtitles -->
+    <dimen name="abc_action_bar_subtitle_text_size_material">16sp</dimen>
+
+</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values/ids.xml b/v7/appcompat/res/values/ids.xml
index ebeed83..2e6ef24 100644
--- a/v7/appcompat/res/values/ids.xml
+++ b/v7/appcompat/res/values/ids.xml
@@ -15,7 +15,10 @@
 -->
 <resources>
     <item type="id" name="home"/>
+    <item type="id" name="up"/>
+    <item type="id" name="action_bar_spinner"/>
     <item type="id" name="action_bar_activity_content"/>
+    <item type="id" name="split_action_bar"/>
     <item type="id" name="action_menu_divider"/>
     <item type="id" name="action_menu_presenter"/>
     <item type="id" name="progress_circular"/>
diff --git a/v7/appcompat/res/values/strings.xml b/v7/appcompat/res/values/strings.xml
index eceee56..765833d 100644
--- a/v7/appcompat/res/values/strings.xml
+++ b/v7/appcompat/res/values/strings.xml
@@ -24,6 +24,18 @@
     <!-- Content description for the action menu overflow button. [CHAR LIMIT=NONE] -->
     <string name="abc_action_menu_overflow_description">More options</string>
 
+    <!-- Formatting string for describing the action bar's title/home/up affordance.
+         This is a single tappable "button" that includes the app icon, the Up indicator
+         (usually a "<" chevron) and the window title text.
+         %1$s is the title. %2$s is the description of what tapping/clicking the whole
+         thing is going to do. -->
+    <string name="abc_action_bar_home_description_format">%1$s, %2$s</string>
+    <!-- Just like action_bar_home_description_format, but this one will be used
+         if the window is also providing subtitle text.
+         %1$s is the title. %2$s is the subtitle. %3$s is the description of what
+         tapping/clicking the whole thing is going to do. -->
+    <string name="abc_action_bar_home_subtitle_description_format">%1$s, %2$s, %3$s</string>
+
     <!-- SearchView accessibility description for search button [CHAR LIMIT=NONE] -->
     <string name="abc_searchview_description_search">Search</string>
     <!-- SearchView accessibility description for search text field [CHAR LIMIT=NONE] -->
diff --git a/v7/appcompat/res/values/styles.xml b/v7/appcompat/res/values/styles.xml
index 94d2e88..bade768 100644
--- a/v7/appcompat/res/values/styles.xml
+++ b/v7/appcompat/res/values/styles.xml
@@ -17,229 +17,311 @@
 <resources>
 
     <!-- Styles in here can be extended for customisation in your application. Each utilises
-         one of the Base styles. If Holo themes are available on the current platform version
+         one of the.styles. If Holo themes are available on the current platform version
          they will be used instead of the compat styles. -->
 
-    <style name="Widget.AppCompat.ActionBar" parent="Widget.AppCompat.Base.ActionBar">
+    <style name="Widget.AppCompat.ActionBar" parent="Base.Widget.AppCompat.ActionBar">
     </style>
 
-    <style name="Widget.AppCompat.Light.ActionBar" parent="Widget.AppCompat.Light.Base.ActionBar">
+    <style name="Widget.AppCompat.Light.ActionBar" parent="Base.Widget.AppCompat.Light.ActionBar">
     </style>
 
     <style name="Widget.AppCompat.ActionBar.Solid"
-           parent="Widget.AppCompat.Base.ActionBar.Solid">
+           parent="Base.Widget.AppCompat.ActionBar.Solid">
     </style>
 
     <style name="Widget.AppCompat.Light.ActionBar.Solid"
-           parent="Widget.AppCompat.Light.Base.ActionBar.Solid">
-    </style>
-
-    <style name="Widget.AppCompat.Light.ActionBar.Solid.Inverse"
-           parent="Widget.AppCompat.Light.Base.ActionBar.Solid.Inverse">
+           parent="Base.Widget.AppCompat.Light.ActionBar.Solid">
     </style>
 
     <style name="TextAppearance.AppCompat.Widget.ActionBar.Title"
-           parent="TextAppearance.AppCompat.Widget.Base.ActionBar.Title">
+           parent="Base.TextAppearance.AppCompat.Widget.ActionBar.Title">
     </style>
 
     <style name="TextAppearance.AppCompat.Widget.ActionBar.Subtitle"
-           parent="TextAppearance.AppCompat.Widget.Base.ActionBar.Subtitle">
+           parent="Base.TextAppearance.AppCompat.Widget.ActionBar.Subtitle">
     </style>
 
     <style name="TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse"
-           parent="TextAppearance.AppCompat.Widget.Base.ActionBar.Title.Inverse">
+           parent="Base.TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse">
     </style>
 
     <style name="TextAppearance.AppCompat.Widget.ActionBar.Subtitle.Inverse"
-           parent="TextAppearance.AppCompat.Widget.Base.ActionBar.Subtitle.Inverse">
+           parent="Base.TextAppearance.AppCompat.Widget.ActionBar.Subtitle.Inverse">
     </style>
 
     <style name="Widget.AppCompat.ProgressBar.Horizontal"
-           parent="Widget.AppCompat.Base.ProgressBar.Horizontal">
+           parent="Base.Widget.AppCompat.ProgressBar.Horizontal">
     </style>
 
     <style name="Widget.AppCompat.ProgressBar"
-           parent="Widget.AppCompat.Base.ProgressBar">
+           parent="Base.Widget.AppCompat.ProgressBar">
     </style>
 
-    <style name="Widget.AppCompat.ActionButton" parent="Widget.AppCompat.Base.ActionButton">
+    <style name="Widget.AppCompat.ActionButton" parent="Base.Widget.AppCompat.ActionButton">
     </style>
 
     <style name="Widget.AppCompat.Light.ActionButton"
-           parent="Widget.AppCompat.Light.Base.ActionButton">
+           parent="Base.Widget.AppCompat.Light.ActionButton">
     </style>
 
     <style name="Widget.AppCompat.ActionButton.CloseMode"
-           parent="Widget.AppCompat.Base.ActionButton.CloseMode">
+           parent="Base.Widget.AppCompat.ActionButton.CloseMode">
     </style>
 
     <style name="Widget.AppCompat.Light.ActionButton.CloseMode"
-           parent="Widget.AppCompat.Light.Base.ActionButton.CloseMode">
+           parent="Base.Widget.AppCompat.Light.ActionButton.CloseMode">
     </style>
 
     <style name="Widget.AppCompat.ActionButton.Overflow"
-           parent="Widget.AppCompat.Base.ActionButton.Overflow">
+           parent="Base.Widget.AppCompat.ActionButton.Overflow">
     </style>
 
     <style name="Widget.AppCompat.Light.ActionButton.Overflow"
-           parent="Widget.AppCompat.Light.Base.ActionButton.Overflow">
+           parent="Base.Widget.AppCompat.Light.ActionButton.Overflow">
     </style>
 
-    <style name="Widget.AppCompat.ActionBar.TabBar" parent="Widget.AppCompat.Base.ActionBar.TabBar">
+    <style name="Widget.AppCompat.ActionBar.TabBar"
+           parent="Base.Widget.AppCompat.ActionBar.TabBar">
     </style>
 
     <style name="Widget.AppCompat.Light.ActionBar.TabBar"
-           parent="Widget.AppCompat.Light.Base.ActionBar.TabBar">
+           parent="Base.Widget.AppCompat.Light.ActionBar.TabBar">
     </style>
 
-    <style name="Widget.AppCompat.Light.ActionBar.TabBar.Inverse"
-           parent="Widget.AppCompat.Light.Base.ActionBar.TabBar.Inverse"></style>
-
     <style name="Widget.AppCompat.ActionBar.TabView"
-           parent="Widget.AppCompat.Base.ActionBar.TabView">
+           parent="Base.Widget.AppCompat.ActionBar.TabView">
     </style>
 
     <style name="Widget.AppCompat.Light.ActionBar.TabView"
-           parent="Widget.AppCompat.Light.Base.ActionBar.TabView">
-    </style>
-
-    <style name="Widget.AppCompat.Light.ActionBar.TabView.Inverse"
-           parent="Widget.AppCompat.Light.Base.ActionBar.TabView.Inverse">
+           parent="Base.Widget.AppCompat.Light.ActionBar.TabView">
     </style>
 
     <style name="Widget.AppCompat.ActionBar.TabText"
-           parent="Widget.AppCompat.Base.ActionBar.TabText">
+           parent="Base.Widget.AppCompat.ActionBar.TabText">
     </style>
 
     <style name="Widget.AppCompat.Light.ActionBar.TabText"
-           parent="Widget.AppCompat.Light.Base.ActionBar.TabText">
+           parent="Base.Widget.AppCompat.Light.ActionBar.TabText">
     </style>
 
     <style name="Widget.AppCompat.Light.ActionBar.TabText.Inverse"
-           parent="Widget.AppCompat.Light.Base.ActionBar.TabText.Inverse">
+           parent="Base.Widget.AppCompat.Light.ActionBar.TabText.Inverse">
     </style>
 
     <style name="TextAppearance.AppCompat.Widget.ActionBar.Menu"
-           parent="TextAppearance.AppCompat.Widget.Base.ActionBar.Menu">
+           parent="Base.TextAppearance.AppCompat.Widget.ActionBar.Menu">
     </style>
 
-    <style name="Widget.AppCompat.ActionMode" parent="Widget.AppCompat.Base.ActionMode">
+    <style name="Widget.AppCompat.ActionMode" parent="Base.Widget.AppCompat.ActionMode">
     </style>
 
     <style name="Widget.AppCompat.Light.ActionMode.Inverse"
-           parent="Widget.AppCompat.Light.Base.ActionMode.Inverse">
+           parent="Base.Widget.AppCompat.Light.ActionMode.Inverse">
     </style>
 
     <style name="TextAppearance.AppCompat.Widget.ActionMode.Title"
-           parent="TextAppearance.AppCompat.Widget.Base.ActionMode.Title">
+           parent="Base.TextAppearance.AppCompat.Widget.ActionMode.Title">
     </style>
 
     <style name="TextAppearance.AppCompat.Widget.ActionMode.Subtitle"
-           parent="TextAppearance.AppCompat.Widget.Base.ActionMode.Subtitle">
+           parent="Base.TextAppearance.AppCompat.Widget.ActionMode.Subtitle">
     </style>
 
     <style name="TextAppearance.AppCompat.Widget.ActionMode.Title.Inverse"
-           parent="TextAppearance.AppCompat.Widget.Base.ActionMode.Title.Inverse">
+           parent="Base.TextAppearance.AppCompat.Widget.ActionMode.Title.Inverse">
     </style>
 
     <style name="TextAppearance.AppCompat.Widget.ActionMode.Subtitle.Inverse"
-           parent="TextAppearance.AppCompat.Widget.Base.ActionMode.Subtitle.Inverse">
+           parent="Base.TextAppearance.AppCompat.Widget.ActionMode.Subtitle.Inverse">
     </style>
 
     <style name="TextAppearance.AppCompat.Widget.DropDownItem"
-           parent="TextAppearance.AppCompat.Widget.Base.DropDownItem">
+           parent="Base.TextAppearance.AppCompat.Widget.DropDownItem">
     </style>
 
-    <style name="Widget.AppCompat.Spinner.DropDown.ActionBar"
-           parent="Widget.AppCompat.Base.Spinner">
-    </style>
+    <style name="Widget.AppCompat.Spinner"
+           parent="Base.Widget.AppCompat.Spinner" />
+
+    <style name="Widget.AppCompat.Spinner.DropDown" />
+
+    <style name="Widget.AppCompat.Spinner.DropDown.ActionBar" />
 
     <style name="Widget.AppCompat.Light.Spinner.DropDown.ActionBar"
-           parent="Widget.AppCompat.Light.Base.Spinner">
+           parent="Base.Widget.AppCompat.Light.Spinner">
     </style>
 
     <style name="Widget.AppCompat.DropDownItem.Spinner"
-           parent="Widget.AppCompat.Base.DropDownItem.Spinner">
+           parent="Base.Widget.AppCompat.DropDownItem.Spinner">
     </style>
+
     <style name="Widget.AppCompat.Light.DropDownItem.Spinner"
-           parent="Widget.AppCompat.Light.Base.DropDownItem.Spinner">
+           parent="Base.Widget.AppCompat.Light.DropDownItem.Spinner">
     </style>
 
     <style name="Widget.AppCompat.ListView.DropDown"
-           parent="Widget.AppCompat.Base.ListView.DropDown">
+           parent="Base.Widget.AppCompat.ListView.DropDown">
     </style>
 
     <style name="Widget.AppCompat.Light.ListView.DropDown"
-           parent="Widget.AppCompat.Light.Base.ListView.DropDown">
+           parent="Base.Widget.AppCompat.Light.ListView.DropDown">
     </style>
 
     <style name="TextAppearance.Widget.AppCompat.ExpandedMenu.Item"
-           parent="TextAppearance.Widget.AppCompat.Base.ExpandedMenu.Item">
+           parent="Base.TextAppearance.Widget.AppCompat.ExpandedMenu.Item">
     </style>
 
-    <style name="Widget.AppCompat.ListPopupWindow" parent="Widget.AppCompat.Base.ListPopupWindow">
+    <style name="Widget.AppCompat.ListPopupWindow" parent="Base.Widget.AppCompat.ListPopupWindow">
     </style>
 
     <style name="Widget.AppCompat.Light.ListPopupWindow"
-           parent="Widget.AppCompat.Light.Base.ListPopupWindow">
+           parent="Base.Widget.AppCompat.Light.ListPopupWindow">
     </style>
 
-    <style name="Widget.AppCompat.PopupMenu" parent="Widget.AppCompat.Base.PopupMenu">
+    <style name="Widget.AppCompat.PopupMenu.Overflow"
+           parent="Base.Widget.AppCompat.PopupMenu.Overflow">
+    </style>
+
+    <style name="Widget.AppCompat.Light.PopupMenu.Overflow"
+           parent="Base.Widget.AppCompat.Light.PopupMenu.Overflow">
+    </style>
+
+    <style name="Widget.AppCompat.PopupMenu" parent="Base.Widget.AppCompat.PopupMenu">
     </style>
 
     <style name="Widget.AppCompat.Light.PopupMenu"
-           parent="Widget.AppCompat.Light.Base.PopupMenu">
+           parent="Base.Widget.AppCompat.Light.PopupMenu">
     </style>
 
-    <style name="Widget.AppCompat.ListView.Menu" parent="Widget.AppCompat.Base.ListView.Menu">
+    <style name="Widget.AppCompat.ListView.Menu" parent="Base.Widget.AppCompat.ListView.Menu">
+    </style>
+
+    <style name="Widget.AppCompat.PopupWindow" parent="Base.Widget.AppCompat.PopupWindow">
     </style>
 
     <style name="TextAppearance.AppCompat.Widget.PopupMenu.Large"
-           parent="TextAppearance.AppCompat.Base.Widget.PopupMenu.Large">
+           parent="Base.TextAppearance.AppCompat.Widget.PopupMenu.Large">
     </style>
 
     <style name="TextAppearance.AppCompat.Widget.PopupMenu.Small"
-           parent="TextAppearance.AppCompat.Base.Widget.PopupMenu.Small">
+           parent="Base.TextAppearance.AppCompat.Widget.PopupMenu.Small">
     </style>
 
     <style name="TextAppearance.AppCompat.Light.Widget.PopupMenu.Large"
-           parent="TextAppearance.AppCompat.Light.Base.Widget.PopupMenu.Large">
+           parent="Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Large">
     </style>
 
     <style name="TextAppearance.AppCompat.Light.Widget.PopupMenu.Small"
-           parent="TextAppearance.AppCompat.Light.Base.Widget.PopupMenu.Small">
+           parent="Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Small">
     </style>
 
     <style name="TextAppearance.AppCompat.SearchResult.Title"
-           parent="TextAppearance.AppCompat.Base.SearchResult.Title">
+           parent="Base.TextAppearance.AppCompat.SearchResult.Title">
     </style>
 
     <style name="TextAppearance.AppCompat.SearchResult.Subtitle"
-           parent="TextAppearance.AppCompat.Base.SearchResult.Subtitle">
+           parent="Base.TextAppearance.AppCompat.SearchResult.Subtitle">
     </style>
 
     <style name="TextAppearance.AppCompat.Light.SearchResult.Title"
-           parent="TextAppearance.AppCompat.Light.Base.SearchResult.Title">
+           parent="Base.TextAppearance.AppCompat.Light.SearchResult.Title">
     </style>
 
     <style name="TextAppearance.AppCompat.Light.SearchResult.Subtitle"
-           parent="TextAppearance.AppCompat.Light.Base.SearchResult.Subtitle">
+           parent="Base.TextAppearance.AppCompat.Light.SearchResult.Subtitle">
     </style>
 
     <style name="Widget.AppCompat.AutoCompleteTextView"
-           parent="Widget.AppCompat.Base.AutoCompleteTextView">
+           parent="Base.Widget.AppCompat.AutoCompleteTextView">
     </style>
 
     <style name="Widget.AppCompat.Light.AutoCompleteTextView"
-           parent="Widget.AppCompat.Light.Base.AutoCompleteTextView">
+           parent="Base.Widget.AppCompat.Light.AutoCompleteTextView">
     </style>
 
     <style name="Widget.AppCompat.ActivityChooserView"
-           parent="Widget.AppCompat.Base.ActivityChooserView">
+           parent="Base.Widget.AppCompat.ActivityChooserView">
     </style>
 
     <style name="Widget.AppCompat.Light.ActivityChooserView"
-           parent="Widget.AppCompat.Light.Base.ActivityChooserView">
+           parent="Base.Widget.AppCompat.Light.ActivityChooserView">
     </style>
 
+    <style name="Widget.AppCompat.SearchView" parent="Base.Widget.AppCompat.SearchView">
+    </style>
+
+    <style name="Widget.AppCompat.Light.SearchView" parent="Base.Widget.AppCompat.Light.SearchView">
+    </style>
+
+    <style name="Widget.AppCompat.EditText"
+           parent="Base.Widget.AppCompat.EditText">
+    </style>
+
+    <!-- Toolbar -->
+
+    <style name="Widget.AppCompat.Toolbar" parent="Base.Widget.AppCompat.Toolbar" />
+
+    <style name="Widget.AppCompat.Toolbar.Button.Navigation"
+           parent="Base.Widget.AppCompat.Toolbar.Button.Navigation" />
+
+    <style name="TextAppearance.Widget.AppCompat.Toolbar.Title"
+           parent="Base.TextAppearance.Widget.AppCompat.Toolbar.Title">
+    </style>
+
+    <style name="TextAppearance.Widget.AppCompat.Toolbar.Subtitle"
+           parent="Base.TextAppearance.Widget.AppCompat.Toolbar.Subtitle">
+    </style>
+
+
+    <eat-comment />
+    <!-- Text styles -->
+
+    <style name="TextAppearance.AppCompat" parent="Base.TextAppearance.AppCompat" />
+
+    <style name="TextAppearance.AppCompat.Display4" parent="Base.TextAppearance.AppCompat.Display4" />
+
+    <style name="TextAppearance.AppCompat.Display3" parent="Base.TextAppearance.AppCompat.Display3" />
+
+    <style name="TextAppearance.AppCompat.Display2" parent="Base.TextAppearance.AppCompat.Display2" />
+
+    <style name="TextAppearance.AppCompat.Display1" parent="Base.TextAppearance.AppCompat.Display1" />
+
+    <style name="TextAppearance.AppCompat.Headline" parent="Base.TextAppearance.AppCompat.Headline" />
+
+    <style name="TextAppearance.AppCompat.Title" parent="Base.TextAppearance.AppCompat.Title" />
+
+    <style name="TextAppearance.AppCompat.Subhead" parent="Base.TextAppearance.AppCompat.Subhead" />
+
+    <style name="TextAppearance.AppCompat.Body2" parent="Base.TextAppearance.AppCompat.Body2" />
+
+    <style name="TextAppearance.AppCompat.Body1" parent="Base.TextAppearance.AppCompat.Body1" />
+
+    <style name="TextAppearance.AppCompat.Caption" parent="Base.TextAppearance.AppCompat.Caption" />
+
+    <style name="TextAppearance.AppCompat.Menu" parent="Base.TextAppearance.AppCompat.Menu" />
+
+    <style name="TextAppearance.AppCompat.Inverse" parent="Base.TextAppearance.AppCompat.Inverse" />
+
+    <style name="TextAppearance.AppCompat.Large" parent="Base.TextAppearance.AppCompat.Large" />
+
+    <style name="TextAppearance.AppCompat.Large.Inverse" parent="Base.TextAppearance.AppCompat.Large.Inverse" />
+
+    <style name="TextAppearance.AppCompat.Medium" parent="Base.TextAppearance.AppCompat.Medium" />
+
+    <style name="TextAppearance.AppCompat.Medium.Inverse" parent="Base.TextAppearance.AppCompat.Medium.Inverse" />
+
+    <style name="TextAppearance.AppCompat.Small" parent="Base.TextAppearance.AppCompat.Small" />
+
+    <style name="TextAppearance.AppCompat.Small.Inverse" parent="Base.TextAppearance.AppCompat.Small.Inverse" />
+
+
+    <!--
+         The following themes are deprecated and now resolve to their parent.
+    -->
+    <style name="Widget.AppCompat.Light.ActionBar.Solid.Inverse" />
+
+    <style name="Widget.AppCompat.Light.ActionBar.TabBar.Inverse" />
+
+    <style name="Widget.AppCompat.Light.ActionBar.TabView.Inverse" />
+
 </resources>
diff --git a/v7/appcompat/res/values/styles_base.xml b/v7/appcompat/res/values/styles_base.xml
index 79dd732..cfec354 100644
--- a/v7/appcompat/res/values/styles_base.xml
+++ b/v7/appcompat/res/values/styles_base.xml
@@ -16,154 +16,128 @@
 
 <resources>
 
-    <!-- Like in themes_base.xml, the namespace "*.AppCompat.Base" is used to
+    <!-- Like in themes_base.xml, the namespace "Base.AppCompat.*" is used to
          define base styles for the platform version. The "*.AppCompat"
          variants are for direct use or use as parent styles by the app. -->
     <eat-comment/>
 
-    <style name="Widget.AppCompat.Base.ActionBar" parent="">
-        <item name="displayOptions">useLogo|showHome|showTitle</item>
+    <style name="Base.Widget.AppCompat.ActionBar" parent="">
+        <item name="displayOptions">showTitle</item>
         <item name="divider">?attr/dividerVertical</item>
         <item name="height">?attr/actionBarSize</item>
         <item name="homeLayout">@layout/abc_action_bar_home</item>
 
         <item name="titleTextStyle">@style/TextAppearance.AppCompat.Widget.ActionBar.Title</item>
-        <item name="subtitleTextStyle">@style/TextAppearance.AppCompat.Widget.ActionBar.Subtitle
-        </item>
+        <item name="subtitleTextStyle">@style/TextAppearance.AppCompat.Widget.ActionBar.Subtitle</item>
 
-        <item name="background">@drawable/abc_ab_transparent_dark_holo</item>
-        <item name="backgroundStacked">@drawable/abc_ab_stacked_transparent_dark_holo</item>
-        <item name="backgroundSplit">@drawable/abc_ab_bottom_transparent_dark_holo</item>
+        <item name="background">@null</item>
+        <item name="backgroundStacked">@null</item>
+        <item name="backgroundSplit">@null</item>
 
         <item name="actionButtonStyle">@style/Widget.AppCompat.ActionButton</item>
         <item name="actionOverflowButtonStyle">@style/Widget.AppCompat.ActionButton.Overflow</item>
 
         <item name="progressBarStyle">@style/Widget.AppCompat.ProgressBar.Horizontal</item>
         <item name="indeterminateProgressStyle">@style/Widget.AppCompat.ProgressBar</item>
+
+        <item name="android:gravity">center_vertical</item>
+        <item name="elevation">8dp</item>
+        <item name="popupTheme">?attr/actionBarPopupTheme</item>
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.ActionBar" parent="Widget.AppCompat.Base.ActionBar">
-        <item name="background">@drawable/abc_ab_transparent_light_holo</item>
-        <item name="backgroundStacked">@drawable/abc_ab_stacked_transparent_light_holo</item>
-        <item name="backgroundSplit">@drawable/abc_ab_bottom_transparent_light_holo</item>
-
+    <style name="Base.Widget.AppCompat.Light.ActionBar" parent="Base.Widget.AppCompat.ActionBar">
         <item name="actionButtonStyle">@style/Widget.AppCompat.Light.ActionButton</item>
-        <item name="actionOverflowButtonStyle">@style/Widget.AppCompat.Light.ActionButton.Overflow
-        </item>
-
-        <item name="progressBarStyle">@style/Widget.AppCompat.ProgressBar.Horizontal</item>
-        <item name="indeterminateProgressStyle">@style/Widget.AppCompat.ProgressBar</item>
+        <item name="actionOverflowButtonStyle">@style/Widget.AppCompat.Light.ActionButton.Overflow</item>
     </style>
 
-    <style name="Widget.AppCompat.Base.ActionBar.Solid" parent="Widget.AppCompat.Base.ActionBar">
-        <item name="background">@drawable/abc_ab_solid_dark_holo</item>
-        <item name="backgroundStacked">@drawable/abc_ab_stacked_solid_dark_holo</item>
-        <item name="backgroundSplit">@drawable/abc_ab_bottom_solid_dark_holo</item>
+    <style name="Base.Widget.AppCompat.ActionBar.Solid">
+        <item name="background">?attr/colorPrimary</item>
+        <item name="backgroundStacked">?attr/colorPrimary</item>
+        <item name="backgroundSplit">?attr/colorPrimary</item>
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.ActionBar.Solid"
-           parent="Widget.AppCompat.Light.Base.ActionBar">
-        <item name="background">@drawable/abc_ab_solid_light_holo</item>
-        <item name="backgroundStacked">@drawable/abc_ab_stacked_solid_light_holo</item>
-        <item name="backgroundSplit">@drawable/abc_ab_bottom_solid_light_holo</item>
+    <style name="Base.Widget.AppCompat.Light.ActionBar.Solid">
+        <item name="background">?attr/colorPrimary</item>
+        <item name="backgroundStacked">?attr/colorPrimary</item>
+        <item name="backgroundSplit">?attr/colorPrimary</item>
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.ActionBar.Solid.Inverse"
-           parent="Widget.AppCompat.Base.ActionBar.Solid">
-        <item name="titleTextStyle">
-            @style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse
-        </item>
-        <item name="subtitleTextStyle">
-            @style/TextAppearance.AppCompat.Widget.ActionBar.Subtitle.Inverse
-        </item>
-        <item name="actionButtonStyle">@style/Widget.AppCompat.ActionButton</item>
-        <item name="actionOverflowButtonStyle">@style/Widget.AppCompat.ActionButton.Overflow
-        </item>
-    </style>
-
-    <style name="Widget.AppCompat.Base.ActionButton" parent="">
+    <style name="Base.Widget.AppCompat.ActionButton" parent="">
         <item name="android:background">?attr/actionBarItemBackground</item>
         <item name="android:paddingLeft">12dip</item>
         <item name="android:paddingRight">12dip</item>
-        <item name="android:minWidth">@dimen/abc_action_button_min_width</item>
-        <item name="android:minHeight">?attr/actionBarSize</item>
+        <item name="android:minWidth">@dimen/abc_action_button_min_width_material</item>
+        <item name="android:minHeight">@dimen/abc_action_button_min_height_material</item>
+        <item name="android:scaleType">center</item>
         <item name="android:gravity">center</item>
         <item name="android:maxLines">2</item>
         <item name="textAllCaps">@bool/abc_config_actionMenuItemAllCaps</item>
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.ActionButton"
-           parent="Widget.AppCompat.Base.ActionButton">
+    <style name="Base.Widget.AppCompat.Light.ActionButton" parent="Base.Widget.AppCompat.ActionButton">
     </style>
 
-    <style name="Widget.AppCompat.Base.ActionButton.CloseMode"
-           parent="Widget.AppCompat.Base.ActionButton">
+    <style name="Base.Widget.AppCompat.ActionButton.CloseMode" parent="Base.Widget.AppCompat.ActionButton">
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.ActionButton.CloseMode"
-           parent="Widget.AppCompat.Light.Base.ActionButton">
+    <style name="Base.Widget.AppCompat.Light.ActionButton.CloseMode" parent="Base.Widget.AppCompat.Light.ActionButton">
     </style>
 
-    <style name="Widget.AppCompat.Base.ActionButton.Overflow"
-           parent="Widget.AppCompat.Base.ActionButton">
-        <item name="android:src">@drawable/abc_ic_menu_moreoverflow_normal_holo_dark</item>
+    <style name="Base.Widget.AppCompat.ActionButton.Overflow" parent="Base.Widget.AppCompat.ActionButton">
+        <item name="android:src">@drawable/abc_ic_menu_moreoverflow_material_dark</item>
+        <item name="android:minWidth">@dimen/abc_action_overflow_min_width_material</item>
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.ActionButton.Overflow"
-           parent="Widget.AppCompat.Light.Base.ActionButton">
-        <item name="android:src">@drawable/abc_ic_menu_moreoverflow_normal_holo_light</item>
+    <style name="Base.Widget.AppCompat.Light.ActionButton.Overflow" parent="Base.Widget.AppCompat.Light.ActionButton">
+        <item name="android:src">@drawable/abc_ic_menu_moreoverflow_material_light</item>
+        <item name="android:minWidth">@dimen/abc_action_overflow_min_width_material</item>
     </style>
 
-    <style name="Widget.AppCompat.Base.ActionBar.TabBar" parent="">
+    <style name="Base.Widget.AppCompat.ActionBar.TabBar" parent="">
         <item name="divider">?attr/actionBarDivider</item>
         <item name="showDividers">middle</item>
-        <item name="dividerPadding">12dip</item>
+        <item name="dividerPadding">8dip</item>
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.ActionBar.TabBar"
-           parent="Widget.AppCompat.Base.ActionBar.TabBar">
+    <style name="Base.Widget.AppCompat.Light.ActionBar.TabBar"
+           parent="Base.Widget.AppCompat.ActionBar.TabBar">
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.ActionBar.TabBar.Inverse"
-           parent="Widget.AppCompat.Light.Base.ActionBar.TabBar">
-    </style>
-
-    <style name="Widget.AppCompat.Base.ActionBar.TabView" parent="">
-        <item name="android:background">@drawable/abc_tab_indicator_ab_holo</item>
+    <style name="Base.Widget.AppCompat.ActionBar.TabView" parent="">
+        <item name="android:background">@drawable/abc_tab_indicator_material_dark</item>
         <item name="android:gravity">center_horizontal</item>
         <item name="android:paddingLeft">16dip</item>
         <item name="android:paddingRight">16dip</item>
+        <item name="android:layout_width">0dip</item>
+        <item name="android:layout_weight">1</item>
         <item name="android:minWidth">80dip</item>
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.ActionBar.TabView"
-           parent="Widget.AppCompat.Base.ActionBar.TabView">
+    <style name="Base.Widget.AppCompat.Light.ActionBar.TabView" parent="Base.Widget.AppCompat.ActionBar.TabView">
+        <item name="android:background">@drawable/abc_tab_indicator_material_light</item>
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.ActionBar.TabView.Inverse"
-           parent="Widget.AppCompat.Light.Base.ActionBar.TabView">
-    </style>
-
-    <style name="Widget.AppCompat.Base.ActionBar.TabText" parent="">
-        <item name="android:textAppearance">@null</item>
-        <item name="android:textColor">?android:attr/textColorPrimaryDisableOnly</item>
+    <style name="Base.Widget.AppCompat.ActionBar.TabText" parent="">
+        <item name="android:textAppearance">@style/TextAppearance.AppCompat.Medium</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:textSize">12sp</item>
         <item name="android:textStyle">bold</item>
         <item name="android:ellipsize">marquee</item>
         <item name="android:maxLines">2</item>
+        <item name="android:maxWidth">180dp</item>
         <item name="textAllCaps">true</item>
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.ActionBar.TabText"
-           parent="Widget.AppCompat.Base.ActionBar.TabText">
+    <style name="Base.Widget.AppCompat.Light.ActionBar.TabText"
+           parent="Base.Widget.AppCompat.ActionBar.TabText">
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.ActionBar.TabText.Inverse"
-           parent="Widget.AppCompat.Light.Base.ActionBar.TabText">
-        <item name="android:textColor">?android:attr/textColorPrimaryInverseDisableOnly</item>
+    <style name="Base.Widget.AppCompat.Light.ActionBar.TabText.Inverse"
+           parent="Base.Widget.AppCompat.Light.ActionBar.TabText">
+        <item name="android:textAppearance">@style/TextAppearance.AppCompat.Medium.Inverse</item>
     </style>
 
-    <style name="Widget.AppCompat.Base.ActionMode" parent="">
+    <style name="Base.Widget.AppCompat.ActionMode" parent="">
         <item name="background">?attr/actionModeBackground</item>
         <item name="backgroundSplit">?attr/actionModeSplitBackground</item>
         <item name="height">?attr/actionBarSize</item>
@@ -172,66 +146,54 @@
         </item>
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.ActionMode.Inverse"
-           parent="Widget.AppCompat.Base.ActionMode">
-        <item name="titleTextStyle">
-            @style/TextAppearance.AppCompat.Widget.ActionMode.Title.Inverse
-        </item>
-        <item name="subtitleTextStyle">
-            @style/TextAppearance.AppCompat.Widget.ActionMode.Subtitle.Inverse
-        </item>
+    <style name="Base.Widget.AppCompat.Light.ActionMode.Inverse" parent="Base.Widget.AppCompat.ActionMode">
+        <item name="titleTextStyle">@style/TextAppearance.AppCompat.Widget.ActionMode.Title.Inverse</item>
+        <item name="subtitleTextStyle">@style/TextAppearance.AppCompat.Widget.ActionMode.Subtitle.Inverse</item>
     </style>
 
-    <style name="TextAppearance.AppCompat.Widget.Base.ActionMode.Title"
-           parent="android:TextAppearance.Medium">
+    <style name="Base.TextAppearance.AppCompat.Widget.ActionMode.Title" parent="TextAppearance.AppCompat.Medium">
+        <item name="android:textSize">@dimen/abc_action_bar_title_text_size_material</item>
     </style>
 
-    <style name="TextAppearance.AppCompat.Widget.Base.ActionMode.Subtitle"
-           parent="android:TextAppearance.Small">
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+    <style name="Base.TextAppearance.AppCompat.Widget.ActionMode.Subtitle" parent="TextAppearance.AppCompat.Small">
+        <item name="android:textSize">@dimen/abc_action_bar_subtitle_text_size_material</item>
     </style>
 
-    <style name="TextAppearance.AppCompat.Widget.Base.ActionMode.Title.Inverse"
-           parent="android:TextAppearance.Medium.Inverse">
+    <style name="Base.TextAppearance.AppCompat.Widget.ActionMode.Title.Inverse" parent="TextAppearance.AppCompat.Medium.Inverse">
+        <item name="android:textSize">@dimen/abc_action_bar_title_text_size_material</item>
     </style>
 
-    <style name="TextAppearance.AppCompat.Widget.Base.ActionMode.Subtitle.Inverse"
-           parent="android:TextAppearance.Small.Inverse">
-        <item name="android:textColor">?android:attr/textColorSecondaryInverse</item>
+    <style name="Base.TextAppearance.AppCompat.Widget.ActionMode.Subtitle.Inverse" parent="TextAppearance.AppCompat.Small.Inverse">
+        <item name="android:textSize">@dimen/abc_action_bar_subtitle_text_size_material</item>
     </style>
 
-    <style name="TextAppearance.AppCompat.Widget.Base.ActionBar.Menu"
-           parent="android:TextAppearance.Small">
+    <style name="Base.TextAppearance.AppCompat.Widget.ActionBar.Menu" parent="android:TextAppearance.Small">
         <item name="android:textSize">12sp</item>
         <item name="android:textStyle">bold</item>
         <item name="android:textColor">?attr/actionMenuTextColor</item>
     </style>
 
-    <style name="TextAppearance.AppCompat.Widget.Base.ActionBar.Title"
-           parent="android:TextAppearance.Medium">
-        <item name="android:textSize">@dimen/abc_action_bar_title_text_size</item>
+    <style name="Base.TextAppearance.AppCompat.Widget.ActionBar.Title" parent="TextAppearance.AppCompat.Medium">
+        <item name="android:textSize">@dimen/abc_action_bar_title_text_size_material</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
 
-    <style name="TextAppearance.AppCompat.Widget.Base.ActionBar.Subtitle"
-           parent="android:TextAppearance.Small">
-        <item name="android:textSize">@dimen/abc_action_bar_subtitle_text_size</item>
+    <style name="Base.TextAppearance.AppCompat.Widget.ActionBar.Subtitle" parent="TextAppearance.AppCompat.Small">
+        <item name="android:textSize">@dimen/abc_action_bar_subtitle_text_size_material</item>
     </style>
 
-    <style name="TextAppearance.AppCompat.Widget.Base.ActionBar.Title.Inverse"
-           parent="android:TextAppearance.Medium.Inverse">
-        <item name="android:textSize">@dimen/abc_action_bar_title_text_size</item>
+    <style name="Base.TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse" parent="TextAppearance.AppCompat.Medium.Inverse">
+        <item name="android:textSize">@dimen/abc_action_bar_title_text_size_material</item>
     </style>
 
-    <style name="TextAppearance.AppCompat.Widget.Base.ActionBar.Subtitle.Inverse"
-           parent="android:TextAppearance.Small.Inverse">
-        <item name="android:textSize">@dimen/abc_action_bar_subtitle_text_size</item>
+    <style name="Base.TextAppearance.AppCompat.Widget.ActionBar.Subtitle.Inverse" parent="TextAppearance.AppCompat.Small.Inverse">
+        <item name="android:textSize">@dimen/abc_action_bar_subtitle_text_size_material</item>
     </style>
 
-    <style name="Widget.AppCompat.Base.ProgressBar.Horizontal"
-           parent="android:Widget.ProgressBar.Horizontal">
+    <style name="Base.Widget.AppCompat.ProgressBar.Horizontal" parent="android:Widget.ProgressBar.Horizontal">
     </style>
 
-    <style name="Widget.AppCompat.Base.ProgressBar" parent="android:Widget.ProgressBar">
+    <style name="Base.Widget.AppCompat.ProgressBar" parent="android:Widget.ProgressBar">
         <item name="android:minWidth">@dimen/abc_action_bar_progress_bar_size</item>
         <item name="android:maxWidth">@dimen/abc_action_bar_progress_bar_size</item>
         <item name="android:minHeight">@dimen/abc_action_bar_progress_bar_size</item>
@@ -240,155 +202,147 @@
 
     <!-- Action Bar Spinner Widgets -->
 
-    <style name="Widget.AppCompat.Base.Spinner" parent="">
+    <style name="Base.Widget.AppCompat.Spinner" parent="">
         <item name="spinnerMode">dropdown</item>
-        <item name="android:popupBackground">@drawable/abc_menu_dropdown_panel_holo_dark</item>
-        <item name="android:dropDownSelector">@drawable/abc_list_selector_holo_dark</item>
+
+        <item name="android:clickable">true</item>
+        <item name="android:background">@drawable/abc_spinner_background_material_dark</item>
+        <item name="android:dropDownSelector">?attr/listChoiceBackgroundIndicator</item>
+        <item name="android:popupBackground">@drawable/abc_popup_background_material_dark</item>
         <item name="android:dropDownVerticalOffset">0dip</item>
         <item name="android:dropDownHorizontalOffset">0dip</item>
         <item name="android:dropDownWidth">wrap_content</item>
-        <item name="android:gravity">left|center_vertical</item>
-        <item name="android:clickable">true</item>
-        <item name="android:background">@drawable/abc_spinner_ab_holo_dark</item>
+        <item name="popupPromptView">@layout/abc_simple_dropdown_hint</item>
+        <item name="android:gravity">left|start|center_vertical</item>
+        <item name="disableChildrenWhenDisabled">true</item>
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.Spinner" parent="Widget.AppCompat.Base.Spinner">
+    <style name="Base.Widget.AppCompat.Light.Spinner" parent="Base.Widget.AppCompat.Spinner">
         <item name="android:dropDownSelector">@drawable/abc_list_selector_holo_light</item>
-        <item name="android:popupBackground">@drawable/abc_menu_dropdown_panel_holo_light</item>
-        <item name="android:background">@drawable/abc_spinner_ab_holo_light</item>
+        <item name="android:popupBackground">@drawable/abc_popup_background_material_light</item>
+        <item name="android:background">@drawable/abc_spinner_background_material_light</item>
     </style>
 
-    <style name="Widget.AppCompat.Base.DropDownItem.Spinner" parent="">
-        <item name="android:textAppearance">@style/TextAppearance.AppCompat.Widget.DropDownItem
-        </item>
+    <style name="Base.Widget.AppCompat.DropDownItem.Spinner" parent="">
+        <item name="android:textAppearance">@style/TextAppearance.AppCompat.Widget.DropDownItem</item>
         <item name="android:paddingLeft">8dp</item>
         <item name="android:paddingRight">8dp</item>
         <item name="android:gravity">center_vertical</item>
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.DropDownItem.Spinner"
-           parent="Widget.AppCompat.Base.DropDownItem.Spinner">
+    <style name="Base.Widget.AppCompat.Light.DropDownItem.Spinner"
+           parent="Base.Widget.AppCompat.DropDownItem.Spinner">
     </style>
 
-    <style name="Widget.AppCompat.Base.ListView.DropDown" parent="android:Widget.ListView">
+    <style name="Base.Widget.AppCompat.ListView.DropDown" parent="android:Widget.ListView">
         <item name="android:listSelector">@drawable/abc_list_selector_holo_dark</item>
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.ListView.DropDown"
+    <style name="Base.Widget.AppCompat.Light.ListView.DropDown"
            parent="android:Widget.ListView">
         <item name="android:listSelector">@drawable/abc_list_selector_holo_light</item>
     </style>
 
-    <style name="TextAppearance.AppCompat.Widget.Base.DropDownItem"
+    <style name="Base.TextAppearance.AppCompat.Widget.DropDownItem"
            parent="android:TextAppearance.Small">
         <item name="android:textColor">?android:attr/textColorPrimaryDisableOnly</item>
     </style>
 
-    <style name="TextAppearance.Widget.AppCompat.Base.ExpandedMenu.Item"
+    <style name="Base.TextAppearance.Widget.AppCompat.ExpandedMenu.Item"
            parent="android:TextAppearance.Medium">
         <item name="android:textColor">?android:attr/textColorPrimaryDisableOnly</item>
     </style>
 
-    <!-- Mimic text appearance in select_dialog_item.xml -->
-    <style name="TextAppearance.AppCompat.Base.CompactMenu.Dialog"
-           parent="android:TextAppearance.Medium">
-        <item name="android:textColor">@android:color/primary_text_light</item>
-    </style>
-
-    <style name="Widget.AppCompat.Base.ListView.Menu" parent="android:Widget.ListView.Menu">
+    <style name="Base.Widget.AppCompat.ListView.Menu" parent="android:Widget.ListView.Menu">
         <item name="android:listSelector">?attr/listChoiceBackgroundIndicator</item>
         <item name="android:divider">?attr/dividerHorizontal</item>
     </style>
 
-    <style name="Widget.AppCompat.Base.ListPopupWindow" parent="">
+    <style name="Base.Widget.AppCompat.ListPopupWindow" parent="">
         <item name="android:dropDownSelector">@drawable/abc_list_selector_holo_dark</item>
-        <item name="android:popupBackground">@drawable/abc_menu_dropdown_panel_holo_dark</item>
+        <item name="android:popupBackground">@drawable/abc_popup_background_material_dark</item>
         <item name="android:dropDownVerticalOffset">0dip</item>
         <item name="android:dropDownHorizontalOffset">0dip</item>
         <item name="android:dropDownWidth">wrap_content</item>
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.ListPopupWindow" parent="">
+    <style name="Base.Widget.AppCompat.Light.ListPopupWindow" parent="">
         <item name="android:dropDownSelector">@drawable/abc_list_selector_holo_light</item>
-        <item name="android:popupBackground">@drawable/abc_menu_dropdown_panel_holo_light</item>
+        <item name="android:popupBackground">@drawable/abc_popup_background_material_light</item>
         <item name="android:dropDownVerticalOffset">0dip</item>
         <item name="android:dropDownHorizontalOffset">0dip</item>
         <item name="android:dropDownWidth">wrap_content</item>
     </style>
 
-    <style name="Widget.AppCompat.Base.PopupMenu"
-           parent="@style/Widget.AppCompat.Base.ListPopupWindow">
+    <style name="Base.Widget.AppCompat.PopupMenu.Overflow">
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.PopupMenu"
-           parent="@style/Widget.AppCompat.Light.Base.ListPopupWindow">
+    <style name="Base.Widget.AppCompat.Light.PopupMenu.Overflow">
     </style>
 
-    <style name="TextAppearance.AppCompat.Base.Widget.PopupMenu.Large"
-           parent="android:TextAppearance.Widget">
-        <item name="android:textColor">?android:attr/textColorPrimaryDisableOnly</item>
-        <item name="android:textSize">18sp</item>
+    <style name="Base.Widget.AppCompat.PopupMenu" parent="@style/Widget.AppCompat.ListPopupWindow">
     </style>
 
-    <style name="TextAppearance.AppCompat.Base.Widget.PopupMenu.Small"
-           parent="android:TextAppearance.Widget">
-        <item name="android:textColor">?android:attr/textColorPrimaryDisableOnly</item>
-        <item name="android:textSize">14sp</item>
+    <style name="Base.Widget.AppCompat.Light.PopupMenu" parent="@style/Widget.AppCompat.Light.ListPopupWindow">
     </style>
 
-    <style name="TextAppearance.AppCompat.Light.Base.Widget.PopupMenu.Large"
-           parent="TextAppearance.AppCompat.Base.Widget.PopupMenu.Large">
+    <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Large" parent="TextAppearance.AppCompat.Menu">
     </style>
 
-    <style name="TextAppearance.AppCompat.Light.Base.Widget.PopupMenu.Small"
-           parent="TextAppearance.AppCompat.Base.Widget.PopupMenu.Small">
+    <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Small" parent="TextAppearance.AppCompat.Menu">
     </style>
 
-    <style name="TextAppearance.AppCompat.Base.SearchResult" parent="">
+    <style name="Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Large" parent="TextAppearance.AppCompat.Menu">
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Small" parent="TextAppearance.AppCompat.Menu">
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.SearchResult" parent="">
         <item name="android:textStyle">normal</item>
         <item name="android:textColor">?android:textColorPrimary</item>
         <item name="android:textColorHint">?android:textColorHint</item>
     </style>
 
-    <style name="TextAppearance.AppCompat.Base.SearchResult.Title">
+    <style name="Base.TextAppearance.AppCompat.SearchResult.Title">
         <item name="android:textSize">18sp</item>
     </style>
 
-    <style name="TextAppearance.AppCompat.Base.SearchResult.Subtitle">
+    <style name="Base.TextAppearance.AppCompat.SearchResult.Subtitle">
         <item name="android:textSize">14sp</item>
         <item name="android:textColor">?android:textColorSecondary</item>
     </style>
 
-    <style name="TextAppearance.AppCompat.Light.Base.SearchResult"
-           parent="TextAppearance.AppCompat.Base.SearchResult">
+    <style name="Base.TextAppearance.AppCompat.Light.SearchResult"
+           parent="Base.TextAppearance.AppCompat.SearchResult">
         <item name="android:textColor">?android:textColorPrimary</item>
         <item name="android:textColorHint">?android:textColorHint</item>
     </style>
 
-    <style name="TextAppearance.AppCompat.Light.Base.SearchResult.Title">
+    <style name="Base.TextAppearance.AppCompat.Light.SearchResult.Title">
         <item name="android:textSize">18sp</item>
     </style>
 
-    <style name="TextAppearance.AppCompat.Light.Base.SearchResult.Subtitle">
+    <style name="Base.TextAppearance.AppCompat.Light.SearchResult.Subtitle">
         <item name="android:textSize">14sp</item>
         <item name="android:textColor">?android:textColorSecondary</item>
     </style>
 
-    <style name="Widget.AppCompat.Base.AutoCompleteTextView"
+    <style name="Base.Widget.AppCompat.AutoCompleteTextView"
            parent="android:Widget.AutoCompleteTextView">
         <item name="android:textColor">?attr/actionMenuTextColor</item>
         <item name="android:dropDownSelector">@drawable/abc_list_selector_holo_dark</item>
         <item name="android:popupBackground">@drawable/abc_menu_dropdown_panel_holo_dark</item>
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.AutoCompleteTextView"
+    <style name="Base.Widget.AppCompat.Light.AutoCompleteTextView"
            parent="android:Widget.AutoCompleteTextView">
         <item name="android:textColor">?attr/actionMenuTextColor</item>
         <item name="android:dropDownSelector">@drawable/abc_list_selector_holo_light</item>
         <item name="android:popupBackground">@drawable/abc_menu_dropdown_panel_holo_light</item>
     </style>
 
-    <style name="Widget.AppCompat.Base.ActivityChooserView" parent="">
+    <style name="Base.Widget.AppCompat.ActivityChooserView" parent="">
         <item name="android:gravity">center</item>
         <item name="android:background">@drawable/abc_ab_share_pack_holo_dark</item>
         <item name="divider">?attr/dividerVertical</item>
@@ -396,9 +350,67 @@
         <item name="dividerPadding">6dip</item>
     </style>
 
-    <style name="Widget.AppCompat.Light.Base.ActivityChooserView"
-           parent="Widget.AppCompat.Base.ActivityChooserView">
+    <style name="Base.Widget.AppCompat.Light.ActivityChooserView"
+           parent="Base.Widget.AppCompat.ActivityChooserView">
         <item name="android:background">@drawable/abc_ab_share_pack_holo_light</item>
     </style>
 
+    <style name="Base.Widget.AppCompat.PopupWindow" parent="android:Widget.PopupWindow">
+    </style>
+
+    <style name="Base.Widget.AppCompat.Toolbar" parent="android:Widget">
+        <item name="titleTextAppearance">@style/TextAppearance.Widget.AppCompat.Toolbar.Title</item>
+        <item name="subtitleTextAppearance">@style/TextAppearance.Widget.AppCompat.Toolbar.Subtitle</item>
+        <item name="android:minHeight">?attr/actionBarSize</item>
+        <item name="titleMargins">4dp</item>
+        <item name="maxButtonHeight">56dp</item>
+        <item name="buttonGravity">top</item>
+        <item name="collapseIcon">?attr/homeAsUpIndicator</item>
+        <item name="contentInsetStart">16dp</item>
+    </style>
+
+    <style name="Base.Widget.AppCompat.Toolbar.Button.Navigation" parent="android:Widget">
+        <item name="android:minWidth">56dp</item>
+        <item name="android:scaleType">center</item>
+        <item name="android:background">?attr/selectableItemBackground</item>
+    </style>
+
+    <style name="Base.TextAppearance.Widget.AppCompat.Toolbar.Title"
+           parent="TextAppearance.AppCompat.Widget.ActionBar.Title">
+    </style>
+
+    <style name="Base.TextAppearance.Widget.AppCompat.Toolbar.Subtitle"
+           parent="TextAppearance.AppCompat.Widget.ActionBar.Subtitle">
+    </style>
+
+    <style name="Base.Widget.AppCompat.SearchView" parent="android:Widget">
+        <item name="layout">@layout/abc_search_view</item>
+        <item name="queryBackground">@drawable/abc_textfield_search_material_dark</item>
+        <item name="submitBackground">@drawable/abc_textfield_search_material_dark</item>
+        <item name="closeIcon">@drawable/abc_ic_clear_material_dark</item>
+        <item name="searchIcon">@drawable/abc_ic_search_api_material_dark</item>
+        <item name="goIcon">@drawable/abc_ic_go_search_api_material_dark</item>
+        <item name="voiceIcon">@drawable/abc_ic_voice_search_api_material_dark</item>
+        <item name="commitIcon">@drawable/abc_ic_commit_search_api_material_dark</item>
+        <item name="suggestionRowLayout">@layout/abc_search_dropdown_item_icons_2line</item>
+    </style>
+
+    <style name="Base.Widget.AppCompat.Light.SearchView" parent="android:Widget">
+        <item name="layout">@layout/abc_search_view</item>
+        <item name="queryBackground">@drawable/abc_textfield_search_material_light</item>
+        <item name="submitBackground">@drawable/abc_textfield_search_material_light</item>
+        <item name="closeIcon">@drawable/abc_ic_clear_material_light</item>
+        <item name="searchIcon">@drawable/abc_ic_search_api_material_light</item>
+        <item name="goIcon">@drawable/abc_ic_go_search_api_material_light</item>
+        <item name="voiceIcon">@drawable/abc_ic_voice_search_api_material_light</item>
+        <item name="commitIcon">@drawable/abc_ic_commit_search_api_material_light</item>
+        <item name="suggestionRowLayout">@layout/abc_search_dropdown_item_icons_2line</item>
+    </style>
+
+    <style name="Base.Widget.AppCompat.EditText" parent="android:Widget.EditText">
+        <item name="android:background">?attr/editTextBackground</item>
+        <item name="android:textColor">?attr/editTextColor</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceMediumInverse</item>
+    </style>
+
 </resources>
diff --git a/v7/appcompat/res/values/styles_base_text.xml b/v7/appcompat/res/values/styles_base_text.xml
new file mode 100644
index 0000000..6409df6
--- /dev/null
+++ b/v7/appcompat/res/values/styles_base_text.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+
+    <style name="Base.TextAppearance.AppCompat" parent="android:TextAppearance">
+        <item name="android:textColor">?android:textColorPrimary</item>
+        <item name="android:textColorHint">?android:textColorHint</item>
+        <item name="android:textColorHighlight">?android:textColorHighlight</item>
+        <item name="android:textColorLink">?android:textColorLink</item>
+        <item name="android:textSize">@dimen/text_size_body_1_material</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Display4">
+        <item name="android:textSize">@dimen/text_size_display_4_material</item>
+        <item name="android:textColor">?android:textColorSecondary</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Display3">
+        <item name="android:textSize">@dimen/text_size_display_3_material</item>
+        <item name="android:textColor">?android:textColorSecondary</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Display2">
+        <item name="android:textSize">@dimen/text_size_display_2_material</item>
+        <item name="android:textColor">?android:textColorSecondary</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Display1">
+        <item name="android:textSize">@dimen/text_size_display_1_material</item>
+        <item name="android:textColor">?android:textColorSecondary</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Headline">
+        <item name="android:textSize">@dimen/text_size_headline_material</item>
+        <item name="android:textColor">?android:textColorPrimary</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Title">
+        <item name="android:textSize">@dimen/text_size_title_material</item>
+        <item name="android:textColor">?android:textColorPrimary</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Subhead">
+        <item name="android:textSize">@dimen/text_size_subhead_material</item>
+        <item name="android:textColor">?android:textColorPrimary</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Body2">
+        <item name="android:textSize">@dimen/text_size_body_2_material</item>
+        <item name="android:textColor">?android:textColorPrimary</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Body1">
+        <item name="android:textSize">@dimen/text_size_body_1_material</item>
+        <item name="android:textColor">?android:textColorPrimary</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Caption">
+        <item name="android:textSize">@dimen/text_size_caption_material</item>
+        <item name="android:textColor">?android:textColorSecondary</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Menu">
+        <item name="android:textSize">@dimen/text_size_menu_material</item>
+        <item name="android:textColor">?android:textColorPrimary</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Button">
+        <item name="android:textSize">@dimen/text_size_button_material</item>
+        <item name="textAllCaps">true</item>
+        <item name="android:textColor">?android:textColorPrimary</item>
+    </style>
+
+    <!-- Deprecated text styles -->
+
+    <style name="Base.TextAppearance.AppCompat.Inverse">
+        <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+        <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Large">
+        <item name="android:textSize">@dimen/text_size_large_material</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Large.Inverse">
+        <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
+        <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Medium">
+        <item name="android:textSize">@dimen/text_size_medium_material</item>
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Medium.Inverse">
+        <item name="android:textColor">?android:attr/textColorSecondaryInverse</item>
+        <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Small">
+        <item name="android:textSize">@dimen/text_size_small_material</item>
+        <item name="android:textColor">?android:attr/textColorTertiary</item>
+    </style>
+
+    <style name="Base.TextAppearance.AppCompat.Small.Inverse">
+        <item name="android:textColor">?android:attr/textColorTertiaryInverse</item>
+        <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values/themes.xml b/v7/appcompat/res/values/themes.xml
index f761155..a0848b7 100644
--- a/v7/appcompat/res/values/themes.xml
+++ b/v7/appcompat/res/values/themes.xml
@@ -26,175 +26,64 @@
          "Theme.Base" theme. -->
 
     <!-- Platform-independent theme providing an action bar in a dark-themed activity. -->
-    <style name="Theme.AppCompat" parent="Theme.Base.AppCompat">
-        <item name="isLightTheme">false</item>
-
-        <!-- Required for use of support_simple_spinner_dropdown_item.xml -->
-        <item name="spinnerDropDownItemStyle">
-            @style/Widget.AppCompat.DropDownItem.Spinner
-        </item>
-        <item name="dropdownListPreferredItemHeight">?attr/listPreferredItemHeightSmall</item>
-        <item name="searchResultListItemHeight">58dip</item>
-
-        <!-- Popup Menu styles -->
-        <item name="popupMenuStyle">@style/Widget.AppCompat.PopupMenu</item>
-        <item name="textAppearanceLargePopupMenu">
-            @style/TextAppearance.AppCompat.Widget.PopupMenu.Large
-        </item>
-        <item name="textAppearanceSmallPopupMenu">
-            @style/TextAppearance.AppCompat.Widget.PopupMenu.Small
-        </item>
-
-        <item name="listPopupWindowStyle">@style/Widget.AppCompat.ListPopupWindow</item>
-        <item name="dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
-
-        <!-- SearchView attributes -->
-        <item name="searchDropdownBackground">@drawable/abc_search_dropdown_dark</item>
-        <item name="searchViewTextField">@drawable/abc_textfield_searchview_holo_dark</item>
-        <item name="searchViewTextFieldRight">@drawable/abc_textfield_searchview_right_holo_dark
-        </item>
-        <item name="searchViewCloseIcon">@drawable/abc_ic_clear</item>
-        <item name="searchViewSearchIcon">@drawable/abc_ic_search</item>
-        <item name="searchViewGoIcon">@drawable/abc_ic_go</item>
-        <item name="searchViewVoiceIcon">@drawable/abc_ic_voice_search</item>
-        <item name="searchViewEditQuery">@drawable/abc_ic_commit_search_api_holo_dark</item>
-        <item name="searchViewEditQueryBackground">?attr/selectableItemBackground</item>
-        <item name="searchViewAutoCompleteTextView">
-            @style/Widget.AppCompat.AutoCompleteTextView</item>
-        <item name="textColorSearchUrl">@color/abc_search_url_text_holo</item>
-        <item name="textAppearanceSearchResultTitle">
-            @style/TextAppearance.AppCompat.SearchResult.Title
-        </item>
-        <item name="textAppearanceSearchResultSubtitle">
-            @style/TextAppearance.AppCompat.SearchResult.Subtitle
-        </item>
-
-        <item name="actionModeShareDrawable">@drawable/abc_ic_menu_share_holo_dark</item>
-
-        <!-- ShareActionProvider attributes -->
-        <item name="activityChooserViewStyle">@style/Widget.AppCompat.ActivityChooserView</item>
-
+    <style name="Theme.AppCompat"
+           parent="Base.Theme.AppCompat">
     </style>
 
     <!-- Platform-independent theme providing an action bar in a light-themed activity. -->
-    <style name="Theme.AppCompat.Light" parent="Theme.Base.AppCompat.Light">
-        <item name="isLightTheme">true</item>
-
-        <!-- Required for use of support_simple_spinner_dropdown_item.xml -->
-        <item name="spinnerDropDownItemStyle">
-            @style/Widget.AppCompat.Light.DropDownItem.Spinner
-        </item>
-        <item name="dropdownListPreferredItemHeight">?attr/listPreferredItemHeightSmall</item>
-        <item name="searchResultListItemHeight">58dip</item>
-
-        <!-- Popup Menu styles -->
-        <item name="popupMenuStyle">@style/Widget.AppCompat.Light.PopupMenu</item>
-        <item name="textAppearanceLargePopupMenu">
-            @style/TextAppearance.AppCompat.Light.Widget.PopupMenu.Large
-        </item>
-        <item name="textAppearanceSmallPopupMenu">
-            @style/TextAppearance.AppCompat.Light.Widget.PopupMenu.Small
-        </item>
-
-        <item name="listPopupWindowStyle">@style/Widget.AppCompat.Light.ListPopupWindow</item>
-        <item name="dropDownListViewStyle">@style/Widget.AppCompat.Light.ListView.DropDown</item>
-
-        <!-- SearchView attributes -->
-        <item name="searchDropdownBackground">@drawable/abc_search_dropdown_light</item>
-        <item name="searchViewTextField">@drawable/abc_textfield_searchview_holo_light</item>
-        <item name="searchViewTextFieldRight">@drawable/abc_textfield_searchview_right_holo_light
-        </item>
-        <item name="searchViewCloseIcon">@drawable/abc_ic_clear_holo_light</item>
-        <item name="searchViewSearchIcon">@drawable/abc_ic_search_api_holo_light</item>
-        <item name="searchViewGoIcon">@drawable/abc_ic_go_search_api_holo_light</item>
-        <item name="searchViewVoiceIcon">@drawable/abc_ic_voice_search_api_holo_light</item>
-        <item name="searchViewEditQuery">@drawable/abc_ic_commit_search_api_holo_light</item>
-        <item name="searchViewEditQueryBackground">?attr/selectableItemBackground</item>
-        <item name="searchViewAutoCompleteTextView">
-            @style/Widget.AppCompat.Light.AutoCompleteTextView</item>
-        <item name="textColorSearchUrl">@color/abc_search_url_text_holo</item>
-        <item name="textAppearanceSearchResultTitle">
-            @style/TextAppearance.AppCompat.Light.SearchResult.Title
-        </item>
-        <item name="textAppearanceSearchResultSubtitle">
-            @style/TextAppearance.AppCompat.Light.SearchResult.Subtitle
-        </item>
-
-        <item name="actionModeShareDrawable">@drawable/abc_ic_menu_share_holo_light</item>
-
-        <!-- ShareActionProvider attributes -->
-        <item name="activityChooserViewStyle">@style/Widget.AppCompat.Light.ActivityChooserView
-        </item>
-
+    <style name="Theme.AppCompat.Light"
+           parent="Base.Theme.AppCompat.Light">
     </style>
 
     <!-- Platform-independent theme providing an action bar in a dark-themed activity. -->
     <style name="Theme.AppCompat.Light.DarkActionBar"
-           parent="Theme.Base.AppCompat.Light.DarkActionBar">
-        <item name="isLightTheme">true</item>
+           parent="Base.Theme.AppCompat.Light.DarkActionBar">
+    </style>
 
-        <!-- Required for use of support_simple_spinner_dropdown_item.xml -->
-        <item name="spinnerDropDownItemStyle">
-            @style/Widget.AppCompat.Light.DropDownItem.Spinner
-        </item>
-        <item name="dropdownListPreferredItemHeight">?attr/listPreferredItemHeightSmall</item>
-        <item name="searchResultListItemHeight">58dip</item>
+    <style name="Theme.AppCompat.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
+    </style>
 
-        <!-- Popup Menu styles -->
-        <item name="popupMenuStyle">@style/Widget.AppCompat.Light.PopupMenu</item>
-        <item name="textAppearanceLargePopupMenu">
-            @style/TextAppearance.AppCompat.Light.Widget.PopupMenu.Large
-        </item>
-        <item name="textAppearanceSmallPopupMenu">
-            @style/TextAppearance.AppCompat.Light.Widget.PopupMenu.Small
-        </item>
-
-        <item name="listPopupWindowStyle">@style/Widget.AppCompat.ListPopupWindow</item>
-        <item name="dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
-
-        <!-- SearchView attributes -->
-        <item name="searchDropdownBackground">@drawable/abc_search_dropdown_dark</item>
-        <item name="searchViewTextField">@drawable/abc_textfield_searchview_holo_dark</item>
-        <item name="searchViewTextFieldRight">@drawable/abc_textfield_searchview_right_holo_dark
-        </item>
-        <item name="searchViewCloseIcon">@drawable/abc_ic_clear</item>
-        <item name="searchViewSearchIcon">@drawable/abc_ic_search</item>
-        <item name="searchViewGoIcon">@drawable/abc_ic_go</item>
-        <item name="searchViewVoiceIcon">@drawable/abc_ic_voice_search</item>
-        <item name="searchViewEditQuery">@drawable/abc_ic_commit_search_api_holo_dark</item>
-        <item name="searchViewEditQueryBackground">?attr/selectableItemBackground</item>
-        <item name="searchViewAutoCompleteTextView">
-            @style/Widget.AppCompat.AutoCompleteTextView</item>
-        <item name="textColorSearchUrl">@color/abc_search_url_text_holo</item>
-        <item name="textAppearanceSearchResultTitle">
-            @style/TextAppearance.AppCompat.SearchResult.Title
-        </item>
-        <item name="textAppearanceSearchResultSubtitle">
-            @style/TextAppearance.AppCompat.SearchResult.Subtitle
-        </item>
-
-        <item name="actionModeShareDrawable">@drawable/abc_ic_menu_share_holo_dark</item>
-
-        <!-- ShareActionProvider attributes -->
-        <item name="activityChooserViewStyle">@style/Widget.AppCompat.ActivityChooserView
-        </item>
-
+    <style name="Theme.AppCompat.Light.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
     </style>
 
     <style name="Theme.AppCompat.DialogWhenLarge"
-           parent="Theme.Base.AppCompat.DialogWhenLarge">
+           parent="Base.Theme.AppCompat.DialogWhenLarge">
     </style>
 
     <style name="Theme.AppCompat.Light.DialogWhenLarge"
-           parent="Theme.Base.AppCompat.Light.DialogWhenLarge">
+           parent="Base.Theme.AppCompat.Light.DialogWhenLarge">
     </style>
 
+    <style name="Theme.AppCompat.Dialog" parent="Base.Theme.AppCompat.Dialog" />
+
+    <style name="Theme.AppCompat.Light.Dialog" parent="Base.Theme.AppCompat.Light.Dialog" />
+
     <!-- Menu/item attributes -->
-    <style name="Theme.AppCompat.CompactMenu" parent="Theme.AppCompat.Base.CompactMenu">
+    <style name="Theme.AppCompat.CompactMenu"
+           parent="Base.Theme.AppCompat.CompactMenu">
     </style>
 
-    <style name="Theme.AppCompat.CompactMenu.Dialog"
-           parent="Theme.AppCompat.Base.CompactMenu.Dialog">
-    </style>
+    <style name="ThemeOverlay.AppCompat" parent="Base.ThemeOverlay.AppCompat" />
+
+    <!-- Theme overlay that replaces colors with their light versions but preserves
+         the value of colorAccent, colorPrimary and its variants. -->
+    <style name="ThemeOverlay.AppCompat.Light" parent="Base.ThemeOverlay.AppCompat.Light" />
+
+    <!-- Theme overlay that replaces colors with their dark versions but preserves
+         the value of colorAccent, colorPrimary and its variants. -->
+    <style name="ThemeOverlay.AppCompat.Dark" parent="Base.ThemeOverlay.AppCompat.Dark" />
+
+    <!-- Theme overlay that replaces the normal control color, which by default is the same as the
+         secondary text color, with the primary text color. -->
+    <style name="ThemeOverlay.AppCompat.ActionBar" parent="Base.ThemeOverlay.AppCompat.ActionBar" />
+
+    <!-- Theme overlay that replaces colors with their dark versions and replaces the normal
+         control color, which by default is the same as the secondary text color, with the primary
+         text color. -->
+    <style name="ThemeOverlay.AppCompat.Dark.ActionBar" parent="Base.ThemeOverlay.AppCompat.Dark.ActionBar" />
 
 </resources>
diff --git a/v7/appcompat/res/values/themes_base.xml b/v7/appcompat/res/values/themes_base.xml
index c48b57c..66842bf 100644
--- a/v7/appcompat/res/values/themes_base.xml
+++ b/v7/appcompat/res/values/themes_base.xml
@@ -16,7 +16,124 @@
 
 <resources>
 
-    <!-- Themes in the "Theme.Base" family vary based on the current platform
+    <!--
+        Theme in the "Platform.AppCompat" family are designed to be aliases for the default
+        theme on a given platform version and should set up the default theme ready for adding our
+        unbundled Action Bar.
+    -->
+    <eat-comment/>
+    <style name="Platform.AppCompat" parent="android:Theme">
+        <item name="android:windowNoTitle">true</item>
+
+        <!-- Window colors -->
+        <item name="android:colorForeground">@color/bright_foreground_material_dark</item>
+        <item name="android:colorForegroundInverse">@color/bright_foreground_material_light</item>
+        <item name="android:colorBackground">@color/background_material_dark</item>
+        <item name="android:colorBackgroundCacheHint">@color/abc_background_cache_hint_selector_material_dark</item>
+        <item name="android:disabledAlpha">0.5</item>
+        <item name="android:backgroundDimAmount">0.6</item>
+        <item name="android:windowBackground">@color/background_material_dark</item>
+
+        <!-- Text colors -->
+        <item name="android:textColorPrimary">@color/abc_primary_text_material_dark</item>
+        <item name="android:textColorPrimaryInverse">@color/abc_primary_text_material_light</item>
+        <item name="android:textColorPrimaryDisableOnly">@color/abc_primary_text_disable_only_material_dark</item>
+        <item name="android:textColorSecondary">@color/secondary_text_material_dark</item>
+        <item name="android:textColorSecondaryInverse">@color/secondary_text_material_light</item>
+        <item name="android:textColorTertiary">@color/tertiary_text_material_dark</item>
+        <item name="android:textColorTertiaryInverse">@color/tertiary_text_material_light</item>
+        <item name="android:textColorHint">@color/hint_foreground_material_dark</item>
+        <item name="android:textColorHintInverse">@color/hint_foreground_material_light</item>
+        <item name="android:textColorHighlight">@color/highlighted_text_material_dark</item>
+        <item name="android:textColorLink">@color/material_teal_500</item>
+
+        <!-- Text styles -->
+        <item name="android:textAppearance">@style/TextAppearance.AppCompat</item>
+        <item name="android:textAppearanceInverse">@style/TextAppearance.AppCompat.Inverse</item>
+        <item name="android:textAppearanceLarge">@style/TextAppearance.AppCompat.Large</item>
+        <item name="android:textAppearanceLargeInverse">@style/TextAppearance.AppCompat.Large.Inverse</item>
+        <item name="android:textAppearanceMedium">@style/TextAppearance.AppCompat.Medium</item>
+        <item name="android:textAppearanceMediumInverse">@style/TextAppearance.AppCompat.Medium.Inverse</item>
+        <item name="android:textAppearanceSmall">@style/TextAppearance.AppCompat.Small</item>
+        <item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
+    </style>
+
+    <style name="Platform.AppCompat.Light" parent="android:Theme.Light">
+        <item name="android:windowNoTitle">true</item>
+
+        <!-- Window colors -->
+        <item name="android:colorForeground">@color/bright_foreground_material_light</item>
+        <item name="android:colorForegroundInverse">@color/bright_foreground_material_dark</item>
+        <item name="android:colorBackground">@color/background_material_light</item>
+        <item name="android:colorBackgroundCacheHint">@color/abc_background_cache_hint_selector_material_light</item>
+        <item name="android:disabledAlpha">0.5</item>
+        <item name="android:backgroundDimAmount">0.6</item>
+        <item name="android:windowBackground">@color/background_material_light</item>
+
+        <!-- Text colors -->
+        <item name="android:textColorPrimary">@color/abc_primary_text_material_light</item>
+        <item name="android:textColorPrimaryInverse">@color/abc_primary_text_material_dark</item>
+        <item name="android:textColorSecondary">@color/secondary_text_material_light</item>
+        <item name="android:textColorSecondaryInverse">@color/secondary_text_material_dark</item>
+        <item name="android:textColorTertiary">@color/tertiary_text_material_light</item>
+        <item name="android:textColorTertiaryInverse">@color/tertiary_text_material_dark</item>
+        <item name="android:textColorPrimaryDisableOnly">@color/abc_primary_text_disable_only_material_light</item>
+        <item name="android:textColorPrimaryInverseDisableOnly">@color/abc_primary_text_disable_only_material_dark</item>
+        <item name="android:textColorHint">@color/hint_foreground_material_light</item>
+        <item name="android:textColorHintInverse">@color/hint_foreground_material_dark</item>
+        <item name="android:textColorHighlight">@color/highlighted_text_material_light</item>
+        <item name="android:textColorLink">@color/material_teal_500</item>
+
+        <!-- Text styles -->
+        <item name="android:textAppearance">@style/TextAppearance.AppCompat</item>
+        <item name="android:textAppearanceInverse">@style/TextAppearance.AppCompat.Inverse</item>
+        <item name="android:textAppearanceLarge">@style/TextAppearance.AppCompat.Large</item>
+        <item name="android:textAppearanceLargeInverse">@style/TextAppearance.AppCompat.Large.Inverse</item>
+        <item name="android:textAppearanceMedium">@style/TextAppearance.AppCompat.Medium</item>
+        <item name="android:textAppearanceMediumInverse">@style/TextAppearance.AppCompat.Medium.Inverse</item>
+        <item name="android:textAppearanceSmall">@style/TextAppearance.AppCompat.Small</item>
+        <item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
+    </style>
+
+    <style name="Platform.AppCompat.Dialog" parent="android:Theme.Dialog">
+        <item name="android:windowNoTitle">true</item>
+
+        <!-- Window colors -->
+        <item name="android:colorForeground">@color/bright_foreground_material_dark</item>
+        <item name="android:colorForegroundInverse">@color/bright_foreground_material_light</item>
+        <item name="android:colorBackground">@color/background_material_dark</item>
+        <item name="android:colorBackgroundCacheHint">@color/abc_background_cache_hint_selector_material_dark</item>
+        <item name="android:disabledAlpha">0.5</item>
+        <item name="android:backgroundDimAmount">0.6</item>
+        <item name="android:windowBackground">@color/background_material_dark</item>
+
+        <!-- Text colors -->
+        <item name="android:textColorPrimary">@color/abc_primary_text_material_dark</item>
+        <item name="android:textColorPrimaryInverse">@color/abc_primary_text_material_light</item>
+        <item name="android:textColorPrimaryDisableOnly">@color/abc_primary_text_disable_only_material_dark</item>
+        <item name="android:textColorSecondary">@color/secondary_text_material_dark</item>
+        <item name="android:textColorSecondaryInverse">@color/secondary_text_material_light</item>
+        <item name="android:textColorTertiary">@color/tertiary_text_material_dark</item>
+        <item name="android:textColorTertiaryInverse">@color/tertiary_text_material_light</item>
+        <item name="android:textColorHint">@color/hint_foreground_material_dark</item>
+        <item name="android:textColorHintInverse">@color/hint_foreground_material_light</item>
+        <item name="android:textColorHighlight">@color/highlighted_text_material_dark</item>
+        <item name="android:textColorLink">@color/material_teal_500</item>
+
+        <!-- Text styles -->
+        <item name="android:textAppearance">@style/TextAppearance.AppCompat</item>
+        <item name="android:textAppearanceInverse">@style/TextAppearance.AppCompat.Inverse</item>
+        <item name="android:textAppearanceLarge">@style/TextAppearance.AppCompat.Large</item>
+        <item name="android:textAppearanceLargeInverse">@style/TextAppearance.AppCompat.Large.Inverse</item>
+        <item name="android:textAppearanceMedium">@style/TextAppearance.AppCompat.Medium</item>
+        <item name="android:textAppearanceMediumInverse">@style/TextAppearance.AppCompat.Medium.Inverse</item>
+        <item name="android:textAppearanceSmall">@style/TextAppearance.AppCompat.Small</item>
+        <item name="android:textAppearanceSmallInverse">@style/TextAppearance.AppCompat.Small.Inverse</item>
+    </style>
+
+    <style name="Platform.AppCompat.Light.Dialog" parent="Platform.AppCompat.Dialog" />
+
+    <!-- Themes in the "Base.Theme" family vary based on the current platform
          version to provide the correct basis on each device. You probably don't
          want to use them directly in your apps.
 
@@ -24,49 +141,36 @@
          directly by apps. -->
     <eat-comment/>
 
-    <style name="Theme.Base" parent="android:Theme">
-    </style>
-
-    <style name="Theme.Base.Light" parent="android:Theme.Light">
-    </style>
-
     <!-- Base platform-dependent theme providing an action bar in a dark-themed activity. -->
-    <style name="Theme.Base.AppCompat" parent="Theme.Base">
+    <style name="Base.V7.Theme.AppCompat" parent="Platform.AppCompat">
         <item name="windowActionBar">true</item>
-        <!-- Remove system title bars; we will add the action bar ourselves. -->
-        <item name="android:windowNoTitle">true</item>
+        <item name="windowActionBarOverlay">false</item>
+        <item name="isLightTheme">false</item>
 
         <item name="buttonBarStyle">@android:style/ButtonBar</item>
         <item name="buttonBarButtonStyle">@android:style/Widget.Button</item>
         <item name="selectableItemBackground">@drawable/abc_item_background_holo_dark</item>
-        <item name="homeAsUpIndicator">@drawable/abc_ic_ab_back_holo_dark</item>
+        <item name="selectableItemBackgroundBorderless">?attr/selectableItemBackground</item>
+        <item name="homeAsUpIndicator">@drawable/abc_ic_ab_back_material_dark</item>
 
         <item name="dividerVertical">@drawable/abc_list_divider_holo_dark</item>
         <item name="dividerHorizontal">@drawable/abc_list_divider_holo_dark</item>
 
-        <item name="listPreferredItemHeight">?android:attr/listPreferredItemHeight</item>
-        <item name="listPreferredItemHeightSmall">48dp</item>
-        <item name="listPreferredItemHeightLarge">80dp</item>
-        <item name="listPreferredItemPaddingLeft">8dip</item>
-        <item name="listPreferredItemPaddingRight">8dip</item>
-
-        <item name="textAppearanceListItem">?android:attr/textAppearanceLarge</item>
-        <item name="textAppearanceListItemSmall">?android:attr/textAppearanceLarge</item>
-
+        <!-- Action Bar Styles -->
         <item name="actionBarTabStyle">@style/Widget.AppCompat.ActionBar.TabView</item>
         <item name="actionBarTabBarStyle">@style/Widget.AppCompat.ActionBar.TabBar</item>
         <item name="actionBarTabTextStyle">@style/Widget.AppCompat.ActionBar.TabText</item>
         <item name="actionButtonStyle">@style/Widget.AppCompat.ActionButton</item>
         <item name="actionOverflowButtonStyle">@style/Widget.AppCompat.ActionButton.Overflow</item>
-        <item name="actionBarStyle">@style/Widget.AppCompat.ActionBar</item>
+        <item name="actionOverflowMenuStyle">@style/Widget.AppCompat.PopupMenu.Overflow</item>
+        <item name="actionBarStyle">@style/Widget.AppCompat.ActionBar.Solid</item>
         <item name="actionBarSplitStyle">?attr/actionBarStyle</item>
         <item name="actionBarWidgetTheme">@null</item>
-        <item name="actionBarSize">@dimen/abc_action_bar_default_height</item>
+        <item name="actionBarTheme">@style/ThemeOverlay.AppCompat.ActionBar</item>
+        <item name="actionBarSize">@dimen/abc_action_bar_default_height_material</item>
         <item name="actionBarDivider">?attr/dividerVertical</item>
-        <item name="actionBarItemBackground">?attr/selectableItemBackground</item>
-        <item name="actionMenuTextAppearance">
-            @style/TextAppearance.AppCompat.Widget.ActionBar.Menu
-        </item>
+        <item name="actionBarItemBackground">?attr/selectableItemBackgroundBorderless</item>
+        <item name="actionMenuTextAppearance">@style/TextAppearance.AppCompat.Widget.ActionBar.Menu</item>
         <item name="actionMenuTextColor">?android:attr/textColorPrimaryDisableOnly</item>
 
         <!-- Dropdown Spinner Attributes -->
@@ -74,11 +178,10 @@
 
         <!-- Action Mode -->
         <item name="actionModeStyle">@style/Widget.AppCompat.ActionMode</item>
-        <item name="actionModeBackground">@drawable/abc_cab_background_top_holo_dark</item>
-        <item name="actionModeSplitBackground">@drawable/abc_cab_background_bottom_holo_dark</item>
+        <item name="actionModeBackground">?attr/colorPrimaryDark</item>
+        <item name="actionModeSplitBackground">?attr/colorPrimaryDark</item>
         <item name="actionModeCloseDrawable">@drawable/abc_ic_cab_done_holo_dark</item>
-        <item name="actionModeCloseButtonStyle">@style/Widget.AppCompat.ActionButton.CloseMode
-        </item>
+        <item name="actionModeCloseButtonStyle">@style/Widget.AppCompat.ActionButton.CloseMode</item>
 
         <!-- Panel attributes -->
         <item name="panelMenuListWidth">@dimen/abc_panel_menu_list_width</item>
@@ -86,62 +189,99 @@
         <item name="android:panelBackground">@drawable/abc_menu_hardkey_panel_holo_dark</item>
         <item name="listChoiceBackgroundIndicator">@drawable/abc_list_selector_holo_dark</item>
 
+        <!-- List attributes -->
+        <item name="textAppearanceListItem">@style/TextAppearance.AppCompat.Subhead</item>
+        <item name="textAppearanceListItemSmall">@style/TextAppearance.AppCompat.Subhead</item>
+        <item name="listPreferredItemHeight">64dp</item>
+        <item name="listPreferredItemHeightSmall">48dp</item>
+        <item name="listPreferredItemHeightLarge">80dp</item>
+        <item name="listPreferredItemPaddingLeft">16dip</item>
+        <item name="listPreferredItemPaddingRight">16dip</item>
+
+        <!-- Required for use of support_simple_spinner_dropdown_item.xml -->
+        <item name="spinnerDropDownItemStyle">@style/Widget.AppCompat.DropDownItem.Spinner</item>
+        <item name="dropdownListPreferredItemHeight">?attr/listPreferredItemHeightSmall</item>
+
+        <!-- Popup Menu styles -->
+        <item name="popupMenuStyle">@style/Widget.AppCompat.PopupMenu</item>
+        <item name="textAppearanceLargePopupMenu">@style/TextAppearance.AppCompat.Widget.PopupMenu.Large</item>
+        <item name="textAppearanceSmallPopupMenu">@style/TextAppearance.AppCompat.Widget.PopupMenu.Small</item>
+        <item name="listPopupWindowStyle">@style/Widget.AppCompat.ListPopupWindow</item>
+        <item name="dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
+
+        <!-- SearchView attributes -->
+        <item name="searchViewStyle">@style/Widget.AppCompat.SearchView</item>
+        <item name="android:dropDownItemStyle">@style/Widget.AppCompat.DropDownItem.Spinner</item>
+        <item name="textColorSearchUrl">@color/abc_search_url_text</item>
+        <item name="textAppearanceSearchResultTitle">@style/TextAppearance.AppCompat.SearchResult.Title</item>
+        <item name="textAppearanceSearchResultSubtitle">@style/TextAppearance.AppCompat.SearchResult.Subtitle</item>
+
+        <item name="actionModeShareDrawable">@drawable/abc_ic_menu_share_material_dark</item>
+
+        <!-- ShareActionProvider attributes -->
+        <item name="activityChooserViewStyle">@style/Widget.AppCompat.ActivityChooserView</item>
+
+        <!-- Toolbar styles -->
+        <item name="toolbarStyle">@style/Widget.AppCompat.Toolbar</item>
+        <item name="toolbarNavigationButtonStyle">@style/Widget.AppCompat.Toolbar.Button.Navigation</item>
+
+        <item name="android:editTextStyle">@style/Widget.AppCompat.EditText</item>
+        <item name="editTextBackground">@drawable/abc_edit_text_material_dark</item>
+        <item name="editTextColor">?android:attr/textColorPrimary</item>
+
+        <!-- Color palette -->
+        <item name="colorPrimaryDark">@color/material_blue_grey_900</item>
+        <item name="colorPrimary">@color/material_blue_grey_800</item>
+        <item name="colorAccent">@color/material_light_blue_A200</item>
+
+        <item name="colorControlNormal">?android:attr/textColorSecondary</item>
+        <item name="colorControlActivated">?attr/colorAccent</item>
+
+        <item name="colorControlHighlight">@color/ripple_material_dark</item>
+        <!-- TODO: <item name="colorButtonNormal">@color/btn_default_material_dark</item>-->
     </style>
 
     <!-- Base platform-dependent theme providing an action bar in a light-themed activity. -->
-    <style name="Theme.Base.AppCompat.Light" parent="Theme.Base.Light">
+    <style name="Base.V7.Theme.AppCompat.Light" parent="Platform.AppCompat.Light">
         <item name="windowActionBar">true</item>
-        <!-- Remove system title bars; we will add the action bar ourselves. -->
-        <item name="android:windowNoTitle">true</item>
+        <item name="windowActionBarOverlay">false</item>
+        <item name="isLightTheme">true</item>
 
         <item name="buttonBarStyle">@android:style/ButtonBar</item>
         <item name="buttonBarButtonStyle">@android:style/Widget.Button</item>
         <item name="selectableItemBackground">@drawable/abc_item_background_holo_light</item>
-        <item name="homeAsUpIndicator">@drawable/abc_ic_ab_back_holo_light</item>
+        <item name="selectableItemBackgroundBorderless">?attr/selectableItemBackground</item>
+        <item name="homeAsUpIndicator">@drawable/abc_ic_ab_back_material_light</item>
 
         <item name="dividerVertical">@drawable/abc_list_divider_holo_light</item>
         <item name="dividerHorizontal">@drawable/abc_list_divider_holo_light</item>
 
-        <item name="listPreferredItemHeight">?android:attr/listPreferredItemHeight</item>
-        <item name="listPreferredItemHeightSmall">48dp</item>
-        <item name="listPreferredItemHeightLarge">80dp</item>
-        <item name="listPreferredItemPaddingLeft">8dip</item>
-        <item name="listPreferredItemPaddingRight">8dip</item>
-
-        <item name="textAppearanceListItem">?android:attr/textAppearanceLarge</item>
-        <item name="textAppearanceListItemSmall">?android:attr/textAppearanceLarge</item>
-
         <!-- Action Bar Styles -->
         <item name="actionBarTabStyle">@style/Widget.AppCompat.Light.ActionBar.TabView</item>
         <item name="actionBarTabBarStyle">@style/Widget.AppCompat.Light.ActionBar.TabBar</item>
         <item name="actionBarTabTextStyle">@style/Widget.AppCompat.Light.ActionBar.TabText</item>
         <item name="actionButtonStyle">@style/Widget.AppCompat.Light.ActionButton</item>
-        <item name="actionOverflowButtonStyle">
-            @style/Widget.AppCompat.Light.ActionButton.Overflow
-        </item>
-        <item name="actionBarStyle">@style/Widget.AppCompat.Light.ActionBar</item>
+        <item name="actionOverflowButtonStyle">@style/Widget.AppCompat.Light.ActionButton.Overflow</item>
+        <item name="actionOverflowMenuStyle">@style/Widget.AppCompat.Light.PopupMenu.Overflow</item>
+        <item name="actionBarStyle">@style/Widget.AppCompat.Light.ActionBar.Solid</item>
         <item name="actionBarSplitStyle">?attr/actionBarStyle</item>
         <item name="actionBarWidgetTheme">@null</item>
-        <item name="actionBarSize">@dimen/abc_action_bar_default_height</item>
+        <item name="actionBarTheme">@style/ThemeOverlay.AppCompat.ActionBar</item>
+        <item name="actionBarSize">@dimen/abc_action_bar_default_height_material</item>
         <item name="actionBarDivider">?attr/dividerVertical</item>
-        <item name="actionBarItemBackground">?attr/selectableItemBackground</item>
-        <item name="actionMenuTextAppearance">
-            @style/TextAppearance.AppCompat.Widget.ActionBar.Menu
-        </item>
+        <item name="actionBarItemBackground">?attr/selectableItemBackgroundBorderless</item>
+        <item name="actionMenuTextAppearance">@style/TextAppearance.AppCompat.Widget.ActionBar.Menu</item>
         <item name="actionMenuTextColor">?android:attr/textColorPrimaryDisableOnly</item>
 
         <!-- Action Mode -->
         <item name="actionModeStyle">@style/Widget.AppCompat.ActionMode</item>
-        <item name="actionModeBackground">@drawable/abc_cab_background_top_holo_light</item>
-        <item name="actionModeSplitBackground">@drawable/abc_cab_background_bottom_holo_light</item>
+        <item name="actionModeBackground">?attr/colorPrimaryDark</item>
+        <item name="actionModeSplitBackground">?attr/colorPrimaryDark</item>
         <item name="actionModeCloseDrawable">@drawable/abc_ic_cab_done_holo_light</item>
-        <item name="actionModeCloseButtonStyle">@style/Widget.AppCompat.Light.ActionButton.CloseMode
-        </item>
+        <item name="actionModeCloseButtonStyle">@style/Widget.AppCompat.Light.ActionButton.CloseMode</item>
 
         <!-- Dropdown Spinner Attributes -->
-        <item name="actionDropDownStyle">
-            @style/Widget.AppCompat.Light.Spinner.DropDown.ActionBar
-        </item>
+        <item name="actionDropDownStyle">@style/Widget.AppCompat.Light.Spinner.DropDown.ActionBar</item>
 
         <!-- Panel attributes -->
         <item name="panelMenuListWidth">@dimen/abc_panel_menu_list_width</item>
@@ -149,105 +289,289 @@
         <item name="android:panelBackground">@drawable/abc_menu_hardkey_panel_holo_light</item>
         <item name="listChoiceBackgroundIndicator">@drawable/abc_list_selector_holo_light</item>
 
+        <!-- List attributes -->
+        <item name="textAppearanceListItem">@style/TextAppearance.AppCompat.Subhead</item>
+        <item name="textAppearanceListItemSmall">@style/TextAppearance.AppCompat.Subhead</item>
+        <item name="listPreferredItemHeight">64dp</item>
+        <item name="listPreferredItemHeightSmall">48dp</item>
+        <item name="listPreferredItemHeightLarge">80dp</item>
+        <item name="listPreferredItemPaddingLeft">16dip</item>
+        <item name="listPreferredItemPaddingRight">16dip</item>
+
+        <!-- Required for use of support_simple_spinner_dropdown_item.xml -->
+        <item name="spinnerDropDownItemStyle">@style/Widget.AppCompat.Light.DropDownItem.Spinner</item>
+        <item name="dropdownListPreferredItemHeight">?attr/listPreferredItemHeightSmall</item>
+
+        <!-- Popup Menu styles -->
+        <item name="popupMenuStyle">@style/Widget.AppCompat.Light.PopupMenu</item>
+        <item name="textAppearanceLargePopupMenu">@style/TextAppearance.AppCompat.Light.Widget.PopupMenu.Large</item>
+        <item name="textAppearanceSmallPopupMenu">@style/TextAppearance.AppCompat.Light.Widget.PopupMenu.Small</item>
+        <item name="listPopupWindowStyle">@style/Widget.AppCompat.Light.ListPopupWindow</item>
+        <item name="dropDownListViewStyle">@style/Widget.AppCompat.Light.ListView.DropDown</item>
+
+        <!-- SearchView attributes -->
+        <item name="searchViewStyle">@style/Widget.AppCompat.Light.SearchView</item>
+        <item name="android:dropDownItemStyle">@style/Widget.AppCompat.Light.DropDownItem.Spinner</item>
+        <item name="textColorSearchUrl">@color/abc_search_url_text</item>
+        <item name="textAppearanceSearchResultTitle">@style/TextAppearance.AppCompat.Light.SearchResult.Title</item>
+        <item name="textAppearanceSearchResultSubtitle">@style/TextAppearance.AppCompat.Light.SearchResult.Subtitle</item>
+
+        <item name="actionModeShareDrawable">@drawable/abc_ic_menu_share_material_light</item>
+
+        <!-- ShareActionProvider attributes -->
+        <item name="activityChooserViewStyle">@style/Widget.AppCompat.Light.ActivityChooserView</item>
+
+        <!-- Toolbar styles -->
+        <item name="toolbarStyle">@style/Widget.AppCompat.Toolbar</item>
+        <item name="toolbarNavigationButtonStyle">@style/Widget.AppCompat.Toolbar.Button.Navigation</item>
+
+        <item name="android:editTextStyle">@style/Widget.AppCompat.EditText</item>
+        <item name="editTextBackground">@drawable/abc_edit_text_material_light</item>
+        <item name="editTextColor">?android:attr/textColorPrimary</item>
+
+        <!-- Color palette -->
+        <item name="colorPrimaryDark">@color/material_blue_grey_600</item>
+        <item name="colorPrimary">@color/material_blue_grey_400</item>
+        <item name="colorAccent">@color/material_light_blue_A200</item>
+
+        <item name="colorControlNormal">?android:attr/textColorSecondary</item>
+        <item name="colorControlActivated">?attr/colorAccent</item>
+
+        <item name="colorControlHighlight">@color/ripple_material_light</item>
+        <!-- TODO: <item name="colorButtonNormal">@color/btn_default_material_light</item>-->
     </style>
 
-    <!-- Base platform-dependent theme providing a dark action bar in a light-themed activity. -->
-    <style name="Theme.Base.AppCompat.Light.DarkActionBar" parent="Theme.Base.AppCompat.Light">
-        <item name="homeAsUpIndicator">@drawable/abc_ic_ab_back_holo_dark</item>
+    <style name="Base.Theme.AppCompat" parent="Base.V7.Theme.AppCompat">
+    </style>
+
+    <style name="Base.Theme.AppCompat.Light" parent="Base.V7.Theme.AppCompat.Light">
+    </style>
+
+    <style name="Base.Theme.AppCompat.Light.DarkActionBar" parent="Base.Theme.AppCompat.Light">
+        <item name="homeAsUpIndicator">@drawable/abc_ic_ab_back_material_dark</item>
 
         <item name="actionOverflowButtonStyle">@style/Widget.AppCompat.ActionButton.Overflow</item>
-        <item name="actionBarStyle">@style/Widget.AppCompat.Light.ActionBar.Solid.Inverse</item>
-        <item name="actionBarWidgetTheme">@style/Theme.AppCompat</item>
+        <item name="actionBarPopupTheme">@style/ThemeOverlay.AppCompat.Light</item>
+        <item name="actionBarWidgetTheme">@null</item>
+        <item name="actionBarTheme">@style/ThemeOverlay.AppCompat.Dark</item>
         <item name="actionBarDivider">@drawable/abc_list_divider_holo_dark</item>
         <item name="actionBarItemBackground">@drawable/abc_item_background_holo_dark</item>
-        <item name="actionBarTabStyle">@style/Widget.AppCompat.Light.ActionBar.TabView.Inverse
-        </item>
-        <item name="actionBarTabBarStyle">@style/Widget.AppCompat.Light.ActionBar.TabBar.Inverse
-        </item>
-        <item name="actionBarTabTextStyle">@style/Widget.AppCompat.Light.ActionBar.TabText.Inverse
-        </item>
-        <item name="actionMenuTextColor">?android:attr/textColorPrimaryInverseDisableOnly</item>
 
         <!-- Action Mode -->
         <item name="actionModeStyle">@style/Widget.AppCompat.Light.ActionMode.Inverse</item>
-        <item name="actionModeBackground">@drawable/abc_cab_background_top_holo_dark</item>
-        <item name="actionModeSplitBackground">@drawable/abc_cab_background_bottom_holo_dark</item>
         <item name="actionModeCloseDrawable">@drawable/abc_ic_cab_done_holo_dark</item>
-        <item name="actionModeCloseButtonStyle">@style/Widget.AppCompat.ActionButton.CloseMode
-        </item>
+        <item name="actionModeCloseButtonStyle">@style/Widget.AppCompat.ActionButton.CloseMode</item>
+        <item name="actionModeShareDrawable">@drawable/abc_ic_menu_share_material_dark</item>
+
+        <!-- SearchView attributes -->
+        <item name="searchViewStyle">@style/Widget.AppCompat.SearchView</item>
+        <item name="android:dropDownItemStyle">@style/Widget.AppCompat.DropDownItem.Spinner</item>
+        <item name="textColorSearchUrl">@color/abc_search_url_text</item>
+        <item name="textAppearanceSearchResultTitle">@style/TextAppearance.AppCompat.SearchResult.Title</item>
+        <item name="textAppearanceSearchResultSubtitle">@style/TextAppearance.AppCompat.SearchResult.Subtitle</item>
 
         <!-- Dropdown Spinner Attributes -->
         <item name="actionDropDownStyle">@style/Widget.AppCompat.Spinner.DropDown.ActionBar</item>
 
         <!-- Panel attributes -->
-        <item name="android:panelBackground">@drawable/abc_menu_hardkey_panel_holo_dark</item>
         <item name="listChoiceBackgroundIndicator">@drawable/abc_list_selector_holo_dark</item>
 
+        <item name="colorPrimaryDark">@color/material_blue_grey_900</item>
+        <item name="colorPrimary">@color/material_blue_grey_800</item>
     </style>
 
     <!-- Menu/item attributes -->
-    <style name="Theme.AppCompat.Base.CompactMenu" parent="">
-        <item name="android:itemTextAppearance">
-            @style/TextAppearance.Widget.AppCompat.ExpandedMenu.Item</item>
+    <style name="Base.Theme.AppCompat.CompactMenu" parent="">
+        <item name="android:itemTextAppearance">?android:attr/textAppearanceMedium</item>
         <item name="android:listViewStyle">@style/Widget.AppCompat.ListView.Menu</item>
     </style>
 
-    <style name="Theme.AppCompat.Base.CompactMenu.Dialog" parent="">
-        <item name="android:itemTextAppearance">
-            @style/TextAppearance.AppCompat.Base.CompactMenu.Dialog</item>
-        <item name="android:listViewStyle">@android:style/Widget.ListView.Menu</item>
-    </style>
-
-    <style name="Theme.Base.AppCompat.DialogWhenLarge"
-           parent="Theme.Base.AppCompat">
-    </style>
-
-    <style name="Theme.Base.AppCompat.Light.DialogWhenLarge"
-           parent="Theme.Base.AppCompat.Light">
-    </style>
-
-    <style name="Theme.Base.AppCompat.Dialog.FixedSize" parent="android:Theme.Dialog">
-        <item name="windowFixedWidthMajor">@dimen/dialog_fixed_width_major</item>
-        <item name="windowFixedWidthMinor">@dimen/dialog_fixed_width_minor</item>
-        <item name="windowFixedHeightMajor">@dimen/dialog_fixed_height_major</item>
-        <item name="windowFixedHeightMinor">@dimen/dialog_fixed_height_minor</item>
-
+    <style name="Base.V7.Theme.AppCompat.Dialog" parent="Platform.AppCompat.Dialog">
         <item name="windowActionBar">true</item>
-        <!-- Remove system title bars; we will add the action bar ourselves. -->
-        <item name="android:windowNoTitle">true</item>
+        <item name="windowActionBarOverlay">false</item>
+        <item name="isLightTheme">false</item>
 
         <item name="buttonBarStyle">@android:style/ButtonBar</item>
         <item name="buttonBarButtonStyle">@android:style/Widget.Button</item>
         <item name="selectableItemBackground">@drawable/abc_item_background_holo_dark</item>
+        <item name="selectableItemBackgroundBorderless">?attr/selectableItemBackground</item>
+        <item name="homeAsUpIndicator">@drawable/abc_ic_ab_back_material_dark</item>
 
         <item name="dividerVertical">@drawable/abc_list_divider_holo_dark</item>
         <item name="dividerHorizontal">@drawable/abc_list_divider_holo_dark</item>
 
-        <item name="listPreferredItemHeight">?android:attr/listPreferredItemHeight</item>
-        <item name="listPreferredItemHeightSmall">48dp</item>
-        <item name="listPreferredItemHeightLarge">80dp</item>
-        <item name="listPreferredItemPaddingLeft">8dip</item>
-        <item name="listPreferredItemPaddingRight">8dip</item>
+        <!-- Action Bar Styles -->
+        <item name="actionBarTabStyle">@style/Widget.AppCompat.ActionBar.TabView</item>
+        <item name="actionBarTabBarStyle">@style/Widget.AppCompat.ActionBar.TabBar</item>
+        <item name="actionBarTabTextStyle">@style/Widget.AppCompat.ActionBar.TabText</item>
+        <item name="actionButtonStyle">@style/Widget.AppCompat.ActionButton</item>
+        <item name="actionOverflowButtonStyle">@style/Widget.AppCompat.ActionButton.Overflow</item>
+        <item name="actionOverflowMenuStyle">@style/Widget.AppCompat.PopupMenu.Overflow</item>
+        <item name="actionBarStyle">@style/Widget.AppCompat.ActionBar.Solid</item>
+        <item name="actionBarSplitStyle">?attr/actionBarStyle</item>
+        <item name="actionBarWidgetTheme">@null</item>
+        <item name="actionBarTheme">@style/ThemeOverlay.AppCompat.ActionBar</item>
+        <item name="actionBarSize">@dimen/abc_action_bar_default_height_material</item>
+        <item name="actionBarDivider">?attr/dividerVertical</item>
+        <item name="actionBarItemBackground">?attr/selectableItemBackground</item>
+        <item name="actionMenuTextAppearance">@style/TextAppearance.AppCompat.Widget.ActionBar.Menu</item>
+        <item name="actionMenuTextColor">?android:attr/textColorPrimaryDisableOnly</item>
 
-        <item name="textAppearanceListItem">?android:attr/textAppearanceMedium</item>
-        <item name="textAppearanceListItemSmall">?android:attr/textAppearanceMedium</item>
+        <!-- Dropdown Spinner Attributes -->
+        <item name="actionDropDownStyle">@style/Widget.AppCompat.Spinner.DropDown.ActionBar</item>
 
         <!-- Action Mode -->
         <item name="actionModeStyle">@style/Widget.AppCompat.ActionMode</item>
-        <item name="actionModeBackground">@drawable/abc_cab_background_top_holo_dark</item>
-        <item name="actionModeSplitBackground">@drawable/abc_cab_background_bottom_holo_dark</item>
+        <item name="actionModeBackground">?attr/colorPrimaryDark</item>
+        <item name="actionModeSplitBackground">?attr/colorPrimaryDark</item>
         <item name="actionModeCloseDrawable">@drawable/abc_ic_cab_done_holo_dark</item>
-        <item name="actionModeCloseButtonStyle">@style/Widget.AppCompat.ActionButton.CloseMode
-        </item>
+        <item name="actionModeCloseButtonStyle">@style/Widget.AppCompat.ActionButton.CloseMode</item>
 
         <!-- Panel attributes -->
         <item name="panelMenuListWidth">@dimen/abc_panel_menu_list_width</item>
         <item name="panelMenuListTheme">@style/Theme.AppCompat.CompactMenu</item>
         <item name="android:panelBackground">@drawable/abc_menu_hardkey_panel_holo_dark</item>
         <item name="listChoiceBackgroundIndicator">@drawable/abc_list_selector_holo_dark</item>
+
+        <!-- List attributes -->
+        <item name="textAppearanceListItem">@style/TextAppearance.AppCompat.Subhead</item>
+        <item name="textAppearanceListItemSmall">@style/TextAppearance.AppCompat.Subhead</item>
+        <item name="listPreferredItemHeight">64dp</item>
+        <item name="listPreferredItemHeightSmall">48dp</item>
+        <item name="listPreferredItemHeightLarge">80dp</item>
+        <item name="listPreferredItemPaddingLeft">16dip</item>
+        <item name="listPreferredItemPaddingRight">16dip</item>
+
+        <!-- Required for use of support_simple_spinner_dropdown_item.xml -->
+        <item name="spinnerDropDownItemStyle">@style/Widget.AppCompat.DropDownItem.Spinner</item>
+        <item name="dropdownListPreferredItemHeight">?attr/listPreferredItemHeightSmall</item>
+
+        <!-- Popup Menu styles -->
+        <item name="popupMenuStyle">@style/Widget.AppCompat.PopupMenu</item>
+        <item name="textAppearanceLargePopupMenu">@style/TextAppearance.AppCompat.Widget.PopupMenu.Large</item>
+        <item name="textAppearanceSmallPopupMenu">@style/TextAppearance.AppCompat.Widget.PopupMenu.Small</item>
+        <item name="listPopupWindowStyle">@style/Widget.AppCompat.ListPopupWindow</item>
+        <item name="dropDownListViewStyle">@style/Widget.AppCompat.ListView.DropDown</item>
+
+        <!-- SearchView attributes -->
+        <item name="searchViewStyle">@style/Widget.AppCompat.SearchView</item>
+        <item name="android:dropDownItemStyle">@style/Widget.AppCompat.DropDownItem.Spinner</item>
+        <item name="textColorSearchUrl">@color/abc_search_url_text</item>
+        <item name="textAppearanceSearchResultTitle">@style/TextAppearance.AppCompat.SearchResult.Title</item>
+        <item name="textAppearanceSearchResultSubtitle">@style/TextAppearance.AppCompat.SearchResult.Subtitle</item>
+
+        <item name="actionModeShareDrawable">@drawable/abc_ic_menu_share_material_dark</item>
+
+        <!-- ShareActionProvider attributes -->
+        <item name="activityChooserViewStyle">@style/Widget.AppCompat.ActivityChooserView</item>
+
+        <!-- Toolbar styles -->
+        <item name="toolbarStyle">@style/Widget.AppCompat.Toolbar</item>
+        <item name="toolbarNavigationButtonStyle">@style/Widget.AppCompat.Toolbar.Button.Navigation</item>
+
+        <item name="android:editTextStyle">@style/Widget.AppCompat.EditText</item>
+        <item name="editTextBackground">@drawable/abc_edit_text_material_dark</item>
+        <item name="editTextColor">?android:attr/textColorPrimary</item>
+
+        <!-- Color palette -->
+        <item name="colorPrimaryDark">@color/material_blue_grey_900</item>
+        <item name="colorPrimary">@color/material_blue_grey_800</item>
+        <item name="colorAccent">@color/material_light_blue_A200</item>
+
+        <item name="colorControlNormal">?android:attr/textColorSecondary</item>
+        <item name="colorControlActivated">?attr/colorAccent</item>
+
+        <item name="colorControlHighlight">@color/ripple_material_dark</item>
+        <!-- TODO: <item name="colorButtonNormal">@color/btn_default_material_dark</item>-->
     </style>
 
-    <style name="Theme.Base.AppCompat.Dialog.Light.FixedSize"
-           parent="Theme.Base.AppCompat.Dialog.FixedSize" />
+    <style name="Base.Theme.AppCompat.Dialog" parent="Base.V7.Theme.AppCompat.Dialog" />
+
+    <style name="Base.Theme.AppCompat.Light.Dialog" parent="Base.Theme.AppCompat.Dialog" />
+
+    <style name="Base.Theme.AppCompat.Dialog.FixedSize">
+        <item name="windowFixedWidthMajor">@dimen/dialog_fixed_width_major</item>
+        <item name="windowFixedWidthMinor">@dimen/dialog_fixed_width_minor</item>
+        <item name="windowFixedHeightMajor">@dimen/dialog_fixed_height_major</item>
+        <item name="windowFixedHeightMinor">@dimen/dialog_fixed_height_minor</item>
+    </style>
+
+    <style name="Base.Theme.AppCompat.Light.Dialog.FixedSize">
+        <item name="windowFixedWidthMajor">@dimen/dialog_fixed_width_major</item>
+        <item name="windowFixedWidthMinor">@dimen/dialog_fixed_width_minor</item>
+        <item name="windowFixedHeightMajor">@dimen/dialog_fixed_height_major</item>
+        <item name="windowFixedHeightMinor">@dimen/dialog_fixed_height_minor</item>
+    </style>
+
+    <!-- We're not large, so redirect to Theme.AppCompat -->
+    <style name="Base.Theme.AppCompat.DialogWhenLarge" parent="Theme.AppCompat" />
+
+    <style name="Base.Theme.AppCompat.Light.DialogWhenLarge" parent="Theme.AppCompat.Light" />
+
+    <!-- Overlay themes -->
+    <style name="Base.ThemeOverlay.AppCompat" parent="" />
+
+    <style name="Base.ThemeOverlay.AppCompat.Light">
+        <item name="android:colorForeground">@color/bright_foreground_material_light</item>
+        <item name="android:colorForegroundInverse">@color/bright_foreground_material_dark</item>
+        <item name="android:colorBackground">@color/background_material_light</item>
+        <item name="android:colorBackgroundCacheHint">@color/abc_background_cache_hint_selector_material_light</item>
+
+        <item name="android:textColorPrimary">@color/abc_primary_text_material_light</item>
+        <item name="android:textColorPrimaryInverse">@color/abc_primary_text_material_dark</item>
+        <item name="android:textColorSecondary">@color/secondary_text_material_light</item>
+        <item name="android:textColorSecondaryInverse">@color/secondary_text_material_dark</item>
+        <item name="android:textColorTertiary">@color/tertiary_text_material_light</item>
+        <item name="android:textColorTertiaryInverse">@color/tertiary_text_material_dark</item>
+        <item name="android:textColorPrimaryDisableOnly">@color/abc_primary_text_disable_only_material_light</item>
+        <item name="android:textColorPrimaryInverseDisableOnly">@color/abc_primary_text_disable_only_material_dark</item>
+        <item name="android:textColorHint">@color/hint_foreground_material_light</item>
+        <item name="android:textColorHintInverse">@color/hint_foreground_material_dark</item>
+        <item name="android:textColorHighlight">@color/highlighted_text_material_light</item>
+        <item name="android:textColorLink">@color/material_teal_500</item>
+
+        <item name="android:windowBackground">@color/background_material_light</item>
+
+        <item name="selectableItemBackground">@drawable/abc_item_background_holo_light</item>
+
+        <item name="colorControlNormal">?android:attr/textColorSecondary</item>
+        <item name="colorControlHighlight">@color/ripple_material_light</item>
+        <!--<item name="colorButtonNormal">@color/btn_default_material_light</item>-->
+    </style>
+
+    <style name="Base.ThemeOverlay.AppCompat.Dark">
+        <item name="android:colorForeground">@color/bright_foreground_material_dark</item>
+        <item name="android:colorForegroundInverse">@color/bright_foreground_material_light</item>
+        <item name="android:colorBackground">@color/background_material_dark</item>
+        <item name="android:colorBackgroundCacheHint">@color/abc_background_cache_hint_selector_material_dark</item>
+
+        <item name="android:textColorPrimary">@color/abc_primary_text_material_dark</item>
+        <item name="android:textColorPrimaryInverse">@color/abc_primary_text_material_light</item>
+        <item name="android:textColorPrimaryDisableOnly">@color/abc_primary_text_disable_only_material_dark</item>
+        <item name="android:textColorSecondary">@color/secondary_text_material_dark</item>
+        <item name="android:textColorSecondaryInverse">@color/secondary_text_material_light</item>
+        <item name="android:textColorTertiary">@color/tertiary_text_material_dark</item>
+        <item name="android:textColorTertiaryInverse">@color/tertiary_text_material_light</item>
+        <item name="android:textColorHint">@color/hint_foreground_material_dark</item>
+        <item name="android:textColorHintInverse">@color/hint_foreground_material_light</item>
+        <item name="android:textColorHighlight">@color/highlighted_text_material_dark</item>
+        <item name="android:textColorLink">@color/material_teal_500</item>
+
+        <item name="android:windowBackground">@color/background_material_dark</item>
+
+        <item name="selectableItemBackground">@drawable/abc_item_background_holo_dark</item>
+
+        <item name="colorControlNormal">?android:attr/textColorSecondary</item>
+        <item name="colorControlHighlight">@color/ripple_material_dark</item>
+        <!--<item name="colorButtonNormal">@color/btn_default_material_dark</item>-->
+    </style>
+
+    <style name="Base.ThemeOverlay.AppCompat.ActionBar">
+        <item name="colorControlNormal">?android:attr/textColorPrimary</item>
+    </style>
+
+    <style name="Base.ThemeOverlay.AppCompat.Dark.ActionBar">
+        <item name="colorControlNormal">?android:attr/textColorPrimary</item>
+    </style>
 
 </resources>
diff --git a/v7/appcompat/scripts/tint_drawables.sh b/v7/appcompat/scripts/tint_drawables.sh
new file mode 100755
index 0000000..4888b80
--- /dev/null
+++ b/v7/appcompat/scripts/tint_drawables.sh
@@ -0,0 +1,145 @@
+#!/bin/bash
+
+###############################################################################
+#
+#  Copyright (C) 2014 The Android Open Source Project
+# 
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+# 
+#       http://www.apache.org/licenses/LICENSE-2.0
+# 
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+###############################################################################
+
+###################################################################################
+#
+# Tints all of the drawables in the appcompat drawable folders which are meant to
+# be used with L's Drawable tinting. This script precomputes the Drawables for when
+# use pre-L.
+#
+# Requires imagemagick to be in installed.
+#
+####################################################################################
+
+RES_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )/../res" && pwd )
+
+COLOR_CONTROL_NORMAL_LIGHT=#000000
+COLOR_CONTROL_NORMAL_DARK=#ffffff
+COLOR_CONTROL_NORMAL_DISABLED_LIGHT=#00000045
+COLOR_CONTROL_NORMAL_DISABLED_DARK=#ffffff30
+COLOR_ACCENT_LIGHT=#009688
+COLOR_ACCENT_DARK=#80cbc4
+COLOR_BACKGROUND_LIGHT=#fafafa
+COLOR_BACKGROUND_DARK=#212121
+
+NORMAL_NO_PREFIX=" \
+    abc_ic_ab_back_mtrl_am_alpha.png
+    abc_ic_go_search_api_mtrl_alpha.png \
+    abc_ic_search_api_mtrl_alpha.png \
+    abc_ic_commit_search_api_mtrl_alpha.png \
+    abc_ic_clear_mtrl_alpha.png \
+    abc_ic_menu_share_mtrl_alpha.png \
+    abc_ic_menu_moreoverflow_mtrl_alpha.png \
+    abc_ic_voice_search_api_mtrl_alpha.png \
+    abc_textfield_search_default_mtrl_alpha.9.png \
+    abc_textfield_default_mtrl_alpha.9.png \
+    abc_list_divider_mtrl_alpha.9.png"
+
+NORMAL_PREFIX_DEFAULT="abc_spinner_mtrl_am_alpha.9.png"
+
+NORMAL_DISABLED="abc_textfield_default_mtrl_alpha.9.png"
+
+ACTIVATED_NO_PREFIX="abc_textfield_search_activated_mtrl_alpha.9.png \
+    abc_textfield_activated_mtrl_alpha.9.png"
+
+ACTIVATED_PREFIX_CHECKED="abc_spinner_mtrl_am_alpha.9.png"
+
+ACTIVATED_PREFIX_SELECTED="abc_tab_indicator_mtrl_alpha.9.png"
+
+ACTIVATED_PREFIX_PRESSED="abc_spinner_mtrl_am_alpha.9.png"
+
+BACKGROUND_MULTIPLY="abc_popup_background_mtrl_mult.9.png"
+
+function tintDrawable {
+  METHOD=$1
+  SOURCE=`basename $2`
+  COLOR=$3
+  THEME=$4
+  SUFFIX=$5
+
+  OUTPUT=${SOURCE%_mtrl*png}$SUFFIX$THEME.${SOURCE#*.}
+
+  # If we're dealing with a 9-patch then we need to make sure we do not re-color
+  # the 1px border which contain the strechable area definitions
+  REGION_ARGS=
+  if [[ "$SOURCE" == *".9.png" ]]; then
+    WIDTH=`identify -format "%w" $SOURCE`
+    HEIGHT=`identify -format "%h" $SOURCE`
+    REGION_ARGS="-region "$(($WIDTH - 2))x$(($HEIGHT - 2))!+1+1""
+  fi
+
+  convert $SOURCE \( +clone $REGION_ARGS -fill "$COLOR" -colorize 100% \)  -compose $METHOD -composite $OUTPUT
+
+  # ImageMagick's Multiply doesn't do alpha composition, so we need to copy the alpha channel over from source
+  if [[ "$METHOD" == "Multiply" ]]; then
+    convert $OUTPUT $SOURCE -compose copy-opacity -composite $OUTPUT
+  fi
+
+  if command -v optipng &>/dev/null; then
+    # if optipng is installed, run the output through it
+    optipng $OUTPUT -quiet
+  fi
+
+  echo "Tinted $SOURCE -> $OUTPUT"
+}
+
+export -f tintDrawable
+
+function tintDrawables {
+  method=$1
+  files=$2
+  color_light=$3
+  color_dark=$4
+  suffix=$5
+
+  echo "------------------------------------"
+  echo "   Tinting Drawables"
+  echo "      Method: $method"
+  echo "      Suffix: $suffix"
+  echo "------------------------------------"
+
+  if [[ "$color_light" == "$color_dark" ]]; then
+    # if color_light and color_dark are the same, then only produce one file with a _material theme
+    for f in $files; do
+      find $RES_DIR -name $f -execdir bash -c 'tintDrawable "$0" "$1" "$2" "_material" "$3"' $method {} $color_dark $suffix \;
+    done
+  else
+    for f in $files; do
+      # if color_light and color_dark are different, produce two appropriate files
+      find $RES_DIR -name $f -execdir bash -c 'tintDrawable "$0" "$1" "$2" "_material_dark" "$3"' $method {} $color_dark $suffix \;
+      find $RES_DIR -name $f -execdir bash -c 'tintDrawable "$0" "$1" "$2" "_material_light" "$3"' $method {} $color_light $suffix \;
+    done
+  fi
+  echo -e "\n"
+}
+
+if ! command -v convert &>/dev/null; then
+  echo "ImageMagick is not installed. Exiting..."
+  exit 1
+fi
+
+tintDrawables "Src-In" "$NORMAL_NO_PREFIX" $COLOR_CONTROL_NORMAL_LIGHT $COLOR_CONTROL_NORMAL_DARK
+tintDrawables "Src-In" "$NORMAL_PREFIX_DEFAULT" $COLOR_CONTROL_NORMAL_LIGHT $COLOR_CONTROL_NORMAL_DARK "_default"
+tintDrawables "Src-In" "$NORMAL_DISABLED" $COLOR_CONTROL_NORMAL_DISABLED_LIGHT $COLOR_CONTROL_NORMAL_DISABLED_DARK "_disabled"
+tintDrawables "Src-In" "$ACTIVATED_NO_PREFIX" $COLOR_ACCENT_LIGHT $COLOR_ACCENT_DARK
+tintDrawables "Src-In" "$ACTIVATED_PREFIX_CHECKED" $COLOR_ACCENT_LIGHT $COLOR_ACCENT_DARK "_checked"
+tintDrawables "Src-In" "$ACTIVATED_PREFIX_PRESSED" $COLOR_ACCENT_LIGHT $COLOR_ACCENT_DARK "_pressed"
+tintDrawables "Src-In" "$ACTIVATED_PREFIX_SELECTED" $COLOR_ACCENT_LIGHT $COLOR_ACCENT_DARK "_selected"
+tintDrawables "Multiply" "$BACKGROUND_MULTIPLY" $COLOR_BACKGROUND_LIGHT $COLOR_BACKGROUND_DARK
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBar.java b/v7/appcompat/src/android/support/v7/app/ActionBar.java
index a8a6383..f1594b0 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBar.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBar.java
@@ -17,80 +17,140 @@
 package android.support.v7.app;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.GravityCompat;
 import android.support.v7.appcompat.R;
+import android.support.v7.view.ActionMode;
 import android.util.AttributeSet;
 import android.view.Gravity;
+import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewGroup.MarginLayoutParams;
 import android.view.Window;
 import android.widget.SpinnerAdapter;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
- * A window feature at the top of the activity that may display the activity title, navigation
- * modes, and other interactive items.
+ * A primary toolbar within the activity that may display the activity title, application-level
+ * navigation affordances, and other interactive items.
  *
- * <p class="note"><strong>Note:</strong> This class is included in the <a
- * href="{@docRoot}tools/extras/support-library.html">support library</a> for compatibility
- * with API level 7 and higher. If you're developing your app for API level 11 and higher
- * <em>only</em>, you should instead use the framework {@link android.app.ActionBar} class.</p>
+ * <p>Beginning with Android 3.0 (API level 11), the action bar appears at the top of an
+ * activity's window when the activity uses the system's {@link
+ * android.R.style#Theme_Holo Holo} theme (or one of its descendant themes), which is the default.
+ * You may otherwise add the action bar by calling {@link
+ * android.view.Window#requestFeature requestFeature(FEATURE_ACTION_BAR)} or by declaring it in a
+ * custom theme with the {@link android.R.styleable#Theme_windowActionBar windowActionBar} property.
+ * </p>
  *
- * <p>When using the support library, you can add the action bar to the top of your activity
- * window by extending the {@link ActionBarActivity} class and setting the activity theme to
- * {@link android.support.v7.appcompat.R.style#Theme_AppCompat Theme.AppCompat} or a similar theme.
+ * <p>Beginning with Android L (API level 21), the action bar may be represented by any
+ * Toolbar widget within the application layout. The application may signal to the Activity
+ * which Toolbar should be treated as the Activity's action bar. Activities that use this
+ * feature should use one of the supplied <code>.NoActionBar</code> themes, set the
+ * {@link android.R.styleable#Theme_windowActionBar windowActionBar} attribute to <code>false</code>
+ * or otherwise not request the window feature.</p>
  *
- * <p>By default, the action bar shows the application icon on
+ * <p>By adjusting the window features requested by the theme and the layouts used for
+ * an Activity's content view, an app can use the standard system action bar on older platform
+ * releases and the newer inline toolbars on newer platform releases. The <code>ActionBar</code>
+ * object obtained from the Activity can be used to control either configuration transparently.</p>
+ *
+ * <p>When using the Holo themes the action bar shows the application icon on
  * the left, followed by the activity title. If your activity has an options menu, you can make
  * select items accessible directly from the action bar as "action items". You can also
  * modify various characteristics of the action bar or remove it completely.</p>
  *
+ * <p>When using the Material themes (default in API 21 or newer) the navigation button
+ * (formerly "Home") takes over the space previously occupied by the application icon.
+ * Apps wishing to express a stronger branding should use their brand colors heavily
+ * in the action bar and other application chrome or use a {@link #setLogo(int) logo}
+ * in place of their standard title text.</p>
+ *
  * <p>From your activity, you can retrieve an instance of {@link ActionBar} by calling {@link
- * android.support.v7.app.ActionBarActivity#getSupportActionBar}.</p>
+ * android.app.Activity#getActionBar getActionBar()}.</p>
  *
  * <p>In some cases, the action bar may be overlayed by another bar that enables contextual actions,
- * using an {@link android.support.v7.view.ActionMode}. For example, when the user selects one or
- * more items in your activity, you can enable an action mode that offers actions specific to the
- * selected items, with a UI that temporarily replaces the action bar. Although the UI may occupy
- * the same space, the {@link android.support.v7.view.ActionMode} APIs are distinct and independent
- * from those for {@link ActionBar}.
+ * using an {@link android.view.ActionMode}. For example, when the user selects one or more items in
+ * your activity, you can enable an action mode that offers actions specific to the selected
+ * items, with a UI that temporarily replaces the action bar. Although the UI may occupy the
+ * same space, the {@link android.view.ActionMode} APIs are distinct and independent from those for
+ * {@link ActionBar}.</p>
  *
  * <div class="special reference">
  * <h3>Developer Guides</h3>
- *
  * <p>For information about how to use the action bar, including how to add action items, navigation
  * modes and more, read the <a href="{@docRoot}guide/topics/ui/actionbar.html">Action
- * Bar</a> API guide.</p>
+ * Bar</a> developer guide.</p>
  * </div>
  */
 public abstract class ActionBar {
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+    public @interface NavigationMode {}
+
     /**
-     * Standard navigation mode. Consists of either a logo or icon and title text with an optional
-     * subtitle. Clicking any of these elements will dispatch onOptionsItemSelected to the host
-     * Activity with a MenuItem with item ID android.R.id.home.
+     * Standard navigation mode. Consists of either a logo or icon
+     * and title text with an optional subtitle. Clicking any of these elements
+     * will dispatch onOptionsItemSelected to the host Activity with
+     * a MenuItem with item ID android.R.id.home.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public static final int NAVIGATION_MODE_STANDARD = 0;
 
     /**
-     * List navigation mode. Instead of static title text this mode presents a list menu for
-     * navigation within the activity. e.g. this might be presented to the user as a dropdown list.
+     * List navigation mode. Instead of static title text this mode
+     * presents a list menu for navigation within the activity.
+     * e.g. this might be presented to the user as a dropdown list.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public static final int NAVIGATION_MODE_LIST = 1;
 
     /**
-     * Tab navigation mode. Instead of static title text this mode presents a series of tabs for
-     * navigation within the activity.
+     * Tab navigation mode. Instead of static title text this mode
+     * presents a series of tabs for navigation within the activity.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public static final int NAVIGATION_MODE_TABS = 2;
 
+    /** @hide */
+    @IntDef(flag=true, value={
+            DISPLAY_USE_LOGO,
+            DISPLAY_SHOW_HOME,
+            DISPLAY_HOME_AS_UP,
+            DISPLAY_SHOW_TITLE,
+            DISPLAY_SHOW_CUSTOM
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DisplayOptions {}
+
     /**
-     * Use logo instead of icon if available. This flag will cause appropriate navigation modes to
-     * use a wider logo in place of the standard icon.
+     * Use logo instead of icon if available. This flag will cause appropriate
+     * navigation modes to use a wider logo in place of the standard icon.
      *
      * @see #setDisplayOptions(int)
      * @see #setDisplayOptions(int, int)
@@ -98,8 +158,8 @@
     public static final int DISPLAY_USE_LOGO = 0x1;
 
     /**
-     * Show 'home' elements in this action bar, leaving more space for other navigation elements.
-     * This includes logo and icon.
+     * Show 'home' elements in this action bar, leaving more space for other
+     * navigation elements. This includes logo and icon.
      *
      * @see #setDisplayOptions(int)
      * @see #setDisplayOptions(int, int)
@@ -107,14 +167,14 @@
     public static final int DISPLAY_SHOW_HOME = 0x2;
 
     /**
-     * Display the 'home' element such that it appears as an 'up' affordance. e.g. show an arrow to
-     * the left indicating the action that will be taken.
+     * Display the 'home' element such that it appears as an 'up' affordance.
+     * e.g. show an arrow to the left indicating the action that will be taken.
      *
-     * Set this flag if selecting the 'home' button in the action bar to return up by a single level
-     * in your UI rather than back to the top level or front page.
+     * Set this flag if selecting the 'home' button in the action bar to return
+     * up by a single level in your UI rather than back to the top level or front page.
      *
-     * <p>Setting this option will implicitly enable interaction with the home/up button. See {@link
-     * #setHomeButtonEnabled(boolean)}.
+     * <p>Setting this option will implicitly enable interaction with the home/up
+     * button. See {@link #setHomeButtonEnabled(boolean)}.
      *
      * @see #setDisplayOptions(int)
      * @see #setDisplayOptions(int, int)
@@ -143,11 +203,13 @@
     public static final int DISPLAY_SHOW_CUSTOM = 0x10;
 
     /**
-     * Set the action bar into custom navigation mode, supplying a view for custom navigation.
+     * Set the action bar into custom navigation mode, supplying a view
+     * for custom navigation.
      *
-     * Custom navigation views appear between the application icon and any action buttons and may
-     * use any space available there. Common use cases for custom navigation views might include an
-     * auto-suggesting address bar for a browser or other navigation mechanisms that do not
+     * Custom navigation views appear between the application icon and
+     * any action buttons and may use any space available there. Common
+     * use cases for custom navigation views might include an auto-suggesting
+     * address bar for a browser or other navigation mechanisms that do not
      * translate well to provided navigation modes.
      *
      * @param view Custom navigation view to place in the ActionBar.
@@ -155,85 +217,99 @@
     public abstract void setCustomView(View view);
 
     /**
-     * Set the action bar into custom navigation mode, supplying a view for custom navigation.
+     * Set the action bar into custom navigation mode, supplying a view
+     * for custom navigation.
      *
-     * <p>Custom navigation views appear between the application icon and any action buttons and may
-     * use any space available there. Common use cases for custom navigation views might include an
-     * auto-suggesting address bar for a browser or other navigation mechanisms that do not
+     * <p>Custom navigation views appear between the application icon and
+     * any action buttons and may use any space available there. Common
+     * use cases for custom navigation views might include an auto-suggesting
+     * address bar for a browser or other navigation mechanisms that do not
      * translate well to provided navigation modes.</p>
      *
-     * <p>The display option {@link #DISPLAY_SHOW_CUSTOM} must be set for the custom view to be
-     * displayed.</p>
+     * <p>The display option {@link #DISPLAY_SHOW_CUSTOM} must be set for
+     * the custom view to be displayed.</p>
      *
-     * @param view         Custom navigation view to place in the ActionBar.
+     * @param view Custom navigation view to place in the ActionBar.
      * @param layoutParams How this custom view should layout in the bar.
+     *
      * @see #setDisplayOptions(int, int)
      */
     public abstract void setCustomView(View view, LayoutParams layoutParams);
 
     /**
-     * Set the action bar into custom navigation mode, supplying a view for custom navigation.
+     * Set the action bar into custom navigation mode, supplying a view
+     * for custom navigation.
      *
-     * <p>Custom navigation views appear between the application icon and any action buttons and may
-     * use any space available there. Common use cases for custom navigation views might include an
-     * auto-suggesting address bar for a browser or other navigation mechanisms that do not
+     * <p>Custom navigation views appear between the application icon and
+     * any action buttons and may use any space available there. Common
+     * use cases for custom navigation views might include an auto-suggesting
+     * address bar for a browser or other navigation mechanisms that do not
      * translate well to provided navigation modes.</p>
      *
-     * <p>The display option {@link #DISPLAY_SHOW_CUSTOM} must be set for the custom view to be
-     * displayed.</p>
+     * <p>The display option {@link #DISPLAY_SHOW_CUSTOM} must be set for
+     * the custom view to be displayed.</p>
      *
      * @param resId Resource ID of a layout to inflate into the ActionBar.
+     *
      * @see #setDisplayOptions(int, int)
      */
     public abstract void setCustomView(int resId);
 
     /**
-     * Set the icon to display in the 'home' section of the action bar. The action bar will use an
-     * icon specified by its style or the activity icon by default.
+     * Set the icon to display in the 'home' section of the action bar.
+     * The action bar will use an icon specified by its style or the
+     * activity icon by default.
      *
-     * Whether the home section shows an icon or logo is controlled by the display option {@link
-     * #DISPLAY_USE_LOGO}.
+     * Whether the home section shows an icon or logo is controlled
+     * by the display option {@link #DISPLAY_USE_LOGO}.
      *
      * @param resId Resource ID of a drawable to show as an icon.
+     *
      * @see #setDisplayUseLogoEnabled(boolean)
      * @see #setDisplayShowHomeEnabled(boolean)
      */
-    public abstract void setIcon(int resId);
+    public abstract void setIcon(@DrawableRes int resId);
 
     /**
-     * Set the icon to display in the 'home' section of the action bar. The action bar will use an
-     * icon specified by its style or the activity icon by default.
+     * Set the icon to display in the 'home' section of the action bar.
+     * The action bar will use an icon specified by its style or the
+     * activity icon by default.
      *
-     * Whether the home section shows an icon or logo is controlled by the display option {@link
-     * #DISPLAY_USE_LOGO}.
+     * Whether the home section shows an icon or logo is controlled
+     * by the display option {@link #DISPLAY_USE_LOGO}.
      *
      * @param icon Drawable to show as an icon.
+     *
      * @see #setDisplayUseLogoEnabled(boolean)
      * @see #setDisplayShowHomeEnabled(boolean)
      */
     public abstract void setIcon(Drawable icon);
 
     /**
-     * Set the logo to display in the 'home' section of the action bar. The action bar will use a
-     * logo specified by its style or the activity logo by default.
+     * Set the logo to display in the 'home' section of the action bar.
+     * The action bar will use a logo specified by its style or the
+     * activity logo by default.
      *
-     * Whether the home section shows an icon or logo is controlled by the display option {@link
-     * #DISPLAY_USE_LOGO}.
+     * Whether the home section shows an icon or logo is controlled
+     * by the display option {@link #DISPLAY_USE_LOGO}.
      *
      * @param resId Resource ID of a drawable to show as a logo.
+     *
      * @see #setDisplayUseLogoEnabled(boolean)
      * @see #setDisplayShowHomeEnabled(boolean)
      */
-    public abstract void setLogo(int resId);
+    public abstract void setLogo(@DrawableRes int resId);
 
     /**
-     * Set the logo to display in the 'home' section of the action bar. The action bar will use a
-     * logo specified by its style or the activity logo by default.
+     * Set the logo to display in the 'home' section of the action bar.
+     * The action bar will use a logo specified by its style or the
+     * activity logo by default.
      *
-     * Whether the home section shows an icon or logo is controlled by the display option {@link
-     * #DISPLAY_USE_LOGO}.
+     * Whether the home section shows an icon or logo is controlled
+     * by the display option {@link #DISPLAY_USE_LOGO}.
      *
      * @param logo Drawable to show as a logo.
+     *
      * @see #setDisplayUseLogoEnabled(boolean)
      * @see #setDisplayShowHomeEnabled(boolean)
      */
@@ -242,16 +318,22 @@
     /**
      * Set the adapter and navigation callback for list navigation mode.
      *
-     * The supplied adapter will provide views for the expanded list as well as the currently
-     * selected item. (These may be displayed differently.)
+     * The supplied adapter will provide views for the expanded list as well as
+     * the currently selected item. (These may be displayed differently.)
      *
-     * The supplied OnNavigationListener will alert the application when the user changes the
-     * current list selection.
+     * The supplied OnNavigationListener will alert the application when the user
+     * changes the current list selection.
      *
-     * @param adapter  An adapter that will provide views both to display the current navigation
-     *                 selection and populate views within the dropdown navigation menu.
-     * @param callback An OnNavigationListener that will receive events when the user selects a
-     *                 navigation item.
+     * @param adapter An adapter that will provide views both to display
+     *                the current navigation selection and populate views
+     *                within the dropdown navigation menu.
+     * @param callback An OnNavigationListener that will receive events when the user
+     *                 selects a navigation item.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public abstract void setListNavigationCallbacks(SpinnerAdapter adapter,
             OnNavigationListener callback);
@@ -260,6 +342,11 @@
      * Set the selected navigation item in list or tabbed navigation modes.
      *
      * @param position Position of the item to select.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public abstract void setSelectedNavigationItem(int position);
 
@@ -267,6 +354,11 @@
      * Get the position of the selected navigation item in list or tabbed navigation modes.
      *
      * @return Position of the selected item.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public abstract int getSelectedNavigationIndex();
 
@@ -274,106 +366,119 @@
      * Get the number of navigation items present in the current navigation mode.
      *
      * @return Number of navigation items.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public abstract int getNavigationItemCount();
 
     /**
-     * Set the action bar's title. This will only be displayed if {@link #DISPLAY_SHOW_TITLE} is
-     * set.
+     * Set the action bar's title. This will only be displayed if
+     * {@link #DISPLAY_SHOW_TITLE} is set.
      *
      * @param title Title to set
+     *
      * @see #setTitle(int)
      * @see #setDisplayOptions(int, int)
      */
     public abstract void setTitle(CharSequence title);
 
     /**
-     * Set the action bar's title. This will only be displayed if {@link #DISPLAY_SHOW_TITLE} is
-     * set.
+     * Set the action bar's title. This will only be displayed if
+     * {@link #DISPLAY_SHOW_TITLE} is set.
      *
      * @param resId Resource ID of title string to set
+     *
      * @see #setTitle(CharSequence)
      * @see #setDisplayOptions(int, int)
      */
-    public abstract void setTitle(int resId);
+    public abstract void setTitle(@StringRes int resId);
 
     /**
-     * Set the action bar's subtitle. This will only be displayed if {@link #DISPLAY_SHOW_TITLE} is
-     * set. Set to null to disable the subtitle entirely.
+     * Set the action bar's subtitle. This will only be displayed if
+     * {@link #DISPLAY_SHOW_TITLE} is set. Set to null to disable the
+     * subtitle entirely.
      *
      * @param subtitle Subtitle to set
+     *
      * @see #setSubtitle(int)
      * @see #setDisplayOptions(int, int)
      */
     public abstract void setSubtitle(CharSequence subtitle);
 
     /**
-     * Set the action bar's subtitle. This will only be displayed if {@link #DISPLAY_SHOW_TITLE} is
-     * set.
+     * Set the action bar's subtitle. This will only be displayed if
+     * {@link #DISPLAY_SHOW_TITLE} is set.
      *
      * @param resId Resource ID of subtitle string to set
+     *
      * @see #setSubtitle(CharSequence)
      * @see #setDisplayOptions(int, int)
      */
     public abstract void setSubtitle(int resId);
 
     /**
-     * Set display options. This changes all display option bits at once. To change a limited subset
-     * of display options, see {@link #setDisplayOptions(int, int)}.
+     * Set display options. This changes all display option bits at once. To change
+     * a limited subset of display options, see {@link #setDisplayOptions(int, int)}.
      *
-     * @param options A combination of the bits defined by the DISPLAY_ constants defined in
-     *                ActionBar.
+     * @param options A combination of the bits defined by the DISPLAY_ constants
+     *                defined in ActionBar.
      */
-    public abstract void setDisplayOptions(int options);
+    public abstract void setDisplayOptions(@DisplayOptions int options);
 
     /**
-     * Set selected display options. Only the options specified by mask will be changed. To change
-     * all display option bits at once, see {@link #setDisplayOptions(int)}.
+     * Set selected display options. Only the options specified by mask will be changed.
+     * To change all display option bits at once, see {@link #setDisplayOptions(int)}.
      *
-     * <p>Example: setDisplayOptions(0, DISPLAY_SHOW_HOME) will disable the {@link
-     * #DISPLAY_SHOW_HOME} option. setDisplayOptions(DISPLAY_SHOW_HOME, DISPLAY_SHOW_HOME |
-     * DISPLAY_USE_LOGO) will enable {@link #DISPLAY_SHOW_HOME} and disable {@link
-     * #DISPLAY_USE_LOGO}.
+     * <p>Example: setDisplayOptions(0, DISPLAY_SHOW_HOME) will disable the
+     * {@link #DISPLAY_SHOW_HOME} option.
+     * setDisplayOptions(DISPLAY_SHOW_HOME, DISPLAY_SHOW_HOME | DISPLAY_USE_LOGO)
+     * will enable {@link #DISPLAY_SHOW_HOME} and disable {@link #DISPLAY_USE_LOGO}.
      *
-     * @param options A combination of the bits defined by the DISPLAY_ constants defined in
-     *                ActionBar.
-     * @param mask    A bit mask declaring which display options should be changed.
+     * @param options A combination of the bits defined by the DISPLAY_ constants
+     *                defined in ActionBar.
+     * @param mask A bit mask declaring which display options should be changed.
      */
-    public abstract void setDisplayOptions(int options, int mask);
+    public abstract void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask);
 
     /**
-     * Set whether to display the activity logo rather than the activity icon. A logo is often a
-     * wider, more detailed image.
+     * Set whether to display the activity logo rather than the activity icon.
+     * A logo is often a wider, more detailed image.
      *
      * <p>To set several display options at once, see the setDisplayOptions methods.
      *
      * @param useLogo true to use the activity logo, false to use the activity icon.
+     *
      * @see #setDisplayOptions(int)
      * @see #setDisplayOptions(int, int)
      */
     public abstract void setDisplayUseLogoEnabled(boolean useLogo);
 
     /**
-     * Set whether to include the application home affordance in the action bar. Home is presented
-     * as either an activity icon or logo.
+     * Set whether to include the application home affordance in the action bar.
+     * Home is presented as either an activity icon or logo.
      *
      * <p>To set several display options at once, see the setDisplayOptions methods.
      *
      * @param showHome true to show home, false otherwise.
+     *
      * @see #setDisplayOptions(int)
      * @see #setDisplayOptions(int, int)
      */
     public abstract void setDisplayShowHomeEnabled(boolean showHome);
 
     /**
-     * Set whether home should be displayed as an "up" affordance. Set this to true if selecting
-     * "home" returns up by a single level in your UI rather than back to the top level or front
-     * page.
+     * Set whether home should be displayed as an "up" affordance.
+     * Set this to true if selecting "home" returns up by a single level in your UI
+     * rather than back to the top level or front page.
      *
      * <p>To set several display options at once, see the setDisplayOptions methods.
      *
-     * @param showHomeAsUp true to show the user that selecting home will return one level up rather
-     *                     than to the top level of the app.
+     * @param showHomeAsUp true to show the user that selecting home will return one
+     *                     level up rather than to the top level of the app.
+     *
      * @see #setDisplayOptions(int)
      * @see #setDisplayOptions(int, int)
      */
@@ -395,41 +500,40 @@
      *
      * <p>To set several display options at once, see the setDisplayOptions methods.
      *
-     * @param showCustom true if the currently set custom view should be displayed, false
-     *                   otherwise.
+     * @param showCustom true if the currently set custom view should be displayed, false otherwise.
+     *
      * @see #setDisplayOptions(int)
      * @see #setDisplayOptions(int, int)
      */
     public abstract void setDisplayShowCustomEnabled(boolean showCustom);
 
     /**
-     * Set the ActionBar's background. This will be used for the primary action bar.
+     * Set the ActionBar's background. This will be used for the primary
+     * action bar.
      *
      * @param d Background drawable
      * @see #setStackedBackgroundDrawable(Drawable)
      * @see #setSplitBackgroundDrawable(Drawable)
      */
-    public abstract void setBackgroundDrawable(Drawable d);
+    public abstract void setBackgroundDrawable(@Nullable Drawable d);
 
     /**
-     * Set the ActionBar's stacked background. This will appear in the second row/stacked bar on
-     * some devices and configurations.
+     * Set the ActionBar's stacked background. This will appear
+     * in the second row/stacked bar on some devices and configurations.
      *
      * @param d Background drawable for the stacked row
      */
-    public void setStackedBackgroundDrawable(Drawable d) {
-    }
+    public void setStackedBackgroundDrawable(Drawable d) { }
 
     /**
-     * Set the ActionBar's split background. This will appear in the split action bar containing
-     * menu-provided action buttons on some devices and configurations
-     *
+     * Set the ActionBar's split background. This will appear in
+     * the split action bar containing menu-provided action buttons
+     * on some devices and configurations.
      * <p>You can enable split action bar with {@link android.R.attr#uiOptions}
      *
      * @param d Background drawable for the split bar
      */
-    public void setSplitBackgroundDrawable(Drawable d) {
-    }
+    public void setSplitBackgroundDrawable(Drawable d) { }
 
     /**
      * @return The current custom view.
@@ -437,33 +541,41 @@
     public abstract View getCustomView();
 
     /**
-     * Returns the current ActionBar title in standard mode. Returns null if {@link
-     * #getNavigationMode()} would not return {@link #NAVIGATION_MODE_STANDARD}.
+     * Returns the current ActionBar title in standard mode.
+     * Returns null if {@link #getNavigationMode()} would not return
+     * {@link #NAVIGATION_MODE_STANDARD}.
      *
      * @return The current ActionBar title or null.
      */
+    @Nullable
     public abstract CharSequence getTitle();
 
     /**
-     * Returns the current ActionBar subtitle in standard mode. Returns null if {@link
-     * #getNavigationMode()} would not return {@link #NAVIGATION_MODE_STANDARD}.
+     * Returns the current ActionBar subtitle in standard mode.
+     * Returns null if {@link #getNavigationMode()} would not return
+     * {@link #NAVIGATION_MODE_STANDARD}.
      *
      * @return The current ActionBar subtitle or null.
      */
+    @Nullable
     public abstract CharSequence getSubtitle();
 
     /**
      * Returns the current navigation mode. The result will be one of:
-     *
-     * <ul><li>{@link
-     * #NAVIGATION_MODE_STANDARD}</li>
-     *
+     * <ul>
+     * <li>{@link #NAVIGATION_MODE_STANDARD}</li>
      * <li>{@link #NAVIGATION_MODE_LIST}</li>
-     *
-     * <li>{@link #NAVIGATION_MODE_TABS}</li></ul>
+     * <li>{@link #NAVIGATION_MODE_TABS}</li>
+     * </ul>
      *
      * @return The current navigation mode.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
+    @NavigationMode
     public abstract int getNavigationMode();
 
     /**
@@ -473,51 +585,79 @@
      * @see #NAVIGATION_MODE_STANDARD
      * @see #NAVIGATION_MODE_LIST
      * @see #NAVIGATION_MODE_TABS
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
-    public abstract void setNavigationMode(int mode);
+    public abstract void setNavigationMode(@NavigationMode int mode);
 
     /**
      * @return The current set of display options.
      */
+    @DisplayOptions
     public abstract int getDisplayOptions();
 
     /**
-     * Create and return a new {@link Tab}. This tab will not be included in the action bar until it
-     * is added.
+     * Create and return a new {@link Tab}.
+     * This tab will not be included in the action bar until it is added.
      *
-     * <p>Very often tabs will be used to switch between {@link Fragment} objects.  Here is a
-     * typical implementation of such tabs:</p>
+     * <p>Very often tabs will be used to switch between {@link Fragment}
+     * objects.  Here is a typical implementation of such tabs:</p>
      *
      * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabs.java
-     * complete}
+     *      complete}
      *
      * @return A new Tab
+     *
      * @see #addTab(Tab)
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public abstract Tab newTab();
 
     /**
-     * Add a tab for use in tabbed navigation mode. The tab will be added at the end of the list. If
-     * this is the first tab to be added it will become the selected tab.
+     * Add a tab for use in tabbed navigation mode. The tab will be added at the end of the list.
+     * If this is the first tab to be added it will become the selected tab.
      *
      * @param tab Tab to add
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public abstract void addTab(Tab tab);
 
     /**
      * Add a tab for use in tabbed navigation mode. The tab will be added at the end of the list.
      *
-     * @param tab         Tab to add
+     * @param tab Tab to add
      * @param setSelected True if the added tab should become the selected tab.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public abstract void addTab(Tab tab, boolean setSelected);
 
     /**
      * Add a tab for use in tabbed navigation mode. The tab will be inserted at
-     * <code>position</code>. If this is the first tab to be added it will become the selected tab.
+     * <code>position</code>. If this is the first tab to be added it will become
+     * the selected tab.
      *
-     * @param tab      The tab to add
+     * @param tab The tab to add
      * @param position The new position of the tab
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public abstract void addTab(Tab tab, int position);
 
@@ -525,30 +665,50 @@
      * Add a tab for use in tabbed navigation mode. The tab will be insterted at
      * <code>position</code>.
      *
-     * @param tab         The tab to add
-     * @param position    The new position of the tab
+     * @param tab The tab to add
+     * @param position The new position of the tab
      * @param setSelected True if the added tab should become the selected tab.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public abstract void addTab(Tab tab, int position, boolean setSelected);
 
     /**
-     * Remove a tab from the action bar. If the removed tab was selected it will be deselected and
-     * another tab will be selected if present.
+     * Remove a tab from the action bar. If the removed tab was selected it will be deselected
+     * and another tab will be selected if present.
      *
      * @param tab The tab to remove
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public abstract void removeTab(Tab tab);
 
     /**
-     * Remove a tab from the action bar. If the removed tab was selected it will be deselected and
-     * another tab will be selected if present.
+     * Remove a tab from the action bar. If the removed tab was selected it will be deselected
+     * and another tab will be selected if present.
      *
      * @param position Position of the tab to remove
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public abstract void removeTabAt(int position);
 
     /**
      * Remove all tabs from the action bar and deselect the current tab.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public abstract void removeAllTabs();
 
@@ -558,21 +718,38 @@
      * <p>Note: If you want to select by index, use {@link #setSelectedNavigationItem(int)}.</p>
      *
      * @param tab Tab to select
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public abstract void selectTab(Tab tab);
 
     /**
-     * Returns the currently selected tab if in tabbed navigation mode and there is at least one tab
-     * present.
+     * Returns the currently selected tab if in tabbed navigation mode and there is at least
+     * one tab present.
      *
      * @return The currently selected tab or null
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
+    @Nullable
     public abstract Tab getSelectedTab();
 
     /**
      * Returns the tab at the specified index.
      *
      * @param index Index value in the range 0-get
+     * @return
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public abstract Tab getTabAt(int index);
 
@@ -580,6 +757,11 @@
      * Returns the number of tabs currently registered with the action bar.
      *
      * @return Tab count
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public abstract int getTabCount();
 
@@ -591,24 +773,28 @@
     public abstract int getHeight();
 
     /**
-     * Show the ActionBar if it is not currently showing. If the window hosting the ActionBar does
-     * not have the feature {@link Window#FEATURE_ACTION_BAR_OVERLAY} it will resize application
+     * Show the ActionBar if it is not currently showing.
+     * If the window hosting the ActionBar does not have the feature
+     * {@link Window#FEATURE_ACTION_BAR_OVERLAY} it will resize application
      * content to fit the new space available.
      *
-     * <p>If you are hiding the ActionBar through {@link View#SYSTEM_UI_FLAG_FULLSCREEN
-     * View.SYSTEM_UI_FLAG_FULLSCREEN}, you should not call this function directly.
+     * <p>If you are hiding the ActionBar through
+     * {@link View#SYSTEM_UI_FLAG_FULLSCREEN View.SYSTEM_UI_FLAG_FULLSCREEN},
+     * you should not call this function directly.
      */
     public abstract void show();
 
     /**
-     * Hide the ActionBar if it is currently showing. If the window hosting the ActionBar does not
-     * have the feature {@link Window#FEATURE_ACTION_BAR_OVERLAY} it will resize application content
-     * to fit the new space available.
+     * Hide the ActionBar if it is currently showing.
+     * If the window hosting the ActionBar does not have the feature
+     * {@link Window#FEATURE_ACTION_BAR_OVERLAY} it will resize application
+     * content to fit the new space available.
      *
-     * <p>Instead of calling this function directly, you can also cause an ActionBar using the
-     * overlay feature to hide through {@link View#SYSTEM_UI_FLAG_FULLSCREEN
-     * View.SYSTEM_UI_FLAG_FULLSCREEN}. Hiding the ActionBar through this system UI flag allows you
-     * to more seamlessly hide it in conjunction with other screen decorations.
+     * <p>Instead of calling this function directly, you can also cause an
+     * ActionBar using the overlay feature to hide through
+     * {@link View#SYSTEM_UI_FLAG_FULLSCREEN View.SYSTEM_UI_FLAG_FULLSCREEN}.
+     * Hiding the ActionBar through this system UI flag allows you to more
+     * seamlessly hide it in conjunction with other screen decorations.
      */
     public abstract void hide();
 
@@ -625,35 +811,35 @@
     public abstract void addOnMenuVisibilityListener(OnMenuVisibilityListener listener);
 
     /**
-     * Remove a menu visibility listener. This listener will no longer receive menu visibility
-     * change events.
+     * Remove a menu visibility listener. This listener will no longer receive menu
+     * visibility change events.
      *
      * @param listener A listener to remove that was previously added
      */
     public abstract void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener);
 
     /**
-     * Enable or disable the "home" button in the corner of the action bar. (Note that this is the
-     * application home/up affordance on the action bar, not the systemwide home button.)
+     * Enable or disable the "home" button in the corner of the action bar. (Note that this
+     * is the application home/up affordance on the action bar, not the systemwide home
+     * button.)
      *
-     * <p>This defaults to true for packages targeting &lt; API 14. For packages targeting API 14 or
-     * greater, the application should call this method to enable interaction with the home/up
-     * affordance.
+     * <p>This defaults to true for packages targeting &lt; API 14. For packages targeting
+     * API 14 or greater, the application should call this method to enable interaction
+     * with the home/up affordance.
      *
-     * <p>Setting the {@link #DISPLAY_HOME_AS_UP} display option will automatically enable the home
-     * button.
+     * <p>Setting the {@link #DISPLAY_HOME_AS_UP} display option will automatically enable
+     * the home button.
      *
      * @param enabled true to enable the home button, false to disable the home button.
      */
-    public void setHomeButtonEnabled(boolean enabled) {
-    }
+    public void setHomeButtonEnabled(boolean enabled) { }
 
     /**
-     * Returns a {@link Context} with an appropriate theme for creating views that will appear in
-     * the action bar. If you are inflating or instantiating custom views that will appear in an
-     * action bar, you should use the Context returned by this method. (This includes adapters used
-     * for list navigation mode.) This will ensure that views contrast properly against the action
-     * bar.
+     * Returns a {@link Context} with an appropriate theme for creating views that
+     * will appear in the action bar. If you are inflating or instantiating custom views
+     * that will appear in an action bar, you should use the Context returned by this method.
+     * (This includes adapters used for list navigation mode.)
+     * This will ensure that views contrast properly against the action bar.
      *
      * @return A themed Context for creating views
      */
@@ -662,6 +848,15 @@
     }
 
     /**
+     * Returns true if the Title field has been truncated during layout for lack
+     * of available space.
+     *
+     * @return true if the Title field has been truncated
+     * @hide pending API approval
+     */
+    public boolean isTitleTruncated() { return false; }
+
+    /**
      * Set an alternate drawable to display next to the icon/logo/title
      * when {@link #DISPLAY_HOME_AS_UP} is enabled. This can be useful if you are using
      * this mode to display an alternate selection for up navigation, such as a sliding drawer.
@@ -679,7 +874,7 @@
      * @see #setDisplayHomeAsUpEnabled(boolean)
      * @see #setHomeActionContentDescription(int)
      */
-    public void setHomeAsUpIndicator(Drawable indicator) {}
+    public void setHomeAsUpIndicator(@Nullable Drawable indicator) {}
 
     /**
      * Set an alternate drawable to display next to the icon/logo/title
@@ -693,14 +888,14 @@
      * call {@link #setHomeActionContentDescription(int) setHomeActionContentDescription()}
      * to provide a correct description of the action for accessibility support.</p>
      *
-     * @param resId Resource ID of a drawable to use for the up indicator, or null
+     * @param resId Resource ID of a drawable to use for the up indicator, or 0
      *              to use the theme's default
      *
      * @see #setDisplayOptions(int, int)
      * @see #setDisplayHomeAsUpEnabled(boolean)
      * @see #setHomeActionContentDescription(int)
      */
-    public void setHomeAsUpIndicator(int resId) {}
+    public void setHomeAsUpIndicator(@DrawableRes int resId) {}
 
     /**
      * Set an alternate description for the Home/Up action, when enabled.
@@ -719,7 +914,7 @@
      * @see #setHomeAsUpIndicator(int)
      * @see #setHomeAsUpIndicator(android.graphics.drawable.Drawable)
      */
-    public void setHomeActionContentDescription(CharSequence description) {}
+    public void setHomeActionContentDescription(@Nullable CharSequence description) {}
 
     /**
      * Set an alternate description for the Home/Up action, when enabled.
@@ -739,67 +934,181 @@
      * @see #setHomeAsUpIndicator(int)
      * @see #setHomeAsUpIndicator(android.graphics.drawable.Drawable)
      */
-    public void setHomeActionContentDescription(int resId) {}
+    public void setHomeActionContentDescription(@StringRes int resId) {}
 
     /**
-     * Listener for receiving {@link ActionBar} navigation events.
+     * Enable hiding the action bar on content scroll.
      *
-     * <p class="note"><strong>Note:</strong> This interface is included in the <a
-     * href="{@docRoot}tools/extras/support-library.html">support library</a> for compatibility
-     * with API level 7 and higher. If you're developing your app for API level 11 and higher
-     * <em>only</em>, you should instead use the framework {@link
-     * android.app.ActionBar.OnNavigationListener} interface.</p>
+     * <p>If enabled, the action bar will scroll out of sight along with a
+     * {@link View#setNestedScrollingEnabled(boolean) nested scrolling child} view's content.
+     * The action bar must be in {@link Window#FEATURE_ACTION_BAR_OVERLAY overlay mode}
+     * to enable hiding on content scroll.</p>
+     *
+     * <p>When partially scrolled off screen the action bar is considered
+     * {@link #hide() hidden}. A call to {@link #show() show} will cause it to return to full view.
+     * </p>
+     * @param hideOnContentScroll true to enable hiding on content scroll.
+     */
+    public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) {
+        if (hideOnContentScroll) {
+            throw new UnsupportedOperationException("Hide on content scroll is not supported in " +
+                    "this action bar configuration.");
+        }
+    }
+
+    /**
+     * Return whether the action bar is configured to scroll out of sight along with
+     * a {@link View#setNestedScrollingEnabled(boolean) nested scrolling child}.
+     *
+     * @return true if hide-on-content-scroll is enabled
+     * @see #setHideOnContentScrollEnabled(boolean)
+     */
+    public boolean isHideOnContentScrollEnabled() {
+        return false;
+    }
+
+    /**
+     * Return the current vertical offset of the action bar.
+     *
+     * <p>The action bar's current hide offset is the distance that the action bar is currently
+     * scrolled offscreen in pixels. The valid range is 0 (fully visible) to the action bar's
+     * current measured {@link #getHeight() height} (fully invisible).</p>
+     *
+     * @return The action bar's offset toward its fully hidden state in pixels
+     */
+    public int getHideOffset() {
+        return 0;
+    }
+
+    /**
+     * Set the current hide offset of the action bar.
+     *
+     * <p>The action bar's current hide offset is the distance that the action bar is currently
+     * scrolled offscreen in pixels. The valid range is 0 (fully visible) to the action bar's
+     * current measured {@link #getHeight() height} (fully invisible).</p>
+     *
+     * @param offset The action bar's offset toward its fully hidden state in pixels.
+     */
+    public void setHideOffset(int offset) {
+        if (offset != 0) {
+            throw new UnsupportedOperationException("Setting an explicit action bar hide offset " +
+                    "is not supported in this action bar configuration.");
+        }
+    }
+
+    /**
+     * Set the Z-axis elevation of the action bar in pixels.
+     *
+     * <p>The action bar's elevation is the distance it is placed from its parent surface. Higher
+     * values are closer to the user.</p>
+     *
+     * @param elevation Elevation value in pixels
+     */
+    public void setElevation(float elevation) {
+        if (elevation != 0) {
+            throw new UnsupportedOperationException("Setting a non-zero elevation is " +
+                    "not supported in this action bar configuration.");
+        }
+    }
+
+    /**
+     * Get the Z-axis elevation of the action bar in pixels.
+     *
+     * <p>The action bar's elevation is the distance it is placed from its parent surface. Higher
+     * values are closer to the user.</p>
+     *
+     * @return Elevation value in pixels
+     */
+    public float getElevation() {
+        return 0;
+    }
+
+    /** @hide */
+    public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) {
+    }
+
+    /** @hide */
+    public void setShowHideAnimationEnabled(boolean enabled) {
+    }
+
+    /** @hide */
+    public void onConfigurationChanged(Configuration config) {
+    }
+
+    /** @hide */
+    public void dispatchMenuVisibilityChanged(boolean visible) {
+    }
+
+    /** @hide */
+    public ActionMode startActionMode(ActionMode.Callback callback) {
+        return null;
+    }
+
+    /** @hide */
+    public boolean openOptionsMenu() {
+        return false;
+    }
+
+    /** @hide */
+    public boolean invalidateOptionsMenu() {
+        return false;
+    }
+
+    /** @hide */
+    public boolean onMenuKeyEvent(KeyEvent event) {
+        return false;
+    }
+
+    /** @hide */
+    public boolean collapseActionView() {
+        return false;
+    }
+
+    /**
+     * Listener interface for ActionBar navigation events.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public interface OnNavigationListener {
-
         /**
-         * This method is called whenever a navigation item in your action bar is selected.
+         * This method is called whenever a navigation item in your action bar
+         * is selected.
          *
          * @param itemPosition Position of the item clicked.
-         * @param itemId       ID of the item clicked.
+         * @param itemId ID of the item clicked.
          * @return True if the event was handled, false otherwise.
          */
         public boolean onNavigationItemSelected(int itemPosition, long itemId);
     }
 
     /**
-     * Listener for receiving events when {@link ActionBar} items are shown or hidden.
-     *
-     * <p class="note"><strong>Note:</strong> This interface is included in the <a
-     * href="{@docRoot}tools/extras/support-library.html">support library</a> for compatibility
-     * with API level 7 and higher. If you're developing your app for API level 11 and higher
-     * <em>only</em>, you should instead use the framework {@link
-     * android.app.ActionBar.OnMenuVisibilityListener} interface.</p>
+     * Listener for receiving events when action bar menus are shown or hidden.
      */
     public interface OnMenuVisibilityListener {
 
         /**
-         * Called when an action bar menu is shown or hidden. Applications may want to use this to
-         * tune auto-hiding behavior for the action bar or pause/resume video playback, gameplay, or
-         * other activity within the main content area.
+         * Called when an action bar menu is shown or hidden. Applications may want to use
+         * this to tune auto-hiding behavior for the action bar or pause/resume video playback,
+         * gameplay, or other activity within the main content area.
          *
-         * @param isVisible True if an action bar menu is now visible, false if no action bar menus
-         *                  are visible.
+         * @param isVisible True if an action bar menu is now visible, false if no action bar
+         *                  menus are visible.
          */
         public void onMenuVisibilityChanged(boolean isVisible);
     }
 
     /**
-     * A tab in the action bar that manages the hiding and showing of {@link Fragment}s.
+     * A tab in the action bar.
      *
-     * <p class="note"><strong>Note:</strong> This class is included in the <a
-     * href="{@docRoot}tools/extras/support-library.html">support library</a> for compatibility
-     * with API level 7 and higher. If you're developing your app for API level 11 and higher
-     * <em>only</em>, you should instead use the framework {@link android.app.ActionBar.Tab}
-     * class.</p>
+     * <p>Tabs manage the hiding and showing of {@link Fragment}s.
      *
-     * <div class="special reference">
-     * <h3>Developer Guides</h3>
-     *
-     * <p>For information about how to use action bar tabs,
-     * read the <a href="{@docRoot}guide/topics/ui/actionbar.html#Tabs">Action
-     * Bar</a> API guide.</p>
-     * </div>
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public static abstract class Tab {
 
@@ -846,11 +1155,11 @@
          * @param resId Resource ID referring to the drawable to use as an icon
          * @return The current instance for call chaining
          */
-        public abstract Tab setIcon(int resId);
+        public abstract Tab setIcon(@DrawableRes int resId);
 
         /**
-         * Set the text displayed on this tab. Text may be truncated if there is not room to display
-         * the entire string.
+         * Set the text displayed on this tab. Text may be truncated if there is not
+         * room to display the entire string.
          *
          * @param text The text to display
          * @return The current instance for call chaining
@@ -858,8 +1167,8 @@
         public abstract Tab setText(CharSequence text);
 
         /**
-         * Set the text displayed on this tab. Text may be truncated if there is not room to display
-         * the entire string.
+         * Set the text displayed on this tab. Text may be truncated if there is not
+         * room to display the entire string.
          *
          * @param resId A resource ID referring to the text that should be displayed
          * @return The current instance for call chaining
@@ -867,8 +1176,8 @@
         public abstract Tab setText(int resId);
 
         /**
-         * Set a custom view to be used for this tab. This overrides values set by {@link
-         * #setText(CharSequence)} and {@link #setIcon(Drawable)}.
+         * Set a custom view to be used for this tab. This overrides values set by
+         * {@link #setText(CharSequence)} and {@link #setIcon(Drawable)}.
          *
          * @param view Custom view to be used as a tab.
          * @return The current instance for call chaining
@@ -876,8 +1185,8 @@
         public abstract Tab setCustomView(View view);
 
         /**
-         * Set a custom view to be used for this tab. This overrides values set by {@link
-         * #setText(CharSequence)} and {@link #setIcon(Drawable)}.
+         * Set a custom view to be used for this tab. This overrides values set by
+         * {@link #setText(CharSequence)} and {@link #setIcon(Drawable)}.
          *
          * @param layoutResId A layout resource to inflate and use as a custom tab view
          * @return The current instance for call chaining
@@ -905,8 +1214,8 @@
         public abstract Object getTag();
 
         /**
-         * Set the {@link TabListener} that will handle switching to and from this tab. All tabs
-         * must have a TabListener set before being added to the ActionBar.
+         * Set the {@link TabListener} that will handle switching to and from this tab.
+         * All tabs must have a TabListener set before being added to the ActionBar.
          *
          * @param listener Listener to handle tab selection events
          * @return The current instance for call chaining
@@ -919,8 +1228,8 @@
         public abstract void select();
 
         /**
-         * Set a description of this tab's content for use in accessibility support. If no content
-         * description is provided the title will be used.
+         * Set a description of this tab's content for use in accessibility support.
+         * If no content description is provided the title will be used.
          *
          * @param resId A resource ID referring to the description text
          * @return The current instance for call chaining
@@ -930,8 +1239,8 @@
         public abstract Tab setContentDescription(int resId);
 
         /**
-         * Set a description of this tab's content for use in accessibility support. If no content
-         * description is provided the title will be used.
+         * Set a description of this tab's content for use in accessibility support.
+         * If no content description is provided the title will be used.
          *
          * @param contentDesc Description of this tab's content
          * @return The current instance for call chaining
@@ -951,22 +1260,12 @@
     }
 
     /**
-     * Callback interface invoked when an {@link ActionBar.Tab} is focused, unfocused, added, or
-     * removed.
+     * Callback interface invoked when a tab is focused, unfocused, added, or removed.
      *
-     * <p class="note"><strong>Note:</strong> This interface is included in the <a
-     * href="{@docRoot}tools/extras/support-library.html">support library</a> for compatibility
-     * with API level 7 and higher. If you're developing your app for API level 11 and higher
-     * <em>only</em>, you should instead use the framework {@link android.app.ActionBar.TabListener}
-     * interface.</p>
-     *
-     * <div class="special reference">
-     * <h3>Developer Guides</h3>
-     *
-     * <p>For information about how to use action bar tabs,
-     * read the <a href="{@docRoot}guide/topics/ui/actionbar.html#Tabs">Action
-     * Bar</a> API guide.</p>
-     * </div>
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
      */
     public interface TabListener {
 
@@ -974,10 +1273,10 @@
          * Called when a tab enters the selected state.
          *
          * @param tab The tab that was selected
-         * @param ft  A {@link FragmentTransaction} for queuing fragment operations to execute
-         *            during a tab switch. The previous tab's unselect and this tab's select will be
-         *            executed in a single transaction. This FragmentTransaction does not support
-         *            being added to the back stack.
+         * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute
+         *        during a tab switch. The previous tab's unselect and this tab's select will be
+         *        executed in a single transaction. This FragmentTransaction does not support
+         *        being added to the back stack.
          */
         public void onTabSelected(Tab tab, FragmentTransaction ft);
 
@@ -985,51 +1284,47 @@
          * Called when a tab exits the selected state.
          *
          * @param tab The tab that was unselected
-         * @param ft  A {@link FragmentTransaction} for queuing fragment operations to execute
-         *            during a tab switch. This tab's unselect and the newly selected tab's select
-         *            will be executed in a single transaction. This FragmentTransaction does not
-         *            support being added to the back stack.
+         * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute
+         *        during a tab switch. This tab's unselect and the newly selected tab's select
+         *        will be executed in a single transaction. This FragmentTransaction does not
+         *        support being added to the back stack.
          */
         public void onTabUnselected(Tab tab, FragmentTransaction ft);
 
         /**
-         * Called when a tab that is already selected is chosen again by the user. Some applications
-         * may use this action to return to the top level of a category.
+         * Called when a tab that is already selected is chosen again by the user.
+         * Some applications may use this action to return to the top level of a category.
          *
          * @param tab The tab that was reselected.
-         * @param ft  A {@link FragmentTransaction} for queuing fragment operations to execute once
-         *            this method returns. This FragmentTransaction does not support being added to
-         *            the back stack.
+         * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute
+         *        once this method returns. This FragmentTransaction does not support
+         *        being added to the back stack.
          */
         public void onTabReselected(Tab tab, FragmentTransaction ft);
     }
 
     /**
      * Per-child layout information associated with action bar custom views.
-     *
-     * @attr ref android.R.styleable#ActionBar_LayoutParams_layout_gravity
      */
-    public static class LayoutParams extends MarginLayoutParams {
-
+    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
         /**
          * Gravity for the view associated with these LayoutParams.
          *
          * @see android.view.Gravity
          */
-        public int gravity = -1;
+        public int gravity = Gravity.NO_GRAVITY;
 
-        public LayoutParams(Context c, AttributeSet attrs) {
+        public LayoutParams(@NonNull Context c, AttributeSet attrs) {
             super(c, attrs);
 
-            TypedArray a = c.obtainStyledAttributes(attrs,
-                    R.styleable.ActionBarLayout);
-            gravity = a.getInt(R.styleable.ActionBarLayout_android_layout_gravity, -1);
+            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ActionBarLayout);
+            gravity = a.getInt(R.styleable.ActionBarLayout_android_layout_gravity, Gravity.NO_GRAVITY);
             a.recycle();
         }
 
         public LayoutParams(int width, int height) {
             super(width, height);
-            this.gravity = Gravity.CENTER_VERTICAL | Gravity.LEFT;
+            this.gravity = Gravity.CENTER_VERTICAL | GravityCompat.START;
         }
 
         public LayoutParams(int width, int height, int gravity) {
@@ -1038,7 +1333,7 @@
         }
 
         public LayoutParams(int gravity) {
-            this(WRAP_CONTENT, FILL_PARENT, gravity);
+            this(WRAP_CONTENT, MATCH_PARENT, gravity);
         }
 
         public LayoutParams(LayoutParams source) {
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
index b56b448..551b00b 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
@@ -21,6 +21,8 @@
 import android.content.res.Configuration;
 import android.os.Build;
 import android.os.Bundle;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.Nullable;
 import android.support.v4.app.ActionBarDrawerToggle;
 import android.support.v4.app.ActivityCompat;
 import android.support.v4.app.FragmentActivity;
@@ -28,9 +30,9 @@
 import android.support.v4.app.TaskStackBuilder;
 import android.support.v4.view.WindowCompat;
 import android.support.v7.view.ActionMode;
+import android.support.v7.widget.Toolbar;
 import android.view.Menu;
 import android.view.MenuInflater;
-import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
@@ -53,7 +55,8 @@
  */
 public class ActionBarActivity extends FragmentActivity implements ActionBar.Callback,
         TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {
-    ActionBarActivityDelegate mImpl;
+
+    private ActionBarActivityDelegate mDelegate;
 
     /**
      * Support library version of {@link Activity#getActionBar}.
@@ -63,63 +66,81 @@
      * @return The Activity's ActionBar, or null if it does not have one.
      */
     public ActionBar getSupportActionBar() {
-        return mImpl.getSupportActionBar();
+        return getDelegate().getSupportActionBar();
+    }
+
+    /**
+     * Set a {@link android.widget.Toolbar Toolbar} to act as the {@link ActionBar} for this
+     * Activity window.
+     *
+     * <p>When set to a non-null value the {@link #getActionBar()} method will return
+     * an {@link ActionBar} object that can be used to control the given toolbar as if it were
+     * a traditional window decor action bar. The toolbar's menu will be populated with the
+     * Activity's options menu and the navigation button will be wired through the standard
+     * {@link android.R.id#home home} menu select action.</p>
+     *
+     * <p>In order to use a Toolbar within the Activity's window content the application
+     * must not request the window feature {@link Window#FEATURE_ACTION_BAR FEATURE_ACTION_BAR}.</p>
+     *
+     * @param toolbar Toolbar to set as the Activity's action bar
+     */
+    public void setSupportActionBar(@Nullable Toolbar toolbar) {
+        getDelegate().setSupportActionBar(toolbar);
     }
 
     @Override
     public MenuInflater getMenuInflater() {
-        return mImpl.getMenuInflater();
+        return getDelegate().getMenuInflater();
     }
 
     @Override
-    public void setContentView(int layoutResID) {
-        mImpl.setContentView(layoutResID);
+    public void setContentView(@LayoutRes int layoutResID) {
+        getDelegate().setContentView(layoutResID);
     }
 
     @Override
     public void setContentView(View view) {
-        mImpl.setContentView(view);
+        getDelegate().setContentView(view);
     }
 
     @Override
     public void setContentView(View view, ViewGroup.LayoutParams params) {
-        mImpl.setContentView(view, params);
+        getDelegate().setContentView(view, params);
     }
 
     @Override
     public void addContentView(View view, ViewGroup.LayoutParams params) {
-        mImpl.addContentView(view, params);
+        getDelegate().addContentView(view, params);
     }
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
-        mImpl = ActionBarActivityDelegate.createDelegate(this);
         super.onCreate(savedInstanceState);
-        mImpl.onCreate(savedInstanceState);
+        getDelegate().onCreate(savedInstanceState);
     }
 
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        mImpl.onConfigurationChanged(newConfig);
+        getDelegate().onConfigurationChanged(newConfig);
     }
 
     @Override
     protected void onStop() {
         super.onStop();
-        mImpl.onStop();
+        getDelegate().onStop();
     }
 
     @Override
     protected void onPostResume() {
         super.onPostResume();
-        mImpl.onPostResume();
+        getDelegate().onPostResume();
     }
 
     @Override
     public View onCreatePanelView(int featureId) {
         if (featureId == Window.FEATURE_OPTIONS_PANEL) {
-            return mImpl.onCreatePanelView(featureId);
+            return getDelegate().onCreatePanelView(featureId);
         } else {
             return super.onCreatePanelView(featureId);
         }
@@ -127,7 +148,7 @@
 
     @Override
     public final boolean onMenuItemSelected(int featureId, android.view.MenuItem item) {
-        if (mImpl.onMenuItemSelected(featureId, item)) {
+        if (super.onMenuItemSelected(featureId, item)) {
             return true;
         }
 
@@ -142,7 +163,7 @@
     @Override
     protected void onTitleChanged(CharSequence title, int color) {
         super.onTitleChanged(title, color);
-        mImpl.onTitleChanged(title);
+        getDelegate().onTitleChanged(title);
     }
 
     /**
@@ -160,16 +181,19 @@
      * @see android.view.Window#requestFeature
      */
     public boolean supportRequestWindowFeature(int featureId) {
-        return mImpl.supportRequestWindowFeature(featureId);
+        return getDelegate().supportRequestWindowFeature(featureId);
     }
 
     @Override
     public void supportInvalidateOptionsMenu() {
-        // Only call up to super on ICS+
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
-            super.supportInvalidateOptionsMenu();
-        }
-        mImpl.supportInvalidateOptionsMenu();
+        getDelegate().supportInvalidateOptionsMenu();
+    }
+
+    /**
+     * @hide
+     */
+    public void invalidateOptionsMenu() {
+        getDelegate().supportInvalidateOptionsMenu();
     }
 
     /**
@@ -191,17 +215,23 @@
     }
 
     public ActionMode startSupportActionMode(ActionMode.Callback callback) {
-        return mImpl.startSupportActionMode(callback);
+        return getDelegate().startSupportActionMode(callback);
     }
 
     @Override
     public boolean onCreatePanelMenu(int featureId, Menu menu) {
-        return mImpl.onCreatePanelMenu(featureId, menu);
+        return getDelegate().onCreatePanelMenu(featureId, menu);
     }
 
     @Override
     public boolean onPreparePanel(int featureId, View view, Menu menu) {
-        return mImpl.onPreparePanel(featureId, view, menu);
+        return getDelegate().onPreparePanel(featureId, view, menu);
+    }
+
+    @Override
+    public void onPanelClosed(int featureId, Menu menu) {
+        super.onPanelClosed(featureId, menu);
+        getDelegate().onPanelClosed(featureId, menu);
     }
 
     /**
@@ -209,7 +239,7 @@
      */
     @Override
     protected boolean onPrepareOptionsPanel(View view, Menu menu) {
-        return mImpl.onPrepareOptionsPanel(view, menu);
+        return getDelegate().onPrepareOptionsPanel(view, menu);
     }
 
     void superSetContentView(int resId) {
@@ -240,13 +270,9 @@
         return super.onPrepareOptionsPanel(view, menu);
     }
 
-    boolean superOnMenuItemSelected(int featureId, MenuItem menuItem) {
-        return super.onMenuItemSelected(featureId, menuItem);
-    }
-
     @Override
     public void onBackPressed() {
-        if (!mImpl.onBackPressed()) {
+        if (!getDelegate().onBackPressed()) {
             super.onBackPressed();
         }
     }
@@ -262,7 +288,7 @@
      * @param visible Whether to show the progress bars in the title.
      */
     public void setSupportProgressBarVisibility(boolean visible) {
-        mImpl.setSupportProgressBarVisibility(visible);
+        getDelegate().setSupportProgressBarVisibility(visible);
     }
 
     /**
@@ -276,7 +302,7 @@
      * @param visible Whether to show the progress bars in the title.
      */
     public void setSupportProgressBarIndeterminateVisibility(boolean visible) {
-        mImpl.setSupportProgressBarIndeterminateVisibility(visible);
+        getDelegate().setSupportProgressBarIndeterminateVisibility(visible);
     }
 
     /**
@@ -291,7 +317,7 @@
      * @param indeterminate Whether the horizontal progress bar should be indeterminate.
      */
     public void setSupportProgressBarIndeterminate(boolean indeterminate) {
-        mImpl.setSupportProgressBarIndeterminate(indeterminate);
+        getDelegate().setSupportProgressBarIndeterminate(indeterminate);
     }
 
     /**
@@ -307,7 +333,7 @@
      *            bar will be completely filled and will fade out.
      */
     public void setSupportProgress(int progress) {
-        mImpl.setSupportProgress(progress);
+        getDelegate().setSupportProgress(progress);
     }
 
     /**
@@ -453,14 +479,14 @@
 
     @Override
     public final ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() {
-        return mImpl.getDrawerToggleDelegate();
+        return getDelegate().getDrawerToggleDelegate();
     }
 
     /**
      * Use {@link #onSupportContentChanged()} instead.
      */
     public final void onContentChanged() {
-        mImpl.onContentChanged();
+        getDelegate().onContentChanged();
     }
 
     /**
@@ -469,4 +495,11 @@
      */
     public void onSupportContentChanged() {
     }
+
+    private ActionBarActivityDelegate getDelegate() {
+        if (mDelegate == null) {
+            mDelegate = ActionBarActivityDelegate.createDelegate(this);
+        }
+        return mDelegate;
+    }
 }
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegate.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegate.java
index 798e359..631d6d2 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegate.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegate.java
@@ -25,10 +25,11 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.support.v4.app.ActionBarDrawerToggle;
-import android.support.v4.app.NavUtils;
 import android.support.v7.appcompat.R;
+import android.support.v7.internal.app.WindowCallback;
 import android.support.v7.internal.view.SupportMenuInflater;
 import android.support.v7.view.ActionMode;
+import android.support.v7.widget.Toolbar;
 import android.util.Log;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -39,18 +40,11 @@
 abstract class ActionBarActivityDelegate {
 
     static final String METADATA_UI_OPTIONS = "android.support.UI_OPTIONS";
-    static final String UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW = "splitActionBarWhenNarrow";
 
     private static final String TAG = "ActionBarActivityDelegate";
 
     static ActionBarActivityDelegate createDelegate(ActionBarActivity activity) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
-            return new ActionBarActivityDelegateJBMR2(activity);
-        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
-            return new ActionBarActivityDelegateJB(activity);
-        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
-            return new ActionBarActivityDelegateICS(activity);
-        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
             return new ActionBarActivityDelegateHC(activity);
         } else {
             return new ActionBarActivityDelegateBase(activity);
@@ -66,8 +60,10 @@
     boolean mHasActionBar;
     // true if this activity's action bar overlays other activity content.
     boolean mOverlayActionBar;
-
-    private boolean mEnableDefaultActionBarUp;
+    // true if this any action modes should overlay the activity content
+    boolean mOverlayActionMode;
+    // true if this activity is floating (e.g. Dialog)
+    boolean mIsFloating;
 
     ActionBarActivityDelegate(ActionBarActivity activity) {
         mActivity = activity;
@@ -81,18 +77,17 @@
         if (mHasActionBar || mOverlayActionBar) {
             if (mActionBar == null) {
                 mActionBar = createSupportActionBar();
-
-                if (mEnableDefaultActionBarUp) {
-                    mActionBar.setDisplayHomeAsUpEnabled(true);
-                }
             }
-        } else {
-            // If we're not set to have a Action Bar, null it just in case it's been set
-            mActionBar = null;
         }
         return mActionBar;
     }
 
+    protected final void setSupportActionBar(ActionBar actionBar) {
+        mActionBar = actionBar;
+    }
+
+    abstract void setSupportActionBar(Toolbar toolbar);
+
     MenuInflater getMenuInflater() {
         if (mMenuInflater == null) {
             mMenuInflater = new SupportMenuInflater(getActionBarThemedContext());
@@ -101,25 +96,19 @@
     }
 
     void onCreate(Bundle savedInstanceState) {
-        TypedArray a = mActivity.obtainStyledAttributes(R.styleable.ActionBarWindow);
+        TypedArray a = mActivity.obtainStyledAttributes(R.styleable.Theme);
 
-        if (!a.hasValue(R.styleable.ActionBarWindow_windowActionBar)) {
+        if (!a.hasValue(R.styleable.Theme_windowActionBar)) {
             a.recycle();
             throw new IllegalStateException(
                     "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
         }
 
-        mHasActionBar = a.getBoolean(R.styleable.ActionBarWindow_windowActionBar, false);
-        mOverlayActionBar = a.getBoolean(R.styleable.ActionBarWindow_windowActionBarOverlay, false);
+        mHasActionBar = a.getBoolean(R.styleable.Theme_windowActionBar, false);
+        mOverlayActionBar = a.getBoolean(R.styleable.Theme_windowActionBarOverlay, false);
+        mOverlayActionMode = a.getBoolean(R.styleable.Theme_windowActionModeOverlay, false);
+        mIsFloating = a.getBoolean(R.styleable.Theme_android_windowIsFloating, false);
         a.recycle();
-
-        if (NavUtils.getParentActivityName(mActivity) != null) {
-            if (mActionBar == null) {
-                mEnableDefaultActionBarUp = true;
-            } else {
-                mActionBar.setDisplayHomeAsUpEnabled(true);
-            }
-        }
     }
 
     abstract void onConfigurationChanged(Configuration newConfig);
@@ -147,6 +136,8 @@
 
     abstract boolean onPreparePanel(int featureId, View view, Menu menu);
 
+    abstract void onPanelClosed(int featureId, Menu menu);
+
     boolean onPrepareOptionsPanel(View view, Menu menu) {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
             // Call straight through to onPrepareOptionsMenu, bypassing super.onPreparePanel().
@@ -159,8 +150,6 @@
 
     abstract boolean onCreatePanelMenu(int featureId, Menu menu);
 
-    abstract boolean onMenuItemSelected(int featureId, MenuItem item);
-
     abstract boolean onBackPressed();
 
     abstract ActionMode startSupportActionMode(ActionMode.Callback callback);
@@ -181,7 +170,7 @@
 
     abstract void onContentChanged();
 
-    protected final String getUiOptionsFromMetadata() {
+    final String getUiOptionsFromMetadata() {
         try {
             PackageManager pm = mActivity.getPackageManager();
             ActivityInfo info = pm.getActivityInfo(mActivity.getComponentName(),
@@ -237,4 +226,28 @@
             }
         }
     }
+
+    abstract ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback);
+
+    final WindowCallback mWindowMenuCallback = new WindowCallback() {
+        @Override
+        public boolean onMenuItemSelected(int featureId, MenuItem menuItem) {
+            return mActivity.onMenuItemSelected(featureId, menuItem);
+        }
+
+        @Override
+        public boolean onCreatePanelMenu(int featureId, Menu menu) {
+            return mActivity.superOnCreatePanelMenu(featureId, menu);
+        }
+
+        @Override
+        public boolean onPreparePanel(int featureId, View menuView, Menu menu) {
+            return mActivity.superOnPreparePanel(featureId, menuView, menu);
+        }
+
+        @Override
+        public ActionMode startActionMode(ActionMode.Callback callback) {
+            return startSupportActionModeFromWindow(callback);
+        }
+    };
 }
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java
index ab5335a..ae28445 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java
@@ -18,49 +18,61 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
 import android.os.Bundle;
-import android.support.v4.app.ActionBarDrawerToggle;
+import android.support.v4.app.NavUtils;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewConfigurationCompat;
 import android.support.v4.view.WindowCompat;
 import android.support.v7.appcompat.R;
+import android.support.v7.internal.app.ToolbarActionBar;
+import android.support.v7.internal.app.WindowDecorActionBar;
+import android.support.v7.internal.view.StandaloneActionMode;
 import android.support.v7.internal.view.menu.ListMenuPresenter;
 import android.support.v7.internal.view.menu.MenuBuilder;
 import android.support.v7.internal.view.menu.MenuPresenter;
 import android.support.v7.internal.view.menu.MenuView;
-import android.support.v7.internal.view.menu.MenuWrapperFactory;
-import android.support.v7.internal.widget.ActionBarContainer;
 import android.support.v7.internal.widget.ActionBarContextView;
-import android.support.v7.internal.widget.ActionBarView;
-import android.support.v7.internal.widget.ProgressBarICS;
+import android.support.v7.internal.widget.DecorContentParent;
+import android.support.v7.internal.widget.ProgressBarCompat;
 import android.support.v7.view.ActionMode;
-import android.util.AttributeSet;
+import android.support.v7.widget.Toolbar;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.Gravity;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.view.ViewStub;
 import android.view.Window;
-import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
+import android.widget.PopupWindow;
 
-class ActionBarActivityDelegateBase extends ActionBarActivityDelegate implements
-        MenuPresenter.Callback, MenuBuilder.Callback {
+class ActionBarActivityDelegateBase extends ActionBarActivityDelegate
+        implements MenuBuilder.Callback {
     private static final String TAG = "ActionBarActivityDelegateBase";
 
-    private static final int[] ACTION_BAR_DRAWABLE_TOGGLE_ATTRS = new int[] {
-            R.attr.homeAsUpIndicator
-    };
+    private DecorContentParent mDecorContentParent;
+    private ActionMenuPresenterCallback mActionMenuPresenterCallback;
+    private PanelMenuPresenterCallback mPanelMenuPresenterCallback;
 
-    private ActionBarView mActionBarView;
     private ListMenuPresenter mListMenuPresenter;
     private MenuBuilder mMenu;
 
-    private ActionMode mActionMode;
+    ActionMode mActionMode;
+    ActionBarContextView mActionModeView;
+    PopupWindow mActionModePopup;
+    Runnable mShowActionModePopup;
 
     // true if we have installed a window sub-decor layout.
     private boolean mSubDecorInstalled;
+    private ViewGroup mWindowDecor;
 
     private CharSequence mTitleToSet;
 
@@ -70,17 +82,57 @@
     // Used for emulating PanelFeatureState
     private boolean mClosingActionMenu;
     private boolean mPanelIsPrepared;
-    private boolean mPanelRefreshContent;
+    private boolean mPanelRefreshMenuContent;
     private Bundle mPanelFrozenActionViewState;
 
+    private boolean mInvalidatePanelMenuPosted;
+    private final Runnable mInvalidatePanelMenuRunnable = new Runnable() {
+        @Override
+        public void run() {
+            supportInvalidateOptionsMenu();
+        }
+    };
+
+    private boolean mEnableDefaultActionBarUp;
+
     ActionBarActivityDelegateBase(ActionBarActivity activity) {
         super(activity);
     }
 
     @Override
+    void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mWindowDecor = (ViewGroup) mActivity.getWindow().getDecorView();
+
+        if (NavUtils.getParentActivityName(mActivity) != null) {
+            ActionBar ab = getSupportActionBar();
+            if (ab == null) {
+                mEnableDefaultActionBarUp = true;
+            } else {
+                ab.setDefaultDisplayHomeAsUpEnabled(true);
+            }
+        }
+    }
+
+    @Override
     public ActionBar createSupportActionBar() {
         ensureSubDecor();
-        return new ActionBarImplBase(mActivity, mActivity);
+        ActionBar ab = new WindowDecorActionBar(mActivity, mOverlayActionBar);
+        ab.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
+        return ab;
+    }
+
+    @Override
+    void setSupportActionBar(Toolbar toolbar) {
+        if (getSupportActionBar() instanceof WindowDecorActionBar) {
+            throw new IllegalStateException("This Activity already has an action bar supplied " +
+                    "by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " +
+                    "windowActionBar to false in your theme to use a Toolbar instead.");
+        }
+        ActionBar ab = new ToolbarActionBar(toolbar, mActivity.getTitle(), mWindowMenuCallback);
+        ab.invalidateOptionsMenu();
+        setSupportActionBar(ab);
     }
 
     @Override
@@ -90,14 +142,16 @@
         if (mHasActionBar && mSubDecorInstalled) {
             // Note: The action bar will need to access
             // view changes from superclass.
-            ActionBarImplBase actionBar = (ActionBarImplBase) getSupportActionBar();
-            actionBar.onConfigurationChanged(newConfig);
+            ActionBar ab = getSupportActionBar();
+            if (ab != null) {
+                ab.onConfigurationChanged(newConfig);
+            }
         }
     }
 
     @Override
     public void onStop() {
-        ActionBarImplBase ab = (ActionBarImplBase) getSupportActionBar();
+        ActionBar ab = getSupportActionBar();
         if (ab != null) {
             ab.setShowHideAnimationEnabled(false);
         }
@@ -105,7 +159,7 @@
 
     @Override
     public void onPostResume() {
-        ActionBarImplBase ab = (ActionBarImplBase) getSupportActionBar();
+        ActionBar ab = getSupportActionBar();
         if (ab != null) {
             ab.setShowHideAnimationEnabled(true);
         }
@@ -154,56 +208,49 @@
     final void ensureSubDecor() {
         if (!mSubDecorInstalled) {
             if (mHasActionBar) {
-                if (mOverlayActionBar) {
-                    mActivity.superSetContentView(R.layout.abc_action_bar_decor_overlay);
-                } else {
-                    mActivity.superSetContentView(R.layout.abc_action_bar_decor);
+                mActivity.superSetContentView(R.layout.abc_screen_toolbar);
+
+                ViewGroup root = (ViewGroup) mActivity.findViewById(R.id.action_bar_root);
+                if (root != null && root.getChildCount() == 0) {
+                    /**
+                     * This needs some explanation. As we can not use the android:theme attribute
+                     * pre-L, we emulate it by manually creating a LayoutInflater using a
+                     * ContextThemeWrapper pointing to actionBarTheme.
+                     */
+                    TypedValue outValue = new TypedValue();
+                    mActivity.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);
+
+                    Context themedContext;
+                    if (outValue.resourceId != 0) {
+                        themedContext = new ContextThemeWrapper(mActivity, outValue.resourceId);
+                    } else {
+                        themedContext = mActivity;
+                    }
+
+                    LayoutInflater.from(themedContext)
+                            .inflate(R.layout.abc_screen_toolbar_include, root, true);
                 }
-                mActionBarView = (ActionBarView) mActivity.findViewById(R.id.action_bar);
-                mActionBarView.setWindowCallback(mActivity);
+
+                mDecorContentParent = (DecorContentParent) mActivity
+                        .findViewById(R.id.decor_content_parent);
+                mDecorContentParent.setWindowCallback(mWindowMenuCallback);
 
                 /**
-                 * Progress Bars
+                 * Propagate features to DecorContentParent
                  */
+                if (mOverlayActionBar) {
+                    mDecorContentParent.initFeature(WindowCompat.FEATURE_ACTION_BAR_OVERLAY);
+                }
                 if (mFeatureProgress) {
-                    mActionBarView.initProgress();
+                    mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
                 }
                 if (mFeatureIndeterminateProgress) {
-                    mActionBarView.initIndeterminateProgress();
+                    mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
                 }
-
-                /**
-                 * Split Action Bar
-                 */
-                boolean splitWhenNarrow = UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW
-                        .equals(getUiOptionsFromMetadata());
-                boolean splitActionBar;
-
-                if (splitWhenNarrow) {
-                    splitActionBar = mActivity.getResources()
-                            .getBoolean(R.bool.abc_split_action_bar_is_narrow);
-                } else {
-                    TypedArray a = mActivity.obtainStyledAttributes(R.styleable.ActionBarWindow);
-                    splitActionBar = a
-                            .getBoolean(R.styleable.ActionBarWindow_windowSplitActionBar, false);
-                    a.recycle();
-                }
-
-                final ActionBarContainer splitView = (ActionBarContainer) mActivity.findViewById(
-                        R.id.split_action_bar);
-                if (splitView != null) {
-                    mActionBarView.setSplitView(splitView);
-                    mActionBarView.setSplitActionBar(splitActionBar);
-                    mActionBarView.setSplitWhenNarrow(splitWhenNarrow);
-
-                    final ActionBarContextView cab = (ActionBarContextView) mActivity.findViewById(
-                            R.id.action_context_bar);
-                    cab.setSplitView(splitView);
-                    cab.setSplitActionBar(splitActionBar);
-                    cab.setSplitWhenNarrow(splitWhenNarrow);
-                }
+            } else if (mOverlayActionMode) {
+                mActivity.superSetContentView(R.layout.abc_screen_simple_overlay_action_mode);
             } else {
-                mActivity.superSetContentView(R.layout.abc_simple_decor);
+                mActivity.superSetContentView(R.layout.abc_screen_simple);
             }
 
             // Change our content FrameLayout to use the android.R.id.content id.
@@ -214,48 +261,46 @@
             abcContent.setId(android.R.id.content);
 
             // A title was set before we've install the decor so set it now.
-            if (mTitleToSet != null) {
-                mActionBarView.setWindowTitle(mTitleToSet);
+            if (mTitleToSet != null && mDecorContentParent != null) {
+                mDecorContentParent.setWindowTitle(mTitleToSet);
                 mTitleToSet = null;
             }
 
             applyFixedSizeWindow();
 
+            onSubDecorInstalled();
+
             mSubDecorInstalled = true;
 
-            // Post supportInvalidateOptionsMenu() so that the menu is invalidated post-onCreate()
-            mActivity.getWindow().getDecorView().post(new Runnable() {
-                @Override
-                public void run() {
-                    supportInvalidateOptionsMenu();
-                }
-            });
+            invalidatePanelMenu();
         }
     }
 
+    void onSubDecorInstalled() {}
+
     private void applyFixedSizeWindow() {
-        TypedArray a = mActivity.obtainStyledAttributes(R.styleable.ActionBarWindow);
+        TypedArray a = mActivity.obtainStyledAttributes(R.styleable.Theme);
 
         TypedValue mFixedWidthMajor = null;
         TypedValue mFixedWidthMinor = null;
         TypedValue mFixedHeightMajor = null;
         TypedValue mFixedHeightMinor = null;
 
-        if (a.hasValue(R.styleable.ActionBarWindow_windowFixedWidthMajor)) {
+        if (a.hasValue(R.styleable.Theme_windowFixedWidthMajor)) {
             if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
-            a.getValue(R.styleable.ActionBarWindow_windowFixedWidthMajor, mFixedWidthMajor);
+            a.getValue(R.styleable.Theme_windowFixedWidthMajor, mFixedWidthMajor);
         }
-        if (a.hasValue(R.styleable.ActionBarWindow_windowFixedWidthMinor)) {
+        if (a.hasValue(R.styleable.Theme_windowFixedWidthMinor)) {
             if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue();
-            a.getValue(R.styleable.ActionBarWindow_windowFixedWidthMinor, mFixedWidthMinor);
+            a.getValue(R.styleable.Theme_windowFixedWidthMinor, mFixedWidthMinor);
         }
-        if (a.hasValue(R.styleable.ActionBarWindow_windowFixedHeightMajor)) {
+        if (a.hasValue(R.styleable.Theme_windowFixedHeightMajor)) {
             if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue();
-            a.getValue(R.styleable.ActionBarWindow_windowFixedHeightMajor, mFixedHeightMajor);
+            a.getValue(R.styleable.Theme_windowFixedHeightMajor, mFixedHeightMajor);
         }
-        if (a.hasValue(R.styleable.ActionBarWindow_windowFixedHeightMinor)) {
+        if (a.hasValue(R.styleable.Theme_windowFixedHeightMinor)) {
             if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue();
-            a.getValue(R.styleable.ActionBarWindow_windowFixedHeightMinor, mFixedHeightMinor);
+            a.getValue(R.styleable.Theme_windowFixedHeightMinor, mFixedHeightMinor);
         }
 
         final DisplayMetrics metrics = mActivity.getResources().getDisplayMetrics();
@@ -297,6 +342,9 @@
             case WindowCompat.FEATURE_ACTION_BAR_OVERLAY:
                 mOverlayActionBar = true;
                 return true;
+            case WindowCompat.FEATURE_ACTION_MODE_OVERLAY:
+                mOverlayActionMode = true;
+                return true;
             case Window.FEATURE_PROGRESS:
                 mFeatureProgress = true;
                 return true;
@@ -310,8 +358,10 @@
 
     @Override
     public void onTitleChanged(CharSequence title) {
-        if (mActionBarView != null) {
-            mActionBarView.setWindowTitle(title);
+        if (mDecorContentParent != null) {
+            mDecorContentParent.setWindowTitle(title);
+        } else if (getSupportActionBar() != null) {
+            getSupportActionBar().setTitle(title);
         } else {
             mTitleToSet = title;
         }
@@ -322,7 +372,7 @@
         View createdPanelView = null;
 
         if (featureId == Window.FEATURE_OPTIONS_PANEL && preparePanel()) {
-            createdPanelView = (View) getListMenuView(mActivity, this);
+            createdPanelView = (View) getListMenuView(mActivity);
         }
 
         return createdPanelView;
@@ -345,11 +395,10 @@
     }
 
     @Override
-    public boolean onMenuItemSelected(int featureId, MenuItem item) {
+    public void onPanelClosed(int featureId, Menu menu) {
         if (featureId == Window.FEATURE_OPTIONS_PANEL) {
-            item = MenuWrapperFactory.createMenuItemWrapper(item);
+            mPanelIsPrepared = false;
         }
-        return mActivity.superOnMenuItemSelected(featureId, item);
     }
 
     @Override
@@ -363,22 +412,6 @@
     }
 
     @Override
-    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
-        if (mClosingActionMenu) {
-            return;
-        }
-        mClosingActionMenu = true;
-        mActivity.closeOptionsMenu();
-        mActionBarView.dismissPopupMenus();
-        mClosingActionMenu = false;
-    }
-
-    @Override
-    public boolean onOpenSubMenu(MenuBuilder subMenu) {
-        return false;
-    }
-
-    @Override
     public ActionMode startSupportActionMode(ActionMode.Callback callback) {
         if (callback == null) {
             throw new IllegalArgumentException("ActionMode callback can not be null.");
@@ -390,19 +423,27 @@
 
         final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback);
 
-        ActionBarImplBase ab = (ActionBarImplBase) getSupportActionBar();
+        ActionBar ab = getSupportActionBar();
         if (ab != null) {
             mActionMode = ab.startActionMode(wrappedCallback);
+            if (mActionMode != null) {
+                mActivity.onSupportActionModeStarted(mActionMode);
+            }
         }
 
-        if (mActionMode != null) {
-            mActivity.onSupportActionModeStarted(mActionMode);
+        if (mActionMode == null) {
+            // If the action bar didn't provide an action mode, start the emulated window one
+            mActionMode = startSupportActionModeFromWindow(wrappedCallback);
         }
+
         return mActionMode;
     }
 
     @Override
     public void supportInvalidateOptionsMenu() {
+        final ActionBar ab = getSupportActionBar();
+        if (ab != null && ab.invalidateOptionsMenu()) return;
+
         if (mMenu != null) {
             Bundle savedActionViewStates = new Bundle();
             mMenu.saveActionViewStates(savedActionViewStates);
@@ -413,35 +454,110 @@
             mMenu.stopDispatchingItemsChanged();
             mMenu.clear();
         }
-        mPanelRefreshContent = true;
+        mPanelRefreshMenuContent = true;
 
         // Prepare the options panel if we have an action bar
-        if (mActionBarView != null) {
+        if (mDecorContentParent != null) {
             mPanelIsPrepared = false;
             preparePanel();
         }
     }
 
+    @Override
+    ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback) {
+        if (mActionMode != null) {
+            mActionMode.finish();
+        }
+
+        final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback);
+        ActionMode mode = null;
+
+        if (mActionModeView == null) {
+            if (mIsFloating) {
+                mActionModeView = new ActionBarContextView(mActivity);
+                mActionModePopup = new PopupWindow(mActivity, null,
+                        R.attr.actionModePopupWindowStyle);
+                mActionModePopup.setContentView(mActionModeView);
+                mActionModePopup.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
+
+                TypedValue heightValue = new TypedValue();
+                mActivity.getTheme().resolveAttribute(R.attr.actionBarSize, heightValue, true);
+                final int height = TypedValue.complexToDimensionPixelSize(heightValue.data,
+                        mActivity.getResources().getDisplayMetrics());
+                mActionModeView.setContentHeight(height);
+                mActionModePopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
+                mShowActionModePopup = new Runnable() {
+                    public void run() {
+                        mActionModePopup.showAtLocation(
+                                mActionModeView,
+                                Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);
+                    }
+                };
+            } else {
+                ViewStub stub = (ViewStub) mActivity.findViewById(R.id.action_mode_bar_stub);
+                if (stub != null) {
+                    mActionModeView = (ActionBarContextView) stub.inflate();
+                }
+            }
+        }
+
+        if (mActionModeView != null) {
+            mActionModeView.killMode();
+            mode = new StandaloneActionMode(mActivity, mActionModeView, wrappedCallback,
+                    mActionModePopup == null);
+            if (callback.onCreateActionMode(mode, mode.getMenu())) {
+                mode.invalidate();
+                mActionModeView.initForMode(mode);
+                mActionModeView.setVisibility(View.VISIBLE);
+                mActionMode = mode;
+                if (mActionModePopup != null) {
+                    mActivity.getWindow().getDecorView().post(mShowActionModePopup);
+                }
+                mActionModeView.sendAccessibilityEvent(
+                        AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+            } else {
+                mActionMode = null;
+            }
+        }
+        if (mActionMode != null && mActivity != null) {
+            mActivity.onSupportActionModeStarted(mActionMode);
+        }
+        return mActionMode;
+    }
+
     private void reopenMenu(MenuBuilder menu, boolean toggleMenuMode) {
-        if (mActionBarView != null && mActionBarView.isOverflowReserved()) {
-            if (!mActionBarView.isOverflowMenuShowing() || !toggleMenuMode) {
-                if (mActionBarView.getVisibility() == View.VISIBLE) {
-                    mActionBarView.showOverflowMenu();
+        if (mDecorContentParent != null && mDecorContentParent.canShowOverflowMenu() &&
+                (!ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mActivity)) ||
+                        mDecorContentParent.isOverflowMenuShowPending())) {
+            if (!mDecorContentParent.isOverflowMenuShowing() || !toggleMenuMode) {
+
+                // If we have a menu invalidation pending, do it now.
+                if (mInvalidatePanelMenuPosted) {
+                    mWindowDecor.removeCallbacks(mInvalidatePanelMenuRunnable);
+                    mInvalidatePanelMenuRunnable.run();
+                }
+
+                // If we don't have a menu or we're waiting for a full content refresh,
+                // forget it. This is a lingering event that no longer matters.
+                if (mMenu != null && !mPanelRefreshMenuContent && preparePanel()) {
+                    mDecorContentParent.showOverflowMenu();
                 }
             } else {
-                mActionBarView.hideOverflowMenu();
+                mDecorContentParent.hideOverflowMenu();
             }
             return;
         }
-
-        menu.close();
     }
 
-    private MenuView getListMenuView(Context context, MenuPresenter.Callback cb) {
+    private MenuView getListMenuView(Context context) {
         if (mMenu == null) {
             return null;
         }
 
+        if (mPanelMenuPresenterCallback == null) {
+            mPanelMenuPresenterCallback = new PanelMenuPresenterCallback();
+        }
+
         if (mListMenuPresenter == null) {
             TypedArray a = context.obtainStyledAttributes(R.styleable.Theme);
             final int listPresenterTheme = a.getResourceId(
@@ -451,14 +567,18 @@
 
             mListMenuPresenter = new ListMenuPresenter(
                     R.layout.abc_list_menu_item_layout, listPresenterTheme);
-            mListMenuPresenter.setCallback(cb);
-            mMenu.addMenuPresenter(mListMenuPresenter);
+            mListMenuPresenter.setCallback(mPanelMenuPresenterCallback);
+            mMenu.addMenuPresenter(mListMenuPresenter, mActivity);
         } else {
             // Make sure we update the ListView
             mListMenuPresenter.updateMenuView(false);
         }
 
-        return mListMenuPresenter.getMenuView(new FrameLayout(context));
+        if (mListMenuPresenter.getAdapter().isEmpty()) {
+            return null;
+        }
+
+        return mListMenuPresenter.getMenuView(mWindowDecor);
     }
 
     @Override
@@ -470,8 +590,8 @@
         }
 
         // Next collapse any expanded action views.
-        if (mActionBarView != null && mActionBarView.hasExpandedActionView()) {
-            mActionBarView.collapseActionView();
+        ActionBar ab = getSupportActionBar();
+        if (ab != null && ab.collapseActionView()) {
             return true;
         }
 
@@ -510,8 +630,8 @@
      * Progress Bar function. Mostly extracted from PhoneWindow.java
      */
     private void updateProgressBars(int value) {
-        ProgressBarICS circularProgressBar = getCircularProgressBar();
-        ProgressBarICS horizontalProgressBar = getHorizontalProgressBar();
+        ProgressBarCompat circularProgressBar = getCircularProgressBar();
+        ProgressBarCompat horizontalProgressBar = getHorizontalProgressBar();
 
         if (value == Window.PROGRESS_VISIBILITY_ON) {
             if (mFeatureProgress) {
@@ -548,8 +668,8 @@
         }
     }
 
-    private void showProgressBars(ProgressBarICS horizontalProgressBar,
-            ProgressBarICS spinnyProgressBar) {
+    private void showProgressBars(ProgressBarCompat horizontalProgressBar,
+            ProgressBarCompat spinnyProgressBar) {
         if (mFeatureIndeterminateProgress && spinnyProgressBar.getVisibility() == View.INVISIBLE) {
             spinnyProgressBar.setVisibility(View.VISIBLE);
         }
@@ -559,8 +679,8 @@
         }
     }
 
-    private void hideProgressBars(ProgressBarICS horizontalProgressBar,
-            ProgressBarICS spinnyProgressBar) {
+    private void hideProgressBars(ProgressBarCompat horizontalProgressBar,
+            ProgressBarCompat spinnyProgressBar) {
         if (mFeatureIndeterminateProgress && spinnyProgressBar.getVisibility() == View.VISIBLE) {
             spinnyProgressBar.setVisibility(View.INVISIBLE);
         }
@@ -569,16 +689,16 @@
         }
     }
 
-    private ProgressBarICS getCircularProgressBar() {
-        ProgressBarICS pb = (ProgressBarICS) mActionBarView.findViewById(R.id.progress_circular);
+    private ProgressBarCompat getCircularProgressBar() {
+        ProgressBarCompat pb = (ProgressBarCompat) mActivity.findViewById(R.id.progress_circular);
         if (pb != null) {
             pb.setVisibility(View.INVISIBLE);
         }
         return pb;
     }
 
-    private ProgressBarICS getHorizontalProgressBar() {
-        ProgressBarICS pb = (ProgressBarICS) mActionBarView.findViewById(R.id.progress_horizontal);
+    private ProgressBarCompat getHorizontalProgressBar() {
+        ProgressBarCompat pb = (ProgressBarCompat) mActivity.findViewById(R.id.progress_horizontal);
         if (pb != null) {
             pb.setVisibility(View.INVISIBLE);
         }
@@ -586,8 +706,42 @@
     }
 
     private boolean initializePanelMenu() {
-        mMenu = new MenuBuilder(getActionBarThemedContext());
+        Context context = mActivity;
+
+        if (mDecorContentParent != null) {
+            final TypedValue outValue = new TypedValue();
+            final Resources.Theme baseTheme = context.getTheme();
+            baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
+
+            Resources.Theme widgetTheme = null;
+            if (outValue.resourceId != 0) {
+                widgetTheme = context.getResources().newTheme();
+                widgetTheme.setTo(baseTheme);
+                widgetTheme.applyStyle(outValue.resourceId, true);
+                widgetTheme.resolveAttribute(
+                        R.attr.actionBarWidgetTheme, outValue, true);
+            } else {
+                baseTheme.resolveAttribute(
+                        R.attr.actionBarWidgetTheme, outValue, true);
+            }
+
+            if (outValue.resourceId != 0) {
+                if (widgetTheme == null) {
+                    widgetTheme = context.getResources().newTheme();
+                    widgetTheme.setTo(baseTheme);
+                }
+                widgetTheme.applyStyle(outValue.resourceId, true);
+            }
+
+            if (widgetTheme != null) {
+                context = new ContextThemeWrapper(context, 0);
+                context.getTheme().setTo(widgetTheme);
+            }
+        }
+
+        mMenu = new MenuBuilder(context);
         mMenu.setCallback(this);
+
         return true;
     }
 
@@ -597,16 +751,25 @@
             return true;
         }
 
+        if (mDecorContentParent != null) {
+            // Enforce ordering guarantees around events so that the action bar never
+            // dispatches menu-related events before the panel is prepared.
+            mDecorContentParent.setMenuPrepared();
+        }
+
         // Init the panel state's menu--return false if init failed
-        if (mMenu == null || mPanelRefreshContent) {
+        if (mMenu == null || mPanelRefreshMenuContent) {
             if (mMenu == null) {
                 if (!initializePanelMenu() || (mMenu == null)) {
                     return false;
                 }
             }
 
-            if (mActionBarView != null) {
-                mActionBarView.setMenu(mMenu, this);
+            if (mDecorContentParent != null) {
+                if (mActionMenuPresenterCallback == null) {
+                    mActionMenuPresenterCallback = new ActionMenuPresenterCallback();
+                }
+                mDecorContentParent.setMenu(mMenu, mActionMenuPresenterCallback);
             }
 
             // Creating the panel menu will involve a lot of manipulation;
@@ -618,15 +781,15 @@
                 // Ditch the menu created above
                 mMenu = null;
 
-                if (mActionBarView != null) {
+                if (mDecorContentParent != null) {
                     // Don't show it in the action bar either
-                    mActionBarView.setMenu(null, this);
+                    mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
                 }
 
                 return false;
             }
 
-            mPanelRefreshContent = false;
+            mPanelRefreshMenuContent = false;
         }
 
         // Preparing the panel menu can involve a lot of manipulation;
@@ -642,10 +805,10 @@
 
         // Callback and return if the callback does not want to show the menu
         if (!mActivity.superOnPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, mMenu)) {
-            if (mActionBarView != null) {
+            if (mDecorContentParent != null) {
                 // The app didn't want to show the menu for now but it still exists.
                 // Clear it out of the action bar.
-                mActionBarView.setMenu(null, this);
+                mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
             }
             mMenu.startDispatchingItemsChanged();
             return false;
@@ -659,6 +822,34 @@
         return true;
     }
 
+    private void checkCloseActionMenu() {
+        if (mClosingActionMenu) {
+            return;
+        }
+
+        mClosingActionMenu = true;
+        mDecorContentParent.dismissPopups();
+        mClosingActionMenu = false;
+    }
+
+    private void closePanel(int featureId) {
+        if (featureId == Window.FEATURE_OPTIONS_PANEL && mDecorContentParent != null &&
+                mDecorContentParent.canShowOverflowMenu() &&
+                !ViewConfigurationCompat.hasPermanentMenuKey(ViewConfiguration.get(mActivity))) {
+            mDecorContentParent.hideOverflowMenu();
+        } else {
+            mActivity.closeOptionsMenu();
+            mPanelIsPrepared = false;
+        }
+    }
+
+    private void invalidatePanelMenu() {
+        if (!mInvalidatePanelMenuPosted && mWindowDecor != null) {
+            ViewCompat.postOnAnimation(mWindowDecor, mInvalidatePanelMenuRunnable);
+            mInvalidatePanelMenuPosted = true;
+        }
+    }
+
     /**
      * Clears out internal reference when the action mode is destroyed.
      */
@@ -683,9 +874,48 @@
 
         public void onDestroyActionMode(ActionMode mode) {
             mWrapped.onDestroyActionMode(mode);
-            mActivity.onSupportActionModeFinished(mode);
+            if (mActionModePopup != null) {
+                mActivity.getWindow().getDecorView().removeCallbacks(mShowActionModePopup);
+                mActionModePopup.dismiss();
+            } else if (mActionModeView != null) {
+                mActionModeView.setVisibility(View.GONE);
+            }
+            if (mActionModeView != null) {
+                mActionModeView.removeAllViews();
+            }
+            if (mActivity != null) {
+                try {
+                    mActivity.onSupportActionModeFinished(mActionMode);
+                } catch (AbstractMethodError ame) {
+                    // Older apps might not implement this callback method.
+                }
+            }
             mActionMode = null;
         }
     }
 
+    private final class PanelMenuPresenterCallback implements MenuPresenter.Callback {
+        @Override
+        public boolean onOpenSubMenu(MenuBuilder subMenu) {
+            return false;
+        }
+
+        @Override
+        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+            closePanel(Window.FEATURE_OPTIONS_PANEL);
+        }
+    }
+
+    private final class ActionMenuPresenterCallback implements MenuPresenter.Callback {
+        @Override
+        public boolean onOpenSubMenu(MenuBuilder subMenu) {
+            return false;
+        }
+
+        @Override
+        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+            checkCloseActionMenu();
+        }
+    }
+
 }
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateHC.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateHC.java
index db8ddaf..3368293 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateHC.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateHC.java
@@ -16,15 +16,50 @@
 
 package android.support.v7.app;
 
-class ActionBarActivityDelegateHC extends ActionBarActivityDelegateBase {
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.support.v7.appcompat.R;
+import android.support.v7.internal.view.SupportActionModeWrapper;
+import android.support.v7.internal.widget.NativeActionModeAwareLayout;
+import android.view.ActionMode;
+import android.view.View;
+
+@TargetApi(Build.VERSION_CODES.HONEYCOMB)
+class ActionBarActivityDelegateHC extends ActionBarActivityDelegateBase
+        implements NativeActionModeAwareLayout.OnActionModeForChildListener {
+
+    private NativeActionModeAwareLayout mNativeActionModeAwareLayout;
 
     ActionBarActivityDelegateHC(ActionBarActivity activity) {
         super(activity);
     }
 
     @Override
-    public ActionBar createSupportActionBar() {
-        ensureSubDecor();
-        return new ActionBarImplHC(mActivity, mActivity);
+    void onSubDecorInstalled() {
+        // NativeActionModeAwareLayout is used to notify us when a native Action Mode is started
+        mNativeActionModeAwareLayout = (NativeActionModeAwareLayout) mActivity
+                .findViewById(android.R.id.content);
+
+        // Can be null when using FEATURE_ACTION_BAR_OVERLAY
+        if (mNativeActionModeAwareLayout != null) {
+            mNativeActionModeAwareLayout.setActionModeForChildListener(this);
+        }
+    }
+
+    // From NativeActionModeAwareLayout.OnActionModeForChildListener
+    @Override
+    public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) {
+        Context context = originalView.getContext();
+
+        // Try and start a support action mode, wrapping the callback
+        final android.support.v7.view.ActionMode supportActionMode = startSupportActionMode(
+                new SupportActionModeWrapper.CallbackWrapper(context, callback));
+
+        if (supportActionMode != null) {
+            // If we received a support action mode, wrap and return it
+            return new SupportActionModeWrapper(mActivity, supportActionMode);
+        }
+        return null;
     }
 }
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateICS.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateICS.java
deleted file mode 100644
index 22d23f9..0000000
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateICS.java
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v7.app;
-
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.support.v4.view.WindowCompat;
-import android.support.v7.internal.view.ActionModeWrapper;
-import android.support.v7.internal.view.menu.MenuWrapperFactory;
-import android.support.v7.view.ActionMode;
-import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-
-class ActionBarActivityDelegateICS extends ActionBarActivityDelegate {
-    Menu mMenu;
-
-    ActionBarActivityDelegateICS(ActionBarActivity activity) {
-        super(activity);
-    }
-
-    @Override
-    public ActionBar createSupportActionBar() {
-        return new ActionBarImplICS(mActivity, mActivity);
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        // Set framework uiOptions from the support metadata value
-        if (UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW.equals(getUiOptionsFromMetadata())) {
-            mActivity.getWindow().setUiOptions(ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW,
-                    ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW);
-        }
-
-        super.onCreate(savedInstanceState);
-
-        if (mHasActionBar) {
-            // If action bar is requested by inheriting from the appcompat theme,
-            // the system will not know about that. So explicitly request for an action bar.
-            mActivity.requestWindowFeature(WindowCompat.FEATURE_ACTION_BAR);
-        }
-        if (mOverlayActionBar) {
-            mActivity.requestWindowFeature(WindowCompat.FEATURE_ACTION_BAR_OVERLAY);
-        }
-
-        /*
-         * This goofy move needs some explanation.
-         *
-         * The verifier on older platform versions has some interesting side effects if
-         * a class defines a method that takes a parameter of a type that doesn't exist.
-         * In this case, that type is android.view.ActionMode. Therefore, ActionBarActivity
-         * cannot override the onActionModeStarted/Finished methods without causing nastiness
-         * when it is loaded on older platform versions.
-         *
-         * Since these methods are actually part of the window callback and not intrinsic to
-         * Activity itself, we can install a little shim with the window instead that knows
-         * about the ActionMode class. Note that this means that any new methods added to
-         * Window.Callback in the future won't get proxied without updating the support lib,
-         * but we shouldn't be adding new methods to public interfaces that way anyway...right? ;)
-         */
-        final Window w = mActivity.getWindow();
-        w.setCallback(createWindowCallbackWrapper(w.getCallback()));
-    }
-
-    Window.Callback createWindowCallbackWrapper(Window.Callback cb) {
-        return new WindowCallbackWrapper(cb);
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-    }
-
-    @Override
-    public void onStop() {
-    }
-
-    @Override
-    public void onPostResume() {
-    }
-
-    @Override
-    public void setContentView(View v) {
-        mActivity.superSetContentView(v);
-    }
-
-    @Override
-    public void setContentView(int resId) {
-        mActivity.superSetContentView(resId);
-    }
-
-    @Override
-    public void setContentView(View v, ViewGroup.LayoutParams lp) {
-        mActivity.superSetContentView(v, lp);
-    }
-
-    @Override
-    public void addContentView(View v, ViewGroup.LayoutParams lp) {
-        mActivity.superAddContentView(v, lp);
-    }
-
-    @Override
-    public void onContentChanged() {
-        // Call straight through to the support version of the method
-        mActivity.onSupportContentChanged();
-    }
-
-    @Override
-    public boolean supportRequestWindowFeature(int featureId) {
-        return mActivity.requestWindowFeature(featureId);
-    }
-
-    @Override
-    public View onCreatePanelView(int featureId) {
-        // Do not create custom options menu on HC+
-        return null;
-    }
-
-    @Override
-    public boolean onCreatePanelMenu(int featureId, Menu menu) {
-        if (featureId == Window.FEATURE_OPTIONS_PANEL || featureId == Window.FEATURE_ACTION_BAR) {
-            if (mMenu == null) {
-                mMenu = MenuWrapperFactory.createMenuWrapper(menu);
-            }
-            return mActivity.superOnCreatePanelMenu(featureId, mMenu);
-        }
-        return mActivity.superOnCreatePanelMenu(featureId, menu);
-    }
-
-    @Override
-    public boolean onPreparePanel(int featureId, View view, Menu menu) {
-        if (featureId == Window.FEATURE_OPTIONS_PANEL || featureId == Window.FEATURE_ACTION_BAR) {
-            return mActivity.superOnPreparePanel(featureId, view, mMenu);
-        }
-        return mActivity.superOnPreparePanel(featureId, view, menu);
-    }
-
-    @Override
-    public boolean onMenuItemSelected(int featureId, MenuItem item) {
-        if (featureId == Window.FEATURE_OPTIONS_PANEL) {
-            item = MenuWrapperFactory.createMenuItemWrapper(item);
-        }
-        return mActivity.superOnMenuItemSelected(featureId, item);
-    }
-
-    @Override
-    public void onTitleChanged(CharSequence title) {
-        // Handled by framework
-    }
-
-    @Override
-    public ActionMode startSupportActionMode(ActionMode.Callback callback) {
-        if (callback == null) {
-            throw new IllegalArgumentException("ActionMode callback can not be null.");
-        }
-
-        Context context = getActionBarThemedContext();
-
-        ActionModeWrapper.CallbackWrapper wrappedCallback = createActionModeCallbackWrapper(
-                context, callback);
-        ActionModeWrapper wrappedMode = null;
-
-        android.view.ActionMode frameworkMode = mActivity.startActionMode(wrappedCallback);
-
-        if (frameworkMode != null) {
-            wrappedMode = createActionModeWrapper(context, frameworkMode);
-            wrappedCallback.setLastStartedActionMode(wrappedMode);
-        }
-
-        return wrappedMode;
-    }
-
-    public void onActionModeStarted(android.view.ActionMode mode) {
-        mActivity.onSupportActionModeStarted(
-                createActionModeWrapper(getActionBarThemedContext(), mode));
-    }
-
-    @Override
-    void setSupportProgressBarVisibility(boolean visible) {
-        mActivity.setProgressBarVisibility(visible);
-    }
-
-    @Override
-    void setSupportProgressBarIndeterminateVisibility(boolean visible) {
-        mActivity.setProgressBarIndeterminateVisibility(visible);
-    }
-
-    @Override
-    void setSupportProgressBarIndeterminate(boolean indeterminate) {
-        mActivity.setProgressBarIndeterminate(indeterminate);
-    }
-
-    @Override
-    void setSupportProgress(int progress) {
-        mActivity.setProgress(progress);
-    }
-
-    public void onActionModeFinished(android.view.ActionMode mode) {
-        mActivity.onSupportActionModeFinished(
-                createActionModeWrapper(getActionBarThemedContext(), mode));
-    }
-
-    @Override
-    public void supportInvalidateOptionsMenu() {
-        mMenu = null;
-    }
-
-    @Override
-    public boolean onBackPressed() {
-        return false;
-    }
-
-    @Override
-    int getHomeAsUpIndicatorAttrId() {
-        return android.R.attr.homeAsUpIndicator;
-    }
-
-    ActionModeWrapper.CallbackWrapper createActionModeCallbackWrapper(Context context,
-            ActionMode.Callback callback) {
-        return new ActionModeWrapper.CallbackWrapper(context, callback);
-    }
-
-    ActionModeWrapper createActionModeWrapper(Context context,
-            android.view.ActionMode frameworkMode) {
-        return new ActionModeWrapper(context, frameworkMode);
-    }
-
-    class WindowCallbackWrapper implements Window.Callback {
-        final Window.Callback mWrapped;
-
-        public WindowCallbackWrapper(Window.Callback wrapped) {
-            mWrapped = wrapped;
-        }
-
-        @Override
-        public boolean dispatchKeyEvent(KeyEvent event) {
-            return mWrapped.dispatchKeyEvent(event);
-        }
-
-        @Override
-        public boolean dispatchKeyShortcutEvent(KeyEvent event) {
-            return mWrapped.dispatchKeyShortcutEvent(event);
-        }
-
-        @Override
-        public boolean dispatchTouchEvent(MotionEvent event) {
-            return mWrapped.dispatchTouchEvent(event);
-        }
-
-        @Override
-        public boolean dispatchTrackballEvent(MotionEvent event) {
-            return mWrapped.dispatchTrackballEvent(event);
-        }
-
-        @Override
-        public boolean dispatchGenericMotionEvent(MotionEvent event) {
-            return mWrapped.dispatchGenericMotionEvent(event);
-        }
-
-        @Override
-        public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-            return mWrapped.dispatchPopulateAccessibilityEvent(event);
-        }
-
-        @Override
-        public View onCreatePanelView(int featureId) {
-            return mWrapped.onCreatePanelView(featureId);
-        }
-
-        @Override
-        public boolean onCreatePanelMenu(int featureId, Menu menu) {
-            return mWrapped.onCreatePanelMenu(featureId, menu);
-        }
-
-        @Override
-        public boolean onPreparePanel(int featureId, View view, Menu menu) {
-            return mWrapped.onPreparePanel(featureId, view, menu);
-        }
-
-        @Override
-        public boolean onMenuOpened(int featureId, Menu menu) {
-            return mWrapped.onMenuOpened(featureId, menu);
-        }
-
-        @Override
-        public boolean onMenuItemSelected(int featureId, MenuItem item) {
-            return mWrapped.onMenuItemSelected(featureId, item);
-        }
-
-        @Override
-        public void onWindowAttributesChanged(WindowManager.LayoutParams attrs) {
-            mWrapped.onWindowAttributesChanged(attrs);
-        }
-
-        @Override
-        public void onContentChanged() {
-            mWrapped.onContentChanged();
-        }
-
-        @Override
-        public void onWindowFocusChanged(boolean hasFocus) {
-            mWrapped.onWindowFocusChanged(hasFocus);
-        }
-
-        @Override
-        public void onAttachedToWindow() {
-            mWrapped.onAttachedToWindow();
-        }
-
-        @Override
-        public void onDetachedFromWindow() {
-            mWrapped.onDetachedFromWindow();
-        }
-
-        @Override
-        public void onPanelClosed(int featureId, Menu menu) {
-            mWrapped.onPanelClosed(featureId, menu);
-        }
-
-        @Override
-        public boolean onSearchRequested() {
-            return mWrapped.onSearchRequested();
-        }
-
-        @Override
-        public android.view.ActionMode onWindowStartingActionMode(
-                android.view.ActionMode.Callback callback) {
-            return mWrapped.onWindowStartingActionMode(callback);
-        }
-
-        /*
-         * And here are the money methods, the reason why this wrapper exists:
-         */
-
-        @Override
-        public void onActionModeStarted(android.view.ActionMode mode) {
-            mWrapped.onActionModeStarted(mode);
-            ActionBarActivityDelegateICS.this.onActionModeStarted(mode);
-        }
-
-        @Override
-        public void onActionModeFinished(android.view.ActionMode mode) {
-            mWrapped.onActionModeFinished(mode);
-            ActionBarActivityDelegateICS.this.onActionModeFinished(mode);
-        }
-    }
-}
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateJB.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateJB.java
deleted file mode 100644
index a0c955f..0000000
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateJB.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v7.app;
-
-import android.content.Context;
-import android.support.v7.internal.view.ActionModeWrapper;
-import android.support.v7.internal.view.ActionModeWrapperJB;
-import android.support.v7.view.ActionMode;
-
-class ActionBarActivityDelegateJB extends ActionBarActivityDelegateICS {
-
-    ActionBarActivityDelegateJB(ActionBarActivity activity) {
-        super(activity);
-    }
-
-    @Override
-    public ActionBar createSupportActionBar() {
-        return new ActionBarImplJB(mActivity, mActivity);
-    }
-
-    @Override
-    ActionModeWrapper.CallbackWrapper createActionModeCallbackWrapper(Context context,
-            ActionMode.Callback callback) {
-        return new ActionModeWrapperJB.CallbackWrapper(context, callback);
-    }
-
-    @Override
-    ActionModeWrapper createActionModeWrapper(Context context, android.view.ActionMode frameworkMode) {
-        return new ActionModeWrapperJB(context, frameworkMode);
-    }
-}
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateJBMR2.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateJBMR2.java
deleted file mode 100644
index 996c8f1..0000000
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateJBMR2.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2013 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;
-
-class ActionBarActivityDelegateJBMR2 extends ActionBarActivityDelegateJB {
-
-    ActionBarActivityDelegateJBMR2(ActionBarActivity activity) {
-        super(activity);
-    }
-
-    @Override
-    public ActionBar createSupportActionBar() {
-        return new ActionBarImplJBMR2(mActivity, mActivity);
-    }
-}
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarImplBase.java b/v7/appcompat/src/android/support/v7/app/ActionBarImplBase.java
deleted file mode 100644
index ece56c5..0000000
--- a/v7/appcompat/src/android/support/v7/app/ActionBarImplBase.java
+++ /dev/null
@@ -1,1056 +0,0 @@
-/*
- * Copyright (C) 2012 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.Dialog;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.support.v4.app.FragmentTransaction;
-import android.support.v7.appcompat.R;
-import android.support.v7.internal.view.ActionBarPolicy;
-import android.support.v7.internal.view.SupportMenuInflater;
-import android.support.v7.internal.view.menu.MenuBuilder;
-import android.support.v7.internal.view.menu.SubMenuBuilder;
-import android.support.v7.internal.widget.ActionBarContainer;
-import android.support.v7.internal.widget.ActionBarContextView;
-import android.support.v7.internal.widget.ActionBarOverlayLayout;
-import android.support.v7.internal.widget.ActionBarView;
-import android.support.v7.internal.widget.ScrollingTabContainerView;
-import android.support.v7.view.ActionMode;
-import android.support.v4.internal.view.SupportMenuItem;
-import android.util.TypedValue;
-import android.view.ContextThemeWrapper;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.widget.SpinnerAdapter;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-
-class ActionBarImplBase extends ActionBar {
-    private Context mContext;
-    private Context mThemedContext;
-    private ActionBarActivity mActivity;
-    private Dialog mDialog;
-
-    private ActionBarOverlayLayout mOverlayLayout;
-    private ActionBarContainer mContainerView;
-    private ViewGroup mTopVisibilityView;
-    private ActionBarView mActionView;
-    private ActionBarContextView mContextView;
-    private ActionBarContainer mSplitView;
-    private View mContentView;
-    private ScrollingTabContainerView mTabScrollView;
-
-    private ArrayList<TabImpl> mTabs = new ArrayList<TabImpl>();
-
-    private TabImpl mSelectedTab;
-    private int mSavedTabPosition = INVALID_POSITION;
-
-    private boolean mDisplayHomeAsUpSet;
-
-    ActionModeImpl mActionMode;
-    ActionMode mDeferredDestroyActionMode;
-    ActionMode.Callback mDeferredModeDestroyCallback;
-
-    private boolean mLastMenuVisibility;
-    private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners =
-            new ArrayList<OnMenuVisibilityListener>();
-
-    private static final int CONTEXT_DISPLAY_NORMAL = 0;
-    private static final int CONTEXT_DISPLAY_SPLIT = 1;
-
-    private static final int INVALID_POSITION = -1;
-
-    private int mContextDisplayMode;
-    private boolean mHasEmbeddedTabs;
-
-    final Handler mHandler = new Handler();
-    Runnable mTabSelector;
-
-    private int mCurWindowVisibility = View.VISIBLE;
-
-    private boolean mHiddenByApp;
-    private boolean mHiddenBySystem;
-    private boolean mShowingForMode;
-
-    private boolean mNowShowing = true;
-    private boolean mShowHideAnimationEnabled;
-
-    private Callback mCallback;
-
-    public ActionBarImplBase(ActionBarActivity activity, Callback callback) {
-        mActivity = activity;
-        mContext = activity;
-        mCallback = callback;
-        init(mActivity);
-    }
-
-    private void init(ActionBarActivity activity) {
-        mOverlayLayout = (ActionBarOverlayLayout) activity.findViewById(
-                R.id.action_bar_overlay_layout);
-        if (mOverlayLayout != null) {
-            mOverlayLayout.setActionBar(this);
-        }
-        mActionView = (ActionBarView) activity.findViewById(R.id.action_bar);
-        mContextView = (ActionBarContextView) activity.findViewById(R.id.action_context_bar);
-        mContainerView = (ActionBarContainer) activity.findViewById(R.id.action_bar_container);
-        mTopVisibilityView = (ViewGroup) activity.findViewById(R.id.top_action_bar);
-        if (mTopVisibilityView == null) {
-            mTopVisibilityView = mContainerView;
-        }
-        mSplitView = (ActionBarContainer) activity.findViewById(R.id.split_action_bar);
-
-        if (mActionView == null || mContextView == null || mContainerView == null) {
-            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
-                    "with a compatible window decor layout");
-        }
-
-        mActionView.setContextView(mContextView);
-        mContextDisplayMode = mActionView.isSplitActionBar() ?
-                CONTEXT_DISPLAY_SPLIT : CONTEXT_DISPLAY_NORMAL;
-
-        // This was initially read from the action bar style
-        final int current = mActionView.getDisplayOptions();
-        final boolean homeAsUp = (current & DISPLAY_HOME_AS_UP) != 0;
-        if (homeAsUp) {
-            mDisplayHomeAsUpSet = true;
-        }
-
-        ActionBarPolicy abp = ActionBarPolicy.get(mContext);
-        setHomeButtonEnabled(abp.enableHomeButtonByDefault() || homeAsUp);
-        setHasEmbeddedTabs(abp.hasEmbeddedTabs());
-        setTitle(mActivity.getTitle());
-    }
-
-    public void onConfigurationChanged(Configuration newConfig) {
-        setHasEmbeddedTabs(ActionBarPolicy.get(mContext).hasEmbeddedTabs());
-    }
-
-    private void setHasEmbeddedTabs(boolean hasEmbeddedTabs) {
-        mHasEmbeddedTabs = hasEmbeddedTabs;
-        // Switch tab layout configuration if needed
-        if (!mHasEmbeddedTabs) {
-            mActionView.setEmbeddedTabView(null);
-            mContainerView.setTabContainer(mTabScrollView);
-        } else {
-            mContainerView.setTabContainer(null);
-            mActionView.setEmbeddedTabView(mTabScrollView);
-        }
-        final boolean isInTabMode = getNavigationMode() == NAVIGATION_MODE_TABS;
-        if (mTabScrollView != null) {
-            if (isInTabMode) {
-                mTabScrollView.setVisibility(View.VISIBLE);
-            } else {
-                mTabScrollView.setVisibility(View.GONE);
-            }
-        }
-        mActionView.setCollapsable(!mHasEmbeddedTabs && isInTabMode);
-    }
-
-    public boolean hasNonEmbeddedTabs() {
-        return !mHasEmbeddedTabs && getNavigationMode() == NAVIGATION_MODE_TABS;
-    }
-
-    @Override
-    public void setCustomView(View view) {
-        mActionView.setCustomNavigationView(view);
-    }
-
-    @Override
-    public void setCustomView(View view, LayoutParams layoutParams) {
-        view.setLayoutParams(layoutParams);
-        mActionView.setCustomNavigationView(view);
-
-    }
-
-    @Override
-    public void setCustomView(int resId) {
-        setCustomView(LayoutInflater.from(getThemedContext())
-                .inflate(resId, mActionView, false));
-    }
-
-    @Override
-    public void setIcon(int resId) {
-        mActionView.setIcon(resId);
-    }
-
-    @Override
-    public void setIcon(Drawable icon) {
-        mActionView.setIcon(icon);
-    }
-
-    @Override
-    public void setLogo(int resId) {
-        mActionView.setLogo(resId);
-    }
-
-    @Override
-    public void setLogo(Drawable logo) {
-        mActionView.setLogo(logo);
-    }
-
-    @Override
-    public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) {
-        mActionView.setDropdownAdapter(adapter);
-        mActionView.setCallback(callback);
-    }
-
-    @Override
-    public void setSelectedNavigationItem(int position) {
-        switch (mActionView.getNavigationMode()) {
-            case NAVIGATION_MODE_TABS:
-                selectTab(mTabs.get(position));
-                break;
-            case NAVIGATION_MODE_LIST:
-                mActionView.setDropdownSelectedPosition(position);
-                break;
-            default:
-                throw new IllegalStateException(
-                        "setSelectedNavigationIndex not valid for current navigation mode");
-        }
-    }
-
-    @Override
-    public int getSelectedNavigationIndex() {
-        switch (mActionView.getNavigationMode()) {
-            case NAVIGATION_MODE_TABS:
-                return mSelectedTab != null ? mSelectedTab.getPosition() : -1;
-            case NAVIGATION_MODE_LIST:
-                return mActionView.getDropdownSelectedPosition();
-            default:
-                return -1;
-        }
-    }
-
-    @Override
-    public int getNavigationItemCount() {
-        switch (mActionView.getNavigationMode()) {
-            case NAVIGATION_MODE_TABS:
-                return mTabs.size();
-            case NAVIGATION_MODE_LIST:
-                SpinnerAdapter adapter = mActionView.getDropdownAdapter();
-                return adapter != null ? adapter.getCount() : 0;
-            default:
-                return 0;
-        }
-    }
-
-    @Override
-    public void setTitle(CharSequence title) {
-        mActionView.setTitle(title);
-    }
-
-    @Override
-    public void setTitle(int resId) {
-        setTitle(mContext.getString(resId));
-    }
-
-    @Override
-    public void setSubtitle(CharSequence subtitle) {
-        mActionView.setSubtitle(subtitle);
-    }
-
-    @Override
-    public void setSubtitle(int resId) {
-        setSubtitle(mContext.getString(resId));
-    }
-
-    @Override
-    public void setDisplayOptions(int options) {
-        if ((options & DISPLAY_HOME_AS_UP) != 0) {
-            mDisplayHomeAsUpSet = true;
-        }
-        mActionView.setDisplayOptions(options);
-    }
-
-    @Override
-    public void setDisplayOptions(int options, int mask) {
-        final int current = mActionView.getDisplayOptions();
-        if ((mask & DISPLAY_HOME_AS_UP) != 0) {
-            mDisplayHomeAsUpSet = true;
-        }
-        mActionView.setDisplayOptions((options & mask) | (current & ~mask));
-    }
-
-    @Override
-    public void setDisplayUseLogoEnabled(boolean useLogo) {
-        setDisplayOptions(useLogo ? DISPLAY_USE_LOGO : 0, DISPLAY_USE_LOGO);
-    }
-
-    @Override
-    public void setDisplayShowHomeEnabled(boolean showHome) {
-        setDisplayOptions(showHome ? DISPLAY_SHOW_HOME : 0, DISPLAY_SHOW_HOME);
-    }
-
-    @Override
-    public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) {
-        setDisplayOptions(showHomeAsUp ? DISPLAY_HOME_AS_UP : 0, DISPLAY_HOME_AS_UP);
-    }
-
-    @Override
-    public void setDisplayShowTitleEnabled(boolean showTitle) {
-        setDisplayOptions(showTitle ? DISPLAY_SHOW_TITLE : 0, DISPLAY_SHOW_TITLE);
-    }
-
-    @Override
-    public void setDisplayShowCustomEnabled(boolean showCustom) {
-        setDisplayOptions(showCustom ? DISPLAY_SHOW_CUSTOM : 0, DISPLAY_SHOW_CUSTOM);
-    }
-
-    @Override
-    public void setHomeButtonEnabled(boolean enable) {
-        mActionView.setHomeButtonEnabled(enable);
-    }
-
-    @Override
-    public void setBackgroundDrawable(Drawable d) {
-        mContainerView.setPrimaryBackground(d);
-    }
-
-    @Override
-    public void setStackedBackgroundDrawable(Drawable d) {
-        mContainerView.setStackedBackground(d);
-    }
-
-    @Override
-    public void setSplitBackgroundDrawable(Drawable d) {
-        mContainerView.setSplitBackground(d);
-    }
-
-    @Override
-    public View getCustomView() {
-        return mActionView.getCustomNavigationView();
-    }
-
-    @Override
-    public CharSequence getTitle() {
-        return mActionView.getTitle();
-    }
-
-    @Override
-    public CharSequence getSubtitle() {
-        return mActionView.getSubtitle();
-    }
-
-    @Override
-    public int getNavigationMode() {
-        return mActionView.getNavigationMode();
-    }
-
-    @Override
-    public void setNavigationMode(int mode) {
-        final int oldMode = mActionView.getNavigationMode();
-        switch (oldMode) {
-            case NAVIGATION_MODE_TABS:
-                mSavedTabPosition = getSelectedNavigationIndex();
-                selectTab(null);
-                mTabScrollView.setVisibility(View.GONE);
-                break;
-        }
-        mActionView.setNavigationMode(mode);
-        switch (mode) {
-            case NAVIGATION_MODE_TABS:
-                ensureTabsExist();
-                mTabScrollView.setVisibility(View.VISIBLE);
-                if (mSavedTabPosition != INVALID_POSITION) {
-                    setSelectedNavigationItem(mSavedTabPosition);
-                    mSavedTabPosition = INVALID_POSITION;
-                }
-                break;
-        }
-        mActionView.setCollapsable(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs);
-    }
-
-    @Override
-    public int getDisplayOptions() {
-        return mActionView.getDisplayOptions();
-    }
-
-    @Override
-    public Tab newTab() {
-        return new TabImpl();
-    }
-
-    @Override
-    public void addTab(Tab tab) {
-        addTab(tab, mTabs.isEmpty());
-    }
-
-    @Override
-    public void addTab(Tab tab, boolean setSelected) {
-        ensureTabsExist();
-        mTabScrollView.addTab(tab, setSelected);
-        configureTab(tab, mTabs.size());
-        if (setSelected) {
-            selectTab(tab);
-        }
-    }
-
-    @Override
-    public void addTab(Tab tab, int position) {
-        addTab(tab, position, mTabs.isEmpty());
-    }
-
-    @Override
-    public void addTab(Tab tab, int position, boolean setSelected) {
-        ensureTabsExist();
-        mTabScrollView.addTab(tab, position, setSelected);
-        configureTab(tab, position);
-        if (setSelected) {
-            selectTab(tab);
-        }
-    }
-
-    @Override
-    public void removeTab(Tab tab) {
-        removeTabAt(tab.getPosition());
-    }
-
-    @Override
-    public void removeTabAt(int position) {
-        if (mTabScrollView == null) {
-            // No tabs around to remove
-            return;
-        }
-
-        int selectedTabPosition = mSelectedTab != null
-                ? mSelectedTab.getPosition() : mSavedTabPosition;
-        mTabScrollView.removeTabAt(position);
-        TabImpl removedTab = mTabs.remove(position);
-        if (removedTab != null) {
-            removedTab.setPosition(-1);
-        }
-
-        final int newTabCount = mTabs.size();
-        for (int i = position; i < newTabCount; i++) {
-            mTabs.get(i).setPosition(i);
-        }
-
-        if (selectedTabPosition == position) {
-            selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1)));
-        }
-    }
-
-    @Override
-    public void removeAllTabs() {
-        cleanupTabs();
-    }
-
-    @Override
-    public void selectTab(Tab tab) {
-        if (getNavigationMode() != NAVIGATION_MODE_TABS) {
-            mSavedTabPosition = tab != null ? tab.getPosition() : INVALID_POSITION;
-            return;
-        }
-
-        final FragmentTransaction trans = mActivity.getSupportFragmentManager().beginTransaction()
-                .disallowAddToBackStack();
-
-        if (mSelectedTab == tab) {
-            if (mSelectedTab != null) {
-                mSelectedTab.getCallback().onTabReselected(mSelectedTab, trans);
-                mTabScrollView.animateToTab(tab.getPosition());
-            }
-        } else {
-            mTabScrollView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION);
-            if (mSelectedTab != null) {
-                mSelectedTab.getCallback().onTabUnselected(mSelectedTab, trans);
-            }
-            mSelectedTab = (TabImpl) tab;
-            if (mSelectedTab != null) {
-                mSelectedTab.getCallback().onTabSelected(mSelectedTab, trans);
-            }
-        }
-
-        if (!trans.isEmpty()) {
-            trans.commit();
-        }
-    }
-
-    @Override
-    public Tab getSelectedTab() {
-        return mSelectedTab;
-    }
-
-    @Override
-    public Tab getTabAt(int index) {
-        return mTabs.get(index);
-    }
-
-    @Override
-    public int getTabCount() {
-        return mTabs.size();
-    }
-
-    @Override
-    public Context getThemedContext() {
-        if (mThemedContext == null) {
-            TypedValue outValue = new TypedValue();
-            Resources.Theme currentTheme = mContext.getTheme();
-            currentTheme.resolveAttribute(R.attr.actionBarWidgetTheme, outValue, true);
-            final int targetThemeRes = outValue.resourceId;
-
-            if (targetThemeRes != 0) {
-                mThemedContext = new ContextThemeWrapper(mContext, targetThemeRes);
-            } else {
-                mThemedContext = mContext;
-            }
-        }
-        return mThemedContext;
-    }
-
-    @Override
-    public void setHomeAsUpIndicator(Drawable indicator) {
-        mActionView.setHomeAsUpIndicator(indicator);
-    }
-
-    @Override
-    public void setHomeAsUpIndicator(int resId) {
-        mActionView.setHomeAsUpIndicator(resId);
-    }
-
-    @Override
-    public int getHeight() {
-        return mContainerView.getHeight();
-    }
-
-    @Override
-    public void show() {
-        if (mHiddenByApp) {
-            mHiddenByApp = false;
-            updateVisibility(false);
-        }
-    }
-
-    void showForActionMode() {
-        if (!mShowingForMode) {
-            mShowingForMode = true;
-            updateVisibility(false);
-        }
-    }
-
-    @Override
-    public void hide() {
-        if (!mHiddenByApp) {
-            mHiddenByApp = true;
-            updateVisibility(false);
-        }
-    }
-
-    void hideForActionMode() {
-        if (mShowingForMode) {
-            mShowingForMode = false;
-            updateVisibility(false);
-        }
-    }
-
-    @Override
-    public boolean isShowing() {
-        return mNowShowing;
-    }
-
-    @Override
-    public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
-        mMenuVisibilityListeners.add(listener);
-    }
-
-    @Override
-    public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
-        mMenuVisibilityListeners.remove(listener);
-    }
-
-    public ActionMode startActionMode(ActionMode.Callback callback) {
-        if (mActionMode != null) {
-            mActionMode.finish();
-        }
-
-        mContextView.killMode();
-        ActionModeImpl mode = new ActionModeImpl(callback);
-        if (mode.dispatchOnCreate()) {
-            mode.invalidate();
-            mContextView.initForMode(mode);
-            animateToMode(true);
-            if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) {
-                if (mSplitView.getVisibility() != View.VISIBLE) {
-                    mSplitView.setVisibility(View.VISIBLE);
-                }
-            }
-            mContextView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-            mActionMode = mode;
-            return mode;
-        }
-        return null;
-    }
-
-    void animateToMode(boolean toActionMode) {
-        if (toActionMode) {
-            showForActionMode();
-        } else {
-            hideForActionMode();
-        }
-
-        mActionView.animateToVisibility(toActionMode ? View.INVISIBLE : View.VISIBLE);
-        mContextView.animateToVisibility(toActionMode ? View.VISIBLE : View.GONE);
-        if (mTabScrollView != null && !mActionView.hasEmbeddedTabs() && mActionView.isCollapsed()) {
-            mTabScrollView.setVisibility(toActionMode ? View.GONE : View.VISIBLE);
-        }
-    }
-
-    /**
-     * @hide
-     */
-    public class TabImpl extends ActionBar.Tab {
-
-        private ActionBar.TabListener mCallback;
-        private Object mTag;
-        private Drawable mIcon;
-        private CharSequence mText;
-        private CharSequence mContentDesc;
-        private int mPosition = -1;
-        private View mCustomView;
-
-        @Override
-        public Object getTag() {
-            return mTag;
-        }
-
-        @Override
-        public Tab setTag(Object tag) {
-            mTag = tag;
-            return this;
-        }
-
-        public ActionBar.TabListener getCallback() {
-            return mCallback;
-        }
-
-        @Override
-        public Tab setTabListener(ActionBar.TabListener callback) {
-            mCallback = callback;
-            return this;
-        }
-
-        @Override
-        public View getCustomView() {
-            return mCustomView;
-        }
-
-        @Override
-        public Tab setCustomView(View view) {
-            mCustomView = view;
-            if (mPosition >= 0) {
-                mTabScrollView.updateTab(mPosition);
-            }
-            return this;
-        }
-
-        @Override
-        public Tab setCustomView(int layoutResId) {
-            return setCustomView(LayoutInflater.from(getThemedContext())
-                    .inflate(layoutResId, null));
-        }
-
-        @Override
-        public Drawable getIcon() {
-            return mIcon;
-        }
-
-        @Override
-        public int getPosition() {
-            return mPosition;
-        }
-
-        public void setPosition(int position) {
-            mPosition = position;
-        }
-
-        @Override
-        public CharSequence getText() {
-            return mText;
-        }
-
-        @Override
-        public Tab setIcon(Drawable icon) {
-            mIcon = icon;
-            if (mPosition >= 0) {
-                mTabScrollView.updateTab(mPosition);
-            }
-            return this;
-        }
-
-        @Override
-        public Tab setIcon(int resId) {
-            return setIcon(mContext.getResources().getDrawable(resId));
-        }
-
-        @Override
-        public Tab setText(CharSequence text) {
-            mText = text;
-            if (mPosition >= 0) {
-                mTabScrollView.updateTab(mPosition);
-            }
-            return this;
-        }
-
-        @Override
-        public Tab setText(int resId) {
-            return setText(mContext.getResources().getText(resId));
-        }
-
-        @Override
-        public void select() {
-            selectTab(this);
-        }
-
-        @Override
-        public Tab setContentDescription(int resId) {
-            return setContentDescription(mContext.getResources().getText(resId));
-        }
-
-        @Override
-        public Tab setContentDescription(CharSequence contentDesc) {
-            mContentDesc = contentDesc;
-            if (mPosition >= 0) {
-                mTabScrollView.updateTab(mPosition);
-            }
-            return this;
-        }
-
-        @Override
-        public CharSequence getContentDescription() {
-            return mContentDesc;
-        }
-    }
-
-    class ActionModeImpl extends ActionMode implements MenuBuilder.Callback {
-
-        private ActionMode.Callback mCallback;
-        private MenuBuilder mMenu;
-        private WeakReference<View> mCustomView;
-
-        public ActionModeImpl(ActionMode.Callback callback) {
-            mCallback = callback;
-            mMenu = new MenuBuilder(getThemedContext())
-                    .setDefaultShowAsAction(SupportMenuItem.SHOW_AS_ACTION_IF_ROOM);
-            mMenu.setCallback(this);
-        }
-
-        @Override
-        public MenuInflater getMenuInflater() {
-            return new SupportMenuInflater(getThemedContext());
-        }
-
-        @Override
-        public Menu getMenu() {
-            return mMenu;
-        }
-
-        @Override
-        public void finish() {
-            if (mActionMode != this) {
-                // Not the active action mode - no-op
-                return;
-            }
-
-            // If this change in state is going to cause the action bar
-            // to be hidden, defer the onDestroy callback until the animation
-            // is finished and associated relayout is about to happen. This lets
-            // apps better anticipate visibility and layout behavior.
-            if (!checkShowingFlags(mHiddenByApp, mHiddenBySystem, false)) {
-                // With the current state but the action bar hidden, our
-                // overall showing state is going to be false.
-                mDeferredDestroyActionMode = this;
-                mDeferredModeDestroyCallback = mCallback;
-            } else {
-                mCallback.onDestroyActionMode(this);
-            }
-            mCallback = null;
-            animateToMode(false);
-
-            // Clear out the context mode views after the animation finishes
-            mContextView.closeMode();
-            mActionView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-
-            mActionMode = null;
-        }
-
-        @Override
-        public void invalidate() {
-            mMenu.stopDispatchingItemsChanged();
-            try {
-                mCallback.onPrepareActionMode(this, mMenu);
-            } finally {
-                mMenu.startDispatchingItemsChanged();
-            }
-        }
-
-        public boolean dispatchOnCreate() {
-            mMenu.stopDispatchingItemsChanged();
-            try {
-                return mCallback.onCreateActionMode(this, mMenu);
-            } finally {
-                mMenu.startDispatchingItemsChanged();
-            }
-        }
-
-        @Override
-        public void setCustomView(View view) {
-            mContextView.setCustomView(view);
-            mCustomView = new WeakReference<View>(view);
-        }
-
-        @Override
-        public void setSubtitle(CharSequence subtitle) {
-            mContextView.setSubtitle(subtitle);
-        }
-
-        @Override
-        public void setTitle(CharSequence title) {
-            mContextView.setTitle(title);
-        }
-
-        @Override
-        public void setTitle(int resId) {
-            setTitle(mContext.getResources().getString(resId));
-        }
-
-        @Override
-        public void setSubtitle(int resId) {
-            setSubtitle(mContext.getResources().getString(resId));
-        }
-
-        @Override
-        public CharSequence getTitle() {
-            return mContextView.getTitle();
-        }
-
-        @Override
-        public CharSequence getSubtitle() {
-            return mContextView.getSubtitle();
-        }
-
-        @Override
-        public void setTitleOptionalHint(boolean titleOptional) {
-            super.setTitleOptionalHint(titleOptional);
-            mContextView.setTitleOptional(titleOptional);
-        }
-
-        @Override
-        public boolean isTitleOptional() {
-            return mContextView.isTitleOptional();
-        }
-
-        @Override
-        public View getCustomView() {
-            return mCustomView != null ? mCustomView.get() : null;
-        }
-
-        public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
-            if (mCallback != null) {
-                return mCallback.onActionItemClicked(this, item);
-            } else {
-                return false;
-            }
-        }
-
-        @Override
-        public void onMenuModeChange(MenuBuilder menu) {
-            if (mCallback == null) {
-                return;
-            }
-            invalidate();
-            mContextView.showOverflowMenu();
-        }
-
-        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
-        }
-
-        public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
-            if (mCallback == null) {
-                return false;
-            }
-
-            if (!subMenu.hasVisibleItems()) {
-                return true;
-            }
-
-            //new MenuPopupHelper(getThemedContext(), subMenu).show();
-            return true;
-        }
-
-        public void onCloseSubMenu(SubMenuBuilder menu) {
-        }
-
-        public void onMenuModeChange(Menu menu) {
-            if (mCallback == null) {
-                return;
-            }
-            invalidate();
-            mContextView.showOverflowMenu();
-        }
-    }
-
-    private void ensureTabsExist() {
-        if (mTabScrollView != null) {
-            return;
-        }
-
-        ScrollingTabContainerView tabScroller = new ScrollingTabContainerView(mContext);
-
-        if (mHasEmbeddedTabs) {
-            tabScroller.setVisibility(View.VISIBLE);
-            mActionView.setEmbeddedTabView(tabScroller);
-        } else {
-            if (getNavigationMode() == NAVIGATION_MODE_TABS) {
-                tabScroller.setVisibility(View.VISIBLE);
-            } else {
-                tabScroller.setVisibility(View.GONE);
-            }
-            mContainerView.setTabContainer(tabScroller);
-        }
-        mTabScrollView = tabScroller;
-    }
-
-    private void configureTab(Tab tab, int position) {
-        final TabImpl tabi = (TabImpl) tab;
-        final ActionBar.TabListener callback = tabi.getCallback();
-
-        if (callback == null) {
-            throw new IllegalStateException("Action Bar Tab must have a Callback");
-        }
-
-        tabi.setPosition(position);
-        mTabs.add(position, tabi);
-
-        final int count = mTabs.size();
-        for (int i = position + 1; i < count; i++) {
-            mTabs.get(i).setPosition(i);
-        }
-    }
-
-    private void cleanupTabs() {
-        if (mSelectedTab != null) {
-            selectTab(null);
-        }
-        mTabs.clear();
-        if (mTabScrollView != null) {
-            mTabScrollView.removeAllTabs();
-        }
-        mSavedTabPosition = INVALID_POSITION;
-    }
-
-    private static boolean checkShowingFlags(boolean hiddenByApp, boolean hiddenBySystem,
-            boolean showingForMode) {
-        if (showingForMode) {
-            return true;
-        } else if (hiddenByApp || hiddenBySystem) {
-            return false;
-        } else {
-            return true;
-        }
-    }
-
-    private void updateVisibility(boolean fromSystem) {
-        // Based on the current state, should we be hidden or shown?
-        final boolean shown = checkShowingFlags(mHiddenByApp, mHiddenBySystem, mShowingForMode);
-
-        if (shown) {
-            if (!mNowShowing) {
-                mNowShowing = true;
-                doShow(fromSystem);
-            }
-        } else {
-            if (mNowShowing) {
-                mNowShowing = false;
-                doHide(fromSystem);
-            }
-        }
-    }
-
-    public void setShowHideAnimationEnabled(boolean enabled) {
-        mShowHideAnimationEnabled = enabled;
-        if (!enabled) {
-            mTopVisibilityView.clearAnimation();
-            if (mSplitView != null) {
-                mSplitView.clearAnimation();
-            }
-        }
-    }
-
-    public void doShow(boolean fromSystem) {
-        mTopVisibilityView.clearAnimation();
-        if (mTopVisibilityView.getVisibility() == View.VISIBLE) {
-            return;
-        }
-
-        final boolean animate = isShowHideAnimationEnabled() || fromSystem;
-
-        if (animate) {
-            Animation anim = AnimationUtils.loadAnimation(mContext, R.anim.abc_slide_in_top);
-            mTopVisibilityView.startAnimation(anim);
-        }
-        mTopVisibilityView.setVisibility(View.VISIBLE);
-
-        if (mSplitView != null && mSplitView.getVisibility() != View.VISIBLE) {
-            if (animate) {
-                Animation anim = AnimationUtils.loadAnimation(mContext, R.anim.abc_slide_in_bottom);
-                mSplitView.startAnimation(anim);
-            }
-            mSplitView.setVisibility(View.VISIBLE);
-        }
-    }
-
-    public void doHide(boolean fromSystem) {
-        mTopVisibilityView.clearAnimation();
-        if (mTopVisibilityView.getVisibility() == View.GONE) {
-            return;
-        }
-
-        final boolean animate = isShowHideAnimationEnabled() || fromSystem;
-
-        if (animate) {
-            Animation anim = AnimationUtils.loadAnimation(mContext, R.anim.abc_slide_out_top);
-            mTopVisibilityView.startAnimation(anim);
-        }
-        mTopVisibilityView.setVisibility(View.GONE);
-
-        if (mSplitView != null && mSplitView.getVisibility() != View.GONE) {
-            if (animate) {
-                Animation anim = AnimationUtils
-                        .loadAnimation(mContext, R.anim.abc_slide_out_bottom);
-                mSplitView.startAnimation(anim);
-            }
-            mSplitView.setVisibility(View.GONE);
-        }
-    }
-
-    boolean isShowHideAnimationEnabled() {
-        return mShowHideAnimationEnabled;
-    }
-
-}
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarImplHC.java b/v7/appcompat/src/android/support/v7/app/ActionBarImplHC.java
deleted file mode 100644
index 35a69b9..0000000
--- a/v7/appcompat/src/android/support/v7/app/ActionBarImplHC.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2013 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.support.v7.appcompat.R;
-import android.support.v7.internal.widget.NativeActionModeAwareLayout;
-import android.view.ActionMode;
-import android.view.Menu;
-import android.view.MenuItem;
-
-class ActionBarImplHC extends ActionBarImplBase
-        implements NativeActionModeAwareLayout.OnActionModeForChildListener {
-
-    final NativeActionModeAwareLayout mNativeActionModeAwareLayout;
-    private ActionMode mCurActionMode;
-
-    public ActionBarImplHC(ActionBarActivity activity, Callback callback) {
-        super(activity, callback);
-
-        // NativeActionModeAwareLayout is used to notify us whena native Action Mode is started
-        mNativeActionModeAwareLayout = (NativeActionModeAwareLayout) activity
-                .findViewById(R.id.action_bar_root);
-
-        // Can be null when using FEATURE_ACTION_BAR_OVERLAY
-        if (mNativeActionModeAwareLayout != null) {
-            mNativeActionModeAwareLayout.setActionModeForChildListener(this);
-        }
-    }
-
-    // From NativeActionModeAwareLayout.OnActionModeForChildListener
-    @Override
-    public ActionMode.Callback onActionModeForChild(ActionMode.Callback callback) {
-        return new CallbackWrapper(callback);
-    }
-
-    @Override
-    public void show() {
-        super.show();
-        if (mCurActionMode != null) {
-            mCurActionMode.finish();
-        }
-    }
-
-    @Override
-    public void hide() {
-        super.hide();
-        if (mCurActionMode != null) {
-            mCurActionMode.finish();
-        }
-    }
-
-    @Override
-    boolean isShowHideAnimationEnabled() {
-        // Only allow animation if we're not currently showing an action mode
-        return mCurActionMode == null && super.isShowHideAnimationEnabled();
-    }
-
-    private class CallbackWrapper implements ActionMode.Callback {
-        private final ActionMode.Callback mWrappedCallback;
-
-        CallbackWrapper(ActionMode.Callback callback) {
-            mWrappedCallback = callback;
-        }
-
-        @Override
-        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
-            final boolean wrappedResult = mWrappedCallback.onCreateActionMode(mode, menu);
-            if (wrappedResult) {
-                // Keep reference to action mode
-                mCurActionMode = mode;
-                // Make sure that the compat Action Bar is shown
-                showForActionMode();
-            }
-            return wrappedResult;
-        }
-
-        @Override
-        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
-            return mWrappedCallback.onPrepareActionMode(mode, menu);
-        }
-
-        @Override
-        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-            return mWrappedCallback.onActionItemClicked(mode, item);
-        }
-
-        @Override
-        public void onDestroyActionMode(ActionMode mode) {
-            mWrappedCallback.onDestroyActionMode(mode);
-
-            // We previously shown the Action Bar for positioning purposes, now hide it again
-            hideForActionMode();
-            // Remove any reference to the mode
-            mCurActionMode = null;
-        }
-    }
-}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarImplICS.java b/v7/appcompat/src/android/support/v7/app/ActionBarImplICS.java
deleted file mode 100644
index 9f47c21..0000000
--- a/v7/appcompat/src/android/support/v7/app/ActionBarImplICS.java
+++ /dev/null
@@ -1,575 +0,0 @@
-/*
- * Copyright (C) 2012 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.Activity;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
-import android.support.v4.app.FragmentTransaction;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.SpinnerAdapter;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-
-class ActionBarImplICS extends ActionBar {
-
-    final Activity mActivity;
-    final Callback mCallback;
-    final android.app.ActionBar mActionBar;
-
-    private ImageView mHomeActionView;
-
-    FragmentTransaction mActiveTransaction;
-
-    private ArrayList<WeakReference<OnMenuVisibilityListenerWrapper>> mAddedMenuVisWrappers =
-            new ArrayList<WeakReference<OnMenuVisibilityListenerWrapper>>();
-
-    public ActionBarImplICS(Activity activity, Callback callback) {
-        this(activity, callback, true);
-    }
-
-    ActionBarImplICS(Activity activity, Callback callback, boolean checkHomeAsUpOption) {
-        mActivity = activity;
-        mCallback = callback;
-        mActionBar = activity.getActionBar();
-
-        if (checkHomeAsUpOption) {
-            // In v4.1+, if the the 'homeAsUp' display flag was set then the Home Button is enabled.
-            // We need to replicate this functionality on ICS.
-            if ((getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
-                setHomeButtonEnabled(true);
-            }
-        }
-    }
-
-    private OnMenuVisibilityListenerWrapper findAndRemoveMenuVisWrapper(
-            OnMenuVisibilityListener compatListener) {
-        for (int i = 0; i < mAddedMenuVisWrappers.size(); i++) {
-            OnMenuVisibilityListenerWrapper wrapper = mAddedMenuVisWrappers.get(i).get();
-            if (wrapper == null) {
-                mAddedMenuVisWrappers.remove(i--);
-            } else if (wrapper.mWrappedListener == compatListener) {
-                mAddedMenuVisWrappers.remove(i);
-                return wrapper;
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public void setCustomView(View view) {
-        mActionBar.setCustomView(view);
-    }
-
-    @Override
-    public void setCustomView(View view, LayoutParams layoutParams) {
-        android.app.ActionBar.LayoutParams lp =
-                new android.app.ActionBar.LayoutParams(layoutParams);
-        lp.gravity = layoutParams.gravity;
-
-        mActionBar.setCustomView(view, lp);
-    }
-
-    @Override
-    public void setCustomView(int resId) {
-        mActionBar.setCustomView(resId);
-    }
-
-    @Override
-    public void setIcon(int resId) {
-        mActionBar.setIcon(resId);
-    }
-
-    @Override
-    public void setIcon(Drawable icon) {
-        mActionBar.setIcon(icon);
-    }
-
-    @Override
-    public void setLogo(int resId) {
-        mActionBar.setLogo(resId);
-    }
-
-    @Override
-    public void setLogo(Drawable logo) {
-        mActionBar.setLogo(logo);
-    }
-
-    @Override
-    public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) {
-        mActionBar.setListNavigationCallbacks(adapter,
-                callback != null ? new OnNavigationListenerWrapper(callback) : null);
-    }
-
-    @Override
-    public void setSelectedNavigationItem(int position) {
-        mActionBar.setSelectedNavigationItem(position);
-    }
-
-    @Override
-    public int getSelectedNavigationIndex() {
-        return mActionBar.getSelectedNavigationIndex();
-    }
-
-    @Override
-    public int getNavigationItemCount() {
-        return mActionBar.getNavigationItemCount();
-    }
-
-    @Override
-    public void setTitle(CharSequence title) {
-        mActionBar.setTitle(title);
-    }
-
-    @Override
-    public void setTitle(int resId) {
-        mActionBar.setTitle(resId);
-    }
-
-    @Override
-    public void setSubtitle(CharSequence subtitle) {
-        mActionBar.setSubtitle(subtitle);
-    }
-
-    @Override
-    public void setSubtitle(int resId) {
-        mActionBar.setSubtitle(resId);
-    }
-
-    @Override
-    public void setDisplayOptions(int options) {
-        mActionBar.setDisplayOptions(options);
-    }
-
-    @Override
-    public void setDisplayOptions(int options, int mask) {
-        mActionBar.setDisplayOptions(options, mask);
-    }
-
-    @Override
-    public void setDisplayUseLogoEnabled(boolean useLogo) {
-        mActionBar.setDisplayUseLogoEnabled(useLogo);
-    }
-
-    @Override
-    public void setDisplayShowHomeEnabled(boolean showHome) {
-        mActionBar.setDisplayShowHomeEnabled(showHome);
-    }
-
-    @Override
-    public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) {
-        mActionBar.setDisplayHomeAsUpEnabled(showHomeAsUp);
-    }
-
-    @Override
-    public void setDisplayShowTitleEnabled(boolean showTitle) {
-        mActionBar.setDisplayShowTitleEnabled(showTitle);
-    }
-
-    @Override
-    public void setDisplayShowCustomEnabled(boolean showCustom) {
-        mActionBar.setDisplayShowCustomEnabled(showCustom);
-    }
-
-    @Override
-    public void setBackgroundDrawable(Drawable d) {
-        mActionBar.setBackgroundDrawable(d);
-    }
-
-    @Override
-    public void setStackedBackgroundDrawable(Drawable d) {
-        mActionBar.setStackedBackgroundDrawable(d);
-    }
-
-    @Override
-    public void setSplitBackgroundDrawable(Drawable d) {
-        mActionBar.setSplitBackgroundDrawable(d);
-    }
-
-    @Override
-    public View getCustomView() {
-        return mActionBar.getCustomView();
-    }
-
-    @Override
-    public CharSequence getTitle() {
-        return mActionBar.getTitle();
-    }
-
-    @Override
-    public CharSequence getSubtitle() {
-        return mActionBar.getSubtitle();
-    }
-
-    @Override
-    public int getNavigationMode() {
-        return mActionBar.getNavigationMode();
-    }
-
-    @Override
-    public void setNavigationMode(int mode) {
-        mActionBar.setNavigationMode(mode);
-    }
-
-    @Override
-    public int getDisplayOptions() {
-        return mActionBar.getDisplayOptions();
-    }
-
-    @Override
-    public Tab newTab() {
-        final android.app.ActionBar.Tab realTab = mActionBar.newTab();
-        final TabWrapper result = new TabWrapper(realTab);
-        realTab.setTag(result);
-        return result;
-    }
-
-    @Override
-    public void addTab(Tab tab) {
-        mActionBar.addTab(((TabWrapper) tab).mWrappedTab);
-    }
-
-    @Override
-    public void addTab(Tab tab, boolean setSelected) {
-        mActionBar.addTab(((TabWrapper) tab).mWrappedTab, setSelected);
-    }
-
-    @Override
-    public void addTab(Tab tab, int position) {
-        mActionBar.addTab(((TabWrapper) tab).mWrappedTab, position);
-    }
-
-    @Override
-    public void addTab(Tab tab, int position, boolean setSelected) {
-        mActionBar.addTab(((TabWrapper) tab).mWrappedTab, position, setSelected);
-    }
-
-    @Override
-    public void removeTab(Tab tab) {
-        mActionBar.removeTab(((TabWrapper) tab).mWrappedTab);
-    }
-
-    @Override
-    public void removeTabAt(int position) {
-        mActionBar.removeTabAt(position);
-    }
-
-    @Override
-    public void removeAllTabs() {
-        mActionBar.removeAllTabs();
-    }
-
-    @Override
-    public void selectTab(Tab tab) {
-        mActionBar.selectTab(((TabWrapper) tab).mWrappedTab);
-    }
-
-    @Override
-    public Tab getSelectedTab() {
-        return (Tab) mActionBar.getSelectedTab().getTag();
-    }
-
-    @Override
-    public Tab getTabAt(int index) {
-        return (Tab) mActionBar.getTabAt(index).getTag();
-    }
-
-    @Override
-    public int getTabCount() {
-        return mActionBar.getTabCount();
-    }
-
-    @Override
-    public Context getThemedContext() {
-        return mActionBar.getThemedContext();
-    }
-
-    @Override
-    public void setHomeAsUpIndicator(Drawable indicator) {
-        ImageView homeActionView = getHomeActionView();
-        if (homeActionView != null) {
-            if (indicator == null) {
-                indicator = getThemeDefaultUpIndicator();
-            }
-            homeActionView.setImageDrawable(indicator);
-        }
-    }
-
-    @Override
-    public void setHomeAsUpIndicator(int resId) {
-        ImageView homeActionView = getHomeActionView();
-        if (homeActionView != null) {
-            if (resId != 0) {
-                homeActionView.setImageResource(resId);
-            } else {
-                homeActionView.setImageDrawable(getThemeDefaultUpIndicator());
-            }
-        }
-    }
-
-    @Override
-    public int getHeight() {
-        return mActionBar.getHeight();
-    }
-
-    @Override
-    public void show() {
-        mActionBar.show();
-    }
-
-    @Override
-    public void hide() {
-        mActionBar.hide();
-    }
-
-    @Override
-    public boolean isShowing() {
-        return mActionBar.isShowing();
-    }
-
-    @Override
-    public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
-        if (listener != null) {
-            OnMenuVisibilityListenerWrapper w = new OnMenuVisibilityListenerWrapper(listener);
-            mAddedMenuVisWrappers.add(new WeakReference<OnMenuVisibilityListenerWrapper>(w));
-            mActionBar.addOnMenuVisibilityListener(w);
-        }
-    }
-
-    @Override
-    public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
-        OnMenuVisibilityListenerWrapper l = findAndRemoveMenuVisWrapper(listener);
-        mActionBar.removeOnMenuVisibilityListener(l);
-    }
-
-    @Override
-    public void setHomeButtonEnabled(boolean enabled) {
-        mActionBar.setHomeButtonEnabled(enabled);
-    }
-
-    FragmentTransaction getActiveTransaction() {
-        if (mActiveTransaction == null) {
-            mActiveTransaction = mCallback.getSupportFragmentManager().beginTransaction()
-                    .disallowAddToBackStack();
-        }
-        return mActiveTransaction;
-    }
-
-    void commitActiveTransaction() {
-        if (mActiveTransaction != null && !mActiveTransaction.isEmpty()) {
-            mActiveTransaction.commit();
-        }
-        mActiveTransaction = null;
-    }
-
-    ImageView getHomeActionView() {
-        if (mHomeActionView == null) {
-            final View home = mActivity.findViewById(android.R.id.home);
-            if (home == null) {
-                // Action bar doesn't have a known configuration, an OEM messed with things.
-                return null;
-            }
-
-            final ViewGroup parent = (ViewGroup) home.getParent();
-            final int childCount = parent.getChildCount();
-            if (childCount != 2) {
-                // No idea which one will be the right one, an OEM messed with things.
-                return null;
-            }
-
-            final View first = parent.getChildAt(0);
-            final View second = parent.getChildAt(1);
-            final View up = first.getId() == android.R.id.home ? second : first;
-
-            if (up instanceof ImageView) {
-                // Jackpot! (Probably...)
-                mHomeActionView = (ImageView) up;
-            }
-        }
-        return mHomeActionView;
-    }
-
-    Drawable getThemeDefaultUpIndicator() {
-        final TypedArray a = mActivity.obtainStyledAttributes(
-                new int[] { android.R.attr.homeAsUpIndicator });
-        final Drawable result = a.getDrawable(0);
-        a.recycle();
-        return result;
-    }
-
-    static class OnNavigationListenerWrapper implements android.app.ActionBar.OnNavigationListener {
-
-        private final OnNavigationListener mWrappedListener;
-
-        public OnNavigationListenerWrapper(OnNavigationListener l) {
-            mWrappedListener = l;
-        }
-
-        @Override
-        public boolean onNavigationItemSelected(int itemPosition, long itemId) {
-            return mWrappedListener.onNavigationItemSelected(itemPosition, itemId);
-        }
-
-    }
-
-    static class OnMenuVisibilityListenerWrapper implements
-            android.app.ActionBar.OnMenuVisibilityListener {
-
-        final OnMenuVisibilityListener mWrappedListener;
-
-        public OnMenuVisibilityListenerWrapper(OnMenuVisibilityListener l) {
-            mWrappedListener = l;
-        }
-
-        @Override
-        public void onMenuVisibilityChanged(boolean isVisible) {
-            mWrappedListener.onMenuVisibilityChanged(isVisible);
-        }
-
-    }
-
-    class TabWrapper extends ActionBar.Tab implements android.app.ActionBar.TabListener {
-        final android.app.ActionBar.Tab mWrappedTab;
-        private Object mTag;
-        private CharSequence mContentDescription;
-        private TabListener mTabListener;
-
-        public TabWrapper(android.app.ActionBar.Tab tab) {
-            mWrappedTab = tab;
-        }
-
-        @Override
-        public int getPosition() {
-            return mWrappedTab.getPosition();
-        }
-
-        @Override
-        public Drawable getIcon() {
-            return mWrappedTab.getIcon();
-        }
-
-        @Override
-        public CharSequence getText() {
-            return mWrappedTab.getText();
-        }
-
-        @Override
-        public Tab setIcon(Drawable icon) {
-            mWrappedTab.setIcon(icon);
-            return this;
-        }
-
-        @Override
-        public Tab setIcon(int resId) {
-            mWrappedTab.setIcon(resId);
-            return this;
-        }
-
-        @Override
-        public Tab setText(CharSequence text) {
-            mWrappedTab.setText(text);
-            return this;
-        }
-
-        @Override
-        public Tab setText(int resId) {
-            mWrappedTab.setText(resId);
-            return this;
-        }
-
-        @Override
-        public Tab setCustomView(View view) {
-            mWrappedTab.setCustomView(view);
-            return this;
-        }
-
-        @Override
-        public Tab setCustomView(int layoutResId) {
-            mWrappedTab.setCustomView(layoutResId);
-            return this;
-        }
-
-        @Override
-        public View getCustomView() {
-            return mWrappedTab.getCustomView();
-        }
-
-        @Override
-        public Tab setTag(Object obj) {
-            mTag = obj;
-            return this;
-        }
-
-        @Override
-        public Object getTag() {
-            return mTag;
-        }
-
-        @Override
-        public Tab setTabListener(TabListener listener) {
-            mTabListener = listener;
-            mWrappedTab.setTabListener(listener != null ? this : null);
-            return this;
-        }
-
-        @Override
-        public void select() {
-            mWrappedTab.select();
-        }
-
-        @Override
-        public Tab setContentDescription(int resId) {
-            mContentDescription = mActivity.getText(resId);
-            return this;
-        }
-
-        @Override
-        public Tab setContentDescription(CharSequence contentDesc) {
-            mContentDescription = contentDesc;
-            return this;
-        }
-
-        @Override
-        public CharSequence getContentDescription() {
-            return mContentDescription;
-        }
-
-        @Override
-        public void onTabSelected(android.app.ActionBar.Tab tab,
-                android.app.FragmentTransaction ft) {
-            mTabListener.onTabSelected(this, ft != null ? getActiveTransaction() : null);
-            commitActiveTransaction();
-        }
-
-        @Override
-        public void onTabUnselected(android.app.ActionBar.Tab tab,
-                android.app.FragmentTransaction ft) {
-            mTabListener.onTabUnselected(this, ft != null ? getActiveTransaction() : null);
-        }
-
-        @Override
-        public void onTabReselected(android.app.ActionBar.Tab tab,
-                android.app.FragmentTransaction ft) {
-            mTabListener.onTabReselected(this, ft != null ? getActiveTransaction() : null);
-            commitActiveTransaction();
-        }
-    }
-}
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarImplJB.java b/v7/appcompat/src/android/support/v7/app/ActionBarImplJB.java
deleted file mode 100644
index d5b09fb..0000000
--- a/v7/appcompat/src/android/support/v7/app/ActionBarImplJB.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2013 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.Activity;
-
-public class ActionBarImplJB extends ActionBarImplICS {
-
-    public ActionBarImplJB(Activity activity, Callback callback) {
-        // checkHomeAsUpOption = false as this was fixed in v4.1's framework
-        super(activity, callback, false);
-    }
-}
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarImplJBMR2.java b/v7/appcompat/src/android/support/v7/app/ActionBarImplJBMR2.java
deleted file mode 100644
index 474aca5..0000000
--- a/v7/appcompat/src/android/support/v7/app/ActionBarImplJBMR2.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2013 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.Activity;
-import android.graphics.drawable.Drawable;
-
-public class ActionBarImplJBMR2 extends ActionBarImplJB {
-
-    public ActionBarImplJBMR2(Activity activity, Callback callback) {
-        super(activity, callback);
-    }
-
-    @Override
-    public void setHomeAsUpIndicator(Drawable indicator) {
-        mActionBar.setHomeAsUpIndicator(indicator);
-    }
-
-    @Override
-    public void setHomeAsUpIndicator(int resId) {
-        mActionBar.setHomeAsUpIndicator(resId);
-    }
-
-    @Override
-    public void setHomeActionContentDescription(CharSequence description) {
-        mActionBar.setHomeActionContentDescription(description);
-    }
-
-    @Override
-    public void setHomeActionContentDescription(int resId) {
-        mActionBar.setHomeActionContentDescription(resId);
-    }
-}
diff --git a/v7/appcompat/src/android/support/v7/internal/VersionUtils.java b/v7/appcompat/src/android/support/v7/internal/VersionUtils.java
new file mode 100644
index 0000000..d711752
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/VersionUtils.java
@@ -0,0 +1,16 @@
+package android.support.v7.internal;
+
+import android.os.Build;
+
+/**
+ * @hide
+ */
+public class VersionUtils {
+
+    private VersionUtils() {}
+
+    public static boolean isAtLeastL() {
+        return Build.VERSION.SDK_INT >= 21 || Build.VERSION.CODENAME.equals("L");
+    }
+
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/app/NavItemSelectedListener.java b/v7/appcompat/src/android/support/v7/internal/app/NavItemSelectedListener.java
new file mode 100644
index 0000000..189260a
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/app/NavItemSelectedListener.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v7.internal.app;
+
+import android.support.v7.app.ActionBar;
+import android.support.v7.internal.widget.AdapterViewCompat;
+import android.view.View;
+
+/**
+ * Wrapper to adapt the ActionBar.OnNavigationListener in an AdapterView.OnItemSelectedListener
+ * for use in Spinner widgets. Used by action bar implementations.
+ *
+ * @hide
+ */
+class NavItemSelectedListener implements AdapterViewCompat.OnItemSelectedListener {
+    private final ActionBar.OnNavigationListener mListener;
+
+    public NavItemSelectedListener(ActionBar.OnNavigationListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void onItemSelected(AdapterViewCompat<?> parent, View view, int position, long id) {
+        if (mListener != null) {
+            mListener.onNavigationItemSelected(position, id);
+        }
+    }
+
+    @Override
+    public void onNothingSelected(AdapterViewCompat<?> parent) {
+        // Do nothing
+    }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java b/v7/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java
new file mode 100644
index 0000000..d5d6108
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/app/ToolbarActionBar.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v7.internal.app;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.app.ActionBar;
+import android.support.v7.internal.view.menu.MenuBuilder;
+import android.support.v7.internal.widget.DecorToolbar;
+import android.support.v7.internal.widget.ToolbarWidgetWrapper;
+import android.support.v7.view.ActionMode;
+import android.support.v7.widget.Toolbar;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
+import android.widget.SpinnerAdapter;
+
+import java.util.ArrayList;
+
+/**
+ * @hide
+ */
+public class ToolbarActionBar extends ActionBar {
+    private Toolbar mToolbar;
+    private DecorToolbar mDecorToolbar;
+    private WindowCallback mWindowCallback;
+
+    private boolean mLastMenuVisibility;
+    private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners =
+            new ArrayList<OnMenuVisibilityListener>();
+
+    private final Runnable mMenuInvalidator = new Runnable() {
+        @Override
+        public void run() {
+            populateOptionsMenu();
+        }
+    };
+
+    private final Toolbar.OnMenuItemClickListener mMenuClicker =
+            new Toolbar.OnMenuItemClickListener() {
+                @Override
+                public boolean onMenuItemClick(MenuItem item) {
+                    return mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item);
+                }
+            };
+
+    public ToolbarActionBar(Toolbar toolbar, CharSequence title,
+            WindowCallback windowCallback) {
+        mToolbar = toolbar;
+        mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false);
+        mWindowCallback = windowCallback;
+        mDecorToolbar.setWindowCallback(mWindowCallback);
+        toolbar.setOnMenuItemClickListener(mMenuClicker);
+        mDecorToolbar.setWindowTitle(title);
+    }
+
+    @Override
+    public void setCustomView(View view) {
+        setCustomView(view, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+    }
+
+    @Override
+    public void setCustomView(View view, LayoutParams layoutParams) {
+        view.setLayoutParams(layoutParams);
+        mDecorToolbar.setCustomView(view);
+    }
+
+    @Override
+    public void setCustomView(int resId) {
+        final LayoutInflater inflater = LayoutInflater.from(mToolbar.getContext());
+        setCustomView(inflater.inflate(resId, mToolbar, false));
+    }
+
+    @Override
+    public void setIcon(int resId) {
+        mDecorToolbar.setIcon(resId);
+    }
+
+    @Override
+    public void setIcon(Drawable icon) {
+        mDecorToolbar.setIcon(icon);
+    }
+
+    @Override
+    public void setLogo(int resId) {
+        mDecorToolbar.setLogo(resId);
+    }
+
+    @Override
+    public void setLogo(Drawable logo) {
+        mDecorToolbar.setLogo(logo);
+    }
+
+    @Override
+    public void setStackedBackgroundDrawable(Drawable d) {
+        // This space for rent (do nothing)
+    }
+
+    @Override
+    public void setSplitBackgroundDrawable(Drawable d) {
+        // This space for rent (do nothing)
+    }
+
+    @Override
+    public void setHomeButtonEnabled(boolean enabled) {
+        // If the nav button on a Toolbar is present, it's enabled. No-op.
+    }
+
+    @Override
+    public void setElevation(float elevation) {
+        ViewCompat.setElevation(mToolbar, elevation);
+    }
+
+    @Override
+    public float getElevation() {
+        return ViewCompat.getElevation(mToolbar);
+    }
+
+    @Override
+    public Context getThemedContext() {
+        return mToolbar.getContext();
+    }
+
+    @Override
+    public boolean isTitleTruncated() {
+        return super.isTitleTruncated();
+    }
+
+    @Override
+    public void setHomeAsUpIndicator(Drawable indicator) {
+        mToolbar.setNavigationIcon(indicator);
+    }
+
+    @Override
+    public void setHomeAsUpIndicator(int resId) {
+        mToolbar.setNavigationIcon(resId);
+    }
+
+    @Override
+    public void setHomeActionContentDescription(CharSequence description) {
+        mToolbar.setNavigationContentDescription(description);
+    }
+
+    @Override
+    public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) {
+        // Do nothing
+    }
+
+    @Override
+    public void setHomeActionContentDescription(int resId) {
+        mToolbar.setNavigationContentDescription(resId);
+    }
+
+    @Override
+    public void setShowHideAnimationEnabled(boolean enabled) {
+        // This space for rent; no-op.
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration config) {
+        super.onConfigurationChanged(config);
+    }
+
+    @Override
+    public ActionMode startActionMode(ActionMode.Callback callback) {
+        return mWindowCallback.startActionMode(callback);
+    }
+
+    @Override
+    public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) {
+        mDecorToolbar.setDropdownParams(adapter, new NavItemSelectedListener(callback));
+    }
+
+    @Override
+    public void setSelectedNavigationItem(int position) {
+        switch (mDecorToolbar.getNavigationMode()) {
+            case NAVIGATION_MODE_LIST:
+                mDecorToolbar.setDropdownSelectedPosition(position);
+                break;
+            default:
+                throw new IllegalStateException(
+                        "setSelectedNavigationIndex not valid for current navigation mode");
+        }
+    }
+
+    @Override
+    public int getSelectedNavigationIndex() {
+        return -1;
+    }
+
+    @Override
+    public int getNavigationItemCount() {
+        return 0;
+    }
+
+    @Override
+    public void setTitle(CharSequence title) {
+        mDecorToolbar.setTitle(title);
+    }
+
+    @Override
+    public void setTitle(int resId) {
+        mDecorToolbar.setTitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null);
+    }
+
+    @Override
+    public void setSubtitle(CharSequence subtitle) {
+        mDecorToolbar.setSubtitle(subtitle);
+    }
+
+    @Override
+    public void setSubtitle(int resId) {
+        mDecorToolbar.setSubtitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null);
+    }
+
+    @Override
+    public void setDisplayOptions(@DisplayOptions int options) {
+        setDisplayOptions(options, 0xffffffff);
+    }
+
+    @Override
+    public void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask) {
+        mDecorToolbar.setDisplayOptions((options & mask) |
+                mDecorToolbar.getDisplayOptions() & ~mask);
+    }
+
+    @Override
+    public void setDisplayUseLogoEnabled(boolean useLogo) {
+        setDisplayOptions(useLogo ? DISPLAY_USE_LOGO : 0, DISPLAY_USE_LOGO);
+    }
+
+    @Override
+    public void setDisplayShowHomeEnabled(boolean showHome) {
+        setDisplayOptions(showHome ? DISPLAY_SHOW_HOME : 0, DISPLAY_SHOW_HOME);
+    }
+
+    @Override
+    public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) {
+        setDisplayOptions(showHomeAsUp ? DISPLAY_HOME_AS_UP : 0, DISPLAY_HOME_AS_UP);
+    }
+
+    @Override
+    public void setDisplayShowTitleEnabled(boolean showTitle) {
+        setDisplayOptions(showTitle ? DISPLAY_SHOW_TITLE : 0, DISPLAY_SHOW_TITLE);
+    }
+
+    @Override
+    public void setDisplayShowCustomEnabled(boolean showCustom) {
+        setDisplayOptions(showCustom ? DISPLAY_SHOW_CUSTOM : 0, DISPLAY_SHOW_CUSTOM);
+    }
+
+    @Override
+    public void setBackgroundDrawable(@Nullable Drawable d) {
+        mToolbar.setBackgroundDrawable(d);
+    }
+
+    @Override
+    public View getCustomView() {
+        return mDecorToolbar.getCustomView();
+    }
+
+    @Override
+    public CharSequence getTitle() {
+        return mToolbar.getTitle();
+    }
+
+    @Override
+    public CharSequence getSubtitle() {
+        return mToolbar.getSubtitle();
+    }
+
+    @Override
+    public int getNavigationMode() {
+        return NAVIGATION_MODE_STANDARD;
+    }
+
+    @Override
+    public void setNavigationMode(@NavigationMode int mode) {
+        if (mode == ActionBar.NAVIGATION_MODE_TABS) {
+            throw new IllegalArgumentException("Tabs not supported in this configuration");
+        }
+        mDecorToolbar.setNavigationMode(mode);
+    }
+
+    @Override
+    public int getDisplayOptions() {
+        return mDecorToolbar.getDisplayOptions();
+    }
+
+    @Override
+    public Tab newTab() {
+        throw new UnsupportedOperationException(
+                "Tabs are not supported in toolbar action bars");
+    }
+
+    @Override
+    public void addTab(Tab tab) {
+        throw new UnsupportedOperationException(
+                "Tabs are not supported in toolbar action bars");
+    }
+
+    @Override
+    public void addTab(Tab tab, boolean setSelected) {
+        throw new UnsupportedOperationException(
+                "Tabs are not supported in toolbar action bars");
+    }
+
+    @Override
+    public void addTab(Tab tab, int position) {
+        throw new UnsupportedOperationException(
+                "Tabs are not supported in toolbar action bars");
+    }
+
+    @Override
+    public void addTab(Tab tab, int position, boolean setSelected) {
+        throw new UnsupportedOperationException(
+                "Tabs are not supported in toolbar action bars");
+    }
+
+    @Override
+    public void removeTab(Tab tab) {
+        throw new UnsupportedOperationException(
+                "Tabs are not supported in toolbar action bars");
+    }
+
+    @Override
+    public void removeTabAt(int position) {
+        throw new UnsupportedOperationException(
+                "Tabs are not supported in toolbar action bars");
+    }
+
+    @Override
+    public void removeAllTabs() {
+        throw new UnsupportedOperationException(
+                "Tabs are not supported in toolbar action bars");
+    }
+
+    @Override
+    public void selectTab(Tab tab) {
+        throw new UnsupportedOperationException(
+                "Tabs are not supported in toolbar action bars");
+    }
+
+    @Override
+    public Tab getSelectedTab() {
+        throw new UnsupportedOperationException(
+                "Tabs are not supported in toolbar action bars");
+    }
+
+    @Override
+    public Tab getTabAt(int index) {
+        throw new UnsupportedOperationException(
+                "Tabs are not supported in toolbar action bars");
+    }
+
+    @Override
+    public int getTabCount() {
+        return 0;
+    }
+
+    @Override
+    public int getHeight() {
+        return mToolbar.getHeight();
+    }
+
+    @Override
+    public void show() {
+        // TODO: Consider a better transition for this.
+        // Right now use no automatic transition so that the app can supply one if desired.
+        mToolbar.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void hide() {
+        // TODO: Consider a better transition for this.
+        // Right now use no automatic transition so that the app can supply one if desired.
+        mToolbar.setVisibility(View.GONE);
+    }
+
+    @Override
+    public boolean isShowing() {
+        return mToolbar.getVisibility() == View.VISIBLE;
+    }
+
+    @Override
+    public boolean openOptionsMenu() {
+        return mToolbar.showOverflowMenu();
+    }
+
+    @Override
+    public boolean invalidateOptionsMenu() {
+        mToolbar.removeCallbacks(mMenuInvalidator);
+        ViewCompat.postOnAnimation(mToolbar, mMenuInvalidator);
+        return true;
+    }
+
+    @Override
+    public boolean collapseActionView() {
+        if (mToolbar.hasExpandedActionView()) {
+            mToolbar.collapseActionView();
+            return true;
+        }
+        return false;
+    }
+
+    void populateOptionsMenu() {
+        final Menu menu = mToolbar.getMenu();
+        final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null;
+        if (mb != null) {
+            mb.stopDispatchingItemsChanged();
+        }
+        try {
+            menu.clear();
+            if (!mWindowCallback.onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu) ||
+                    !mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu)) {
+                menu.clear();
+            }
+        } finally {
+            if (mb != null) {
+                mb.startDispatchingItemsChanged();
+            }
+        }
+    }
+
+    @Override
+    public boolean onMenuKeyEvent(KeyEvent event) {
+        if (event.getAction() == KeyEvent.ACTION_UP) {
+            openOptionsMenu();
+        }
+        return true;
+    }
+
+    public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
+        mMenuVisibilityListeners.add(listener);
+    }
+
+    public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
+        mMenuVisibilityListeners.remove(listener);
+    }
+
+    public void dispatchMenuVisibilityChanged(boolean isVisible) {
+        if (isVisible == mLastMenuVisibility) {
+            return;
+        }
+        mLastMenuVisibility = isVisible;
+
+        final int count = mMenuVisibilityListeners.size();
+        for (int i = 0; i < count; i++) {
+            mMenuVisibilityListeners.get(i).onMenuVisibilityChanged(isVisible);
+        }
+    }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/app/WindowCallback.java b/v7/appcompat/src/android/support/v7/internal/app/WindowCallback.java
new file mode 100644
index 0000000..f9a389c
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/app/WindowCallback.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.app;
+
+import android.support.v7.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+
+/**
+ * Interface which allows us to intercept and control calls to {@link android.view.Window.Callback}.
+ * Used by ActionBarActivityDelegates.
+ *
+ * @hide
+ */
+public interface WindowCallback {
+
+    boolean onMenuItemSelected(int featureId, MenuItem menuItem);
+
+    boolean onCreatePanelMenu(int featureId, Menu menu);
+
+    boolean onPreparePanel(int featureId, View menuView, Menu menu);
+
+    ActionMode startActionMode(ActionMode.Callback callback);
+
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/app/WindowDecorActionBar.java b/v7/appcompat/src/android/support/v7/internal/app/WindowDecorActionBar.java
new file mode 100644
index 0000000..8c8c560
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/app/WindowDecorActionBar.java
@@ -0,0 +1,1337 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.app;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewPropertyAnimatorCompat;
+import android.support.v4.view.ViewPropertyAnimatorListener;
+import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
+import android.support.v4.view.ViewPropertyAnimatorUpdateListener;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.appcompat.R;
+import android.support.v7.internal.view.ActionBarPolicy;
+import android.support.v7.internal.view.AnimatorSetCompat;
+import android.support.v7.internal.view.SupportMenuInflater;
+import android.support.v7.internal.view.menu.MenuBuilder;
+import android.support.v7.internal.view.menu.MenuPopupHelper;
+import android.support.v7.internal.view.menu.SubMenuBuilder;
+import android.support.v7.internal.widget.ActionBarContainer;
+import android.support.v7.internal.widget.ActionBarContextView;
+import android.support.v7.internal.widget.ActionBarOverlayLayout;
+import android.support.v7.internal.widget.DecorToolbar;
+import android.support.v7.internal.widget.ScrollingTabContainerView;
+import android.support.v7.view.ActionMode;
+import android.support.v7.widget.Toolbar;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.Window;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.AnimationUtils;
+import android.widget.SpinnerAdapter;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * WindowDecorActionBar is the ActionBar implementation used
+ * by devices of all screen sizes as part of the window decor layout.
+ * If it detects a compatible decor, it will split contextual modes
+ * across both the ActionBarView at the top of the screen and
+ * a horizontal LinearLayout at the bottom which is normally hidden.
+ *
+ * @hide
+ */
+public class WindowDecorActionBar extends ActionBar implements
+        ActionBarOverlayLayout.ActionBarVisibilityCallback {
+    private static final String TAG = "WindowDecorActionBar";
+
+    /**
+     * Only allow show/hide animations on ICS+, as that is what ViewPropertyAnimatorCompat supports
+     */
+    private static final boolean ALLOW_SHOW_HIDE_ANIMATIONS = Build.VERSION.SDK_INT >= 14;
+
+    private Context mContext;
+    private Context mThemedContext;
+    private FragmentActivity mActivity;
+    private Dialog mDialog;
+
+    private ActionBarOverlayLayout mOverlayLayout;
+    private ActionBarContainer mContainerView;
+    private DecorToolbar mDecorToolbar;
+    private ActionBarContextView mContextView;
+    private ActionBarContainer mSplitView;
+    private View mContentView;
+    private ScrollingTabContainerView mTabScrollView;
+
+    private ArrayList<TabImpl> mTabs = new ArrayList<TabImpl>();
+
+    private TabImpl mSelectedTab;
+    private int mSavedTabPosition = INVALID_POSITION;
+
+    private boolean mDisplayHomeAsUpSet;
+
+    ActionModeImpl mActionMode;
+    ActionMode mDeferredDestroyActionMode;
+    ActionMode.Callback mDeferredModeDestroyCallback;
+
+    private boolean mLastMenuVisibility;
+    private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners =
+            new ArrayList<OnMenuVisibilityListener>();
+
+    private static final int CONTEXT_DISPLAY_NORMAL = 0;
+    private static final int CONTEXT_DISPLAY_SPLIT = 1;
+
+    private static final int INVALID_POSITION = -1;
+
+    private int mContextDisplayMode;
+    private boolean mHasEmbeddedTabs;
+
+    private int mCurWindowVisibility = View.VISIBLE;
+
+    private boolean mContentAnimations = true;
+    private boolean mHiddenByApp;
+    private boolean mHiddenBySystem;
+    private boolean mShowingForMode;
+
+    private boolean mNowShowing = true;
+
+    private AnimatorSetCompat mCurrentShowAnim;
+    private boolean mShowHideAnimationEnabled;
+    boolean mHideOnContentScroll;
+
+    final ViewPropertyAnimatorListener mHideListener = new ViewPropertyAnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(View view) {
+            if (mContentAnimations && mContentView != null) {
+                ViewCompat.setTranslationY(mContentView, 0f);
+                ViewCompat.setTranslationY(mContainerView, 0f);
+            }
+            if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) {
+                mSplitView.setVisibility(View.GONE);
+            }
+            mContainerView.setVisibility(View.GONE);
+            mContainerView.setTransitioning(false);
+            mCurrentShowAnim = null;
+            completeDeferredDestroyActionMode();
+            if (mOverlayLayout != null) {
+                ViewCompat.requestApplyInsets(mOverlayLayout);
+            }
+        }
+    };
+
+    final ViewPropertyAnimatorListener mShowListener = new ViewPropertyAnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(View view) {
+            mCurrentShowAnim = null;
+            mContainerView.requestLayout();
+        }
+    };
+
+    final ViewPropertyAnimatorUpdateListener mUpdateListener =
+            new ViewPropertyAnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(View view) {
+                    final ViewParent parent = mContainerView.getParent();
+                    ((View) parent).invalidate();
+                }
+            };
+
+    public WindowDecorActionBar(ActionBarActivity activity, boolean overlayMode) {
+        mActivity = activity;
+        Window window = activity.getWindow();
+        View decor = window.getDecorView();
+        init(decor);
+        if (!overlayMode) {
+            mContentView = decor.findViewById(android.R.id.content);
+        }
+    }
+
+    public WindowDecorActionBar(Dialog dialog) {
+        mDialog = dialog;
+        init(dialog.getWindow().getDecorView());
+    }
+
+    /**
+     * Only for edit mode.
+     * @hide
+     */
+    public WindowDecorActionBar(View layout) {
+        assert layout.isInEditMode();
+        init(layout);
+    }
+
+    private void init(View decor) {
+        mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(R.id.decor_content_parent);
+        if (mOverlayLayout != null) {
+            mOverlayLayout.setActionBarVisibilityCallback(this);
+        }
+        mDecorToolbar = getDecorToolbar(decor.findViewById(R.id.action_bar));
+        mContextView = (ActionBarContextView) decor.findViewById(
+                R.id.action_context_bar);
+        mContainerView = (ActionBarContainer) decor.findViewById(
+                R.id.action_bar_container);
+
+         mSplitView = (ActionBarContainer) decor.findViewById(R.id.split_action_bar);
+
+        if (mDecorToolbar == null || mContextView == null || mContainerView == null) {
+            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
+                    "with a compatible window decor layout");
+        }
+
+        mContext = mDecorToolbar.getContext();
+        mContextDisplayMode = mDecorToolbar.isSplit() ?
+                CONTEXT_DISPLAY_SPLIT : CONTEXT_DISPLAY_NORMAL;
+
+        // This was initially read from the action bar style
+        final int current = mDecorToolbar.getDisplayOptions();
+        final boolean homeAsUp = (current & DISPLAY_HOME_AS_UP) != 0;
+        if (homeAsUp) {
+            mDisplayHomeAsUpSet = true;
+        }
+
+        ActionBarPolicy abp = ActionBarPolicy.get(mContext);
+        setHomeButtonEnabled(abp.enableHomeButtonByDefault() || homeAsUp);
+        setHasEmbeddedTabs(abp.hasEmbeddedTabs());
+
+        final TypedArray a = mContext.obtainStyledAttributes(null,
+                R.styleable.ActionBar,
+                R.attr.actionBarStyle, 0);
+        if (a.getBoolean(R.styleable.ActionBar_hideOnContentScroll, false)) {
+            setHideOnContentScrollEnabled(true);
+        }
+        final int elevation = a.getDimensionPixelSize(R.styleable.ActionBar_elevation, 0);
+        if (elevation != 0) {
+            setElevation(elevation);
+        }
+        a.recycle();
+    }
+
+    private DecorToolbar getDecorToolbar(View view) {
+        if (view instanceof DecorToolbar) {
+            return (DecorToolbar) view;
+        } else if (view instanceof Toolbar) {
+            return ((Toolbar) view).getWrapper();
+        } else {
+            throw new IllegalStateException("Can't make a decor toolbar out of " +
+                    view.getClass().getSimpleName());
+        }
+    }
+
+    @Override
+    public void setElevation(float elevation) {
+        ViewCompat.setElevation(mContainerView, elevation);
+        if (mSplitView != null) {
+            ViewCompat.setElevation(mSplitView, elevation);
+        }
+    }
+
+    @Override
+    public float getElevation() {
+        return ViewCompat.getElevation(mContainerView);
+    }
+
+    public void onConfigurationChanged(Configuration newConfig) {
+        setHasEmbeddedTabs(ActionBarPolicy.get(mContext).hasEmbeddedTabs());
+    }
+
+    private void setHasEmbeddedTabs(boolean hasEmbeddedTabs) {
+        mHasEmbeddedTabs = hasEmbeddedTabs;
+        // Switch tab layout configuration if needed
+        if (!mHasEmbeddedTabs) {
+            mDecorToolbar.setEmbeddedTabView(null);
+            mContainerView.setTabContainer(mTabScrollView);
+        } else {
+            mContainerView.setTabContainer(null);
+            mDecorToolbar.setEmbeddedTabView(mTabScrollView);
+        }
+        final boolean isInTabMode = getNavigationMode() == NAVIGATION_MODE_TABS;
+        if (mTabScrollView != null) {
+            if (isInTabMode) {
+                mTabScrollView.setVisibility(View.VISIBLE);
+                if (mOverlayLayout != null) {
+                    ViewCompat.requestApplyInsets(mOverlayLayout);
+                }
+            } else {
+                mTabScrollView.setVisibility(View.GONE);
+            }
+        }
+        mDecorToolbar.setCollapsible(!mHasEmbeddedTabs && isInTabMode);
+        mOverlayLayout.setHasNonEmbeddedTabs(!mHasEmbeddedTabs && isInTabMode);
+    }
+
+    private void ensureTabsExist() {
+        if (mTabScrollView != null) {
+            return;
+        }
+
+        ScrollingTabContainerView tabScroller = new ScrollingTabContainerView(mContext);
+
+        if (mHasEmbeddedTabs) {
+            tabScroller.setVisibility(View.VISIBLE);
+            mDecorToolbar.setEmbeddedTabView(tabScroller);
+        } else {
+            if (getNavigationMode() == NAVIGATION_MODE_TABS) {
+                tabScroller.setVisibility(View.VISIBLE);
+                if (mOverlayLayout != null) {
+                    ViewCompat.requestApplyInsets(mOverlayLayout);
+                }
+            } else {
+                tabScroller.setVisibility(View.GONE);
+            }
+            mContainerView.setTabContainer(tabScroller);
+        }
+        mTabScrollView = tabScroller;
+    }
+
+    void completeDeferredDestroyActionMode() {
+        if (mDeferredModeDestroyCallback != null) {
+            mDeferredModeDestroyCallback.onDestroyActionMode(mDeferredDestroyActionMode);
+            mDeferredDestroyActionMode = null;
+            mDeferredModeDestroyCallback = null;
+        }
+    }
+
+    public void onWindowVisibilityChanged(int visibility) {
+        mCurWindowVisibility = visibility;
+    }
+
+    /**
+     * Enables or disables animation between show/hide states.
+     * If animation is disabled using this method, animations in progress
+     * will be finished.
+     *
+     * @param enabled true to animate, false to not animate.
+     */
+    public void setShowHideAnimationEnabled(boolean enabled) {
+        mShowHideAnimationEnabled = enabled;
+        if (!enabled && mCurrentShowAnim != null) {
+            mCurrentShowAnim.cancel();
+        }
+    }
+
+    public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
+        mMenuVisibilityListeners.add(listener);
+    }
+
+    public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
+        mMenuVisibilityListeners.remove(listener);
+    }
+
+    public void dispatchMenuVisibilityChanged(boolean isVisible) {
+        if (isVisible == mLastMenuVisibility) {
+            return;
+        }
+        mLastMenuVisibility = isVisible;
+
+        final int count = mMenuVisibilityListeners.size();
+        for (int i = 0; i < count; i++) {
+            mMenuVisibilityListeners.get(i).onMenuVisibilityChanged(isVisible);
+        }
+    }
+
+    @Override
+    public void setCustomView(int resId) {
+        setCustomView(LayoutInflater.from(getThemedContext()).inflate(resId,
+                mDecorToolbar.getViewGroup(), false));
+    }
+
+    @Override
+    public void setDisplayUseLogoEnabled(boolean useLogo) {
+        setDisplayOptions(useLogo ? DISPLAY_USE_LOGO : 0, DISPLAY_USE_LOGO);
+    }
+
+    @Override
+    public void setDisplayShowHomeEnabled(boolean showHome) {
+        setDisplayOptions(showHome ? DISPLAY_SHOW_HOME : 0, DISPLAY_SHOW_HOME);
+    }
+
+    @Override
+    public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) {
+        setDisplayOptions(showHomeAsUp ? DISPLAY_HOME_AS_UP : 0, DISPLAY_HOME_AS_UP);
+    }
+
+    @Override
+    public void setDisplayShowTitleEnabled(boolean showTitle) {
+        setDisplayOptions(showTitle ? DISPLAY_SHOW_TITLE : 0, DISPLAY_SHOW_TITLE);
+    }
+
+    @Override
+    public void setDisplayShowCustomEnabled(boolean showCustom) {
+        setDisplayOptions(showCustom ? DISPLAY_SHOW_CUSTOM : 0, DISPLAY_SHOW_CUSTOM);
+    }
+
+    @Override
+    public void setHomeButtonEnabled(boolean enable) {
+        mDecorToolbar.setHomeButtonEnabled(enable);
+    }
+
+    @Override
+    public void setTitle(int resId) {
+        setTitle(mContext.getString(resId));
+    }
+
+    @Override
+    public void setSubtitle(int resId) {
+        setSubtitle(mContext.getString(resId));
+    }
+
+    public void setSelectedNavigationItem(int position) {
+        switch (mDecorToolbar.getNavigationMode()) {
+            case NAVIGATION_MODE_TABS:
+                selectTab(mTabs.get(position));
+                break;
+            case NAVIGATION_MODE_LIST:
+                mDecorToolbar.setDropdownSelectedPosition(position);
+                break;
+            default:
+                throw new IllegalStateException(
+                        "setSelectedNavigationIndex not valid for current navigation mode");
+        }
+    }
+
+    public void removeAllTabs() {
+        cleanupTabs();
+    }
+
+    private void cleanupTabs() {
+        if (mSelectedTab != null) {
+            selectTab(null);
+        }
+        mTabs.clear();
+        if (mTabScrollView != null) {
+            mTabScrollView.removeAllTabs();
+        }
+        mSavedTabPosition = INVALID_POSITION;
+    }
+
+    public void setTitle(CharSequence title) {
+        mDecorToolbar.setTitle(title);
+    }
+
+    public void setSubtitle(CharSequence subtitle) {
+        mDecorToolbar.setSubtitle(subtitle);
+    }
+
+    public void setDisplayOptions(int options) {
+        if ((options & DISPLAY_HOME_AS_UP) != 0) {
+            mDisplayHomeAsUpSet = true;
+        }
+        mDecorToolbar.setDisplayOptions(options);
+    }
+
+    public void setDisplayOptions(int options, int mask) {
+        final int current = mDecorToolbar.getDisplayOptions();
+        if ((mask & DISPLAY_HOME_AS_UP) != 0) {
+            mDisplayHomeAsUpSet = true;
+        }
+        mDecorToolbar.setDisplayOptions((options & mask) | (current & ~mask));
+    }
+
+    public void setBackgroundDrawable(Drawable d) {
+        mContainerView.setPrimaryBackground(d);
+    }
+
+    public void setStackedBackgroundDrawable(Drawable d) {
+        mContainerView.setStackedBackground(d);
+    }
+
+    public void setSplitBackgroundDrawable(Drawable d) {
+        if (mSplitView != null) {
+            mSplitView.setSplitBackground(d);
+        }
+    }
+
+    public View getCustomView() {
+        return mDecorToolbar.getCustomView();
+    }
+
+    public CharSequence getTitle() {
+        return mDecorToolbar.getTitle();
+    }
+
+    public CharSequence getSubtitle() {
+        return mDecorToolbar.getSubtitle();
+    }
+
+    public int getNavigationMode() {
+        return mDecorToolbar.getNavigationMode();
+    }
+
+    public int getDisplayOptions() {
+        return mDecorToolbar.getDisplayOptions();
+    }
+
+    public ActionMode startActionMode(ActionMode.Callback callback) {
+        if (mActionMode != null) {
+            mActionMode.finish();
+        }
+
+        mOverlayLayout.setHideOnContentScrollEnabled(false);
+        mContextView.killMode();
+        ActionModeImpl mode = new ActionModeImpl(callback);
+        if (mode.dispatchOnCreate()) {
+            mode.invalidate();
+            mContextView.initForMode(mode);
+            animateToMode(true);
+            if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) {
+                // TODO animate this
+                if (mSplitView.getVisibility() != View.VISIBLE) {
+                    mSplitView.setVisibility(View.VISIBLE);
+                    if (mOverlayLayout != null) {
+                        ViewCompat.requestApplyInsets(mOverlayLayout);
+                    }
+                }
+            }
+            mContextView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+            mActionMode = mode;
+            return mode;
+        }
+        return null;
+    }
+
+    private void configureTab(Tab tab, int position) {
+        final TabImpl tabi = (TabImpl) tab;
+        final ActionBar.TabListener callback = tabi.getCallback();
+
+        if (callback == null) {
+            throw new IllegalStateException("Action Bar Tab must have a Callback");
+        }
+
+        tabi.setPosition(position);
+        mTabs.add(position, tabi);
+
+        final int count = mTabs.size();
+        for (int i = position + 1; i < count; i++) {
+            mTabs.get(i).setPosition(i);
+        }
+    }
+
+    @Override
+    public void addTab(Tab tab) {
+        addTab(tab, mTabs.isEmpty());
+    }
+
+    @Override
+    public void addTab(Tab tab, int position) {
+        addTab(tab, position, mTabs.isEmpty());
+    }
+
+    @Override
+    public void addTab(Tab tab, boolean setSelected) {
+        ensureTabsExist();
+        mTabScrollView.addTab(tab, setSelected);
+        configureTab(tab, mTabs.size());
+        if (setSelected) {
+            selectTab(tab);
+        }
+    }
+
+    @Override
+    public void addTab(Tab tab, int position, boolean setSelected) {
+        ensureTabsExist();
+        mTabScrollView.addTab(tab, position, setSelected);
+        configureTab(tab, position);
+        if (setSelected) {
+            selectTab(tab);
+        }
+    }
+
+    @Override
+    public Tab newTab() {
+        return new TabImpl();
+    }
+
+    @Override
+    public void removeTab(Tab tab) {
+        removeTabAt(tab.getPosition());
+    }
+
+    @Override
+    public void removeTabAt(int position) {
+        if (mTabScrollView == null) {
+            // No tabs around to remove
+            return;
+        }
+
+        int selectedTabPosition = mSelectedTab != null
+                ? mSelectedTab.getPosition() : mSavedTabPosition;
+        mTabScrollView.removeTabAt(position);
+        TabImpl removedTab = mTabs.remove(position);
+        if (removedTab != null) {
+            removedTab.setPosition(-1);
+        }
+
+        final int newTabCount = mTabs.size();
+        for (int i = position; i < newTabCount; i++) {
+            mTabs.get(i).setPosition(i);
+        }
+
+        if (selectedTabPosition == position) {
+            selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1)));
+        }
+    }
+
+    @Override
+    public void selectTab(Tab tab) {
+        if (getNavigationMode() != NAVIGATION_MODE_TABS) {
+            mSavedTabPosition = tab != null ? tab.getPosition() : INVALID_POSITION;
+            return;
+        }
+
+        final FragmentTransaction trans = mDecorToolbar.getViewGroup().isInEditMode() ? null :
+                mActivity.getSupportFragmentManager().beginTransaction().disallowAddToBackStack();
+
+        if (mSelectedTab == tab) {
+            if (mSelectedTab != null) {
+                mSelectedTab.getCallback().onTabReselected(mSelectedTab, trans);
+                mTabScrollView.animateToTab(tab.getPosition());
+            }
+        } else {
+            mTabScrollView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION);
+            if (mSelectedTab != null) {
+                mSelectedTab.getCallback().onTabUnselected(mSelectedTab, trans);
+            }
+            mSelectedTab = (TabImpl) tab;
+            if (mSelectedTab != null) {
+                mSelectedTab.getCallback().onTabSelected(mSelectedTab, trans);
+            }
+        }
+
+        if (trans != null && !trans.isEmpty()) {
+            trans.commit();
+        }
+    }
+
+    @Override
+    public Tab getSelectedTab() {
+        return mSelectedTab;
+    }
+
+    @Override
+    public int getHeight() {
+        return mContainerView.getHeight();
+    }
+
+    public void enableContentAnimations(boolean enabled) {
+        mContentAnimations = enabled;
+    }
+
+    @Override
+    public void show() {
+        if (mHiddenByApp) {
+            mHiddenByApp = false;
+            updateVisibility(false);
+        }
+    }
+
+    private void showForActionMode() {
+        if (!mShowingForMode) {
+            mShowingForMode = true;
+            if (mOverlayLayout != null) {
+                mOverlayLayout.setShowingForActionMode(true);
+            }
+            updateVisibility(false);
+        }
+    }
+
+    public void showForSystem() {
+        if (mHiddenBySystem) {
+            mHiddenBySystem = false;
+            updateVisibility(true);
+        }
+    }
+
+    @Override
+    public void hide() {
+        if (!mHiddenByApp) {
+            mHiddenByApp = true;
+            updateVisibility(false);
+        }
+    }
+
+    private void hideForActionMode() {
+        if (mShowingForMode) {
+            mShowingForMode = false;
+            if (mOverlayLayout != null) {
+                mOverlayLayout.setShowingForActionMode(false);
+            }
+            updateVisibility(false);
+        }
+    }
+
+    public void hideForSystem() {
+        if (!mHiddenBySystem) {
+            mHiddenBySystem = true;
+            updateVisibility(true);
+        }
+    }
+
+    @Override
+    public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) {
+        if (hideOnContentScroll && !mOverlayLayout.isInOverlayMode()) {
+            throw new IllegalStateException("Action bar must be in overlay mode " +
+                    "(Window.FEATURE_OVERLAY_ACTION_BAR) to enable hide on content scroll");
+        }
+        mHideOnContentScroll = hideOnContentScroll;
+        mOverlayLayout.setHideOnContentScrollEnabled(hideOnContentScroll);
+    }
+
+    @Override
+    public boolean isHideOnContentScrollEnabled() {
+        return mOverlayLayout.isHideOnContentScrollEnabled();
+    }
+
+    @Override
+    public int getHideOffset() {
+        return mOverlayLayout.getActionBarHideOffset();
+    }
+
+    @Override
+    public void setHideOffset(int offset) {
+        if (offset != 0 && !mOverlayLayout.isInOverlayMode()) {
+            throw new IllegalStateException("Action bar must be in overlay mode " +
+                    "(Window.FEATURE_OVERLAY_ACTION_BAR) to set a non-zero hide offset");
+        }
+        mOverlayLayout.setActionBarHideOffset(offset);
+    }
+
+    private static boolean checkShowingFlags(boolean hiddenByApp, boolean hiddenBySystem,
+            boolean showingForMode) {
+        if (showingForMode) {
+            return true;
+        } else if (hiddenByApp || hiddenBySystem) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    private void updateVisibility(boolean fromSystem) {
+        // Based on the current state, should we be hidden or shown?
+        final boolean shown = checkShowingFlags(mHiddenByApp, mHiddenBySystem,
+                mShowingForMode);
+
+        if (shown) {
+            if (!mNowShowing) {
+                mNowShowing = true;
+                doShow(fromSystem);
+            }
+        } else {
+            if (mNowShowing) {
+                mNowShowing = false;
+                doHide(fromSystem);
+            }
+        }
+    }
+
+    public void doShow(boolean fromSystem) {
+        if (mCurrentShowAnim != null) {
+            mCurrentShowAnim.cancel();
+        }
+        mContainerView.setVisibility(View.VISIBLE);
+
+        if (mCurWindowVisibility == View.VISIBLE && ALLOW_SHOW_HIDE_ANIMATIONS &&
+                (mShowHideAnimationEnabled || fromSystem)) {
+            // because we're about to ask its window loc
+            ViewCompat.setTranslationY(mContainerView, 0f);
+            float startingY = -mContainerView.getHeight();
+            if (fromSystem) {
+                int topLeft[] = {0, 0};
+                mContainerView.getLocationInWindow(topLeft);
+                startingY -= topLeft[1];
+            }
+            ViewCompat.setTranslationY(mContainerView, startingY);
+            AnimatorSetCompat anim = new AnimatorSetCompat();
+            ViewPropertyAnimatorCompat a = ViewCompat.animate(mContainerView).translationY(0f);
+            a.setUpdateListener(mUpdateListener);
+            anim.play(a);
+            if (mContentAnimations && mContentView != null) {
+                ViewCompat.setTranslationY(mContentView, startingY);
+                anim.play(ViewCompat.animate(mContentView).translationY(0f));
+            }
+            if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) {
+                ViewCompat.setTranslationY(mSplitView, mSplitView.getHeight());
+                mSplitView.setVisibility(View.VISIBLE);
+                anim.play(ViewCompat.animate(mSplitView).translationY(0f));
+            }
+            anim.setInterpolator(AnimationUtils.loadInterpolator(mContext,
+                    android.R.anim.decelerate_interpolator));
+            anim.setDuration(250);
+            // If this is being shown from the system, add a small delay.
+            // This is because we will also be animating in the status bar,
+            // and these two elements can't be done in lock-step.  So we give
+            // a little time for the status bar to start its animation before
+            // the action bar animates.  (This corresponds to the corresponding
+            // case when hiding, where the status bar has a small delay before
+            // starting.)
+            anim.setListener(mShowListener);
+            mCurrentShowAnim = anim;
+            anim.start();
+        } else {
+            ViewCompat.setAlpha(mContainerView, 1f);
+            ViewCompat.setTranslationY(mContainerView, 0);
+            if (mContentAnimations && mContentView != null) {
+                ViewCompat.setTranslationY(mContentView, 0);
+            }
+            if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) {
+                ViewCompat.setAlpha(mSplitView, 1f);
+                ViewCompat.setTranslationY(mSplitView, 0);
+                mSplitView.setVisibility(View.VISIBLE);
+            }
+            mShowListener.onAnimationEnd(null);
+        }
+        if (mOverlayLayout != null) {
+            ViewCompat.requestApplyInsets(mOverlayLayout);
+        }
+    }
+
+    public void doHide(boolean fromSystem) {
+        if (mCurrentShowAnim != null) {
+            mCurrentShowAnim.cancel();
+        }
+
+        if (mCurWindowVisibility == View.VISIBLE && ALLOW_SHOW_HIDE_ANIMATIONS &&
+                (mShowHideAnimationEnabled || fromSystem)) {
+            ViewCompat.setAlpha(mContainerView, 1f);
+            mContainerView.setTransitioning(true);
+            AnimatorSetCompat anim = new AnimatorSetCompat();
+            float endingY = -mContainerView.getHeight();
+            if (fromSystem) {
+                int topLeft[] = {0, 0};
+                mContainerView.getLocationInWindow(topLeft);
+                endingY -= topLeft[1];
+            }
+            ViewPropertyAnimatorCompat a = ViewCompat.animate(mContainerView).translationY(endingY);
+            a.setUpdateListener(mUpdateListener);
+            anim.play(a);
+            if (mContentAnimations && mContentView != null) {
+                anim.play(ViewCompat.animate(mContentView).translationY(endingY));
+            }
+            if (mSplitView != null && mSplitView.getVisibility() == View.VISIBLE) {
+                ViewCompat.setAlpha(mSplitView, 1f);
+                anim.play(ViewCompat.animate(mSplitView).translationY(mSplitView.getHeight()));
+            }
+            anim.setInterpolator(AnimationUtils.loadInterpolator(mContext,
+                    android.R.anim.accelerate_interpolator));
+            anim.setDuration(250);
+            anim.setListener(mHideListener);
+            mCurrentShowAnim = anim;
+            anim.start();
+        } else {
+            mHideListener.onAnimationEnd(null);
+        }
+    }
+
+    public boolean isShowing() {
+        final int height = getHeight();
+        // Take into account the case where the bar has a 0 height due to not being measured yet.
+        return mNowShowing && (height == 0 || getHideOffset() < height);
+    }
+
+    public void animateToMode(boolean toActionMode) {
+        if (toActionMode) {
+            showForActionMode();
+        } else {
+            hideForActionMode();
+        }
+
+        mDecorToolbar.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE);
+        mContextView.animateToVisibility(toActionMode ? View.VISIBLE : View.GONE);
+        if (mTabScrollView != null && !mDecorToolbar.hasEmbeddedTabs() &&
+                isCollapsed(mDecorToolbar.getViewGroup())) {
+            mTabScrollView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE);
+        }
+    }
+
+    private boolean isCollapsed(View view) {
+        return view == null || view.getVisibility() == View.GONE || view.getMeasuredHeight() == 0;
+    }
+
+    public Context getThemedContext() {
+        if (mThemedContext == null) {
+            TypedValue outValue = new TypedValue();
+            Resources.Theme currentTheme = mContext.getTheme();
+            currentTheme.resolveAttribute(R.attr.actionBarWidgetTheme, outValue, true);
+            final int targetThemeRes = outValue.resourceId;
+
+            if (targetThemeRes != 0) {
+                mThemedContext = new ContextThemeWrapper(mContext, targetThemeRes);
+            } else {
+                mThemedContext = mContext;
+            }
+        }
+        return mThemedContext;
+    }
+
+    @Override
+    public boolean isTitleTruncated() {
+        return mDecorToolbar != null && mDecorToolbar.isTitleTruncated();
+    }
+
+    @Override
+    public void setHomeAsUpIndicator(Drawable indicator) {
+        mDecorToolbar.setNavigationIcon(indicator);
+    }
+
+    @Override
+    public void setHomeAsUpIndicator(int resId) {
+        mDecorToolbar.setNavigationIcon(resId);
+    }
+
+    @Override
+    public void setHomeActionContentDescription(CharSequence description) {
+        mDecorToolbar.setNavigationContentDescription(description);
+    }
+
+    @Override
+    public void setHomeActionContentDescription(int resId) {
+        mDecorToolbar.setNavigationContentDescription(resId);
+    }
+
+    @Override
+    public void onContentScrollStarted() {
+        if (mCurrentShowAnim != null) {
+            mCurrentShowAnim.cancel();
+            mCurrentShowAnim = null;
+        }
+    }
+
+    @Override
+    public void onContentScrollStopped() {
+    }
+
+    /**
+     * @hide
+     */
+    public class ActionModeImpl extends ActionMode implements MenuBuilder.Callback {
+        private ActionMode.Callback mCallback;
+        private MenuBuilder mMenu;
+        private WeakReference<View> mCustomView;
+
+        public ActionModeImpl(ActionMode.Callback callback) {
+            mCallback = callback;
+            mMenu = new MenuBuilder(getThemedContext())
+                    .setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+            mMenu.setCallback(this);
+        }
+
+        @Override
+        public MenuInflater getMenuInflater() {
+            return new SupportMenuInflater(getThemedContext());
+        }
+
+        @Override
+        public Menu getMenu() {
+            return mMenu;
+        }
+
+        @Override
+        public void finish() {
+            if (mActionMode != this) {
+                // Not the active action mode - no-op
+                return;
+            }
+
+            // If this change in state is going to cause the action bar
+            // to be hidden, defer the onDestroy callback until the animation
+            // is finished and associated relayout is about to happen. This lets
+            // apps better anticipate visibility and layout behavior.
+            if (!checkShowingFlags(mHiddenByApp, mHiddenBySystem, false)) {
+                // With the current state but the action bar hidden, our
+                // overall showing state is going to be false.
+                mDeferredDestroyActionMode = this;
+                mDeferredModeDestroyCallback = mCallback;
+            } else {
+                mCallback.onDestroyActionMode(this);
+            }
+            mCallback = null;
+            animateToMode(false);
+
+            // Clear out the context mode views after the animation finishes
+            mContextView.closeMode();
+            mDecorToolbar.getViewGroup().sendAccessibilityEvent(
+                    AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+            mOverlayLayout.setHideOnContentScrollEnabled(mHideOnContentScroll);
+
+            mActionMode = null;
+        }
+
+        @Override
+        public void invalidate() {
+            mMenu.stopDispatchingItemsChanged();
+            try {
+                mCallback.onPrepareActionMode(this, mMenu);
+            } finally {
+                mMenu.startDispatchingItemsChanged();
+            }
+        }
+
+        public boolean dispatchOnCreate() {
+            mMenu.stopDispatchingItemsChanged();
+            try {
+                return mCallback.onCreateActionMode(this, mMenu);
+            } finally {
+                mMenu.startDispatchingItemsChanged();
+            }
+        }
+
+        @Override
+        public void setCustomView(View view) {
+            mContextView.setCustomView(view);
+            mCustomView = new WeakReference<View>(view);
+        }
+
+        @Override
+        public void setSubtitle(CharSequence subtitle) {
+            mContextView.setSubtitle(subtitle);
+        }
+
+        @Override
+        public void setTitle(CharSequence title) {
+            mContextView.setTitle(title);
+        }
+
+        @Override
+        public void setTitle(int resId) {
+            setTitle(mContext.getResources().getString(resId));
+        }
+
+        @Override
+        public void setSubtitle(int resId) {
+            setSubtitle(mContext.getResources().getString(resId));
+        }
+
+        @Override
+        public CharSequence getTitle() {
+            return mContextView.getTitle();
+        }
+
+        @Override
+        public CharSequence getSubtitle() {
+            return mContextView.getSubtitle();
+        }
+
+        @Override
+        public void setTitleOptionalHint(boolean titleOptional) {
+            super.setTitleOptionalHint(titleOptional);
+            mContextView.setTitleOptional(titleOptional);
+        }
+
+        @Override
+        public boolean isTitleOptional() {
+            return mContextView.isTitleOptional();
+        }
+
+        @Override
+        public View getCustomView() {
+            return mCustomView != null ? mCustomView.get() : null;
+        }
+
+        public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+            if (mCallback != null) {
+                return mCallback.onActionItemClicked(this, item);
+            } else {
+                return false;
+            }
+        }
+
+        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+        }
+
+        public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+            if (mCallback == null) {
+                return false;
+            }
+
+            if (!subMenu.hasVisibleItems()) {
+                return true;
+            }
+
+            new MenuPopupHelper(getThemedContext(), subMenu).show();
+            return true;
+        }
+
+        public void onCloseSubMenu(SubMenuBuilder menu) {
+        }
+
+        public void onMenuModeChange(MenuBuilder menu) {
+            if (mCallback == null) {
+                return;
+            }
+            invalidate();
+            mContextView.showOverflowMenu();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public class TabImpl extends ActionBar.Tab {
+        private ActionBar.TabListener mCallback;
+        private Object mTag;
+        private Drawable mIcon;
+        private CharSequence mText;
+        private CharSequence mContentDesc;
+        private int mPosition = -1;
+        private View mCustomView;
+
+        @Override
+        public Object getTag() {
+            return mTag;
+        }
+
+        @Override
+        public Tab setTag(Object tag) {
+            mTag = tag;
+            return this;
+        }
+
+        public ActionBar.TabListener getCallback() {
+            return mCallback;
+        }
+
+        @Override
+        public Tab setTabListener(ActionBar.TabListener callback) {
+            mCallback = callback;
+            return this;
+        }
+
+        @Override
+        public View getCustomView() {
+            return mCustomView;
+        }
+
+        @Override
+        public Tab setCustomView(View view) {
+            mCustomView = view;
+            if (mPosition >= 0) {
+                mTabScrollView.updateTab(mPosition);
+            }
+            return this;
+        }
+
+        @Override
+        public Tab setCustomView(int layoutResId) {
+            return setCustomView(LayoutInflater.from(getThemedContext())
+                    .inflate(layoutResId, null));
+        }
+
+        @Override
+        public Drawable getIcon() {
+            return mIcon;
+        }
+
+        @Override
+        public int getPosition() {
+            return mPosition;
+        }
+
+        public void setPosition(int position) {
+            mPosition = position;
+        }
+
+        @Override
+        public CharSequence getText() {
+            return mText;
+        }
+
+        @Override
+        public Tab setIcon(Drawable icon) {
+            mIcon = icon;
+            if (mPosition >= 0) {
+                mTabScrollView.updateTab(mPosition);
+            }
+            return this;
+        }
+
+        @Override
+        public Tab setIcon(int resId) {
+            return setIcon(ContextCompat.getDrawable(mContext, resId));
+        }
+
+        @Override
+        public Tab setText(CharSequence text) {
+            mText = text;
+            if (mPosition >= 0) {
+                mTabScrollView.updateTab(mPosition);
+            }
+            return this;
+        }
+
+        @Override
+        public Tab setText(int resId) {
+            return setText(mContext.getResources().getText(resId));
+        }
+
+        @Override
+        public void select() {
+            selectTab(this);
+        }
+
+        @Override
+        public Tab setContentDescription(int resId) {
+            return setContentDescription(mContext.getResources().getText(resId));
+        }
+
+        @Override
+        public Tab setContentDescription(CharSequence contentDesc) {
+            mContentDesc = contentDesc;
+            if (mPosition >= 0) {
+                mTabScrollView.updateTab(mPosition);
+            }
+            return this;
+        }
+
+        @Override
+        public CharSequence getContentDescription() {
+            return mContentDesc;
+        }
+    }
+
+    @Override
+    public void setCustomView(View view) {
+        mDecorToolbar.setCustomView(view);
+    }
+
+    @Override
+    public void setCustomView(View view, LayoutParams layoutParams) {
+        view.setLayoutParams(layoutParams);
+        mDecorToolbar.setCustomView(view);
+    }
+
+    @Override
+    public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) {
+        mDecorToolbar.setDropdownParams(adapter, new NavItemSelectedListener(callback));
+    }
+
+    @Override
+    public int getSelectedNavigationIndex() {
+        switch (mDecorToolbar.getNavigationMode()) {
+            case NAVIGATION_MODE_TABS:
+                return mSelectedTab != null ? mSelectedTab.getPosition() : -1;
+            case NAVIGATION_MODE_LIST:
+                return mDecorToolbar.getDropdownSelectedPosition();
+            default:
+                return -1;
+        }
+    }
+
+    @Override
+    public int getNavigationItemCount() {
+        switch (mDecorToolbar.getNavigationMode()) {
+            case NAVIGATION_MODE_TABS:
+                return mTabs.size();
+            case NAVIGATION_MODE_LIST:
+                return mDecorToolbar.getDropdownItemCount();
+            default:
+                return 0;
+        }
+    }
+
+    @Override
+    public int getTabCount() {
+        return mTabs.size();
+    }
+
+    @Override
+    public void setNavigationMode(int mode) {
+        final int oldMode = mDecorToolbar.getNavigationMode();
+        switch (oldMode) {
+            case NAVIGATION_MODE_TABS:
+                mSavedTabPosition = getSelectedNavigationIndex();
+                selectTab(null);
+                mTabScrollView.setVisibility(View.GONE);
+                break;
+        }
+        if (oldMode != mode && !mHasEmbeddedTabs) {
+            if (mOverlayLayout != null) {
+                ViewCompat.requestApplyInsets(mOverlayLayout);
+            }
+        }
+        mDecorToolbar.setNavigationMode(mode);
+        switch (mode) {
+            case NAVIGATION_MODE_TABS:
+                ensureTabsExist();
+                mTabScrollView.setVisibility(View.VISIBLE);
+                if (mSavedTabPosition != INVALID_POSITION) {
+                    setSelectedNavigationItem(mSavedTabPosition);
+                    mSavedTabPosition = INVALID_POSITION;
+                }
+                break;
+        }
+        mDecorToolbar.setCollapsible(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs);
+        mOverlayLayout.setHasNonEmbeddedTabs(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs);
+    }
+
+    @Override
+    public Tab getTabAt(int index) {
+        return mTabs.get(index);
+    }
+
+
+    @Override
+    public void setIcon(int resId) {
+        mDecorToolbar.setIcon(resId);
+    }
+
+    @Override
+    public void setIcon(Drawable icon) {
+        mDecorToolbar.setIcon(icon);
+    }
+
+    public boolean hasIcon() {
+        return mDecorToolbar.hasIcon();
+    }
+
+    @Override
+    public void setLogo(int resId) {
+        mDecorToolbar.setLogo(resId);
+    }
+
+    @Override
+    public void setLogo(Drawable logo) {
+        mDecorToolbar.setLogo(logo);
+    }
+
+    public boolean hasLogo() {
+        return mDecorToolbar.hasLogo();
+    }
+
+    public void setDefaultDisplayHomeAsUpEnabled(boolean enable) {
+        if (!mDisplayHomeAsUpSet) {
+            setDisplayHomeAsUpEnabled(enable);
+        }
+    }
+
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/transition/ActionBarTransition.java b/v7/appcompat/src/android/support/v7/internal/transition/ActionBarTransition.java
new file mode 100644
index 0000000..19546b7
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/transition/ActionBarTransition.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v7.internal.transition;
+
+import android.view.ViewGroup;
+
+public class ActionBarTransition {
+
+    private static final boolean TRANSITIONS_ENABLED = false;
+
+    private static final int TRANSITION_DURATION = 120; // ms
+
+//    private static final Transition sTransition;
+//
+//    static {
+//        if (TRANSITIONS_ENABLED) {
+////            final ChangeText tc = new ChangeText();
+////            tc.setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT_IN);
+//            final TransitionSet inner = new TransitionSet();
+//            inner.addTransition(new ChangeBounds());
+//            final TransitionSet tg = new TransitionSet();
+//            tg.addTransition(new Fade(Fade.OUT)).addTransition(inner).
+//                    addTransition(new Fade(Fade.IN));
+//            tg.setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
+//            tg.setDuration(TRANSITION_DURATION);
+//            sTransition = tg;
+//        } else {
+//            sTransition = null;
+//        }
+//    }
+
+    public static void beginDelayedTransition(ViewGroup sceneRoot) {
+//        if (TRANSITIONS_ENABLED) {
+//            TransitionManager.beginDelayedTransition(sceneRoot, sTransition);
+//        }
+    }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/view/ActionBarPolicy.java b/v7/appcompat/src/android/support/v7/internal/view/ActionBarPolicy.java
index e1b66d7..f55cefe 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/ActionBarPolicy.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/ActionBarPolicy.java
@@ -17,10 +17,13 @@
 package android.support.v7.internal.view;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.os.Build;
+import android.support.v4.view.ViewConfigurationCompat;
 import android.support.v7.appcompat.R;
+import android.view.ViewConfiguration;
 
 /**
  * Allows components to query for various configuration policy decisions about how the action bar
@@ -45,8 +48,7 @@
     }
 
     public boolean showsOverflowMenuButton() {
-        // Only show overflow on HC+ devices
-        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
+        return true;
     }
 
     public int getEmbeddedMenuWidthLimit() {
@@ -54,6 +56,11 @@
     }
 
     public boolean hasEmbeddedTabs() {
+        final int targetSdk = mContext.getApplicationInfo().targetSdkVersion;
+        if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN) {
+            return mContext.getResources().getBoolean(R.bool.abc_action_bar_embed_tabs);
+        }
+
         // The embedded tabs policy changed in Jellybean; give older apps the old policy
         // so they get what they expect.
         return mContext.getResources().getBoolean(R.bool.abc_action_bar_embed_tabs_pre_jb);
@@ -76,7 +83,8 @@
     public boolean enableHomeButtonByDefault() {
         // Older apps get the home button interaction enabled by default.
         // Newer apps need to enable it explicitly.
-        return mContext.getApplicationInfo().targetSdkVersion < 14; // ICE_CREAM_SANDWICH
+        return mContext.getApplicationInfo().targetSdkVersion <
+                Build.VERSION_CODES.ICE_CREAM_SANDWICH;
     }
 
     public int getStackedTabMaxWidth() {
diff --git a/v7/appcompat/src/android/support/v7/internal/view/ActionModeWrapper.java b/v7/appcompat/src/android/support/v7/internal/view/ActionModeWrapper.java
deleted file mode 100644
index 2048d2b..0000000
--- a/v7/appcompat/src/android/support/v7/internal/view/ActionModeWrapper.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright (C) 2013 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.internal.view;
-
-import android.content.Context;
-import android.support.v7.internal.view.menu.MenuWrapperFactory;
-import android.support.v7.view.ActionMode;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.View;
-
-/**
- * @hide
- */
-public class ActionModeWrapper extends ActionMode {
-
-    final MenuInflater mInflater;
-    final android.view.ActionMode mWrappedObject;
-
-    public ActionModeWrapper(Context context, android.view.ActionMode frameworkActionMode) {
-        mWrappedObject = frameworkActionMode;
-        mInflater = new SupportMenuInflater(context);
-    }
-
-    @Override
-    public Object getTag() {
-        return mWrappedObject.getTag();
-    }
-
-    @Override
-    public void setTag(Object tag) {
-        mWrappedObject.setTag(tag);
-    }
-
-    @Override
-    public void setTitle(CharSequence title) {
-        mWrappedObject.setTitle(title);
-    }
-
-    @Override
-    public void setSubtitle(CharSequence subtitle) {
-        mWrappedObject.setSubtitle(subtitle);
-    }
-
-    @Override
-    public void invalidate() {
-        mWrappedObject.invalidate();
-    }
-
-    @Override
-    public void finish() {
-        mWrappedObject.finish();
-    }
-
-    @Override
-    public Menu getMenu() {
-        return MenuWrapperFactory.createMenuWrapper(mWrappedObject.getMenu());
-    }
-
-    @Override
-    public CharSequence getTitle() {
-        return mWrappedObject.getTitle();
-    }
-
-    @Override
-    public void setTitle(int resId) {
-        mWrappedObject.setTitle(resId);
-    }
-
-    @Override
-    public CharSequence getSubtitle() {
-        return mWrappedObject.getSubtitle();
-    }
-
-    @Override
-    public void setSubtitle(int resId) {
-        mWrappedObject.setSubtitle(resId);
-    }
-
-    @Override
-    public View getCustomView() {
-        return mWrappedObject.getCustomView();
-    }
-
-    @Override
-    public void setCustomView(View view) {
-        mWrappedObject.setCustomView(view);
-    }
-
-    @Override
-    public MenuInflater getMenuInflater() {
-        return mInflater;
-    }
-
-    /**
-     * @hide
-     */
-    public static class CallbackWrapper implements android.view.ActionMode.Callback {
-        final Callback mWrappedCallback;
-        final Context mContext;
-
-        private ActionModeWrapper mLastStartedActionMode;
-
-        public CallbackWrapper(Context context, Callback supportCallback) {
-            mContext = context;
-            mWrappedCallback = supportCallback;
-        }
-
-        @Override
-        public boolean onCreateActionMode(android.view.ActionMode mode, android.view.Menu menu) {
-            return mWrappedCallback.onCreateActionMode(getActionModeWrapper(mode),
-                    MenuWrapperFactory.createMenuWrapper(menu));
-        }
-
-        @Override
-        public boolean onPrepareActionMode(android.view.ActionMode mode, android.view.Menu menu) {
-            return mWrappedCallback.onPrepareActionMode(getActionModeWrapper(mode),
-                    MenuWrapperFactory.createMenuWrapper(menu));
-        }
-
-        @Override
-        public boolean onActionItemClicked(android.view.ActionMode mode,
-                android.view.MenuItem item) {
-            return mWrappedCallback.onActionItemClicked(getActionModeWrapper(mode),
-                    MenuWrapperFactory.createMenuItemWrapper(item));
-        }
-
-        @Override
-        public void onDestroyActionMode(android.view.ActionMode mode) {
-            mWrappedCallback.onDestroyActionMode(getActionModeWrapper(mode));
-        }
-
-        public void setLastStartedActionMode(ActionModeWrapper modeWrapper) {
-            mLastStartedActionMode = modeWrapper;
-        }
-
-        private ActionMode getActionModeWrapper(android.view.ActionMode mode) {
-            if (mLastStartedActionMode != null && mLastStartedActionMode.mWrappedObject == mode) {
-                // If the given mode equals our wrapped mode, just return it
-                return mLastStartedActionMode;
-            } else {
-                return createActionModeWrapper(mContext, mode);
-            }
-        }
-
-        protected ActionModeWrapper createActionModeWrapper(Context context,
-                android.view.ActionMode mode) {
-            return new ActionModeWrapper(context, mode);
-        }
-    }
-}
diff --git a/v7/appcompat/src/android/support/v7/internal/view/ActionModeWrapperJB.java b/v7/appcompat/src/android/support/v7/internal/view/ActionModeWrapperJB.java
deleted file mode 100644
index 15ede6e..0000000
--- a/v7/appcompat/src/android/support/v7/internal/view/ActionModeWrapperJB.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2013 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.internal.view;
-
-import android.content.Context;
-import android.view.ActionMode;
-
-/**
- * @hide
- */
-public class ActionModeWrapperJB extends ActionModeWrapper {
-
-    public ActionModeWrapperJB(Context context, android.view.ActionMode frameworkActionMode) {
-        super(context, frameworkActionMode);
-    }
-
-    @Override
-    public boolean getTitleOptionalHint() {
-        return mWrappedObject.getTitleOptionalHint();
-    }
-
-    @Override
-    public void setTitleOptionalHint(boolean titleOptional) {
-        mWrappedObject.setTitleOptionalHint(titleOptional);
-    }
-
-    @Override
-    public boolean isTitleOptional() {
-        return mWrappedObject.isTitleOptional();
-    }
-
-    /**
-     * @hide
-     */
-    public static class CallbackWrapper extends ActionModeWrapper.CallbackWrapper {
-
-        public CallbackWrapper(Context context, Callback supportCallback) {
-            super(context, supportCallback);
-        }
-
-        @Override
-        protected ActionModeWrapper createActionModeWrapper(Context context, ActionMode mode) {
-            return new ActionModeWrapperJB(context, mode);
-        }
-    }
-
-}
diff --git a/v7/appcompat/src/android/support/v7/internal/view/AnimatorSetCompat.java b/v7/appcompat/src/android/support/v7/internal/view/AnimatorSetCompat.java
new file mode 100644
index 0000000..dcbca26
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/view/AnimatorSetCompat.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.view;
+
+import android.support.v4.view.ViewPropertyAnimatorCompat;
+import android.support.v4.view.ViewPropertyAnimatorListener;
+import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import java.util.ArrayList;
+
+/**
+ * A very naive implementation of AnimatorSet for ViewPropertyAnimatorCompat. This should be
+ * improved and moved to support-v4.
+ *
+ * @hide
+ */
+public class AnimatorSetCompat {
+
+    private final ArrayList<ViewPropertyAnimatorCompat> mAnimators;
+
+    private long mDuration = -1;
+    private Interpolator mInterpolator;
+    private ViewPropertyAnimatorListener mListener;
+
+    private boolean mIsStarted;
+
+    public AnimatorSetCompat() {
+        mAnimators = new ArrayList<ViewPropertyAnimatorCompat>();
+    }
+
+    public AnimatorSetCompat play(ViewPropertyAnimatorCompat animator) {
+        if (!mIsStarted) {
+            mAnimators.add(animator);
+        }
+        return this;
+    }
+
+    public void start() {
+        if (mIsStarted) return;
+
+        for (ViewPropertyAnimatorCompat animator : mAnimators) {
+            if (mDuration >= 0) {
+                animator.setDuration(mDuration);
+            }
+            if (mInterpolator != null) {
+                animator.setInterpolator(mInterpolator);
+            }
+            if (mListener != null) {
+                animator.setListener(mProxyListener);
+            }
+            animator.start();
+        }
+
+        mIsStarted = true;
+    }
+
+    public void cancel() {
+        if (mIsStarted) return;
+        for (ViewPropertyAnimatorCompat animator : mAnimators) {
+            animator.cancel();
+        }
+    }
+
+    public AnimatorSetCompat setDuration(long duration) {
+        if (!mIsStarted) {
+            mDuration = duration;
+        }
+        return this;
+    }
+
+    public AnimatorSetCompat setInterpolator(Interpolator interpolator) {
+        if (!mIsStarted) {
+            mInterpolator = interpolator;
+        }
+        return this;
+    }
+
+    public AnimatorSetCompat setListener(ViewPropertyAnimatorListener listener) {
+        if (!mIsStarted) {
+            mListener = listener;
+        }
+        return this;
+    }
+
+    private final ViewPropertyAnimatorListenerAdapter mProxyListener
+            = new ViewPropertyAnimatorListenerAdapter() {
+        private boolean mIsStarted = false;
+        private int mEnded = 0;
+
+        @Override
+        public void onAnimationStart(View view) {
+            if (mListener != null && !mIsStarted) {
+                mIsStarted = true;
+                mListener.onAnimationStart(null);
+            }
+        }
+
+        @Override
+        public void onAnimationEnd(View view) {
+            if (mListener != null && ++mEnded == mAnimators.size()) {
+                mListener.onAnimationEnd(null);
+            }
+        }
+    };
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/view/StandaloneActionMode.java b/v7/appcompat/src/android/support/v7/internal/view/StandaloneActionMode.java
new file mode 100644
index 0000000..7284758
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/view/StandaloneActionMode.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.internal.view;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.support.v7.internal.view.menu.MenuBuilder;
+import android.support.v7.internal.view.menu.MenuPopupHelper;
+import android.support.v7.internal.view.menu.SubMenuBuilder;
+import android.support.v7.internal.widget.ActionBarContextView;
+import android.support.v7.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+
+import java.lang.ref.WeakReference;
+
+public class StandaloneActionMode extends ActionMode implements MenuBuilder.Callback {
+    private Context mContext;
+    private ActionBarContextView mContextView;
+    private ActionMode.Callback mCallback;
+    private WeakReference<View> mCustomView;
+    private boolean mFinished;
+    private boolean mFocusable;
+
+    private MenuBuilder mMenu;
+
+    public StandaloneActionMode(Context context, ActionBarContextView view,
+            ActionMode.Callback callback, boolean isFocusable) {
+        mContext = context;
+        mContextView = view;
+        mCallback = callback;
+
+        mMenu = new MenuBuilder(context).setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+        mMenu.setCallback(this);
+        mFocusable = isFocusable;
+    }
+
+    @Override
+    public void setTitle(CharSequence title) {
+        mContextView.setTitle(title);
+    }
+
+    @Override
+    public void setSubtitle(CharSequence subtitle) {
+        mContextView.setSubtitle(subtitle);
+    }
+
+    @Override
+    public void setTitle(int resId) {
+        setTitle(mContext.getString(resId));
+    }
+
+    @Override
+    public void setSubtitle(int resId) {
+        setSubtitle(mContext.getString(resId));
+    }
+
+    @Override
+    public void setTitleOptionalHint(boolean titleOptional) {
+        super.setTitleOptionalHint(titleOptional);
+        mContextView.setTitleOptional(titleOptional);
+    }
+
+    @Override
+    public boolean isTitleOptional() {
+        return mContextView.isTitleOptional();
+    }
+
+    @Override
+    public void setCustomView(View view) {
+        mContextView.setCustomView(view);
+        mCustomView = view != null ? new WeakReference<View>(view) : null;
+    }
+
+    @Override
+    public void invalidate() {
+        mCallback.onPrepareActionMode(this, mMenu);
+    }
+
+    @Override
+    public void finish() {
+        if (mFinished) {
+            return;
+        }
+        mFinished = true;
+
+        mContextView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+        mCallback.onDestroyActionMode(this);
+    }
+
+    @Override
+    public Menu getMenu() {
+        return mMenu;
+    }
+
+    @Override
+    public CharSequence getTitle() {
+        return mContextView.getTitle();
+    }
+
+    @Override
+    public CharSequence getSubtitle() {
+        return mContextView.getSubtitle();
+    }
+
+    @Override
+    public View getCustomView() {
+        return mCustomView != null ? mCustomView.get() : null;
+    }
+
+    @Override
+    public MenuInflater getMenuInflater() {
+        return new MenuInflater(mContext);
+    }
+
+    public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+        return mCallback.onActionItemClicked(this, item);
+    }
+
+    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+    }
+
+    public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+        if (!subMenu.hasVisibleItems()) {
+            return true;
+        }
+
+        new MenuPopupHelper(mContext, subMenu).show();
+        return true;
+    }
+
+    public void onCloseSubMenu(SubMenuBuilder menu) {
+    }
+
+    public void onMenuModeChange(MenuBuilder menu) {
+        invalidate();
+        mContextView.showOverflowMenu();
+    }
+
+    public boolean isUiFocusable() {
+        return mFocusable;
+    }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/view/SupportActionModeWrapper.java b/v7/appcompat/src/android/support/v7/internal/view/SupportActionModeWrapper.java
new file mode 100644
index 0000000..41dfb2b
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/view/SupportActionModeWrapper.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2013 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.internal.view;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.support.v7.internal.view.menu.MenuWrapperFactory;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.View;
+
+/**
+ * Wraps a support {@link android.support.v7.view.ActionMode} as a framework
+ * {@link android.view.ActionMode}.
+ *
+ * @hide
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB)
+public class SupportActionModeWrapper extends ActionMode {
+
+    final MenuInflater mInflater;
+    final android.support.v7.view.ActionMode mWrappedObject;
+
+    public SupportActionModeWrapper(Context context, android.support.v7.view.ActionMode supportActionMode) {
+        mWrappedObject = supportActionMode;
+        mInflater = new SupportMenuInflater(context);
+    }
+
+    @Override
+    public Object getTag() {
+        return mWrappedObject.getTag();
+    }
+
+    @Override
+    public void setTag(Object tag) {
+        mWrappedObject.setTag(tag);
+    }
+
+    @Override
+    public void setTitle(CharSequence title) {
+        mWrappedObject.setTitle(title);
+    }
+
+    @Override
+    public void setSubtitle(CharSequence subtitle) {
+        mWrappedObject.setSubtitle(subtitle);
+    }
+
+    @Override
+    public void invalidate() {
+        mWrappedObject.invalidate();
+    }
+
+    @Override
+    public void finish() {
+        mWrappedObject.finish();
+    }
+
+    @Override
+    public Menu getMenu() {
+        return MenuWrapperFactory.createMenuWrapper(mWrappedObject.getMenu());
+    }
+
+    @Override
+    public CharSequence getTitle() {
+        return mWrappedObject.getTitle();
+    }
+
+    @Override
+    public void setTitle(int resId) {
+        mWrappedObject.setTitle(resId);
+    }
+
+    @Override
+    public CharSequence getSubtitle() {
+        return mWrappedObject.getSubtitle();
+    }
+
+    @Override
+    public void setSubtitle(int resId) {
+        mWrappedObject.setSubtitle(resId);
+    }
+
+    @Override
+    public View getCustomView() {
+        return mWrappedObject.getCustomView();
+    }
+
+    @Override
+    public void setCustomView(View view) {
+        mWrappedObject.setCustomView(view);
+    }
+
+    @Override
+    public MenuInflater getMenuInflater() {
+        return mInflater;
+    }
+
+    @Override
+    public boolean getTitleOptionalHint() {
+        return mWrappedObject.getTitleOptionalHint();
+    }
+
+    @Override
+    public void setTitleOptionalHint(boolean titleOptional) {
+        mWrappedObject.setTitleOptionalHint(titleOptional);
+    }
+
+    @Override
+    public boolean isTitleOptional() {
+        return mWrappedObject.isTitleOptional();
+    }
+
+    /**
+     * @hide
+     */
+    public static class CallbackWrapper implements android.support.v7.view.ActionMode.Callback {
+        final Callback mWrappedCallback;
+        final Context mContext;
+
+        private SupportActionModeWrapper mLastStartedActionMode;
+
+        public CallbackWrapper(Context context, Callback supportCallback) {
+            mContext = context;
+            mWrappedCallback = supportCallback;
+        }
+
+        @Override
+        public boolean onCreateActionMode(android.support.v7.view.ActionMode mode, Menu menu) {
+            return mWrappedCallback.onCreateActionMode(getActionModeWrapper(mode),
+                    MenuWrapperFactory.createMenuWrapper(menu));
+        }
+
+        @Override
+        public boolean onPrepareActionMode(android.support.v7.view.ActionMode mode, Menu menu) {
+            return mWrappedCallback.onPrepareActionMode(getActionModeWrapper(mode),
+                    MenuWrapperFactory.createMenuWrapper(menu));
+        }
+
+        @Override
+        public boolean onActionItemClicked(android.support.v7.view.ActionMode mode,
+                android.view.MenuItem item) {
+            return mWrappedCallback.onActionItemClicked(getActionModeWrapper(mode),
+                    MenuWrapperFactory.createMenuItemWrapper(item));
+        }
+
+        @Override
+        public void onDestroyActionMode(android.support.v7.view.ActionMode mode) {
+            mWrappedCallback.onDestroyActionMode(getActionModeWrapper(mode));
+        }
+
+        public void setLastStartedActionMode(SupportActionModeWrapper modeWrapper) {
+            mLastStartedActionMode = modeWrapper;
+        }
+
+        private ActionMode getActionModeWrapper(android.support.v7.view.ActionMode mode) {
+            if (mLastStartedActionMode != null && mLastStartedActionMode.mWrappedObject == mode) {
+                // If the given mode equals our wrapped mode, just return it
+                return mLastStartedActionMode;
+            } else {
+                return createActionModeWrapper(mContext, mode);
+            }
+        }
+
+        protected SupportActionModeWrapper createActionModeWrapper(Context context,
+                android.support.v7.view.ActionMode mode) {
+            return new SupportActionModeWrapper(context, mode);
+        }
+    }
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/ActionMenuItem.java b/v7/appcompat/src/android/support/v7/internal/view/menu/ActionMenuItem.java
index ddcdfdf..280f505 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/ActionMenuItem.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/ActionMenuItem.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
+import android.support.v4.content.ContextCompat;
 import android.support.v4.view.ActionProvider;
 import android.support.v4.internal.view.SupportMenuItem;
 import android.support.v4.view.MenuItemCompat;
@@ -166,7 +167,7 @@
 
     public MenuItem setIcon(int iconRes) {
         mIconResId = iconRes;
-        mIconDrawable = mContext.getResources().getDrawable(iconRes);
+        mIconDrawable = ContextCompat.getDrawable(mContext, iconRes);
         return this;
     }
 
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/ActionMenuItemView.java b/v7/appcompat/src/android/support/v7/internal/view/menu/ActionMenuItemView.java
index c5d190d..41a220e 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/ActionMenuItemView.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/ActionMenuItemView.java
@@ -17,18 +17,23 @@
 package android.support.v7.internal.view.menu;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.v4.view.GravityCompat;
 import android.support.v7.appcompat.R;
 import android.support.v7.internal.widget.CompatTextView;
+import android.support.v7.widget.ActionMenuView;
+import android.support.v7.widget.ListPopupWindow;
 import android.text.TextUtils;
 import android.text.method.TransformationMethod;
 import android.util.AttributeSet;
 import android.view.Gravity;
+import android.view.MotionEvent;
 import android.view.View;
-import android.widget.TextView;
 import android.widget.Toast;
 
 import java.util.Locale;
@@ -46,12 +51,17 @@
     private CharSequence mTitle;
     private Drawable mIcon;
     private MenuBuilder.ItemInvoker mItemInvoker;
+    private ListPopupWindow.ForwardingListener mForwardingListener;
+    private PopupCallback mPopupCallback;
 
     private boolean mAllowTextWithIcon;
     private boolean mExpandedFormat;
     private int mMinWidth;
     private int mSavedPaddingLeft;
 
+    private static final int MAX_ICON_SIZE = 32; // dp
+    private int mMaxIconSize;
+
     public ActionMenuItemView(Context context) {
         this(context, null);
     }
@@ -64,13 +74,16 @@
         super(context, attrs, defStyle);
         final Resources res = context.getResources();
         mAllowTextWithIcon = res.getBoolean(
-                android.support.v7.appcompat.R.bool.abc_config_allowActionMenuItemTextWithIcon);
+                R.bool.abc_config_allowActionMenuItemTextWithIcon);
         TypedArray a = context.obtainStyledAttributes(attrs,
-                android.support.v7.appcompat.R.styleable.ActionMenuItemView, 0, 0);
+                R.styleable.ActionMenuItemView, defStyle, 0);
         mMinWidth = a.getDimensionPixelSize(
                 R.styleable.ActionMenuItemView_android_minWidth, 0);
         a.recycle();
 
+        final float density = res.getDisplayMetrics().density;
+        mMaxIconSize = (int) (MAX_ICON_SIZE * density + 0.5f);
+
         setOnClickListener(this);
         setOnLongClickListener(this);
 
@@ -79,6 +92,16 @@
         mSavedPaddingLeft = -1;
     }
 
+    public void onConfigurationChanged(Configuration newConfig) {
+        if (Build.VERSION.SDK_INT >= 8) {
+            super.onConfigurationChanged(newConfig);
+        }
+
+        mAllowTextWithIcon = getContext().getResources().getBoolean(
+                R.bool.abc_config_allowActionMenuItemTextWithIcon);
+        updateTextButtonVisibility();
+    }
+
     @Override
     public void setPadding(int l, int t, int r, int b) {
         mSavedPaddingLeft = l;
@@ -98,8 +121,23 @@
 
         setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE);
         setEnabled(itemData.isEnabled());
+        if (itemData.hasSubMenu()) {
+            if (mForwardingListener == null) {
+                mForwardingListener = new ActionMenuItemForwardingListener();
+            }
+        }
     }
 
+    @Override
+    public boolean onTouchEvent(MotionEvent e) {
+        if (mItemData.hasSubMenu() && mForwardingListener != null
+                && mForwardingListener.onTouch(this, e)) {
+            return true;
+        }
+        return super.onTouchEvent(e);
+    }
+
+    @Override
     public void onClick(View v) {
         if (mItemInvoker != null) {
             mItemInvoker.invokeItem(mItemData);
@@ -110,6 +148,10 @@
         mItemInvoker = invoker;
     }
 
+    public void setPopupCallback(PopupCallback popupCallback) {
+        mPopupCallback = popupCallback;
+    }
+
     public boolean prefersCondensedTitle() {
         return true;
     }
@@ -141,7 +183,22 @@
 
     public void setIcon(Drawable icon) {
         mIcon = icon;
-        setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
+        if (icon != null) {
+            int width = icon.getIntrinsicWidth();
+            int height = icon.getIntrinsicHeight();
+            if (width > mMaxIconSize) {
+                final float scale = (float) mMaxIconSize / width;
+                width = mMaxIconSize;
+                height *= scale;
+            }
+            if (height > mMaxIconSize) {
+                final float scale = (float) mMaxIconSize / height;
+                height = mMaxIconSize;
+                width *= scale;
+            }
+            icon.setBounds(0, 0, width, height);
+        }
+        setCompoundDrawables(icon, null, null, null);
 
         updateTextButtonVisibility();
     }
@@ -194,7 +251,7 @@
         Toast cheatSheet = Toast.makeText(context, mItemData.getTitle(), Toast.LENGTH_SHORT);
         if (midy < displayFrame.height()) {
             // Show along the top; follow action buttons
-            cheatSheet.setGravity(Gravity.TOP | Gravity.RIGHT,
+            cheatSheet.setGravity(Gravity.TOP | GravityCompat.END,
                     screenWidth - screenPos[0] - width / 2, height);
         } else {
             // Show along the bottom center
@@ -230,11 +287,45 @@
             // TextView won't center compound drawables in both dimensions without
             // a little coercion. Pad in to center the icon after we've measured.
             final int w = getMeasuredWidth();
-            final int dw = mIcon.getIntrinsicWidth();
+            final int dw = mIcon.getBounds().width();
             super.setPadding((w - dw) / 2, getPaddingTop(), getPaddingRight(), getPaddingBottom());
         }
     }
 
+    private class ActionMenuItemForwardingListener extends ListPopupWindow.ForwardingListener {
+        public ActionMenuItemForwardingListener() {
+            super(ActionMenuItemView.this);
+        }
+
+        @Override
+        public ListPopupWindow getPopup() {
+            if (mPopupCallback != null) {
+                return mPopupCallback.getPopup();
+            }
+            return null;
+        }
+
+        @Override
+        protected boolean onForwardingStarted() {
+            // Call the invoker, then check if the expected popup is showing.
+            if (mItemInvoker != null && mItemInvoker.invokeItem(mItemData)) {
+                final ListPopupWindow popup = getPopup();
+                return popup != null && popup.isShowing();
+            }
+            return false;
+        }
+
+        @Override
+        protected boolean onForwardingStopped() {
+            final ListPopupWindow popup = getPopup();
+            if (popup != null) {
+                popup.dismiss();
+                return true;
+            }
+            return false;
+        }
+    }
+
     private class AllCapsTransformationMethod implements TransformationMethod {
         private Locale mLocale;
 
@@ -252,4 +343,8 @@
                 int direction, Rect previouslyFocusedRect) {
         }
     }
+
+    public static abstract class PopupCallback {
+        public abstract ListPopupWindow getPopup();
+    }
 }
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/ActionMenuPresenter.java b/v7/appcompat/src/android/support/v7/internal/view/menu/ActionMenuPresenter.java
deleted file mode 100644
index d385fc4..0000000
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/ActionMenuPresenter.java
+++ /dev/null
@@ -1,671 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v7.internal.view.menu;
-
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.support.v7.appcompat.R;
-import android.support.v7.internal.view.ActionBarPolicy;
-import android.support.v4.view.ActionProvider;
-import android.support.v7.internal.view.menu.ActionMenuView.ActionMenuChildView;
-import android.util.SparseBooleanArray;
-import android.view.MenuItem;
-import android.view.SoundEffectConstants;
-import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
-
-import java.util.ArrayList;
-
-/**
- * MenuPresenter for building action menus as seen in the action bar and action modes.
- *
- * @hide
- */
-public class ActionMenuPresenter extends BaseMenuPresenter
-        implements ActionProvider.SubUiVisibilityListener {
-
-    private static final String TAG = "ActionMenuPresenter";
-
-    private View mOverflowButton;
-    private boolean mReserveOverflow;
-    private boolean mReserveOverflowSet;
-    private int mWidthLimit;
-    private int mActionItemWidthLimit;
-    private int mMaxItems;
-    private boolean mMaxItemsSet;
-    private boolean mStrictWidthLimit;
-    private boolean mWidthLimitSet;
-    private boolean mExpandedActionViewsExclusive;
-
-    private int mMinCellSize;
-
-    // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
-    private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
-
-    private View mScrapActionButtonView;
-
-    private OverflowPopup mOverflowPopup;
-    private ActionButtonSubmenu mActionButtonPopup;
-
-    private OpenOverflowRunnable mPostedOpenRunnable;
-
-    final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback();
-    int mOpenSubMenuId;
-
-    public ActionMenuPresenter(Context context) {
-        super(context, R.layout.abc_action_menu_layout, R.layout.abc_action_menu_item_layout);
-    }
-
-    @Override
-    public void initForMenu(Context context, MenuBuilder menu) {
-        super.initForMenu(context, menu);
-
-        final Resources res = context.getResources();
-
-        final ActionBarPolicy abp = ActionBarPolicy.get(context);
-        if (!mReserveOverflowSet) {
-            mReserveOverflow = abp.showsOverflowMenuButton();
-        }
-
-        if (!mWidthLimitSet) {
-            mWidthLimit = abp.getEmbeddedMenuWidthLimit();
-        }
-
-        // Measure for initial configuration
-        if (!mMaxItemsSet) {
-            mMaxItems = abp.getMaxActionButtons();
-        }
-
-        int width = mWidthLimit;
-        if (mReserveOverflow) {
-            if (mOverflowButton == null) {
-                mOverflowButton = new OverflowMenuButton(mSystemContext);
-                final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
-                mOverflowButton.measure(spec, spec);
-            }
-            width -= mOverflowButton.getMeasuredWidth();
-        } else {
-            mOverflowButton = null;
-        }
-
-        mActionItemWidthLimit = width;
-
-        mMinCellSize = (int) (ActionMenuView.MIN_CELL_SIZE * res.getDisplayMetrics().density);
-
-        // Drop a scrap view as it may no longer reflect the proper context/config.
-        mScrapActionButtonView = null;
-    }
-
-    public void onConfigurationChanged(Configuration newConfig) {
-        if (!mMaxItemsSet) {
-            mMaxItems = mContext.getResources().getInteger(
-                    R.integer.abc_max_action_buttons);
-        }
-        if (mMenu != null) {
-            mMenu.onItemsChanged(true);
-        }
-    }
-
-    public void setWidthLimit(int width, boolean strict) {
-        mWidthLimit = width;
-        mStrictWidthLimit = strict;
-        mWidthLimitSet = true;
-    }
-
-    public void setReserveOverflow(boolean reserveOverflow) {
-        mReserveOverflow = reserveOverflow;
-        mReserveOverflowSet = true;
-    }
-
-    public void setItemLimit(int itemCount) {
-        mMaxItems = itemCount;
-        mMaxItemsSet = true;
-    }
-
-    public void setExpandedActionViewsExclusive(boolean isExclusive) {
-        mExpandedActionViewsExclusive = isExclusive;
-    }
-
-    @Override
-    public MenuView getMenuView(ViewGroup root) {
-        MenuView result = super.getMenuView(root);
-        ((ActionMenuView) result).setPresenter(this);
-        return result;
-    }
-
-    @Override
-    public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
-        View actionView = item.getActionView();
-        if (actionView == null || item.hasCollapsibleActionView()) {
-            if (!(convertView instanceof ActionMenuItemView)) {
-                convertView = null;
-            }
-            actionView = super.getItemView(item, convertView, parent);
-        }
-        actionView.setVisibility(item.isActionViewExpanded() ? View.GONE : View.VISIBLE);
-
-        final ActionMenuView menuParent = (ActionMenuView) parent;
-        final ViewGroup.LayoutParams lp = actionView.getLayoutParams();
-        if (!menuParent.checkLayoutParams(lp)) {
-            actionView.setLayoutParams(menuParent.generateLayoutParams(lp));
-        }
-        return actionView;
-    }
-
-    @Override
-    public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) {
-        itemView.initialize(item, 0);
-
-        final ActionMenuView menuView = (ActionMenuView) mMenuView;
-        ActionMenuItemView actionItemView = (ActionMenuItemView) itemView;
-        actionItemView.setItemInvoker(menuView);
-    }
-
-    @Override
-    public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
-        return item.isActionButton();
-    }
-
-    @Override
-    public void updateMenuView(boolean cleared) {
-        super.updateMenuView(cleared);
-
-        if (mMenuView == null) {
-            return;
-        }
-
-        if (mMenu != null) {
-            final ArrayList<MenuItemImpl> actionItems = mMenu.getActionItems();
-            final int count = actionItems.size();
-            for (int i = 0; i < count; i++) {
-                final ActionProvider provider = actionItems.get(i).getSupportActionProvider();
-                if (provider != null) {
-                    provider.setSubUiVisibilityListener(this);
-                }
-            }
-        }
-
-        final ArrayList<MenuItemImpl> nonActionItems = mMenu != null ?
-                mMenu.getNonActionItems() : null;
-
-        boolean hasOverflow = false;
-        if (mReserveOverflow && nonActionItems != null) {
-            final int count = nonActionItems.size();
-            if (count == 1) {
-                hasOverflow = !nonActionItems.get(0).isActionViewExpanded();
-            } else {
-                hasOverflow = count > 0;
-            }
-        }
-
-        if (hasOverflow) {
-            if (mOverflowButton == null) {
-                mOverflowButton = new OverflowMenuButton(mSystemContext);
-            }
-            ViewGroup parent = (ViewGroup) mOverflowButton.getParent();
-            if (parent != mMenuView) {
-                if (parent != null) {
-                    parent.removeView(mOverflowButton);
-                }
-                ActionMenuView menuView = (ActionMenuView) mMenuView;
-                menuView.addView(mOverflowButton, menuView.generateOverflowButtonLayoutParams());
-            }
-        } else if (mOverflowButton != null && mOverflowButton.getParent() == mMenuView) {
-            ((ViewGroup) mMenuView).removeView(mOverflowButton);
-        }
-
-        ((ActionMenuView) mMenuView).setOverflowReserved(mReserveOverflow);
-    }
-
-    @Override
-    public boolean filterLeftoverView(ViewGroup parent, int childIndex) {
-        if (parent.getChildAt(childIndex) == mOverflowButton) {
-            return false;
-        }
-        return super.filterLeftoverView(parent, childIndex);
-    }
-
-    public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
-        if (!subMenu.hasVisibleItems()) {
-            return false;
-        }
-
-        SubMenuBuilder topSubMenu = subMenu;
-        while (topSubMenu.getParentMenu() != mMenu) {
-            topSubMenu = (SubMenuBuilder) topSubMenu.getParentMenu();
-        }
-        View anchor = findViewForItem(topSubMenu.getItem());
-        if (anchor == null) {
-            if (mOverflowButton == null) {
-                return false;
-            }
-            anchor = mOverflowButton;
-        }
-
-        mOpenSubMenuId = subMenu.getItem().getItemId();
-        mActionButtonPopup = new ActionButtonSubmenu(subMenu);
-        mActionButtonPopup.show(null);
-        super.onSubMenuSelected(subMenu);
-        return true;
-    }
-
-    private View findViewForItem(MenuItem item) {
-        final ViewGroup parent = (ViewGroup) mMenuView;
-        if (parent == null) {
-            return null;
-        }
-
-        final int count = parent.getChildCount();
-        for (int i = 0; i < count; i++) {
-            final View child = parent.getChildAt(i);
-            if (child instanceof MenuView.ItemView &&
-                    ((MenuView.ItemView) child).getItemData() == item) {
-                return child;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Display the overflow menu if one is present.
-     *
-     * @return true if the overflow menu was shown, false otherwise.
-     */
-    public boolean showOverflowMenu() {
-        if (mReserveOverflow && !isOverflowMenuShowing() && mMenu != null && mMenuView != null &&
-                mPostedOpenRunnable == null) {
-            OverflowPopup popup = new OverflowPopup(mContext, mMenu, mOverflowButton, true);
-            mPostedOpenRunnable = new OpenOverflowRunnable(popup);
-            // Post this for later; we might still need a layout for the anchor to be right.
-            ((View) mMenuView).post(mPostedOpenRunnable);
-
-            // ActionMenuPresenter uses null as a callback argument here
-            // to indicate overflow is opening.
-            super.onSubMenuSelected(null);
-
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Hide the overflow menu if it is currently showing.
-     *
-     * @return true if the overflow menu was hidden, false otherwise.
-     */
-    public boolean hideOverflowMenu() {
-        if (mPostedOpenRunnable != null && mMenuView != null) {
-            ((View) mMenuView).removeCallbacks(mPostedOpenRunnable);
-            mPostedOpenRunnable = null;
-            return true;
-        }
-
-        MenuPopupHelper popup = mOverflowPopup;
-        if (popup != null) {
-            popup.dismiss();
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Dismiss all popup menus - overflow and submenus.
-     *
-     * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
-     */
-    public boolean dismissPopupMenus() {
-        boolean result = hideOverflowMenu();
-        result |= hideSubMenus();
-        return result;
-    }
-
-    /**
-     * Dismiss all submenu popups.
-     *
-     * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
-     */
-    public boolean hideSubMenus() {
-        if (mActionButtonPopup != null) {
-            mActionButtonPopup.dismiss();
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * @return true if the overflow menu is currently showing
-     */
-    public boolean isOverflowMenuShowing() {
-        return mOverflowPopup != null && mOverflowPopup.isShowing();
-    }
-
-    /**
-     * @return true if space has been reserved in the action menu for an overflow item.
-     */
-    public boolean isOverflowReserved() {
-        return mReserveOverflow;
-    }
-
-    public boolean flagActionItems() {
-        final ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
-        final int itemsSize = visibleItems.size();
-        int maxActions = mMaxItems;
-        int widthLimit = mActionItemWidthLimit;
-        final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
-        final ViewGroup parent = (ViewGroup) mMenuView;
-
-        int requiredItems = 0;
-        int requestedItems = 0;
-        int firstActionWidth = 0;
-        boolean hasOverflow = false;
-        for (int i = 0; i < itemsSize; i++) {
-            MenuItemImpl item = visibleItems.get(i);
-            if (item.requiresActionButton()) {
-                requiredItems++;
-            } else if (item.requestsActionButton()) {
-                requestedItems++;
-            } else {
-                hasOverflow = true;
-            }
-            if (mExpandedActionViewsExclusive && item.isActionViewExpanded()) {
-                // Overflow everything if we have an expanded action view and we're
-                // space constrained.
-                maxActions = 0;
-            }
-        }
-
-        // Reserve a spot for the overflow item if needed.
-        if (mReserveOverflow &&
-                (hasOverflow || requiredItems + requestedItems > maxActions)) {
-            maxActions--;
-        }
-        maxActions -= requiredItems;
-
-        final SparseBooleanArray seenGroups = mActionButtonGroups;
-        seenGroups.clear();
-
-        int cellSize = 0;
-        int cellsRemaining = 0;
-        if (mStrictWidthLimit) {
-            cellsRemaining = widthLimit / mMinCellSize;
-            final int cellSizeRemaining = widthLimit % mMinCellSize;
-            cellSize = mMinCellSize + cellSizeRemaining / cellsRemaining;
-        }
-
-        // Flag as many more requested items as will fit.
-        for (int i = 0; i < itemsSize; i++) {
-            MenuItemImpl item = visibleItems.get(i);
-
-            if (item.requiresActionButton()) {
-                View v = getItemView(item, mScrapActionButtonView, parent);
-                if (mScrapActionButtonView == null) {
-                    mScrapActionButtonView = v;
-                }
-                if (mStrictWidthLimit) {
-                    cellsRemaining -= ActionMenuView.measureChildForCells(v,
-                            cellSize, cellsRemaining, querySpec, 0);
-                } else {
-                    v.measure(querySpec, querySpec);
-                }
-                final int measuredWidth = v.getMeasuredWidth();
-                widthLimit -= measuredWidth;
-                if (firstActionWidth == 0) {
-                    firstActionWidth = measuredWidth;
-                }
-                final int groupId = item.getGroupId();
-                if (groupId != 0) {
-                    seenGroups.put(groupId, true);
-                }
-                item.setIsActionButton(true);
-            } else if (item.requestsActionButton()) {
-                // Items in a group with other items that already have an action slot
-                // can break the max actions rule, but not the width limit.
-                final int groupId = item.getGroupId();
-                final boolean inGroup = seenGroups.get(groupId);
-                boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0 &&
-                        (!mStrictWidthLimit || cellsRemaining > 0);
-
-                if (isAction) {
-                    View v = getItemView(item, mScrapActionButtonView, parent);
-                    if (mScrapActionButtonView == null) {
-                        mScrapActionButtonView = v;
-                    }
-                    if (mStrictWidthLimit) {
-                        final int cells = ActionMenuView.measureChildForCells(v,
-                                cellSize, cellsRemaining, querySpec, 0);
-                        cellsRemaining -= cells;
-                        if (cells == 0) {
-                            isAction = false;
-                        }
-                    } else {
-                        v.measure(querySpec, querySpec);
-                    }
-                    final int measuredWidth = v.getMeasuredWidth();
-                    widthLimit -= measuredWidth;
-                    if (firstActionWidth == 0) {
-                        firstActionWidth = measuredWidth;
-                    }
-
-                    if (mStrictWidthLimit) {
-                        isAction &= widthLimit >= 0;
-                    } else {
-                        // Did this push the entire first item past the limit?
-                        isAction &= widthLimit + firstActionWidth > 0;
-                    }
-                }
-
-                if (isAction && groupId != 0) {
-                    seenGroups.put(groupId, true);
-                } else if (inGroup) {
-                    // We broke the width limit. Demote the whole group, they all overflow now.
-                    seenGroups.put(groupId, false);
-                    for (int j = 0; j < i; j++) {
-                        MenuItemImpl areYouMyGroupie = visibleItems.get(j);
-                        if (areYouMyGroupie.getGroupId() == groupId) {
-                            // Give back the action slot
-                            if (areYouMyGroupie.isActionButton()) {
-                                maxActions++;
-                            }
-                            areYouMyGroupie.setIsActionButton(false);
-                        }
-                    }
-                }
-
-                if (isAction) {
-                    maxActions--;
-                }
-
-                item.setIsActionButton(isAction);
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
-        dismissPopupMenus();
-        super.onCloseMenu(menu, allMenusAreClosing);
-    }
-
-    @Override
-    public Parcelable onSaveInstanceState() {
-        SavedState state = new SavedState();
-        state.openSubMenuId = mOpenSubMenuId;
-        return state;
-    }
-
-    @Override
-    public void onRestoreInstanceState(Parcelable state) {
-        SavedState saved = (SavedState) state;
-        if (saved.openSubMenuId > 0) {
-            MenuItem item = mMenu.findItem(saved.openSubMenuId);
-            if (item != null) {
-                SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
-                onSubMenuSelected(subMenu);
-            }
-        }
-    }
-
-    @Override
-    public void onSubUiVisibilityChanged(boolean isVisible) {
-        if (isVisible) {
-            // Not a submenu, but treat it like one.
-            super.onSubMenuSelected(null);
-        } else {
-            mMenu.close(false);
-        }
-    }
-
-    private static class SavedState implements Parcelable {
-
-        public int openSubMenuId;
-
-        SavedState() {
-        }
-
-        SavedState(Parcel in) {
-            openSubMenuId = in.readInt();
-        }
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(openSubMenuId);
-        }
-
-        public static final Parcelable.Creator<SavedState> CREATOR
-                = new Parcelable.Creator<SavedState>() {
-            public SavedState createFromParcel(Parcel in) {
-                return new SavedState(in);
-            }
-
-            public SavedState[] newArray(int size) {
-                return new SavedState[size];
-            }
-        };
-    }
-
-    private class OverflowMenuButton extends ImageButton implements ActionMenuChildView {
-
-        public OverflowMenuButton(Context context) {
-            super(context, null, R.attr.actionOverflowButtonStyle);
-
-            setClickable(true);
-            setFocusable(true);
-            setVisibility(VISIBLE);
-            setEnabled(true);
-        }
-
-        @Override
-        public boolean performClick() {
-            if (super.performClick()) {
-                return true;
-            }
-
-            playSoundEffect(SoundEffectConstants.CLICK);
-            showOverflowMenu();
-            return true;
-        }
-
-        public boolean needsDividerBefore() {
-            return false;
-        }
-
-        public boolean needsDividerAfter() {
-            return false;
-        }
-    }
-
-    private class OverflowPopup extends MenuPopupHelper {
-
-        public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
-                boolean overflowOnly) {
-            super(context, menu, anchorView, overflowOnly);
-            setCallback(mPopupPresenterCallback);
-        }
-
-        @Override
-        public void onDismiss() {
-            super.onDismiss();
-            mMenu.close();
-            mOverflowPopup = null;
-        }
-    }
-
-    private class ActionButtonSubmenu extends MenuDialogHelper {
-
-        public ActionButtonSubmenu(SubMenuBuilder subMenu) {
-            super(subMenu);
-            setCallback(mPopupPresenterCallback);
-        }
-
-        @Override
-        public void onDismiss(DialogInterface dialog) {
-            super.onDismiss(dialog);
-            mActionButtonPopup = null;
-            mOpenSubMenuId = 0;
-        }
-    }
-
-    private class PopupPresenterCallback implements MenuPresenter.Callback {
-
-        @Override
-        public boolean onOpenSubMenu(MenuBuilder subMenu) {
-            if (subMenu == null) {
-                return false;
-            }
-
-            mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId();
-            return false;
-        }
-
-        @Override
-        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
-            if (menu instanceof SubMenuBuilder) {
-                ((SubMenuBuilder) menu).getRootMenu().close(false);
-            }
-        }
-    }
-
-    private class OpenOverflowRunnable implements Runnable {
-
-        private OverflowPopup mPopup;
-
-        public OpenOverflowRunnable(OverflowPopup popup) {
-            mPopup = popup;
-        }
-
-        public void run() {
-            mMenu.changeMenuMode();
-            final View menuView = (View) mMenuView;
-            if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) {
-                mOverflowPopup = mPopup;
-            }
-            mPostedOpenRunnable = null;
-        }
-    }
-}
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/ActionMenuView.java b/v7/appcompat/src/android/support/v7/internal/view/menu/ActionMenuView.java
deleted file mode 100644
index c020a3d..0000000
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/ActionMenuView.java
+++ /dev/null
@@ -1,617 +0,0 @@
-/*
- * Copyright (C) 2010 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.internal.view.menu;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.TypedArray;
-import android.os.Build;
-import android.support.v7.appcompat.R;
-import android.support.v7.internal.widget.LinearLayoutICS;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewDebug;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.LinearLayout;
-
-/**
- * @hide
- */
-public class ActionMenuView extends LinearLayoutICS implements MenuBuilder.ItemInvoker, MenuView {
-
-    private static final String TAG = "ActionMenuView";
-
-    static final int MIN_CELL_SIZE = 56; // dips
-    static final int GENERATED_ITEM_PADDING = 4; // dips
-
-    private MenuBuilder mMenu;
-
-    private boolean mReserveOverflow;
-    private ActionMenuPresenter mPresenter;
-    private boolean mFormatItems;
-    private int mFormatItemsWidth;
-    private int mMinCellSize;
-    private int mGeneratedItemPadding;
-    private int mMeasuredExtraWidth;
-    private int mMaxItemHeight;
-
-    public ActionMenuView(Context context) {
-        this(context, null);
-    }
-
-    public ActionMenuView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        setBaselineAligned(false);
-        final float density = context.getResources().getDisplayMetrics().density;
-        mMinCellSize = (int) (MIN_CELL_SIZE * density);
-        mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
-
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionBar,
-                R.attr.actionBarStyle, 0);
-        mMaxItemHeight = a.getDimensionPixelSize(R.styleable.ActionBar_height, 0);
-        a.recycle();
-    }
-
-    public void setPresenter(ActionMenuPresenter presenter) {
-        mPresenter = presenter;
-    }
-
-    public boolean isExpandedFormat() {
-        return mFormatItems;
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        if (Build.VERSION.SDK_INT >= 8) {
-            super.onConfigurationChanged(newConfig);
-        }
-
-        mPresenter.updateMenuView(false);
-
-        if (mPresenter != null && mPresenter.isOverflowMenuShowing()) {
-            mPresenter.hideOverflowMenu();
-            mPresenter.showOverflowMenu();
-        }
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        // If we've been given an exact size to match, apply special formatting during layout.
-        final boolean wasFormatted = mFormatItems;
-        mFormatItems = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
-
-        if (wasFormatted != mFormatItems) {
-            mFormatItemsWidth = 0; // Reset this when switching modes
-        }
-
-        // Special formatting can change whether items can fit as action buttons.
-        // Kick the menu and update presenters when this changes.
-        final int widthSize = MeasureSpec.getMode(widthMeasureSpec);
-        if (mFormatItems && mMenu != null && widthSize != mFormatItemsWidth) {
-            mFormatItemsWidth = widthSize;
-            mMenu.onItemsChanged(true);
-        }
-
-        if (mFormatItems) {
-            onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec);
-        } else {
-            // Previous measurement at exact format may have set margins - reset them.
-            final int childCount = getChildCount();
-            for (int i = 0; i < childCount; i++) {
-                final View child = getChildAt(i);
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                lp.leftMargin = lp.rightMargin = 0;
-            }
-            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        }
-    }
-
-    private void onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec) {
-        // We already know the width mode is EXACTLY if we're here.
-        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
-        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
-        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
-
-        final int widthPadding = getPaddingLeft() + getPaddingRight();
-        final int heightPadding = getPaddingTop() + getPaddingBottom();
-
-        final int itemHeightSpec = heightMode == MeasureSpec.EXACTLY
-                ? MeasureSpec.makeMeasureSpec(heightSize - heightPadding, MeasureSpec.EXACTLY)
-                : MeasureSpec.makeMeasureSpec(
-                        Math.min(mMaxItemHeight, heightSize - heightPadding), MeasureSpec.AT_MOST);
-
-        widthSize -= widthPadding;
-
-        // Divide the view into cells.
-        final int cellCount = widthSize / mMinCellSize;
-        final int cellSizeRemaining = widthSize % mMinCellSize;
-
-        if (cellCount == 0) {
-            // Give up, nothing fits.
-            setMeasuredDimension(widthSize, 0);
-            return;
-        }
-
-        final int cellSize = mMinCellSize + cellSizeRemaining / cellCount;
-
-        int cellsRemaining = cellCount;
-        int maxChildHeight = 0;
-        int maxCellsUsed = 0;
-        int expandableItemCount = 0;
-        int visibleItemCount = 0;
-        boolean hasOverflow = false;
-
-        // This is used as a bitfield to locate the smallest items present. Assumes childCount < 64.
-        long smallestItemsAt = 0;
-
-        final int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            final View child = getChildAt(i);
-            if (child.getVisibility() == GONE) {
-                continue;
-            }
-
-            final boolean isGeneratedItem = child instanceof ActionMenuItemView;
-            visibleItemCount++;
-
-            if (isGeneratedItem) {
-                // Reset padding for generated menu item views; it may change below
-                // and views are recycled.
-                child.setPadding(mGeneratedItemPadding, 0, mGeneratedItemPadding, 0);
-            }
-
-            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-            lp.expanded = false;
-            lp.extraPixels = 0;
-            lp.cellsUsed = 0;
-            lp.expandable = false;
-            lp.leftMargin = 0;
-            lp.rightMargin = 0;
-            lp.preventEdgeOffset = isGeneratedItem && ((ActionMenuItemView) child).hasText();
-
-            // Overflow always gets 1 cell. No more, no less.
-            final int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining;
-
-            final int cellsUsed = measureChildForCells(child, cellSize, cellsAvailable,
-                    itemHeightSpec, heightPadding);
-
-            maxCellsUsed = Math.max(maxCellsUsed, cellsUsed);
-            if (lp.expandable) {
-                expandableItemCount++;
-            }
-            if (lp.isOverflowButton) {
-                hasOverflow = true;
-            }
-
-            cellsRemaining -= cellsUsed;
-            maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
-            if (cellsUsed == 1) {
-                smallestItemsAt |= (1 << i);
-            }
-        }
-
-        // When we have overflow and a single expanded (text) item, we want to try centering it
-        // visually in the available space even though overflow consumes some of it.
-        final boolean centerSingleExpandedItem = hasOverflow && visibleItemCount == 2;
-
-        // Divide space for remaining cells if we have items that can expand.
-        // Try distributing whole leftover cells to smaller items first.
-
-        boolean needsExpansion = false;
-        while (expandableItemCount > 0 && cellsRemaining > 0) {
-            int minCells = Integer.MAX_VALUE;
-            long minCellsAt = 0; // Bit locations are indices of relevant child views
-            int minCellsItemCount = 0;
-            for (int i = 0; i < childCount; i++) {
-                final View child = getChildAt(i);
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
-                // Don't try to expand items that shouldn't.
-                if (!lp.expandable) {
-                    continue;
-                }
-
-                // Mark indices of children that can receive an extra cell.
-                if (lp.cellsUsed < minCells) {
-                    minCells = lp.cellsUsed;
-                    minCellsAt = 1 << i;
-                    minCellsItemCount = 1;
-                } else if (lp.cellsUsed == minCells) {
-                    minCellsAt |= 1 << i;
-                    minCellsItemCount++;
-                }
-            }
-
-            // Items that get expanded will always be in the set of smallest items when we're done.
-            smallestItemsAt |= minCellsAt;
-
-            if (minCellsItemCount > cellsRemaining) {
-                break; // Couldn't expand anything evenly. Stop.
-            }
-
-            // We have enough cells, all minimum size items will be incremented.
-            minCells++;
-
-            for (int i = 0; i < childCount; i++) {
-                final View child = getChildAt(i);
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                if ((minCellsAt & (1 << i)) == 0) {
-                    // If this item is already at our small item count, mark it for later.
-                    if (lp.cellsUsed == minCells) {
-                        smallestItemsAt |= 1 << i;
-                    }
-                    continue;
-                }
-
-                if (centerSingleExpandedItem && lp.preventEdgeOffset && cellsRemaining == 1) {
-                    // Add padding to this item such that it centers.
-                    child.setPadding(mGeneratedItemPadding + cellSize, 0, mGeneratedItemPadding, 0);
-                }
-                lp.cellsUsed++;
-                lp.expanded = true;
-                cellsRemaining--;
-            }
-
-            needsExpansion = true;
-        }
-
-        // Divide any space left that wouldn't divide along cell boundaries
-        // evenly among the smallest items
-
-        final boolean singleItem = !hasOverflow && visibleItemCount == 1;
-        if (cellsRemaining > 0 && smallestItemsAt != 0 &&
-                (cellsRemaining < visibleItemCount - 1 || singleItem || maxCellsUsed > 1)) {
-            float expandCount = Long.bitCount(smallestItemsAt);
-
-            if (!singleItem) {
-                // The items at the far edges may only expand by half in order to pin to either side.
-                if ((smallestItemsAt & 1) != 0) {
-                    LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams();
-                    if (!lp.preventEdgeOffset) {
-                        expandCount -= 0.5f;
-                    }
-                }
-                if ((smallestItemsAt & (1 << (childCount - 1))) != 0) {
-                    LayoutParams lp = ((LayoutParams) getChildAt(childCount - 1).getLayoutParams());
-                    if (!lp.preventEdgeOffset) {
-                        expandCount -= 0.5f;
-                    }
-                }
-            }
-
-            final int extraPixels = expandCount > 0 ?
-                    (int) (cellsRemaining * cellSize / expandCount) : 0;
-
-            for (int i = 0; i < childCount; i++) {
-                if ((smallestItemsAt & (1 << i)) == 0) {
-                    continue;
-                }
-
-                final View child = getChildAt(i);
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                if (child instanceof ActionMenuItemView) {
-                    // If this is one of our views, expand and measure at the larger size.
-                    lp.extraPixels = extraPixels;
-                    lp.expanded = true;
-                    if (i == 0 && !lp.preventEdgeOffset) {
-                        // First item gets part of its new padding pushed out of sight.
-                        // The last item will get this implicitly from layout.
-                        lp.leftMargin = -extraPixels / 2;
-                    }
-                    needsExpansion = true;
-                } else if (lp.isOverflowButton) {
-                    lp.extraPixels = extraPixels;
-                    lp.expanded = true;
-                    lp.rightMargin = -extraPixels / 2;
-                    needsExpansion = true;
-                } else {
-                    // If we don't know what it is, give it some margins instead
-                    // and let it center within its space. We still want to pin
-                    // against the edges.
-                    if (i != 0) {
-                        lp.leftMargin = extraPixels / 2;
-                    }
-                    if (i != childCount - 1) {
-                        lp.rightMargin = extraPixels / 2;
-                    }
-                }
-            }
-
-            cellsRemaining = 0;
-        }
-
-        // Remeasure any items that have had extra space allocated to them.
-        if (needsExpansion) {
-            for (int i = 0; i < childCount; i++) {
-                final View child = getChildAt(i);
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
-                if (!lp.expanded) {
-                    continue;
-                }
-
-                final int width = lp.cellsUsed * cellSize + lp.extraPixels;
-                child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
-                        itemHeightSpec);
-            }
-        }
-
-        if (heightMode != MeasureSpec.EXACTLY) {
-            heightSize = maxChildHeight;
-        }
-
-        setMeasuredDimension(widthSize, heightSize);
-        mMeasuredExtraWidth = cellsRemaining * cellSize;
-    }
-
-    /**
-     * Measure a child view to fit within cell-based formatting. The child's width will be measured
-     * to a whole multiple of cellSize.
-     *
-     * <p>Sets the expandable and cellsUsed fields of LayoutParams.
-     *
-     * @param child                   Child to measure
-     * @param cellSize                Size of one cell
-     * @param cellsRemaining          Number of cells remaining that this view can expand to fill
-     * @param parentHeightMeasureSpec MeasureSpec used by the parent view
-     * @param parentHeightPadding     Padding present in the parent view
-     * @return Number of cells this child was measured to occupy
-     */
-    static int measureChildForCells(View child, int cellSize, int cellsRemaining,
-            int parentHeightMeasureSpec, int parentHeightPadding) {
-        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
-        final int childHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec) -
-                parentHeightPadding;
-        final int childHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec);
-        final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode);
-
-        final ActionMenuItemView itemView = child instanceof ActionMenuItemView ?
-                (ActionMenuItemView) child : null;
-        final boolean hasText = itemView != null && itemView.hasText();
-
-        int cellsUsed = 0;
-        if (cellsRemaining > 0 && (!hasText || cellsRemaining >= 2)) {
-            final int childWidthSpec = MeasureSpec.makeMeasureSpec(
-                    cellSize * cellsRemaining, MeasureSpec.AT_MOST);
-            child.measure(childWidthSpec, childHeightSpec);
-
-            final int measuredWidth = child.getMeasuredWidth();
-            cellsUsed = measuredWidth / cellSize;
-            if (measuredWidth % cellSize != 0) {
-                cellsUsed++;
-            }
-            if (hasText && cellsUsed < 2) {
-                cellsUsed = 2;
-            }
-        }
-
-        final boolean expandable = !lp.isOverflowButton && hasText;
-        lp.expandable = expandable;
-
-        lp.cellsUsed = cellsUsed;
-        final int targetWidth = cellsUsed * cellSize;
-        child.measure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
-                childHeightSpec);
-        return cellsUsed;
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        if (!mFormatItems) {
-            super.onLayout(changed, left, top, right, bottom);
-            return;
-        }
-
-        final int childCount = getChildCount();
-        final int midVertical = (top + bottom) / 2;
-        final int dividerWidth = getSupportDividerWidth();
-        int overflowWidth = 0;
-        int nonOverflowWidth = 0;
-        int nonOverflowCount = 0;
-        int widthRemaining = right - left - getPaddingRight() - getPaddingLeft();
-        boolean hasOverflow = false;
-        for (int i = 0; i < childCount; i++) {
-            final View v = getChildAt(i);
-            if (v.getVisibility() == GONE) {
-                continue;
-            }
-
-            LayoutParams p = (LayoutParams) v.getLayoutParams();
-            if (p.isOverflowButton) {
-                overflowWidth = v.getMeasuredWidth();
-                if (hasSupportDividerBeforeChildAt(i)) {
-                    overflowWidth += dividerWidth;
-                }
-                int height = v.getMeasuredHeight();
-                int r = getWidth() - getPaddingRight() - p.rightMargin;
-                int l = r - overflowWidth;
-                int t = midVertical - (height / 2);
-                int b = t + height;
-                v.layout(l, t, r, b);
-
-                widthRemaining -= overflowWidth;
-                hasOverflow = true;
-            } else {
-                final int size = v.getMeasuredWidth() + p.leftMargin + p.rightMargin;
-                nonOverflowWidth += size;
-                widthRemaining -= size;
-                if (hasSupportDividerBeforeChildAt(i)) {
-                    nonOverflowWidth += dividerWidth;
-                }
-                nonOverflowCount++;
-            }
-        }
-
-        if (childCount == 1 && !hasOverflow) {
-            // Center a single child
-            final View v = getChildAt(0);
-            final int width = v.getMeasuredWidth();
-            final int height = v.getMeasuredHeight();
-            final int midHorizontal = (right - left) / 2;
-            final int l = midHorizontal - width / 2;
-            final int t = midVertical - height / 2;
-            v.layout(l, t, l + width, t + height);
-            return;
-        }
-
-        final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1);
-        final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0);
-
-        int startLeft = getPaddingLeft();
-        for (int i = 0; i < childCount; i++) {
-            final View v = getChildAt(i);
-            final LayoutParams lp = (LayoutParams) v.getLayoutParams();
-            if (v.getVisibility() == GONE || lp.isOverflowButton) {
-                continue;
-            }
-
-            startLeft += lp.leftMargin;
-            int width = v.getMeasuredWidth();
-            int height = v.getMeasuredHeight();
-            int t = midVertical - height / 2;
-            v.layout(startLeft, t, startLeft + width, t + height);
-            startLeft += width + lp.rightMargin + spacerSize;
-        }
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        mPresenter.dismissPopupMenus();
-    }
-
-    public boolean isOverflowReserved() {
-        return mReserveOverflow;
-    }
-
-    public void setOverflowReserved(boolean reserveOverflow) {
-        mReserveOverflow = reserveOverflow;
-    }
-
-    @Override
-    protected LayoutParams generateDefaultLayoutParams() {
-        LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
-                LayoutParams.WRAP_CONTENT);
-        params.gravity = Gravity.CENTER_VERTICAL;
-        return params;
-    }
-
-    @Override
-    public LayoutParams generateLayoutParams(AttributeSet attrs) {
-        return new LayoutParams(getContext(), attrs);
-    }
-
-    @Override
-    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
-        if (p instanceof LayoutParams) {
-            LayoutParams result = new LayoutParams((LayoutParams) p);
-            if (result.gravity <= Gravity.NO_GRAVITY) {
-                result.gravity = Gravity.CENTER_VERTICAL;
-            }
-            return result;
-        }
-        return generateDefaultLayoutParams();
-    }
-
-    @Override
-    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
-        return p != null && p instanceof LayoutParams;
-    }
-
-    public LayoutParams generateOverflowButtonLayoutParams() {
-        LayoutParams result = generateDefaultLayoutParams();
-        result.isOverflowButton = true;
-        return result;
-    }
-
-    public boolean invokeItem(MenuItemImpl item) {
-        return mMenu.performItemAction(item, 0);
-    }
-
-    public int getWindowAnimations() {
-        return 0;
-    }
-
-    public void initialize(MenuBuilder menu) {
-        mMenu = menu;
-    }
-
-    protected boolean hasSupportDividerBeforeChildAt(int childIndex) {
-        final View childBefore = getChildAt(childIndex - 1);
-        final View child = getChildAt(childIndex);
-        boolean result = false;
-        if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
-            result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
-        }
-        if (childIndex > 0 && child instanceof ActionMenuChildView) {
-            result |= ((ActionMenuChildView) child).needsDividerBefore();
-        }
-        return result;
-    }
-
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        return false;
-    }
-
-    public interface ActionMenuChildView {
-
-        public boolean needsDividerBefore();
-
-        public boolean needsDividerAfter();
-    }
-
-    public static class LayoutParams extends LinearLayout.LayoutParams {
-
-        @ViewDebug.ExportedProperty()
-        public boolean isOverflowButton;
-
-        @ViewDebug.ExportedProperty()
-        public int cellsUsed;
-
-        @ViewDebug.ExportedProperty()
-        public int extraPixels;
-
-        @ViewDebug.ExportedProperty()
-        public boolean expandable;
-
-        @ViewDebug.ExportedProperty()
-        public boolean preventEdgeOffset;
-
-        public boolean expanded;
-
-        public LayoutParams(Context c, AttributeSet attrs) {
-            super(c, attrs);
-        }
-
-        public LayoutParams(LayoutParams other) {
-            super((LinearLayout.LayoutParams) other);
-            isOverflowButton = other.isOverflowButton;
-        }
-
-        public LayoutParams(int width, int height) {
-            super(width, height);
-            isOverflowButton = false;
-        }
-
-        public LayoutParams(int width, int height, boolean isOverflowButton) {
-            super(width, height);
-            this.isOverflowButton = isOverflowButton;
-        }
-    }
-}
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/BaseMenuPresenter.java b/v7/appcompat/src/android/support/v7/internal/view/menu/BaseMenuPresenter.java
index 62b6bf1..5898bb2 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/BaseMenuPresenter.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/BaseMenuPresenter.java
@@ -49,7 +49,7 @@
     /**
      * Construct a new BaseMenuPresenter.
      *
-     * @param context       Context for generating system-supplied views
+     * @param context Context for generating system-supplied views
      * @param menuLayoutRes Layout resource ID for the menu container view
      * @param itemLayoutRes Layout resource ID for a single item view
      */
@@ -67,6 +67,7 @@
         mMenu = menu;
     }
 
+    @Override
     public MenuView getMenuView(ViewGroup root) {
         if (mMenuView == null) {
             mMenuView = (MenuView) mSystemInflater.inflate(mMenuLayoutRes, root, false);
@@ -82,9 +83,7 @@
      */
     public void updateMenuView(boolean cleared) {
         final ViewGroup parent = (ViewGroup) mMenuView;
-        if (parent == null) {
-            return;
-        }
+        if (parent == null) return;
 
         int childIndex = 0;
         if (mMenu != null) {
@@ -123,7 +122,7 @@
     /**
      * Add an item view at the given index.
      *
-     * @param itemView   View to add
+     * @param itemView View to add
      * @param childIndex Index within the parent to insert at
      */
     protected void addItemView(View itemView, int childIndex) {
@@ -136,8 +135,7 @@
 
     /**
      * Filter the child view at index and remove it if appropriate.
-     *
-     * @param parent     Parent to filter from
+     * @param parent Parent to filter from
      * @param childIndex Index to filter
      * @return true if the child view at index was removed
      */
@@ -150,6 +148,10 @@
         mCallback = cb;
     }
 
+    public Callback getCallback() {
+        return mCallback;
+    }
+
     /**
      * Create a new item view that can be re-bound to other item data later.
      *
@@ -160,14 +162,14 @@
     }
 
     /**
-     * Prepare an item view for use. See AdapterView for the basic idea at work here. This may
-     * require creating a new item view, but well-behaved implementations will re-use the view
-     * passed as convertView if present. The returned view will be populated with data from the item
-     * parameter.
+     * Prepare an item view for use. See AdapterView for the basic idea at work here.
+     * This may require creating a new item view, but well-behaved implementations will
+     * re-use the view passed as convertView if present. The returned view will be populated
+     * with data from the item parameter.
      *
-     * @param item        Item to present
+     * @param item Item to present
      * @param convertView Existing view to reuse
-     * @param parent      Intended parent view - use for inflation.
+     * @param parent Intended parent view - use for inflation.
      * @return View that presents the requested menu item
      */
     public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
@@ -184,7 +186,7 @@
     /**
      * Bind item data to an existing item view.
      *
-     * @param item     Item to bind
+     * @param item Item to bind
      * @param itemView View to populate with item data
      */
     public abstract void bindItemView(MenuItemImpl item, MenuView.ItemView itemView);
@@ -193,7 +195,7 @@
      * Filter item by child index and item data.
      *
      * @param childIndex Indended presentation index of this item
-     * @param item       Item to present
+     * @param item Item to present
      * @return true if this item should be included in this menu presentation; false otherwise
      */
     public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/ListMenuItemView.java b/v7/appcompat/src/android/support/v7/internal/view/menu/ListMenuItemView.java
index 282ff9d..98ee87c 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/ListMenuItemView.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/ListMenuItemView.java
@@ -63,9 +63,8 @@
         super(context, attrs);
         mContext = context;
 
-        TypedArray a =
-                context.obtainStyledAttributes(
-                        attrs, R.styleable.MenuView, defStyle, 0);
+        final TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.MenuView, defStyle, 0);
 
         mBackground = a.getDrawable(R.styleable.MenuView_android_itemBackground);
         mTextAppearance = a.getResourceId(R.styleable.
@@ -117,13 +116,9 @@
         if (title != null) {
             mTitleView.setText(title);
 
-            if (mTitleView.getVisibility() != VISIBLE) {
-                mTitleView.setVisibility(VISIBLE);
-            }
+            if (mTitleView.getVisibility() != VISIBLE) mTitleView.setVisibility(VISIBLE);
         } else {
-            if (mTitleView.getVisibility() != GONE) {
-                mTitleView.setVisibility(GONE);
-            }
+            if (mTitleView.getVisibility() != GONE) mTitleView.setVisibility(GONE);
         }
     }
 
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/ListMenuPresenter.java b/v7/appcompat/src/android/support/v7/internal/view/menu/ListMenuPresenter.java
index 1d79f5f..ac98840 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/ListMenuPresenter.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/ListMenuPresenter.java
@@ -98,22 +98,16 @@
 
     @Override
     public MenuView getMenuView(ViewGroup root) {
-        if (mAdapter == null) {
-            mAdapter = new MenuAdapter();
-        }
-
-        if (!mAdapter.isEmpty()) {
-            if (mMenuView == null) {
-                mMenuView = (ExpandedMenuView) mInflater.inflate(
-                        R.layout.abc_expanded_menu_layout, root, false);
-                mMenuView.setAdapter(mAdapter);
-                mMenuView.setOnItemClickListener(this);
+        if (mMenuView == null) {
+            mMenuView = (ExpandedMenuView) mInflater.inflate(
+                    R.layout.abc_expanded_menu_layout, root, false);
+            if (mAdapter == null) {
+                mAdapter = new MenuAdapter();
             }
-            return mMenuView;
+            mMenuView.setAdapter(mAdapter);
+            mMenuView.setOnItemClickListener(this);
         }
-
-        // If we reach here, the Menu is empty so we have nothing to display
-        return null;
+        return mMenuView;
     }
 
     /**
@@ -172,7 +166,7 @@
 
     @Override
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-        mMenu.performItemAction(mAdapter.getItem(position), 0);
+        mMenu.performItemAction(mAdapter.getItem(position), this, 0);
     }
 
     @Override
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuBuilder.java b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuBuilder.java
index 34ccbf2..675a823 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuBuilder.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuBuilder.java
@@ -1,4 +1,18 @@
-// Copyright 2012 Google Inc. All Rights Reserved.
+/*
+ * Copyright (C) 2012 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.internal.view.menu;
 
@@ -12,6 +26,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Parcelable;
+import android.support.v4.content.ContextCompat;
 import android.support.v4.view.MenuItemCompat;
 import android.support.v7.appcompat.R;
 import android.support.v4.view.ActionProvider;
@@ -129,15 +144,18 @@
      * Header icon for menu types that have a header and support icons (context)
      */
     Drawable mHeaderIcon;
-
-    /**
-     * Header custom view for menu types that have a header and support custom views (context)
-     */
+    /** Header custom view for menu types that have a header and support custom views (context) */
     View mHeaderView;
 
     /**
-     * Prevents onItemsChanged from doing its junk, useful for batching commands that may
-     * individually call onItemsChanged.
+     * Contains the state of the View hierarchy for all menu views when the menu
+     * was frozen.
+     */
+    private SparseArray<Parcelable> mFrozenViewStates;
+
+    /**
+     * Prevents onItemsChanged from doing its junk, useful for batching commands
+     * that may individually call onItemsChanged.
      */
     private boolean mPreventDispatchingItemsChanged = false;
 
@@ -185,7 +203,6 @@
      * @hide
      */
     public interface ItemInvoker {
-
         public boolean invokeItem(MenuItemImpl item);
     }
 
@@ -217,8 +234,21 @@
      * @param presenter The presenter to add
      */
     public void addMenuPresenter(MenuPresenter presenter) {
+        addMenuPresenter(presenter, mContext);
+    }
+
+    /**
+     * Add a presenter to this menu that uses an alternate context for
+     * inflating menu items. This will only hold a WeakReference; you do not
+     * need to explicitly remove a presenter, but you can using
+     * {@link #removeMenuPresenter(MenuPresenter)}.
+     *
+     * @param presenter The presenter to add
+     * @param menuContext The context used to inflate menu items
+     */
+    public void addMenuPresenter(MenuPresenter presenter, Context menuContext) {
         mPresenters.add(new WeakReference<MenuPresenter>(presenter));
-        presenter.initForMenu(mContext, this);
+        presenter.initForMenu(menuContext, this);
         mIsActionItemsStale = true;
     }
 
@@ -238,9 +268,7 @@
     }
 
     private void dispatchPresenterUpdate(boolean cleared) {
-        if (mPresenters.isEmpty()) {
-            return;
-        }
+        if (mPresenters.isEmpty()) return;
 
         stopDispatchingItemsChanged();
         for (WeakReference<MenuPresenter> ref : mPresenters) {
@@ -254,13 +282,17 @@
         startDispatchingItemsChanged();
     }
 
-    private boolean dispatchSubMenuSelected(SubMenuBuilder subMenu) {
-        if (mPresenters.isEmpty()) {
-            return false;
-        }
+    private boolean dispatchSubMenuSelected(SubMenuBuilder subMenu,
+            MenuPresenter preferredPresenter) {
+        if (mPresenters.isEmpty()) return false;
 
         boolean result = false;
 
+        // Try the preferred presenter first.
+        if (preferredPresenter != null) {
+            result = preferredPresenter.onSubMenuSelected(subMenu);
+        }
+
         for (WeakReference<MenuPresenter> ref : mPresenters) {
             final MenuPresenter presenter = ref.get();
             if (presenter == null) {
@@ -273,9 +305,7 @@
     }
 
     private void dispatchSaveInstanceState(Bundle outState) {
-        if (mPresenters.isEmpty()) {
-            return;
-        }
+        if (mPresenters.isEmpty()) return;
 
         SparseArray<Parcelable> presenterStates = new SparseArray<Parcelable>();
 
@@ -300,9 +330,7 @@
     private void dispatchRestoreInstanceState(Bundle state) {
         SparseArray<Parcelable> presenterStates = state.getSparseParcelableArray(PRESENTER_KEY);
 
-        if (presenterStates == null || mPresenters.isEmpty()) {
-            return;
-        }
+        if (presenterStates == null || mPresenters.isEmpty()) return;
 
         for (WeakReference<MenuPresenter> ref : mPresenters) {
             final MenuPresenter presenter = ref.get();
@@ -399,8 +427,8 @@
     private MenuItem addInternal(int group, int id, int categoryOrder, CharSequence title) {
         final int ordering = getOrdering(categoryOrder);
 
-        final MenuItemImpl item = new MenuItemImpl(this, group, id, categoryOrder,
-                ordering, title, mDefaultShowAsAction);
+        final MenuItemImpl item = createNewMenuItem(group, id, categoryOrder, ordering, title,
+                mDefaultShowAsAction);
 
         if (mCurrentMenuInfo != null) {
             // Pass along the current menu info
@@ -413,7 +441,13 @@
         return item;
     }
 
-    @Override
+    // Layoutlib overrides this method to return its custom implementation of MenuItemImpl
+    private MenuItemImpl createNewMenuItem(int group, int id, int categoryOrder, int ordering,
+            CharSequence title, int defaultShowAsAction) {
+        return new MenuItemImpl(this, group, id, categoryOrder, ordering, title,
+                defaultShowAsAction);
+    }
+
     public MenuItem add(CharSequence title) {
         return addInternal(0, 0, 0, title);
     }
@@ -510,23 +544,21 @@
     }
 
     /**
-     * Remove the item at the given index and optionally forces menu views to update.
+     * Remove the item at the given index and optionally forces menu views to
+     * update.
      *
-     * @param index                     The index of the item to be removed. If this index is
-     *                                  invalid an exception is thrown.
-     * @param updateChildrenOnMenuViews Whether to force update on menu views. Please make sure you
-     *                                  eventually call this after your batch of removals.
+     * @param index The index of the item to be removed. If this index is
+     *            invalid an exception is thrown.
+     * @param updateChildrenOnMenuViews Whether to force update on menu views.
+     *            Please make sure you eventually call this after your batch of
+     *            removals.
      */
     private void removeItemAtInt(int index, boolean updateChildrenOnMenuViews) {
-        if ((index < 0) || (index >= mItems.size())) {
-            return;
-        }
+        if ((index < 0) || (index >= mItems.size())) return;
 
         mItems.remove(index);
 
-        if (updateChildrenOnMenuViews) {
-            onItemsChanged(true);
-        }
+        if (updateChildrenOnMenuViews) onItemsChanged(true);
     }
 
     public void removeItemAt(int index) {
@@ -559,12 +591,8 @@
         for (int i = 0; i < N; i++) {
             MenuItemImpl curItem = mItems.get(i);
             if (curItem.getGroupId() == group) {
-                if (!curItem.isExclusiveCheckable()) {
-                    continue;
-                }
-                if (!curItem.isCheckable()) {
-                    continue;
-                }
+                if (!curItem.isExclusiveCheckable()) continue;
+                if (!curItem.isCheckable()) continue;
 
                 // Check the item meant to be checked, uncheck the others (that are in the group)
                 curItem.setCheckedInt(curItem == item);
@@ -589,22 +617,18 @@
     public void setGroupVisible(int group, boolean visible) {
         final int N = mItems.size();
 
-        // We handle the notification of items being changed ourselves, so we use setVisibleInt
-        // rather than setVisible and at the end notify of items being changed
+        // We handle the notification of items being changed ourselves, so we use setVisibleInt rather
+        // than setVisible and at the end notify of items being changed
 
         boolean changedAtLeastOneItem = false;
         for (int i = 0; i < N; i++) {
             MenuItemImpl item = mItems.get(i);
             if (item.getGroupId() == group) {
-                if (item.setVisibleInt(visible)) {
-                    changedAtLeastOneItem = true;
-                }
+                if (item.setVisibleInt(visible)) changedAtLeastOneItem = true;
             }
         }
 
-        if (changedAtLeastOneItem) {
-            onItemsChanged(true);
-        }
+        if (changedAtLeastOneItem) onItemsChanged(true);
     }
 
     @Override
@@ -710,14 +734,15 @@
     }
 
     /**
-     * Returns the ordering across all items. This will grab the category from the upper bits, find
-     * out how to order the category with respect to other categories, and combine it with the lower
-     * bits.
+     * Returns the ordering across all items. This will grab the category from
+     * the upper bits, find out how to order the category with respect to other
+     * categories, and combine it with the lower bits.
      *
-     * @param categoryOrder The category order for a particular item (if it has not been or/add with
-     *                      a category, the default category is assumed).
-     * @return An ordering integer that can be used to order this item across all the items (even
-     *         from other categories).
+     * @param categoryOrder The category order for a particular item (if it has
+     *            not been or/add with a category, the default category is
+     *            assumed).
+     * @return An ordering integer that can be used to order this item across
+     *         all the items (even from other categories).
      */
     private static int getOrdering(int categoryOrder) {
         final int index = (categoryOrder & CATEGORY_MASK) >> CATEGORY_SHIFT;
@@ -837,18 +862,16 @@
         for (int i = 0; i < N; i++) {
             MenuItemImpl item = mItems.get(i);
             if (item.hasSubMenu()) {
-                ((MenuBuilder) item.getSubMenu())
-                        .findItemsWithShortcutForKey(items, keyCode, event);
+                ((MenuBuilder)item.getSubMenu()).findItemsWithShortcutForKey(items, keyCode, event);
             }
-            final char shortcutChar = qwerty ? item.getAlphabeticShortcut()
-                    : item.getNumericShortcut();
+            final char shortcutChar = qwerty ? item.getAlphabeticShortcut() : item.getNumericShortcut();
             if (((metaState & (KeyEvent.META_SHIFT_ON | KeyEvent.META_SYM_ON)) == 0) &&
-                    (shortcutChar != 0) &&
-                    (shortcutChar == possibleChars.meta[0]
-                            || shortcutChar == possibleChars.meta[2]
-                            || (qwerty && shortcutChar == '\b' &&
-                            keyCode == KeyEvent.KEYCODE_DEL)) &&
-                    item.isEnabled()) {
+                  (shortcutChar != 0) &&
+                  (shortcutChar == possibleChars.meta[0]
+                      || shortcutChar == possibleChars.meta[2]
+                      || (qwerty && shortcutChar == '\b' &&
+                          keyCode == KeyEvent.KEYCODE_DEL)) &&
+                  item.isEnabled()) {
                 items.add(item);
             }
         }
@@ -896,9 +919,9 @@
                     item.getNumericShortcut();
             if ((shortcutChar == possibleChars.meta[0] &&
                     (metaState & KeyEvent.META_ALT_ON) == 0)
-                    || (shortcutChar == possibleChars.meta[2] &&
+                || (shortcutChar == possibleChars.meta[2] &&
                     (metaState & KeyEvent.META_ALT_ON) != 0)
-                    || (qwerty && shortcutChar == '\b' &&
+                || (qwerty && shortcutChar == '\b' &&
                     keyCode == KeyEvent.KEYCODE_DEL)) {
                 return item;
             }
@@ -913,6 +936,10 @@
     }
 
     public boolean performItemAction(MenuItem item, int flags) {
+        return performItemAction(item, null, flags);
+    }
+
+    public boolean performItemAction(MenuItem item, MenuPresenter preferredPresenter, int flags) {
         MenuItemImpl itemImpl = (MenuItemImpl) item;
 
         if (itemImpl == null || !itemImpl.isEnabled()) {
@@ -925,9 +952,7 @@
         final boolean providerHasSubMenu = provider != null && provider.hasSubMenu();
         if (itemImpl.hasCollapsibleActionView()) {
             invoked |= itemImpl.expandActionView();
-            if (invoked) {
-                close(true);
-            }
+            if (invoked) close(true);
         } else if (itemImpl.hasSubMenu() || providerHasSubMenu) {
             close(false);
 
@@ -939,10 +964,8 @@
             if (providerHasSubMenu) {
                 provider.onPrepareSubMenu(subMenu);
             }
-            invoked |= dispatchSubMenuSelected(subMenu);
-            if (!invoked) {
-                close(true);
-            }
+            invoked |= dispatchSubMenuSelected(subMenu, preferredPresenter);
+            if (!invoked) close(true);
         } else {
             if ((flags & FLAG_PERFORM_NO_CLOSE) == 0) {
                 close(true);
@@ -955,15 +978,14 @@
     /**
      * Closes the visible menu.
      *
-     * @param allMenusAreClosing Whether the menus are completely closing (true), or whether there
-     *                           is another menu coming in this menu's place (false). For example,
-     *                           if the menu is closing because a sub menu is about to be shown,
-     *                           <var>allMenusAreClosing</var> is false.
+     * @param allMenusAreClosing Whether the menus are completely closing (true),
+     *            or whether there is another menu coming in this menu's place
+     *            (false). For example, if the menu is closing because a
+     *            sub menu is about to be shown, <var>allMenusAreClosing</var>
+     *            is false.
      */
-    final void close(boolean allMenusAreClosing) {
-        if (mIsClosing) {
-            return;
-        }
+    public final void close(boolean allMenusAreClosing) {
+        if (mIsClosing) return;
 
         mIsClosing = true;
         for (WeakReference<MenuPresenter> ref : mPresenters) {
@@ -985,11 +1007,11 @@
     /**
      * Called when an item is added or removed.
      *
-     * @param structureChanged true if the menu structure changed, false if only item properties
-     *                         changed. (Visibility is a structural property since it affects
-     *                         layout.)
+     * @param structureChanged true if the menu structure changed,
+     *                         false if only item properties changed.
+     *                         (Visibility is a structural property since it affects layout.)
      */
-    void onItemsChanged(boolean structureChanged) {
+    public void onItemsChanged(boolean structureChanged) {
         if (!mPreventDispatchingItemsChanged) {
             if (structureChanged) {
                 mIsVisibleItemsStale = true;
@@ -1003,9 +1025,9 @@
     }
 
     /**
-     * Stop dispatching item changed events to presenters until {@link
-     * #startDispatchingItemsChanged()} is called. Useful when many menu operations are going to be
-     * performed as a batch.
+     * Stop dispatching item changed events to presenters until
+     * {@link #startDispatchingItemsChanged()} is called. Useful when
+     * many menu operations are going to be performed as a batch.
      */
     public void stopDispatchingItemsChanged() {
         if (!mPreventDispatchingItemsChanged) {
@@ -1045,10 +1067,8 @@
         onItemsChanged(true);
     }
 
-    ArrayList<MenuItemImpl> getVisibleItems() {
-        if (!mIsVisibleItemsStale) {
-            return mVisibleItems;
-        }
+    public ArrayList<MenuItemImpl> getVisibleItems() {
+        if (!mIsVisibleItemsStale) return mVisibleItems;
 
         // Refresh the visible items
         mVisibleItems.clear();
@@ -1057,9 +1077,7 @@
         MenuItemImpl item;
         for (int i = 0; i < itemsSize; i++) {
             item = mItems.get(i);
-            if (item.isVisible()) {
-                mVisibleItems.add(item);
-            }
+            if (item.isVisible()) mVisibleItems.add(item);
         }
 
         mIsVisibleItemsStale = false;
@@ -1093,6 +1111,10 @@
      * to avoid inadvertent reordering that may break the app's intended design.
      */
     public void flagActionItems() {
+        // Important side effect: if getVisibleItems is stale it may refresh,
+        // which can affect action items staleness.
+        final ArrayList<MenuItemImpl> visibleItems = getVisibleItems();
+
         if (!mIsActionItemsStale) {
             return;
         }
@@ -1111,7 +1133,6 @@
         if (flagged) {
             mActionItems.clear();
             mNonActionItems.clear();
-            ArrayList<MenuItemImpl> visibleItems = getVisibleItems();
             final int itemsSize = visibleItems.size();
             for (int i = 0; i < itemsSize; i++) {
                 MenuItemImpl item = visibleItems.get(i);
@@ -1131,12 +1152,12 @@
         mIsActionItemsStale = false;
     }
 
-    ArrayList<MenuItemImpl> getActionItems() {
+    public ArrayList<MenuItemImpl> getActionItems() {
         flagActionItems();
         return mActionItems;
     }
 
-    ArrayList<MenuItemImpl> getNonActionItems() {
+    public ArrayList<MenuItemImpl> getNonActionItems() {
         flagActionItems();
         return mNonActionItems;
     }
@@ -1167,7 +1188,7 @@
             }
 
             if (iconRes > 0) {
-                mHeaderIcon = r.getDrawable(iconRes);
+                mHeaderIcon = ContextCompat.getDrawable(getContext(), iconRes);
             } else if (icon != null) {
                 mHeaderIcon = icon;
             }
@@ -1181,8 +1202,8 @@
     }
 
     /**
-     * Sets the header's title. This replaces the header view. Called by the builder-style methods
-     * of subclasses.
+     * Sets the header's title. This replaces the header view. Called by the
+     * builder-style methods of subclasses.
      *
      * @param title The new title.
      * @return This MenuBuilder so additional setters can be called.
@@ -1193,8 +1214,8 @@
     }
 
     /**
-     * Sets the header's title. This replaces the header view. Called by the builder-style methods
-     * of subclasses.
+     * Sets the header's title. This replaces the header view. Called by the
+     * builder-style methods of subclasses.
      *
      * @param titleRes The new title (as a resource ID).
      * @return This MenuBuilder so additional setters can be called.
@@ -1205,8 +1226,8 @@
     }
 
     /**
-     * Sets the header's icon. This replaces the header view. Called by the builder-style methods of
-     * subclasses.
+     * Sets the header's icon. This replaces the header view. Called by the
+     * builder-style methods of subclasses.
      *
      * @param icon The new icon.
      * @return This MenuBuilder so additional setters can be called.
@@ -1217,8 +1238,8 @@
     }
 
     /**
-     * Sets the header's icon. This replaces the header view. Called by the builder-style methods of
-     * subclasses.
+     * Sets the header's icon. This replaces the header view. Called by the
+     * builder-style methods of subclasses.
      *
      * @param iconRes The new icon (as a resource ID).
      * @return This MenuBuilder so additional setters can be called.
@@ -1229,8 +1250,8 @@
     }
 
     /**
-     * Sets the header's view. This replaces the title and icon. Called by the builder-style methods
-     * of subclasses.
+     * Sets the header's view. This replaces the title and icon. Called by the
+     * builder-style methods of subclasses.
      *
      * @param view The new view.
      * @return This MenuBuilder so additional setters can be called.
@@ -1262,9 +1283,9 @@
     }
 
     /**
-     * Sets the current menu info that is set on all items added to this menu (until this is called
-     * again with different menu info, in which case that one will be added to all subsequent item
-     * additions).
+     * Sets the current menu info that is set on all items added to this menu
+     * (until this is called again with different menu info, in which case that
+     * one will be added to all subsequent item additions).
      *
      * @param menuInfo The extra menu information to add.
      */
@@ -1281,9 +1302,7 @@
     }
 
     public boolean expandItemActionView(MenuItemImpl item) {
-        if (mPresenters.isEmpty()) {
-            return false;
-        }
+        if (mPresenters.isEmpty()) return false;
 
         boolean expanded = false;
 
@@ -1305,9 +1324,7 @@
     }
 
     public boolean collapseItemActionView(MenuItemImpl item) {
-        if (mPresenters.isEmpty() || mExpandedItem != item) {
-            return false;
-        }
+        if (mPresenters.isEmpty() || mExpandedItem != item) return false;
 
         boolean collapsed = false;
 
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuDialogHelper.java b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuDialogHelper.java
index 716dfd2..2707abe 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuDialogHelper.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuDialogHelper.java
@@ -59,7 +59,7 @@
         // Need to force Light Menu theme as list_menu_item_layout is usually against a dark bg and
         // AlertDialog's bg is white
         mPresenter = new ListMenuPresenter(R.layout.abc_list_menu_item_layout,
-                R.style.Theme_AppCompat_CompactMenu_Dialog);
+                R.style.Theme_AppCompat_CompactMenu);
 
         mPresenter.setCallback(this);
         mMenu.addMenuPresenter(mPresenter);
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemImpl.java b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemImpl.java
index 276a0f5..ef3cf21 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemImpl.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuItemImpl.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
+import android.support.v4.content.ContextCompat;
 import android.support.v4.view.ActionProvider;
 import android.support.v4.internal.view.SupportMenuItem;
 import android.support.v4.view.MenuItemCompat;
@@ -53,25 +54,19 @@
     private char mShortcutNumericChar;
     private char mShortcutAlphabeticChar;
 
-    /**
-     * The icon's drawable which is only created as needed
-     */
+    /** The icon's drawable which is only created as needed */
     private Drawable mIconDrawable;
 
     /**
-     * The icon's resource ID which is used to get the Drawable when it is needed (if the Drawable
-     * isn't already obtained--only one of the two is needed).
+     * The icon's resource ID which is used to get the Drawable when it is
+     * needed (if the Drawable isn't already obtained--only one of the two is
+     * needed).
      */
     private int mIconResId = NO_ICON;
 
-    /**
-     * The menu to which this item belongs
-     */
+    /** The menu to which this item belongs */
     private MenuBuilder mMenu;
-
-    /**
-     * If this item should launch a sub menu, this is the sub menu to launch
-     */
+    /** If this item should launch a sub menu, this is the sub menu to launch */
     private SubMenuBuilder mSubMenu;
 
     private Runnable mItemCallback;
@@ -92,14 +87,12 @@
     private MenuItemCompat.OnActionExpandListener mOnActionExpandListener;
     private boolean mIsActionViewExpanded = false;
 
-    /**
-     * Used for the icon resource ID if this item does not have an icon
-     */
+    /** Used for the icon resource ID if this item does not have an icon */
     static final int NO_ICON = 0;
 
     /**
-     * Current use case is for context menu: Extra information linked to the View that added this
-     * item to the context menu.
+     * Current use case is for context menu: Extra information linked to the
+     * View that added this item to the context menu.
      */
     private ContextMenuInfo mMenuInfo;
 
@@ -112,13 +105,14 @@
     /**
      * Instantiates this menu item.
      *
-     * @param group         Item ordering grouping control. The item will be added after all other
-     *                      items whose order is <= this number, and before any that are larger than
-     *                      it. This can also be used to define groups of items for batch state
-     *                      changes. Normally use 0.
-     * @param id            Unique item ID. Use 0 if you do not need a unique ID.
+     * @param menu
+     * @param group Item ordering grouping control. The item will be added after
+     *            all other items whose order is <= this number, and before any
+     *            that are larger than it. This can also be used to define
+     *            groups of items for batch state changes. Normally use 0.
+     * @param id Unique item ID. Use 0 if you do not need a unique ID.
      * @param categoryOrder The ordering for this item.
-     * @param title         The text to display for the item.
+     * @param title The text to display for the item.
      */
     MenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering,
             CharSequence title, int showAsAction) {
@@ -150,8 +144,7 @@
      * @return true if the invocation was handled, false otherwise
      */
     public boolean invoke() {
-        if (mClickListener != null &&
-                mClickListener.onMenuItemClick(this)) {
+        if (mClickListener != null && mClickListener.onMenuItemClick(this)) {
             return true;
         }
 
@@ -288,8 +281,7 @@
      * @return The active shortcut (based on QWERTY-mode of the menu).
      */
     char getShortcut() {
-        //return (mMenu.isQwertyMode() ? mShortcutAlphabeticChar : mShortcutNumericChar);
-        return mShortcutAlphabeticChar;
+        return (mMenu.isQwertyMode() ? mShortcutAlphabeticChar : mShortcutNumericChar);
     }
 
     /**
@@ -328,8 +320,9 @@
     }
 
     /**
-     * @return Whether this menu item should be showing shortcuts (depends on whether the menu
-     *         should show shortcuts and whether this item has a shortcut defined)
+     * @return Whether this menu item should be showing shortcuts (depends on
+     *         whether the menu should show shortcuts and whether this item has
+     *         a shortcut defined)
      */
     boolean shouldShowShortcut() {
         // Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut
@@ -414,7 +407,7 @@
         }
 
         if (mIconResId != NO_ICON) {
-            Drawable icon =  mMenu.getResources().getDrawable(mIconResId);
+            Drawable icon =  ContextCompat.getDrawable(mMenu.getContext(), mIconResId);
             mIconResId = NO_ICON;
             mIconDrawable = icon;
             return icon;
@@ -632,13 +625,13 @@
     @Override
     public MenuItem setActionProvider(android.view.ActionProvider actionProvider) {
         throw new UnsupportedOperationException(
-                "Implementation should use setSupportActionProvider!");
+                "This is not supported, use MenuItemCompat.setActionProvider()");
     }
 
     @Override
     public android.view.ActionProvider getActionProvider() {
         throw new UnsupportedOperationException(
-                "Implementation should use getSupportActionProvider!");
+                "This is not supported, use MenuItemCompat.getActionProvider()");
     }
 
     @Override
@@ -648,18 +641,14 @@
 
     @Override
     public SupportMenuItem setSupportActionProvider(ActionProvider actionProvider) {
-        if (mActionProvider == actionProvider) {
-            return this;
-        }
-
-        mActionView = null;
         if (mActionProvider != null) {
             mActionProvider.setVisibilityListener(null);
         }
+        mActionView = null;
         mActionProvider = actionProvider;
         mMenu.onItemsChanged(true); // Measurement can be changed
-        if (actionProvider != null) {
-            actionProvider.setVisibilityListener(new ActionProvider.VisibilityListener() {
+        if (mActionProvider != null) {
+            mActionProvider.setVisibilityListener(new ActionProvider.VisibilityListener() {
                 @Override
                 public void onActionProviderVisibilityChanged(boolean isVisible) {
                     mMenu.onItemVisibleChanged(MenuItemImpl.this);
@@ -677,7 +666,7 @@
 
     @Override
     public boolean expandActionView() {
-        if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0 || mActionView == null) {
+        if (!hasCollapsibleActionView()) {
             return false;
         }
 
@@ -715,7 +704,13 @@
     }
 
     public boolean hasCollapsibleActionView() {
-        return (mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) != 0 && mActionView != null;
+        if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) != 0) {
+            if (mActionView == null && mActionProvider != null) {
+                mActionView = mActionProvider.onCreateActionView(this);
+            }
+            return mActionView != null;
+        }
+        return false;
     }
 
     public void setActionViewExpanded(boolean isExpanded) {
@@ -731,6 +726,6 @@
     @Override
     public MenuItem setOnActionExpandListener(MenuItem.OnActionExpandListener listener) {
         throw new UnsupportedOperationException(
-                "Implementation should use setSupportOnActionExpandListener!");
+                "This is not supported, use MenuItemCompat.setOnActionExpandListener()");
     }
 }
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuPopupHelper.java b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuPopupHelper.java
index bc845c4..10c096b 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuPopupHelper.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuPopupHelper.java
@@ -20,7 +20,8 @@
 import android.content.res.Resources;
 import android.os.Parcelable;
 import android.support.v7.appcompat.R;
-import android.support.v7.internal.widget.ListPopupWindow;
+import android.support.v7.widget.ListPopupWindow;
+import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MenuItem;
@@ -49,37 +50,47 @@
 
     static final int ITEM_LAYOUT = R.layout.abc_popup_menu_item_layout;
 
-    private Context mContext;
-    private LayoutInflater mInflater;
-    private ListPopupWindow mPopup;
-    private MenuBuilder mMenu;
-    private int mPopupMaxWidth;
+    private final Context mContext;
+    private final LayoutInflater mInflater;
+    private final MenuBuilder mMenu;
+    private final MenuAdapter mAdapter;
+    private final boolean mOverflowOnly;
+    private final int mPopupMaxWidth;
+    private final int mPopupStyleAttr;
+
     private View mAnchorView;
-    private boolean mOverflowOnly;
+    private ListPopupWindow mPopup;
     private ViewTreeObserver mTreeObserver;
-
-    private MenuAdapter mAdapter;
-
     private Callback mPresenterCallback;
 
     boolean mForceShowIcon;
 
     private ViewGroup mMeasureParent;
 
+    /** Whether the cached content width value is valid. */
+    private boolean mHasContentWidth;
+
+    /** Cached content width from {@link #measureContentWidth}. */
+    private int mContentWidth;
+
+    private int mDropDownGravity = Gravity.NO_GRAVITY;
+
     public MenuPopupHelper(Context context, MenuBuilder menu) {
-        this(context, menu, null, false);
+        this(context, menu, null, false, R.attr.popupMenuStyle);
     }
 
     public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) {
-        this(context, menu, anchorView, false);
+        this(context, menu, anchorView, false, R.attr.popupMenuStyle);
     }
 
-    public MenuPopupHelper(Context context, MenuBuilder menu,
-            View anchorView, boolean overflowOnly) {
+    public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
+            boolean overflowOnly, int popupStyleAttr) {
         mContext = context;
         mInflater = LayoutInflater.from(context);
         mMenu = menu;
+        mAdapter = new MenuAdapter(mMenu);
         mOverflowOnly = overflowOnly;
+        mPopupStyleAttr = popupStyleAttr;
 
         final Resources res = context.getResources();
         mPopupMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2,
@@ -87,7 +98,8 @@
 
         mAnchorView = anchorView;
 
-        menu.addMenuPresenter(this);
+        // Present the menu using our context, not the menu builder's context.
+        menu.addMenuPresenter(this, context);
     }
 
     public void setAnchorView(View anchor) {
@@ -98,18 +110,24 @@
         mForceShowIcon = forceShow;
     }
 
+    public void setGravity(int gravity) {
+        mDropDownGravity = gravity;
+    }
+
     public void show() {
         if (!tryShow()) {
             throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
         }
     }
 
+    public ListPopupWindow getPopup() {
+        return mPopup;
+    }
+
     public boolean tryShow() {
-        mPopup = new ListPopupWindow(mContext, null, R.attr.popupMenuStyle);
+        mPopup = new ListPopupWindow(mContext, null, mPopupStyleAttr);
         mPopup.setOnDismissListener(this);
         mPopup.setOnItemClickListener(this);
-
-        mAdapter = new MenuAdapter(mMenu);
         mPopup.setAdapter(mAdapter);
         mPopup.setModal(true);
 
@@ -117,15 +135,19 @@
         if (anchor != null) {
             final boolean addGlobalListener = mTreeObserver == null;
             mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
-            if (addGlobalListener) {
-                mTreeObserver.addOnGlobalLayoutListener(this);
-            }
+            if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this);
             mPopup.setAnchorView(anchor);
+            mPopup.setDropDownGravity(mDropDownGravity);
         } else {
             return false;
         }
 
-        mPopup.setContentWidth(Math.min(measureContentWidth(mAdapter), mPopupMaxWidth));
+        if (!mHasContentWidth) {
+            mContentWidth = measureContentWidth();
+            mHasContentWidth = true;
+        }
+
+        mPopup.setContentWidth(mContentWidth);
         mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
         mPopup.show();
         mPopup.getListView().setOnKeyListener(this);
@@ -142,9 +164,7 @@
         mPopup = null;
         mMenu.close();
         if (mTreeObserver != null) {
-            if (!mTreeObserver.isAlive()) {
-                mTreeObserver = mAnchorView.getViewTreeObserver();
-            }
+            if (!mTreeObserver.isAlive()) mTreeObserver = mAnchorView.getViewTreeObserver();
             mTreeObserver.removeGlobalOnLayoutListener(this);
             mTreeObserver = null;
         }
@@ -168,15 +188,15 @@
         return false;
     }
 
-    private int measureContentWidth(ListAdapter adapter) {
+    private int measureContentWidth() {
         // Menus don't tend to be long, so this is more sane than it looks.
-        int width = 0;
+        int maxWidth = 0;
         View itemView = null;
         int itemType = 0;
-        final int widthMeasureSpec =
-                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
-        final int heightMeasureSpec =
-                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+        final ListAdapter adapter = mAdapter;
+        final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
         final int count = adapter.getCount();
         for (int i = 0; i < count; i++) {
             final int positionType = adapter.getItemViewType(i);
@@ -184,14 +204,23 @@
                 itemType = positionType;
                 itemView = null;
             }
+
             if (mMeasureParent == null) {
                 mMeasureParent = new FrameLayout(mContext);
             }
+
             itemView = adapter.getView(i, itemView, mMeasureParent);
             itemView.measure(widthMeasureSpec, heightMeasureSpec);
-            width = Math.max(width, itemView.getMeasuredWidth());
+
+            final int itemWidth = itemView.getMeasuredWidth();
+            if (itemWidth >= mPopupMaxWidth) {
+                return mPopupMaxWidth;
+            } else if (itemWidth > maxWidth) {
+                maxWidth = itemWidth;
+            }
         }
-        return width;
+
+        return maxWidth;
     }
 
     @Override
@@ -219,6 +248,8 @@
 
     @Override
     public void updateMenuView(boolean cleared) {
+        mHasContentWidth = false;
+
         if (mAdapter != null) {
             mAdapter.notifyDataSetChanged();
         }
@@ -232,7 +263,7 @@
     @Override
     public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
         if (subMenu.hasVisibleItems()) {
-            MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView, false);
+            MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView);
             subPopup.setCallback(mPresenterCallback);
 
             boolean preserveIconSpacing = false;
@@ -259,9 +290,7 @@
     @Override
     public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
         // Only care about the (sub)menu we're presenting.
-        if (menu != mMenu) {
-            return;
-        }
+        if (menu != mMenu) return;
 
         dismiss();
         if (mPresenterCallback != null) {
@@ -297,7 +326,6 @@
     }
 
     private class MenuAdapter extends BaseAdapter {
-
         private MenuBuilder mAdapterMenu;
         private int mExpandedIndex = -1;
 
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuPresenter.java b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuPresenter.java
index f11a8e1..4e92426 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuPresenter.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuPresenter.java
@@ -34,30 +34,32 @@
      * @hide
      */
     public interface Callback {
-
         /**
          * Called when a menu is closing.
+         * @param menu
+         * @param allMenusAreClosing
          */
         public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
 
         /**
-         * Called when a submenu opens. Useful for notifying the application of menu state so that
-         * it does not attempt to hide the action bar while a submenu is open or similar.
+         * Called when a submenu opens. Useful for notifying the application
+         * of menu state so that it does not attempt to hide the action bar
+         * while a submenu is open or similar.
          *
          * @param subMenu Submenu currently being opened
-         * @return true if the Callback will handle presenting the submenu, false if the presenter
-         *         should attempt to do so.
+         * @return true if the Callback will handle presenting the submenu, false if
+         *         the presenter should attempt to do so.
          */
         public boolean onOpenSubMenu(MenuBuilder subMenu);
     }
 
     /**
-     * Initialize this presenter for the given context and menu. This method is called by
-     * MenuBuilder when a presenter is added. See
-     * {@link MenuBuilder#addMenuPresenter(MenuPresenter)}
+     * Initialize this presenter for the given context and menu.
+     * This method is called by MenuBuilder when a presenter is
+     * added. See {@link MenuBuilder#addMenuPresenter(MenuPresenter)}
      *
      * @param context Context for this presenter; used for view creation and resource management
-     * @param menu    Menu to host
+     * @param menu Menu to host
      */
     public void initForMenu(Context context, MenuBuilder menu);
 
@@ -71,24 +73,24 @@
     public MenuView getMenuView(ViewGroup root);
 
     /**
-     * Update the menu UI in response to a change. Called by MenuBuilder during the normal course of
-     * operation.
+     * Update the menu UI in response to a change. Called by
+     * MenuBuilder during the normal course of operation.
      *
      * @param cleared true if the menu was entirely cleared
      */
     public void updateMenuView(boolean cleared);
 
     /**
-     * Set a callback object that will be notified of menu events related to this specific
-     * presentation.
-     *
+     * Set a callback object that will be notified of menu events
+     * related to this specific presentation.
      * @param cb Callback that will be notified of future events
      */
     public void setCallback(Callback cb);
 
     /**
-     * Called by Menu implementations to indicate that a submenu item has been selected. An active
-     * Callback should be notified, and if applicable the presenter should present the submenu.
+     * Called by Menu implementations to indicate that a submenu item
+     * has been selected. An active Callback should be notified, and
+     * if applicable the presenter should present the submenu.
      *
      * @param subMenu SubMenu being opened
      * @return true if the the event was handled, false otherwise.
@@ -96,18 +98,17 @@
     public boolean onSubMenuSelected(SubMenuBuilder subMenu);
 
     /**
-     * Called by Menu implementations to indicate that a menu or submenu is closing. Presenter
-     * implementations should close the representation of the menu indicated as necessary and notify
-     * a registered callback.
+     * Called by Menu implementations to indicate that a menu or submenu is
+     * closing. Presenter implementations should close the representation
+     * of the menu indicated as necessary and notify a registered callback.
      *
-     * @param menu               Menu or submenu that is closing.
+     * @param menu Menu or submenu that is closing.
      * @param allMenusAreClosing True if all associated menus are closing.
      */
     public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
 
     /**
      * Called by Menu implementations to flag items that will be shown as actions.
-     *
      * @return true if this presenter changed the action status of any items.
      */
     public boolean flagActionItems();
@@ -132,23 +133,20 @@
 
     /**
      * Returns an ID for determining how to save/restore instance state.
-     *
      * @return a valid ID value.
      */
     public int getId();
 
     /**
-     * Returns a Parcelable describing the current state of the presenter. It will be passed to the
-     * {@link #onRestoreInstanceState(Parcelable)} method of the presenter sharing the same ID
-     * later.
-     *
+     * Returns a Parcelable describing the current state of the presenter.
+     * It will be passed to the {@link #onRestoreInstanceState(Parcelable)}
+     * method of the presenter sharing the same ID later.
      * @return The saved instance state
      */
     public Parcelable onSaveInstanceState();
 
     /**
      * Supplies the previously saved instance state to be restored.
-     *
      * @param state The previously saved instance state
      */
     public void onRestoreInstanceState(Parcelable state);
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuView.java b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuView.java
index 6a0686a..7a3aabc 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/MenuView.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/MenuView.java
@@ -19,15 +19,15 @@
 import android.graphics.drawable.Drawable;
 
 /**
- * Minimal interface for a menu view.  {@link #initialize(MenuBuilder)} must be called for the menu
- * to be functional.
+ * Minimal interface for a menu view.  {@link #initialize(MenuBuilder)} must be called for the
+ * menu to be functional.
  *
  * @hide
  */
 public interface MenuView {
-
     /**
-     * Initializes the menu to the given menu. This should be called after the view is inflated.
+     * Initializes the menu to the given menu. This should be called after the
+     * view is inflated.
      *
      * @param menu The menu that this MenuView should display.
      */
@@ -35,43 +35,39 @@
 
     /**
      * Returns the default animations to be used for this menu when entering/exiting.
-     *
      * @return A resource ID for the default animations to be used for this menu.
      */
     public int getWindowAnimations();
 
     /**
-     * Minimal interface for a menu item view.  {@link #initialize(MenuItemImpl, int)} must be
-     * called for the item to be functional.
+     * Minimal interface for a menu item view.  {@link #initialize(MenuItemImpl, int)} must be called
+     * for the item to be functional.
      */
     public interface ItemView {
-
         /**
          * Initializes with the provided MenuItemData.  This should be called after the view is
          * inflated.
-         *
          * @param itemData The item that this ItemView should display.
-         * @param menuType The type of this menu, one of TYPE_ICON, TYPE_EXPANDED or TYPE_DIALOG.
+         * @param menuType The type of this menu, one of
+         *            {@link MenuBuilder#TYPE_ICON}, {@link MenuBuilder#TYPE_EXPANDED},
+         *            {@link MenuBuilder#TYPE_DIALOG}).
          */
         public void initialize(MenuItemImpl itemData, int menuType);
 
         /**
          * Gets the item data that this view is displaying.
-         *
          * @return the item data, or null if there is not one
          */
         public MenuItemImpl getItemData();
 
         /**
          * Sets the title of the item view.
-         *
          * @param title The title to set.
          */
         public void setTitle(CharSequence title);
 
         /**
          * Sets the enabled state of the item view.
-         *
          * @param enabled Whether the item view should be enabled.
          */
         public void setEnabled(boolean enabled);
@@ -79,40 +75,38 @@
         /**
          * Displays the checkbox for the item view.  This does not ensure the item view will be
          * checked, for that use {@link #setChecked}.
-         *
          * @param checkable Whether to display the checkbox or to hide it
          */
         public void setCheckable(boolean checkable);
 
         /**
-         * Checks the checkbox for the item view.  If the checkbox is hidden, it will NOT be made
-         * visible, call {@link #setCheckable(boolean)} for that.
-         *
+         * Checks the checkbox for the item view.  If the checkbox is hidden, it will NOT be
+         * made visible, call {@link #setCheckable(boolean)} for that.
          * @param checked Whether the checkbox should be checked
          */
         public void setChecked(boolean checked);
 
         /**
          * Sets the shortcut for the item.
-         *
-         * @param showShortcut Whether a shortcut should be shown(if false, the value of shortcutKey
-         *                     should be ignored).
-         * @param shortcutKey  The shortcut key that should be shown on the ItemView.
+         * @param showShortcut Whether a shortcut should be shown(if false, the value of
+         * shortcutKey should be ignored).
+         * @param shortcutKey The shortcut key that should be shown on the ItemView.
          */
         public void setShortcut(boolean showShortcut, char shortcutKey);
 
         /**
          * Set the icon of this item view.
-         *
          * @param icon The icon of this item. null to hide the icon.
          */
         public void setIcon(Drawable icon);
 
         /**
-         * Whether this item view prefers displaying the condensed title rather than the normal
-         * title. If a condensed title is not available, the normal title will be used.
+         * Whether this item view prefers displaying the condensed title rather
+         * than the normal title. If a condensed title is not available, the
+         * normal title will be used.
          *
-         * @return Whether this item view prefers displaying the condensed title.
+         * @return Whether this item view prefers displaying the condensed
+         *         title.
          */
         public boolean prefersCondensedTitle();
 
diff --git a/v7/appcompat/src/android/support/v7/internal/view/menu/SubMenuBuilder.java b/v7/appcompat/src/android/support/v7/internal/view/menu/SubMenuBuilder.java
index 24a7ddd..90bf77f 100644
--- a/v7/appcompat/src/android/support/v7/internal/view/menu/SubMenuBuilder.java
+++ b/v7/appcompat/src/android/support/v7/internal/view/menu/SubMenuBuilder.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+import android.support.v4.content.ContextCompat;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.SubMenu;
@@ -30,7 +31,6 @@
  * @hide
  */
 public class SubMenuBuilder extends MenuBuilder implements SubMenu {
-
     private MenuBuilder mParentMenu;
     private MenuItemImpl mItem;
 
@@ -74,12 +74,13 @@
         mParentMenu.setCallback(callback);
     }
 
+    @Override
     public MenuBuilder getRootMenu() {
         return mParentMenu;
     }
 
     @Override
-    public boolean dispatchMenuItemSelected(MenuBuilder menu, MenuItem item) {
+    boolean dispatchMenuItemSelected(MenuBuilder menu, MenuItem item) {
         return super.dispatchMenuItemSelected(menu, item) ||
                 mParentMenu.dispatchMenuItemSelected(menu, item);
     }
@@ -100,7 +101,7 @@
     }
 
     public SubMenu setHeaderIcon(int iconRes) {
-        super.setHeaderIconInt(getContext().getResources().getDrawable(iconRes));
+        super.setHeaderIconInt(ContextCompat.getDrawable(getContext(), iconRes));
         return this;
     }
 
@@ -120,10 +121,6 @@
     }
 
     @Override
-    public void clearHeader() {
-    }
-
-    @Override
     public boolean expandItemActionView(MenuItemImpl item) {
         return mParentMenu.expandItemActionView(item);
     }
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/AbsActionBarView.java b/v7/appcompat/src/android/support/v7/internal/widget/AbsActionBarView.java
index 95b9353..b8e26b7 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/AbsActionBarView.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/AbsActionBarView.java
@@ -19,41 +19,58 @@
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.os.Build;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewPropertyAnimatorCompat;
+import android.support.v4.view.ViewPropertyAnimatorListener;
 import android.support.v7.appcompat.R;
-import android.support.v7.internal.view.menu.ActionMenuPresenter;
-import android.support.v7.internal.view.menu.ActionMenuView;
+import android.support.v7.internal.view.AnimatorSetCompat;
+import android.support.v7.widget.ActionMenuPresenter;
+import android.support.v7.widget.ActionMenuView;
 import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
 
 abstract class AbsActionBarView extends ViewGroup {
-
-    protected ActionMenuView mMenuView;
-
-    protected ActionMenuPresenter mActionMenuPresenter;
-
-    protected ActionBarContainer mSplitView;
-
-    protected boolean mSplitActionBar;
-
-    protected boolean mSplitWhenNarrow;
-
-    protected int mContentHeight;
+    private static final Interpolator sAlphaInterpolator = new DecelerateInterpolator();
 
     private static final int FADE_DURATION = 200;
 
+    protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener();
+
+    /** Context against which to inflate popup menus. */
+    protected final Context mPopupContext;
+
+    protected ActionMenuView mMenuView;
+    protected ActionMenuPresenter mActionMenuPresenter;
+    protected ViewGroup mSplitView;
+    protected boolean mSplitActionBar;
+    protected boolean mSplitWhenNarrow;
+    protected int mContentHeight;
+
+    protected ViewPropertyAnimatorCompat mVisibilityAnim;
+
     AbsActionBarView(Context context) {
-        super(context);
+        this(context, null);
     }
 
     AbsActionBarView(Context context, AttributeSet attrs) {
-        super(context, attrs);
+        this(context, attrs, 0);
     }
 
     AbsActionBarView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+
+        final TypedValue tv = new TypedValue();
+        if (context.getTheme().resolveAttribute(R.attr.actionBarPopupTheme, tv, true)
+                && tv.resourceId != 0) {
+            mPopupContext = new ContextThemeWrapper(context, tv.resourceId);
+        } else {
+            mPopupContext = context;
+        }
     }
 
     @Override
@@ -68,10 +85,7 @@
                 R.attr.actionBarStyle, 0);
         setContentHeight(a.getLayoutDimension(R.styleable.ActionBar_height, 0));
         a.recycle();
-        if (mSplitWhenNarrow) {
-            setSplitActionBar(getContext().getResources().getBoolean(
-                    R.bool.abc_split_action_bar_is_narrow));
-        }
+
         if (mActionMenuPresenter != null) {
             mActionMenuPresenter.onConfigurationChanged(newConfig);
         }
@@ -79,16 +93,14 @@
 
     /**
      * Sets whether the bar should be split right now, no questions asked.
-     *
      * @param split true if the bar should split
      */
-    public void setSplitActionBar(boolean split) {
+    public void setSplitToolbar(boolean split) {
         mSplitActionBar = split;
     }
 
     /**
      * Sets whether the bar should split if we enter a narrow screen configuration.
-     *
      * @param splitWhenNarrow true if the bar should check to split after a config change
      */
     public void setSplitWhenNarrow(boolean splitWhenNarrow) {
@@ -104,7 +116,7 @@
         return mContentHeight;
     }
 
-    public void setSplitView(ActionBarContainer splitView) {
+    public void setSplitView(ViewGroup splitView) {
         mSplitView = splitView;
     }
 
@@ -112,30 +124,52 @@
      * @return Current visibility or if animating, the visibility being animated to.
      */
     public int getAnimatedVisibility() {
+        if (mVisibilityAnim != null) {
+            return mVisAnimListener.mFinalVisibility;
+        }
         return getVisibility();
     }
 
     public void animateToVisibility(int visibility) {
-        clearAnimation();
-
-        if (visibility != getVisibility()) {
-            Animation anim = AnimationUtils.loadAnimation(getContext(),
-                    visibility == View.VISIBLE ? R.anim.abc_fade_in : R.anim.abc_fade_out);
-
-            startAnimation(anim);
-            setVisibility(visibility);
-
-            if (mSplitView != null && mMenuView != null) {
-                mMenuView.startAnimation(anim);
-                mMenuView.setVisibility(visibility);
-            }
+        if (mVisibilityAnim != null) {
+            mVisibilityAnim.cancel();
         }
-    }
-
-    @Override
-    public void setVisibility(int visibility) {
-        if (visibility != getVisibility()) {
-            super.setVisibility(visibility);
+        if (visibility == VISIBLE) {
+            if (getVisibility() != VISIBLE) {
+                ViewCompat.setAlpha(this, 0f);
+                if (mSplitView != null && mMenuView != null) {
+                    ViewCompat.setAlpha(mMenuView, 0f);
+                }
+            }
+            ViewPropertyAnimatorCompat anim = ViewCompat.animate(this).alpha(1f);
+            anim.setDuration(FADE_DURATION);
+            anim.setInterpolator(sAlphaInterpolator);
+            if (mSplitView != null && mMenuView != null) {
+                AnimatorSetCompat set = new AnimatorSetCompat();
+                ViewPropertyAnimatorCompat splitAnim = ViewCompat.animate(mMenuView).alpha(1f);
+                splitAnim.setDuration(FADE_DURATION);
+                set.setListener(mVisAnimListener.withFinalVisibility(anim, visibility));
+                set.play(anim).play(splitAnim);
+                set.start();
+            } else {
+                anim.setListener(mVisAnimListener.withFinalVisibility(anim, visibility));
+                anim.start();
+            }
+        } else {
+            ViewPropertyAnimatorCompat anim = ViewCompat.animate(this).alpha(0f);
+            anim.setDuration(FADE_DURATION);
+            anim.setInterpolator(sAlphaInterpolator);
+            if (mSplitView != null && mMenuView != null) {
+                AnimatorSetCompat set = new AnimatorSetCompat();
+                ViewPropertyAnimatorCompat splitAnim = ViewCompat.animate(mMenuView).alpha(0f);
+                splitAnim.setDuration(FADE_DURATION);
+                set.setListener(mVisAnimListener.withFinalVisibility(anim, visibility));
+                set.play(anim).play(splitAnim);
+                set.start();
+            } else {
+                anim.setListener(mVisAnimListener.withFinalVisibility(anim, visibility));
+                anim.start();
+            }
         }
     }
 
@@ -168,10 +202,21 @@
         return false;
     }
 
+    public boolean isOverflowMenuShowPending() {
+        if (mActionMenuPresenter != null) {
+            return mActionMenuPresenter.isOverflowMenuShowPending();
+        }
+        return false;
+    }
+
     public boolean isOverflowReserved() {
         return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved();
     }
 
+    public boolean canShowOverflowMenu() {
+        return isOverflowReserved() && getVisibility() == VISIBLE;
+    }
+
     public void dismissPopupMenus() {
         if (mActionMenuPresenter != null) {
             mActionMenuPresenter.dismissPopupMenus();
@@ -189,24 +234,55 @@
         return Math.max(0, availableWidth);
     }
 
-    protected int positionChild(View child, int x, int y, int contentHeight) {
+    static protected int next(int x, int val, boolean isRtl) {
+        return isRtl ? x - val : x + val;
+    }
+
+    protected int positionChild(View child, int x, int y, int contentHeight, boolean reverse) {
         int childWidth = child.getMeasuredWidth();
         int childHeight = child.getMeasuredHeight();
         int childTop = y + (contentHeight - childHeight) / 2;
 
-        child.layout(x, childTop, x + childWidth, childTop + childHeight);
+        if (reverse) {
+            child.layout(x - childWidth, childTop, x, childTop + childHeight);
+        } else {
+            child.layout(x, childTop, x + childWidth, childTop + childHeight);
+        }
 
-        return childWidth;
+        return  (reverse ? -childWidth : childWidth);
     }
 
-    protected int positionChildInverse(View child, int x, int y, int contentHeight) {
-        int childWidth = child.getMeasuredWidth();
-        int childHeight = child.getMeasuredHeight();
-        int childTop = y + (contentHeight - childHeight) / 2;
+    protected class VisibilityAnimListener implements ViewPropertyAnimatorListener {
+        private boolean mCanceled = false;
+        int mFinalVisibility;
 
-        child.layout(x - childWidth, childTop, x, childTop + childHeight);
+        public VisibilityAnimListener withFinalVisibility(ViewPropertyAnimatorCompat animation,
+                int visibility) {
+            mVisibilityAnim = animation;
+            mFinalVisibility = visibility;
+            return this;
+        }
 
-        return childWidth;
+        @Override
+        public void onAnimationStart(View view) {
+            setVisibility(VISIBLE);
+            mCanceled = false;
+        }
+
+        @Override
+        public void onAnimationEnd(View view) {
+            if (mCanceled) return;
+
+            mVisibilityAnim = null;
+            setVisibility(mFinalVisibility);
+            if (mSplitView != null && mMenuView != null) {
+                mMenuView.setVisibility(mFinalVisibility);
+            }
+        }
+
+        @Override
+        public void onAnimationCancel(View view) {
+            mCanceled = true;
+        }
     }
-
 }
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/AbsSpinnerCompat.java b/v7/appcompat/src/android/support/v7/internal/widget/AbsSpinnerCompat.java
new file mode 100644
index 0000000..0ec8e7a
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/AbsSpinnerCompat.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2006 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.internal.widget;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.SpinnerAdapter;
+
+/**
+ * An abstract base class for spinner widgets. SDK users will probably not
+ * need to use this class.
+ */
+abstract class AbsSpinnerCompat extends AdapterViewCompat<SpinnerAdapter> {
+    SpinnerAdapter mAdapter;
+
+    int mHeightMeasureSpec;
+    int mWidthMeasureSpec;
+
+    int mSelectionLeftPadding = 0;
+    int mSelectionTopPadding = 0;
+    int mSelectionRightPadding = 0;
+    int mSelectionBottomPadding = 0;
+    final Rect mSpinnerPadding = new Rect();
+
+    final RecycleBin mRecycler = new RecycleBin();
+    private DataSetObserver mDataSetObserver;
+
+    /** Temporary frame to hold a child View's frame rectangle */
+    private Rect mTouchFrame;
+
+    AbsSpinnerCompat(Context context) {
+        super(context);
+        initAbsSpinner();
+    }
+
+    AbsSpinnerCompat(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    AbsSpinnerCompat(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        initAbsSpinner();
+    }
+
+    /**
+     * Common code for different constructor flavors
+     */
+    private void initAbsSpinner() {
+        setFocusable(true);
+        setWillNotDraw(false);
+    }
+
+    /**
+     * The Adapter is used to provide the data which backs this Spinner.
+     * It also provides methods to transform spinner items based on their position
+     * relative to the selected item.
+     * @param adapter The SpinnerAdapter to use for this Spinner
+     */
+    @Override
+    public void setAdapter(SpinnerAdapter adapter) {
+        if (null != mAdapter) {
+            mAdapter.unregisterDataSetObserver(mDataSetObserver);
+            resetList();
+        }
+
+        mAdapter = adapter;
+
+        mOldSelectedPosition = INVALID_POSITION;
+        mOldSelectedRowId = INVALID_ROW_ID;
+
+        if (mAdapter != null) {
+            mOldItemCount = mItemCount;
+            mItemCount = mAdapter.getCount();
+            checkFocus();
+
+            mDataSetObserver = new AdapterDataSetObserver();
+            mAdapter.registerDataSetObserver(mDataSetObserver);
+
+            int position = mItemCount > 0 ? 0 : INVALID_POSITION;
+
+            setSelectedPositionInt(position);
+            setNextSelectedPositionInt(position);
+
+            if (mItemCount == 0) {
+                // Nothing selected
+                checkSelectionChanged();
+            }
+
+        } else {
+            checkFocus();
+            resetList();
+            // Nothing selected
+            checkSelectionChanged();
+        }
+
+        requestLayout();
+    }
+
+    /**
+     * Clear out all children from the list
+     */
+    void resetList() {
+        mDataChanged = false;
+        mNeedSync = false;
+
+        removeAllViewsInLayout();
+        mOldSelectedPosition = INVALID_POSITION;
+        mOldSelectedRowId = INVALID_ROW_ID;
+
+        setSelectedPositionInt(INVALID_POSITION);
+        setNextSelectedPositionInt(INVALID_POSITION);
+        invalidate();
+    }
+
+    /**
+     * @see android.view.View#measure(int, int)
+     *
+     * Figure out the dimensions of this Spinner. The width comes from
+     * the widthMeasureSpec as Spinnners can't have their width set to
+     * UNSPECIFIED. The height is based on the height of the selected item
+     * plus padding.
+     */
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        int widthSize;
+        int heightSize;
+
+        final int paddingLeft = getPaddingLeft();
+        final int paddingTop = getPaddingTop();
+        final int paddingRight = getPaddingRight();
+        final int paddingBottom = getPaddingBottom();
+
+        mSpinnerPadding.left = paddingLeft > mSelectionLeftPadding ? paddingLeft
+                : mSelectionLeftPadding;
+        mSpinnerPadding.top = paddingTop > mSelectionTopPadding ? paddingTop
+                : mSelectionTopPadding;
+        mSpinnerPadding.right = paddingRight > mSelectionRightPadding ? paddingRight
+                : mSelectionRightPadding;
+        mSpinnerPadding.bottom = paddingBottom > mSelectionBottomPadding ? paddingBottom
+                : mSelectionBottomPadding;
+
+        if (mDataChanged) {
+            handleDataChanged();
+        }
+
+        int preferredHeight = 0;
+        int preferredWidth = 0;
+        boolean needsMeasuring = true;
+
+        int selectedPosition = getSelectedItemPosition();
+        if (selectedPosition >= 0 && mAdapter != null && selectedPosition < mAdapter.getCount()) {
+            // Try looking in the recycler. (Maybe we were measured once already)
+            View view = mRecycler.get(selectedPosition);
+            if (view == null) {
+                // Make a new one
+                view = mAdapter.getView(selectedPosition, null, this);
+            }
+
+            if (view != null) {
+                // Put in recycler for re-measuring and/or layout
+                mRecycler.put(selectedPosition, view);
+
+                if (view.getLayoutParams() == null) {
+                    mBlockLayoutRequests = true;
+                    view.setLayoutParams(generateDefaultLayoutParams());
+                    mBlockLayoutRequests = false;
+                }
+                measureChild(view, widthMeasureSpec, heightMeasureSpec);
+
+                preferredHeight = getChildHeight(view) + mSpinnerPadding.top + mSpinnerPadding.bottom;
+                preferredWidth = getChildWidth(view) + mSpinnerPadding.left + mSpinnerPadding.right;
+
+                needsMeasuring = false;
+            }
+        }
+
+        if (needsMeasuring) {
+            // No views -- just use padding
+            preferredHeight = mSpinnerPadding.top + mSpinnerPadding.bottom;
+            if (widthMode == MeasureSpec.UNSPECIFIED) {
+                preferredWidth = mSpinnerPadding.left + mSpinnerPadding.right;
+            }
+        }
+
+        preferredHeight = Math.max(preferredHeight, getSuggestedMinimumHeight());
+        preferredWidth = Math.max(preferredWidth, getSuggestedMinimumWidth());
+
+        heightSize = ViewCompat.resolveSizeAndState(preferredHeight, heightMeasureSpec, 0);
+        widthSize = ViewCompat.resolveSizeAndState(preferredWidth, widthMeasureSpec, 0);
+
+        setMeasuredDimension(widthSize, heightSize);
+        mHeightMeasureSpec = heightMeasureSpec;
+        mWidthMeasureSpec = widthMeasureSpec;
+    }
+
+    int getChildHeight(View child) {
+        return child.getMeasuredHeight();
+    }
+
+    int getChildWidth(View child) {
+        return child.getMeasuredWidth();
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+        return new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+    }
+
+    void recycleAllViews() {
+        final int childCount = getChildCount();
+        final AbsSpinnerCompat.RecycleBin recycleBin = mRecycler;
+        final int position = mFirstPosition;
+
+        // All views go in recycler
+        for (int i = 0; i < childCount; i++) {
+            View v = getChildAt(i);
+            int index = position + i;
+            recycleBin.put(index, v);
+        }
+    }
+
+    /**
+     * Jump directly to a specific item in the adapter data.
+     */
+    public void setSelection(int position, boolean animate) {
+        // Animate only if requested position is already on screen somewhere
+        boolean shouldAnimate = animate && mFirstPosition <= position &&
+                position <= mFirstPosition + getChildCount() - 1;
+        setSelectionInt(position, shouldAnimate);
+    }
+
+    @Override
+    public void setSelection(int position) {
+        setNextSelectedPositionInt(position);
+        requestLayout();
+        invalidate();
+    }
+
+
+    /**
+     * Makes the item at the supplied position selected.
+     *
+     * @param position Position to select
+     * @param animate Should the transition be animated
+     *
+     */
+    void setSelectionInt(int position, boolean animate) {
+        if (position != mOldSelectedPosition) {
+            mBlockLayoutRequests = true;
+            int delta  = position - mSelectedPosition;
+            setNextSelectedPositionInt(position);
+            layout(delta, animate);
+            mBlockLayoutRequests = false;
+        }
+    }
+
+    abstract void layout(int delta, boolean animate);
+
+    @Override
+    public View getSelectedView() {
+        if (mItemCount > 0 && mSelectedPosition >= 0) {
+            return getChildAt(mSelectedPosition - mFirstPosition);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Override to prevent spamming ourselves with layout requests
+     * as we place views
+     *
+     * @see android.view.View#requestLayout()
+     */
+    @Override
+    public void requestLayout() {
+        if (!mBlockLayoutRequests) {
+            super.requestLayout();
+        }
+    }
+
+    @Override
+    public SpinnerAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    @Override
+    public int getCount() {
+        return mItemCount;
+    }
+
+    /**
+     * Maps a point to a position in the list.
+     *
+     * @param x X in local coordinate
+     * @param y Y in local coordinate
+     * @return The position of the item which contains the specified point, or
+     *         {@link #INVALID_POSITION} if the point does not intersect an item.
+     */
+    public int pointToPosition(int x, int y) {
+        Rect frame = mTouchFrame;
+        if (frame == null) {
+            mTouchFrame = new Rect();
+            frame = mTouchFrame;
+        }
+
+        final int count = getChildCount();
+        for (int i = count - 1; i >= 0; i--) {
+            View child = getChildAt(i);
+            if (child.getVisibility() == View.VISIBLE) {
+                child.getHitRect(frame);
+                if (frame.contains(x, y)) {
+                    return mFirstPosition + i;
+                }
+            }
+        }
+        return INVALID_POSITION;
+    }
+
+    static class SavedState extends BaseSavedState {
+        long selectedId;
+        int position;
+
+        /**
+         * Constructor called from {@link AbsSpinnerCompat#onSaveInstanceState()}
+         */
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        /**
+         * Constructor called from {@link #CREATOR}
+         */
+        SavedState(Parcel in) {
+            super(in);
+            selectedId = in.readLong();
+            position = in.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeLong(selectedId);
+            out.writeInt(position);
+        }
+
+        @Override
+        public String toString() {
+            return "AbsSpinner.SavedState{"
+                    + Integer.toHexString(System.identityHashCode(this))
+                    + " selectedId=" + selectedId
+                    + " position=" + position + "}";
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+        SavedState ss = new SavedState(superState);
+        ss.selectedId = getSelectedItemId();
+        if (ss.selectedId >= 0) {
+            ss.position = getSelectedItemPosition();
+        } else {
+            ss.position = INVALID_POSITION;
+        }
+        return ss;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+
+        super.onRestoreInstanceState(ss.getSuperState());
+
+        if (ss.selectedId >= 0) {
+            mDataChanged = true;
+            mNeedSync = true;
+            mSyncRowId = ss.selectedId;
+            mSyncPosition = ss.position;
+            mSyncMode = SYNC_SELECTED_POSITION;
+            requestLayout();
+        }
+    }
+
+    class RecycleBin {
+        private final SparseArray<View> mScrapHeap = new SparseArray<View>();
+
+        public void put(int position, View v) {
+            mScrapHeap.put(position, v);
+        }
+
+        View get(int position) {
+            // System.out.print("Looking for " + position);
+            View result = mScrapHeap.get(position);
+            if (result != null) {
+                // System.out.println(" HIT");
+                mScrapHeap.delete(position);
+            } else {
+                // System.out.println(" MISS");
+            }
+            return result;
+        }
+
+        void clear() {
+            final SparseArray<View> scrapHeap = mScrapHeap;
+            final int count = scrapHeap.size();
+            for (int i = 0; i < count; i++) {
+                final View view = scrapHeap.valueAt(i);
+                if (view != null) {
+                    removeDetachedView(view, true);
+                }
+            }
+            scrapHeap.clear();
+        }
+    }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/AbsSpinnerICS.java b/v7/appcompat/src/android/support/v7/internal/widget/AbsSpinnerICS.java
deleted file mode 100644
index 4bb4c0f..0000000
--- a/v7/appcompat/src/android/support/v7/internal/widget/AbsSpinnerICS.java
+++ /dev/null
@@ -1,453 +0,0 @@
-/*
- * Copyright (C) 2006 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.internal.widget;
-
-import android.content.Context;
-import android.database.DataSetObserver;
-import android.graphics.Rect;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.util.SparseArray;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.SpinnerAdapter;
-
-/**
- * An abstract base class for spinner widgets. SDK users will probably not
- * need to use this class.
- */
-abstract class AbsSpinnerICS extends AdapterViewICS<SpinnerAdapter> {
-    SpinnerAdapter mAdapter;
-
-    int mHeightMeasureSpec;
-    int mWidthMeasureSpec;
-    boolean mBlockLayoutRequests;
-
-    int mSelectionLeftPadding = 0;
-    int mSelectionTopPadding = 0;
-    int mSelectionRightPadding = 0;
-    int mSelectionBottomPadding = 0;
-    final Rect mSpinnerPadding = new Rect();
-
-    final RecycleBin mRecycler = new RecycleBin();
-    private DataSetObserver mDataSetObserver;
-
-    /** Temporary frame to hold a child View's frame rectangle */
-    private Rect mTouchFrame;
-
-    AbsSpinnerICS(Context context) {
-        super(context);
-        initAbsSpinner();
-    }
-
-    AbsSpinnerICS(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    AbsSpinnerICS(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-        initAbsSpinner();
-    }
-
-    /**
-     * Common code for different constructor flavors
-     */
-    private void initAbsSpinner() {
-        setFocusable(true);
-        setWillNotDraw(false);
-    }
-
-    /**
-     * The Adapter is used to provide the data which backs this Spinner.
-     * It also provides methods to transform spinner items based on their position
-     * relative to the selected item.
-     * @param adapter The SpinnerAdapter to use for this Spinner
-     */
-    @Override
-    public void setAdapter(SpinnerAdapter adapter) {
-        if (null != mAdapter) {
-            mAdapter.unregisterDataSetObserver(mDataSetObserver);
-            resetList();
-        }
-
-        mAdapter = adapter;
-
-        mOldSelectedPosition = INVALID_POSITION;
-        mOldSelectedRowId = INVALID_ROW_ID;
-
-        if (mAdapter != null) {
-            mOldItemCount = mItemCount;
-            mItemCount = mAdapter.getCount();
-            checkFocus();
-
-            mDataSetObserver = new AdapterDataSetObserver();
-            mAdapter.registerDataSetObserver(mDataSetObserver);
-
-            int position = mItemCount > 0 ? 0 : INVALID_POSITION;
-
-            setSelectedPositionInt(position);
-            setNextSelectedPositionInt(position);
-
-            if (mItemCount == 0) {
-                // Nothing selected
-                checkSelectionChanged();
-            }
-
-        } else {
-            checkFocus();
-            resetList();
-            // Nothing selected
-            checkSelectionChanged();
-        }
-
-        requestLayout();
-    }
-
-    /**
-     * Clear out all children from the list
-     */
-    void resetList() {
-        mDataChanged = false;
-        mNeedSync = false;
-
-        removeAllViewsInLayout();
-        mOldSelectedPosition = INVALID_POSITION;
-        mOldSelectedRowId = INVALID_ROW_ID;
-
-        setSelectedPositionInt(INVALID_POSITION);
-        setNextSelectedPositionInt(INVALID_POSITION);
-        invalidate();
-    }
-
-    /**
-     * @see android.view.View#measure(int, int)
-     *
-     * Figure out the dimensions of this Spinner. The width comes from
-     * the widthMeasureSpec as Spinnners can't have their width set to
-     * UNSPECIFIED. The height is based on the height of the selected item
-     * plus padding.
-     */
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
-        int widthSize;
-        int heightSize;
-
-        final int paddingLeft = getPaddingLeft();
-        final int paddingTop = getPaddingTop();
-        final int paddingRight = getPaddingRight();
-        final int paddingBottom = getPaddingBottom();
-
-        mSpinnerPadding.left = paddingLeft > mSelectionLeftPadding ? paddingLeft
-                : mSelectionLeftPadding;
-        mSpinnerPadding.top = paddingTop > mSelectionTopPadding ? paddingTop
-                : mSelectionTopPadding;
-        mSpinnerPadding.right = paddingRight > mSelectionRightPadding ? paddingRight
-                : mSelectionRightPadding;
-        mSpinnerPadding.bottom = paddingBottom > mSelectionBottomPadding ? paddingBottom
-                : mSelectionBottomPadding;
-
-        if (mDataChanged) {
-            handleDataChanged();
-        }
-
-        int preferredHeight = 0;
-        int preferredWidth = 0;
-        boolean needsMeasuring = true;
-
-        int selectedPosition = getSelectedItemPosition();
-        if (selectedPosition >= 0 && mAdapter != null && selectedPosition < mAdapter.getCount()) {
-            // Try looking in the recycler. (Maybe we were measured once already)
-            View view = mRecycler.get(selectedPosition);
-            if (view == null) {
-                // Make a new one
-                view = mAdapter.getView(selectedPosition, null, this);
-            }
-
-            if (view != null) {
-                // Put in recycler for re-measuring and/or layout
-                mRecycler.put(selectedPosition, view);
-            }
-
-            if (view != null) {
-                if (view.getLayoutParams() == null) {
-                    mBlockLayoutRequests = true;
-                    view.setLayoutParams(generateDefaultLayoutParams());
-                    mBlockLayoutRequests = false;
-                }
-                measureChild(view, widthMeasureSpec, heightMeasureSpec);
-
-                preferredHeight = getChildHeight(view) + mSpinnerPadding.top + mSpinnerPadding.bottom;
-                preferredWidth = getChildWidth(view) + mSpinnerPadding.left + mSpinnerPadding.right;
-
-                needsMeasuring = false;
-            }
-        }
-
-        if (needsMeasuring) {
-            // No views -- just use padding
-            preferredHeight = mSpinnerPadding.top + mSpinnerPadding.bottom;
-            if (widthMode == MeasureSpec.UNSPECIFIED) {
-                preferredWidth = mSpinnerPadding.left + mSpinnerPadding.right;
-            }
-        }
-
-        preferredHeight = Math.max(preferredHeight, getSuggestedMinimumHeight());
-        preferredWidth = Math.max(preferredWidth, getSuggestedMinimumWidth());
-
-        heightSize = resolveSize(preferredHeight, heightMeasureSpec);
-        widthSize = resolveSize(preferredWidth, widthMeasureSpec);
-
-        setMeasuredDimension(widthSize, heightSize);
-        mHeightMeasureSpec = heightMeasureSpec;
-        mWidthMeasureSpec = widthMeasureSpec;
-    }
-
-    int getChildHeight(View child) {
-        return child.getMeasuredHeight();
-    }
-
-    int getChildWidth(View child) {
-        return child.getMeasuredWidth();
-    }
-
-    @Override
-    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
-        return new ViewGroup.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT);
-    }
-
-    void recycleAllViews() {
-        final int childCount = getChildCount();
-        final AbsSpinnerICS.RecycleBin recycleBin = mRecycler;
-        final int position = mFirstPosition;
-
-        // All views go in recycler
-        for (int i = 0; i < childCount; i++) {
-            View v = getChildAt(i);
-            int index = position + i;
-            recycleBin.put(index, v);
-        }
-    }
-
-    /**
-     * Jump directly to a specific item in the adapter data.
-     */
-    public void setSelection(int position, boolean animate) {
-        // Animate only if requested position is already on screen somewhere
-        boolean shouldAnimate = animate && mFirstPosition <= position &&
-                position <= mFirstPosition + getChildCount() - 1;
-        setSelectionInt(position, shouldAnimate);
-    }
-
-    @Override
-    public void setSelection(int position) {
-        setNextSelectedPositionInt(position);
-        requestLayout();
-        invalidate();
-    }
-
-
-    /**
-     * Makes the item at the supplied position selected.
-     *
-     * @param position Position to select
-     * @param animate Should the transition be animated
-     *
-     */
-    void setSelectionInt(int position, boolean animate) {
-        if (position != mOldSelectedPosition) {
-            mBlockLayoutRequests = true;
-            int delta  = position - mSelectedPosition;
-            setNextSelectedPositionInt(position);
-            layout(delta, animate);
-            mBlockLayoutRequests = false;
-        }
-    }
-
-    abstract void layout(int delta, boolean animate);
-
-    @Override
-    public View getSelectedView() {
-        if (mItemCount > 0 && mSelectedPosition >= 0) {
-            return getChildAt(mSelectedPosition - mFirstPosition);
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Override to prevent spamming ourselves with layout requests
-     * as we place views
-     *
-     * @see android.view.View#requestLayout()
-     */
-    @Override
-    public void requestLayout() {
-        if (!mBlockLayoutRequests) {
-            super.requestLayout();
-        }
-    }
-
-    @Override
-    public SpinnerAdapter getAdapter() {
-        return mAdapter;
-    }
-
-    @Override
-    public int getCount() {
-        return mItemCount;
-    }
-
-    /**
-     * Maps a point to a position in the list.
-     *
-     * @param x X in local coordinate
-     * @param y Y in local coordinate
-     * @return The position of the item which contains the specified point, or
-     *         {@link #INVALID_POSITION} if the point does not intersect an item.
-     */
-    public int pointToPosition(int x, int y) {
-        Rect frame = mTouchFrame;
-        if (frame == null) {
-            mTouchFrame = new Rect();
-            frame = mTouchFrame;
-        }
-
-        final int count = getChildCount();
-        for (int i = count - 1; i >= 0; i--) {
-            View child = getChildAt(i);
-            if (child.getVisibility() == View.VISIBLE) {
-                child.getHitRect(frame);
-                if (frame.contains(x, y)) {
-                    return mFirstPosition + i;
-                }
-            }
-        }
-        return INVALID_POSITION;
-    }
-
-    static class SavedState extends BaseSavedState {
-        long selectedId;
-        int position;
-
-        /**
-         * Constructor called from {@link AbsSpinnerICS#onSaveInstanceState()}
-         */
-        SavedState(Parcelable superState) {
-            super(superState);
-        }
-
-        /**
-         * Constructor called from {@link #CREATOR}
-         */
-        private SavedState(Parcel in) {
-            super(in);
-            selectedId = in.readLong();
-            position = in.readInt();
-        }
-
-        @Override
-        public void writeToParcel(Parcel out, int flags) {
-            super.writeToParcel(out, flags);
-            out.writeLong(selectedId);
-            out.writeInt(position);
-        }
-
-        @Override
-        public String toString() {
-            return "AbsSpinner.SavedState{"
-                    + Integer.toHexString(System.identityHashCode(this))
-                    + " selectedId=" + selectedId
-                    + " position=" + position + "}";
-        }
-
-        public static final Parcelable.Creator<SavedState> CREATOR
-                = new Parcelable.Creator<SavedState>() {
-            public SavedState createFromParcel(Parcel in) {
-                return new SavedState(in);
-            }
-
-            public SavedState[] newArray(int size) {
-                return new SavedState[size];
-            }
-        };
-    }
-
-    @Override
-    public Parcelable onSaveInstanceState() {
-        Parcelable superState = super.onSaveInstanceState();
-        SavedState ss = new SavedState(superState);
-        ss.selectedId = getSelectedItemId();
-        if (ss.selectedId >= 0) {
-            ss.position = getSelectedItemPosition();
-        } else {
-            ss.position = INVALID_POSITION;
-        }
-        return ss;
-    }
-
-    @Override
-    public void onRestoreInstanceState(Parcelable state) {
-        SavedState ss = (SavedState) state;
-
-        super.onRestoreInstanceState(ss.getSuperState());
-
-        if (ss.selectedId >= 0) {
-            mDataChanged = true;
-            mNeedSync = true;
-            mSyncRowId = ss.selectedId;
-            mSyncPosition = ss.position;
-            mSyncMode = SYNC_SELECTED_POSITION;
-            requestLayout();
-        }
-    }
-
-    class RecycleBin {
-        private final SparseArray<View> mScrapHeap = new SparseArray<View>();
-
-        public void put(int position, View v) {
-            mScrapHeap.put(position, v);
-        }
-
-        View get(int position) {
-            // System.out.print("Looking for " + position);
-            View result = mScrapHeap.get(position);
-            if (result != null) {
-                // System.out.println(" HIT");
-                mScrapHeap.delete(position);
-            } else {
-                // System.out.println(" MISS");
-            }
-            return result;
-        }
-
-        void clear() {
-            final SparseArray<View> scrapHeap = mScrapHeap;
-            final int count = scrapHeap.size();
-            for (int i = 0; i < count; i++) {
-                final View view = scrapHeap.valueAt(i);
-                if (view != null) {
-                    removeDetachedView(view, true);
-                }
-            }
-            scrapHeap.clear();
-        }
-    }
-}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ActionBarBackgroundDrawable.java b/v7/appcompat/src/android/support/v7/internal/widget/ActionBarBackgroundDrawable.java
new file mode 100644
index 0000000..6248f79
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ActionBarBackgroundDrawable.java
@@ -0,0 +1,44 @@
+package android.support.v7.internal.widget;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.drawable.Drawable;
+
+class ActionBarBackgroundDrawable extends Drawable {
+
+    final ActionBarContainer mContainer;
+
+    public ActionBarBackgroundDrawable(ActionBarContainer container) {
+        mContainer = container;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mContainer.mIsSplit) {
+            if (mContainer.mSplitBackground != null) {
+                mContainer.mSplitBackground.draw(canvas);
+            }
+        } else {
+            if (mContainer.mBackground != null) {
+                mContainer.mBackground.draw(canvas);
+            }
+            if (mContainer.mStackedBackground != null && mContainer.mIsStacked) {
+                mContainer.mStackedBackground.draw(canvas);
+            }
+        }
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter cf) {
+    }
+
+    @Override
+    public int getOpacity() {
+        return 0;
+    }
+
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ActionBarBackgroundDrawableV21.java b/v7/appcompat/src/android/support/v7/internal/widget/ActionBarBackgroundDrawableV21.java
new file mode 100644
index 0000000..19cd5a1
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ActionBarBackgroundDrawableV21.java
@@ -0,0 +1,26 @@
+package android.support.v7.internal.widget;
+
+import android.graphics.Outline;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+
+class ActionBarBackgroundDrawableV21 extends ActionBarBackgroundDrawable {
+
+    public ActionBarBackgroundDrawableV21(ActionBarContainer container) {
+        super(container);
+    }
+
+    @Override
+    public void getOutline(@NonNull Outline outline) {
+        if (mContainer.mIsSplit) {
+            if (mContainer.mSplitBackground != null) {
+                mContainer.mSplitBackground.getOutline(outline);
+            }
+        } else {
+            // ignore the stacked background for shadow casting
+            if (mContainer.mBackground != null) {
+                mContainer.mBackground.getOutline(outline);
+            }
+        }
+    }
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ActionBarContainer.java b/v7/appcompat/src/android/support/v7/internal/widget/ActionBarContainer.java
index 230eae9..8f17da5 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/ActionBarContainer.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ActionBarContainer.java
@@ -18,13 +18,10 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
-import android.support.v7.app.ActionBar;
 import android.support.v7.appcompat.R;
+import android.support.v7.internal.VersionUtils;
 import android.support.v7.view.ActionMode;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
@@ -33,22 +30,21 @@
 import android.widget.FrameLayout;
 
 /**
- * This class acts as a container for the action bar view and action mode context views. It applies
- * special styles as needed to help handle animated transitions between them.
- *
+ * This class acts as a container for the action bar view and action mode context views.
+ * It applies special styles as needed to help handle animated transitions between them.
  * @hide
  */
 public class ActionBarContainer extends FrameLayout {
-
     private boolean mIsTransitioning;
     private View mTabContainer;
-    private ActionBarView mActionBarView;
+    private View mActionBarView;
 
-    private Drawable mBackground;
-    private Drawable mStackedBackground;
-    private Drawable mSplitBackground;
-    private boolean mIsSplit;
-    private boolean mIsStacked;
+    Drawable mBackground;
+    Drawable mStackedBackground;
+    Drawable mSplitBackground;
+    boolean mIsSplit;
+    boolean mIsStacked;
+    private int mHeight;
 
     public ActionBarContainer(Context context) {
         this(context, null);
@@ -57,18 +53,22 @@
     public ActionBarContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        setBackgroundDrawable(null);
+        // Set a transparent background so that we project appropriately.
+        final Drawable bg = VersionUtils.isAtLeastL()
+                ? new ActionBarBackgroundDrawableV21(this)
+                : new ActionBarBackgroundDrawable(this);
+        setBackgroundDrawable(bg);
 
         TypedArray a = context.obtainStyledAttributes(attrs,
                 R.styleable.ActionBar);
         mBackground = a.getDrawable(R.styleable.ActionBar_background);
         mStackedBackground = a.getDrawable(
                 R.styleable.ActionBar_backgroundStacked);
+        mHeight = a.getDimensionPixelSize(R.styleable.ActionBar_height, -1);
 
         if (getId() == R.id.split_action_bar) {
             mIsSplit = true;
-            mSplitBackground = a.getDrawable(
-                    R.styleable.ActionBar_backgroundSplit);
+            mSplitBackground = a.getDrawable(R.styleable.ActionBar_backgroundSplit);
         }
         a.recycle();
 
@@ -79,7 +79,7 @@
     @Override
     public void onFinishInflate() {
         super.onFinishInflate();
-        mActionBarView = (ActionBarView) findViewById(R.id.action_bar);
+        mActionBarView = findViewById(R.id.action_bar);
     }
 
     public void setPrimaryBackground(Drawable bg) {
@@ -95,7 +95,6 @@
                         mActionBarView.getRight(), mActionBarView.getBottom());
             }
         }
-
         setWillNotDraw(mIsSplit ? mSplitBackground == null :
                 mBackground == null && mStackedBackground == null);
         invalidate();
@@ -165,6 +164,21 @@
         }
     }
 
+    public void jumpDrawablesToCurrentState() {
+        if (Build.VERSION.SDK_INT >= 11) {
+            super.jumpDrawablesToCurrentState();
+            if (mBackground != null) {
+                mBackground.jumpToCurrentState();
+            }
+            if (mStackedBackground != null) {
+                mStackedBackground.jumpToCurrentState();
+            }
+            if (mSplitBackground != null) {
+                mSplitBackground.jumpToCurrentState();
+            }
+        }
+    }
+
     /**
      * Set the action bar into a "transitioning" state. While transitioning the bar will block focus
      * and touch from all of its descendants. This prevents the user from interacting with the bar
@@ -191,14 +205,6 @@
         return true;
     }
 
-    //@Override
-    public boolean onHoverEvent(MotionEvent ev) {
-        //super.onHoverEvent(ev);
-
-        // An action bar always eats hover events.
-        return true;
-    }
-
     public void setTabContainer(ScrollingTabContainerView tabView) {
         if (mTabContainer != null) {
             removeView(mTabContainer);
@@ -207,7 +213,7 @@
         if (tabView != null) {
             addView(tabView);
             final ViewGroup.LayoutParams lp = tabView.getLayoutParams();
-            lp.width = LayoutParams.FILL_PARENT;
+            lp.width = LayoutParams.MATCH_PARENT;
             lp.height = LayoutParams.WRAP_CONTENT;
             tabView.setAllowCollapse(false);
         }
@@ -217,42 +223,29 @@
         return mTabContainer;
     }
 
-    @Override
-    public void onDraw(Canvas canvas) {
-        if (getWidth() == 0 || getHeight() == 0) {
-            return;
-        }
-
-        if (mIsSplit) {
-            if (mSplitBackground != null) {
-                drawBackgroundDrawable(mSplitBackground, canvas);
-            }
-        } else {
-            if (mBackground != null) {
-                drawBackgroundDrawable(mBackground, canvas);
-            }
-            if (mStackedBackground != null && mIsStacked) {
-                drawBackgroundDrawable(mStackedBackground, canvas);
-            }
-        }
-    }
-
     //@Override
     public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
         // No starting an action mode for an action bar child! (Where would it go?)
         return null;
     }
 
+    private boolean isCollapsed(View view) {
+        return view == null || view.getVisibility() == GONE || view.getMeasuredHeight() == 0;
+    }
+
     @Override
     public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (mActionBarView == null &&
+                MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST && mHeight >= 0) {
+            heightMeasureSpec = MeasureSpec.makeMeasureSpec(
+                    Math.min(mHeight, MeasureSpec.getSize(heightMeasureSpec)), MeasureSpec.AT_MOST);
+        }
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
-        if (mActionBarView == null) {
-            return;
-        }
+        if (mActionBarView == null) return;
 
         final LayoutParams lp = (LayoutParams) mActionBarView.getLayoutParams();
-        final int actionBarViewHeight = mActionBarView.isCollapsed() ? 0 :
+        final int actionBarViewHeight = isCollapsed(mActionBarView) ? 0 :
                 mActionBarView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
 
         if (mTabContainer != null && mTabContainer.getVisibility() != GONE) {
@@ -270,30 +263,13 @@
     public void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
 
-        final boolean hasTabs = mTabContainer != null && mTabContainer.getVisibility() != GONE;
+        final View tabContainer = mTabContainer;
+        final boolean hasTabs = tabContainer != null && tabContainer.getVisibility() != GONE;
 
-        if (mTabContainer != null && mTabContainer.getVisibility() != GONE) {
+        if (tabContainer != null && tabContainer.getVisibility() != GONE) {
             final int containerHeight = getMeasuredHeight();
-            final int tabHeight = mTabContainer.getMeasuredHeight();
-
-            if ((mActionBarView.getDisplayOptions() & ActionBar.DISPLAY_SHOW_HOME) == 0) {
-                // Not showing home, put tabs on top.
-                final int count = getChildCount();
-                for (int i = 0; i < count; i++) {
-                    final View child = getChildAt(i);
-
-                    if (child == mTabContainer) {
-                        continue;
-                    }
-
-                    if (!mActionBarView.isCollapsed()) {
-                        child.offsetTopAndBottom(tabHeight);
-                    }
-                }
-                mTabContainer.layout(l, 0, r, tabHeight);
-            } else {
-                mTabContainer.layout(l, containerHeight - tabHeight, r, containerHeight);
-            }
+            final int tabHeight = tabContainer.getMeasuredHeight();
+            tabContainer.layout(l, containerHeight - tabHeight, r, containerHeight);
         }
 
         boolean needsInvalidate = false;
@@ -308,9 +284,10 @@
                         mActionBarView.getRight(), mActionBarView.getBottom());
                 needsInvalidate = true;
             }
-            if ((mIsStacked = hasTabs && mStackedBackground != null)) {
-                mStackedBackground.setBounds(mTabContainer.getLeft(), mTabContainer.getTop(),
-                        mTabContainer.getRight(), mTabContainer.getBottom());
+            mIsStacked = hasTabs;
+            if (hasTabs && mStackedBackground != null) {
+                mStackedBackground.setBounds(tabContainer.getLeft(), tabContainer.getTop(),
+                        tabContainer.getRight(), tabContainer.getBottom());
                 needsInvalidate = true;
             }
         }
@@ -319,17 +296,4 @@
             invalidate();
         }
     }
-
-    private void drawBackgroundDrawable(Drawable d, Canvas canvas) {
-        final Rect bounds = d.getBounds();
-        if (d instanceof ColorDrawable && !bounds.isEmpty() && Build.VERSION.SDK_INT < 11) {
-            // Pre-Honeycomb ColorDrawable does not respect it's bounds so we need to force it to
-            canvas.save();
-            canvas.clipRect(bounds);
-            d.draw(canvas);
-            canvas.restore();
-        } else {
-            d.draw(canvas);
-        }
-    }
 }
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ActionBarContextView.java b/v7/appcompat/src/android/support/v7/internal/widget/ActionBarContextView.java
index 07df4de..5ce9f3a 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/ActionBarContextView.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ActionBarContextView.java
@@ -13,29 +13,35 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.support.v7.internal.widget;
 
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewPropertyAnimatorCompat;
+import android.support.v4.view.ViewPropertyAnimatorListener;
 import android.support.v7.appcompat.R;
 import android.support.v7.view.ActionMode;
-import android.support.v7.internal.view.menu.ActionMenuPresenter;
-import android.support.v7.internal.view.menu.ActionMenuView;
+import android.support.v7.widget.ActionMenuPresenter;
+import android.support.v7.widget.ActionMenuView;
 import android.support.v7.internal.view.menu.MenuBuilder;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.DecelerateInterpolator;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 /**
  * @hide
  */
-public class ActionBarContextView extends AbsActionBarView {
-
+public class ActionBarContextView extends AbsActionBarView implements ViewPropertyAnimatorListener {
     private static final String TAG = "ActionBarContextView";
 
     private CharSequence mTitle;
@@ -51,6 +57,14 @@
     private Drawable mSplitBackground;
     private boolean mTitleOptional;
 
+    //private ViewPropertyAnimatorCompat mCurrentAnimation;
+    private boolean mAnimateInOnLayout;
+    private int mAnimationMode;
+
+    private static final int ANIMATE_IDLE = 0;
+    private static final int ANIMATE_IN = 1;
+    private static final int ANIMATE_OUT = 2;
+
     public ActionBarContextView(Context context) {
         this(context, null);
     }
@@ -89,20 +103,17 @@
     }
 
     @Override
-    public void setSplitActionBar(boolean split) {
+    public void setSplitToolbar(boolean split) {
         if (mSplitActionBar != split) {
             if (mActionMenuPresenter != null) {
                 // Mode is already active; move everything over and adjust the menu itself.
-                final ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
-                        ViewGroup.LayoutParams.WRAP_CONTENT,
-                        ViewGroup.LayoutParams.FILL_PARENT);
+                final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
+                        LayoutParams.MATCH_PARENT);
                 if (!split) {
                     mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
                     mMenuView.setBackgroundDrawable(null);
                     final ViewGroup oldParent = (ViewGroup) mMenuView.getParent();
-                    if (oldParent != null) {
-                        oldParent.removeView(mMenuView);
-                    }
+                    if (oldParent != null) oldParent.removeView(mMenuView);
                     addView(mMenuView, layoutParams);
                 } else {
                     // Allow full screen width in split mode.
@@ -111,18 +122,16 @@
                     // No limit to the item count; use whatever will fit.
                     mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
                     // Span the whole width
-                    layoutParams.width = ViewGroup.LayoutParams.FILL_PARENT;
+                    layoutParams.width = LayoutParams.MATCH_PARENT;
                     layoutParams.height = mContentHeight;
                     mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
                     mMenuView.setBackgroundDrawable(mSplitBackground);
                     final ViewGroup oldParent = (ViewGroup) mMenuView.getParent();
-                    if (oldParent != null) {
-                        oldParent.removeView(mMenuView);
-                    }
+                    if (oldParent != null) oldParent.removeView(mMenuView);
                     mSplitView.addView(mMenuView, layoutParams);
                 }
             }
-            super.setSplitActionBar(split);
+            super.setSplitToolbar(split);
         }
     }
 
@@ -200,7 +209,7 @@
         }
 
         View closeButton = mClose.findViewById(R.id.action_mode_close_button);
-        closeButton.setOnClickListener(new View.OnClickListener() {
+        closeButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 mode.finish();
             }
@@ -213,11 +222,10 @@
         mActionMenuPresenter = new ActionMenuPresenter(getContext());
         mActionMenuPresenter.setReserveOverflow(true);
 
-        final ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
-                ViewGroup.LayoutParams.WRAP_CONTENT,
-                ViewGroup.LayoutParams.FILL_PARENT);
+        final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
+                LayoutParams.MATCH_PARENT);
         if (!mSplitActionBar) {
-            menu.addMenuPresenter(mActionMenuPresenter);
+            menu.addMenuPresenter(mActionMenuPresenter, mPopupContext);
             mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
             mMenuView.setBackgroundDrawable(null);
             addView(mMenuView, layoutParams);
@@ -228,20 +236,29 @@
             // No limit to the item count; use whatever will fit.
             mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
             // Span the whole width
-            layoutParams.width = ViewGroup.LayoutParams.FILL_PARENT;
+            layoutParams.width = LayoutParams.MATCH_PARENT;
             layoutParams.height = mContentHeight;
-            menu.addMenuPresenter(mActionMenuPresenter);
+            menu.addMenuPresenter(mActionMenuPresenter, mPopupContext);
             mMenuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
             mMenuView.setBackgroundDrawable(mSplitBackground);
             mSplitView.addView(mMenuView, layoutParams);
         }
+
+        mAnimateInOnLayout = true;
     }
 
     public void closeMode() {
+        if (mAnimationMode == ANIMATE_OUT) {
+            // Called again during close; just finish what we were doing.
+            return;
+        }
         if (mClose == null) {
             killMode();
             return;
         }
+
+        mAnimationMode = ANIMATE_OUT;
+        startOutAnimation();
     }
 
     public void killMode() {
@@ -251,6 +268,7 @@
         }
         mCustomView = null;
         mMenuView = null;
+        mAnimateInOnLayout = false;
     }
 
     @Override
@@ -281,44 +299,41 @@
     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
         // Used by custom views if they don't supply layout params. Everything else
         // added to an ActionBarContextView should have them already.
-        return new ViewGroup.MarginLayoutParams(
-                ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
     }
 
     @Override
     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
-        return new ViewGroup.MarginLayoutParams(getContext(), attrs);
+        return new MarginLayoutParams(getContext(), attrs);
     }
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        final int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
-        if (widthMode != View.MeasureSpec.EXACTLY) {
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        if (widthMode != MeasureSpec.EXACTLY) {
             throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
-                    "with android:layout_width=\"FILL_PARENT\" (or fill_parent)");
+                    "with android:layout_width=\"match_parent\" (or fill_parent)");
         }
 
-        final int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
-        if (heightMode == View.MeasureSpec.UNSPECIFIED) {
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        if (heightMode == MeasureSpec.UNSPECIFIED) {
             throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
                     "with android:layout_height=\"wrap_content\"");
         }
 
-        final int contentWidth = View.MeasureSpec.getSize(widthMeasureSpec);
+        final int contentWidth = MeasureSpec.getSize(widthMeasureSpec);
 
         int maxHeight = mContentHeight > 0 ?
-                mContentHeight : View.MeasureSpec.getSize(heightMeasureSpec);
+                mContentHeight : MeasureSpec.getSize(heightMeasureSpec);
 
         final int verticalPadding = getPaddingTop() + getPaddingBottom();
         int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight();
         final int height = maxHeight - verticalPadding;
-        final int childSpecHeight = View.MeasureSpec
-                .makeMeasureSpec(height, View.MeasureSpec.AT_MOST);
+        final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
 
         if (mClose != null) {
             availableWidth = measureChildView(mClose, availableWidth, childSpecHeight, 0);
-            ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mClose
-                    .getLayoutParams();
+            MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams();
             availableWidth -= lp.leftMargin + lp.rightMargin;
         }
 
@@ -329,8 +344,7 @@
 
         if (mTitleLayout != null && mCustomView == null) {
             if (mTitleOptional) {
-                final int titleWidthSpec = View.MeasureSpec
-                        .makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+                final int titleWidthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
                 mTitleLayout.measure(titleWidthSpec, childSpecHeight);
                 final int titleWidth = mTitleLayout.getMeasuredWidth();
                 final boolean titleFits = titleWidth <= availableWidth;
@@ -345,16 +359,16 @@
 
         if (mCustomView != null) {
             ViewGroup.LayoutParams lp = mCustomView.getLayoutParams();
-            final int customWidthMode = lp.width != ViewGroup.LayoutParams.WRAP_CONTENT ?
-                    View.MeasureSpec.EXACTLY : View.MeasureSpec.AT_MOST;
+            final int customWidthMode = lp.width != LayoutParams.WRAP_CONTENT ?
+                    MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
             final int customWidth = lp.width >= 0 ?
                     Math.min(lp.width, availableWidth) : availableWidth;
-            final int customHeightMode = lp.height != ViewGroup.LayoutParams.WRAP_CONTENT ?
-                    View.MeasureSpec.EXACTLY : View.MeasureSpec.AT_MOST;
+            final int customHeightMode = lp.height != LayoutParams.WRAP_CONTENT ?
+                    MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
             final int customHeight = lp.height >= 0 ?
                     Math.min(lp.height, height) : height;
-            mCustomView.measure(View.MeasureSpec.makeMeasureSpec(customWidth, customWidthMode),
-                    View.MeasureSpec.makeMeasureSpec(customHeight, customHeightMode));
+            mCustomView.measure(MeasureSpec.makeMeasureSpec(customWidth, customWidthMode),
+                    MeasureSpec.makeMeasureSpec(customHeight, customHeightMode));
         }
 
         if (mContentHeight <= 0) {
@@ -373,33 +387,121 @@
         }
     }
 
+    private void startInAnimation() {
+        int leftMargin = ((MarginLayoutParams) mClose.getLayoutParams()).leftMargin;
+        ViewCompat.setTranslationX(mClose, -mClose.getWidth() - leftMargin);
+
+        ViewPropertyAnimatorCompat buttonAnimator = ViewCompat.animate(mClose).translationX(0f);
+        buttonAnimator.setDuration(200);
+        buttonAnimator.setListener(this);
+        buttonAnimator.setInterpolator(new DecelerateInterpolator());
+
+        if (mMenuView != null) {
+            final int count = mMenuView.getChildCount();
+            if (count > 0) {
+                for (int i = count - 1, j = 0; i >= 0; i--, j++) {
+                    View child = mMenuView.getChildAt(i);
+                    ViewCompat.setScaleY(child, 0f);
+                    ViewCompat.animate(child).scaleY(1f).setDuration(300).start();
+                }
+            }
+        }
+
+        buttonAnimator.start();
+    }
+
+    private void startOutAnimation() {
+        int leftMargin = ((MarginLayoutParams) mClose.getLayoutParams()).leftMargin;
+        ViewPropertyAnimatorCompat buttonAnimator = ViewCompat.animate(mClose)
+                .translationX(-mClose.getWidth() - leftMargin);
+        buttonAnimator.setDuration(200);
+        buttonAnimator.setListener(this);
+        buttonAnimator.setInterpolator(new DecelerateInterpolator());
+
+        if (mMenuView != null) {
+            final int count = mMenuView.getChildCount();
+            if (count > 0) {
+                for (int i = count - 1, j = 0; i >= 0; i--, j++) {
+                    View child = mMenuView.getChildAt(i);
+                    ViewCompat.setScaleY(child, 1f);
+                    ViewCompat.animate(child).scaleY(0f).setDuration(300).start();
+                }
+            }
+        }
+
+        buttonAnimator.start();
+    }
+
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        int x = getPaddingLeft();
+        final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this);
+        int x = isLayoutRtl ? r - l - getPaddingRight() : getPaddingLeft();
         final int y = getPaddingTop();
         final int contentHeight = b - t - getPaddingTop() - getPaddingBottom();
 
         if (mClose != null && mClose.getVisibility() != GONE) {
-            ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mClose
-                    .getLayoutParams();
-            x += lp.leftMargin;
-            x += positionChild(mClose, x, y, contentHeight);
-            x += lp.rightMargin;
+            MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams();
+            final int startMargin = (isLayoutRtl ? lp.rightMargin : lp.leftMargin);
+            final int endMargin = (isLayoutRtl ? lp.leftMargin : lp.rightMargin);
+            x = next(x, startMargin, isLayoutRtl);
+            x += positionChild(mClose, x, y, contentHeight, isLayoutRtl);
+            x = next(x, endMargin, isLayoutRtl);
 
+            if (mAnimateInOnLayout) {
+                mAnimationMode = ANIMATE_IN;
+                startInAnimation();
+                mAnimateInOnLayout = false;
+            }
         }
 
         if (mTitleLayout != null && mCustomView == null && mTitleLayout.getVisibility() != GONE) {
-            x += positionChild(mTitleLayout, x, y, contentHeight);
+            x += positionChild(mTitleLayout, x, y, contentHeight, isLayoutRtl);
         }
 
         if (mCustomView != null) {
-            x += positionChild(mCustomView, x, y, contentHeight);
+            x += positionChild(mCustomView, x, y, contentHeight, isLayoutRtl);
         }
 
-        x = r - l - getPaddingRight();
+        x = isLayoutRtl ? getPaddingLeft() : r - l - getPaddingRight();
 
         if (mMenuView != null) {
-            x -= positionChildInverse(mMenuView, x, y, contentHeight);
+            x += positionChild(mMenuView, x, y, contentHeight, !isLayoutRtl);
+        }
+    }
+
+    @Override
+    public void onAnimationStart(View view) {
+    }
+
+    @Override
+    public void onAnimationEnd(View view) {
+        if (mAnimationMode == ANIMATE_OUT) {
+            killMode();
+        }
+        mAnimationMode = ANIMATE_IDLE;
+    }
+
+    @Override
+    public void onAnimationCancel(View view) {
+    }
+
+    @Override
+    public boolean shouldDelayChildPressedState() {
+        return false;
+    }
+
+    @Override
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        if (Build.VERSION.SDK_INT >= 14) {
+            if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+                // Action mode started
+                event.setSource(this);
+                event.setClassName(getClass().getName());
+                event.setPackageName(getContext().getPackageName());
+                event.setContentDescription(mTitle);
+            } else {
+                super.onInitializeAccessibilityEvent(event);
+            }
         }
     }
 
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ActionBarOverlayLayout.java b/v7/appcompat/src/android/support/v7/internal/widget/ActionBarOverlayLayout.java
index fefa3ea..ada27a1 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/ActionBarOverlayLayout.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ActionBarOverlayLayout.java
@@ -17,13 +17,29 @@
 package android.support.v7.internal.widget;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.TypedArray;
+import android.graphics.Canvas;
 import android.graphics.Rect;
-import android.support.v7.app.ActionBar;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Parcelable;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewPropertyAnimatorCompat;
+import android.support.v4.view.ViewPropertyAnimatorListener;
+import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
+import android.support.v4.widget.ScrollerCompat;
 import android.support.v7.appcompat.R;
+import android.support.v7.internal.app.WindowCallback;
+import android.support.v7.internal.view.menu.MenuPresenter;
+import android.support.v7.widget.Toolbar;
 import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.view.KeyEvent;
+import android.view.Menu;
 import android.view.View;
-import android.widget.FrameLayout;
+import android.view.ViewGroup;
+import android.view.Window;
 
 /**
  * Special layout for the containing of an overlay action bar (and its content) to correctly handle
@@ -31,19 +47,120 @@
  *
  * @hide
  */
-public class ActionBarOverlayLayout extends FrameLayout {
+public class ActionBarOverlayLayout extends ViewGroup implements DecorContentParent {
+    private static final String TAG = "ActionBarOverlayLayout";
 
     private int mActionBarHeight;
-    private ActionBar mActionBar;
-    private View mContent;
-    private View mActionBarTop;
-    private ActionBarContainer mContainerView;
-    private ActionBarView mActionView;
-    private View mActionBarBottom;
-    private final Rect mZeroRect = new Rect(0, 0, 0, 0);
+    //private WindowDecorActionBar mActionBar;
+    private int mWindowVisibility = View.VISIBLE;
 
-    static final int[] mActionBarSizeAttr = new int[]{
-            R.attr.actionBarSize
+    // The main UI elements that we handle the layout of.
+    private ContentFrameLayout mContent;
+    private ActionBarContainer mActionBarBottom;
+    private ActionBarContainer mActionBarTop;
+
+    // Some interior UI elements.
+    private DecorToolbar mDecorToolbar;
+
+    // Content overlay drawable - generally the action bar's shadow
+    private Drawable mWindowContentOverlay;
+    private boolean mIgnoreWindowContentOverlay;
+
+    private boolean mOverlayMode;
+    private boolean mHasNonEmbeddedTabs;
+    private boolean mHideOnContentScroll;
+    private boolean mAnimatingForFling;
+    private int mHideOnContentScrollReference;
+    private int mLastSystemUiVisibility;
+    private final Rect mBaseContentInsets = new Rect();
+    private final Rect mLastBaseContentInsets = new Rect();
+    private final Rect mContentInsets = new Rect();
+    private final Rect mBaseInnerInsets = new Rect();
+    private final Rect mInnerInsets = new Rect();
+    private final Rect mLastInnerInsets = new Rect();
+
+    private ActionBarVisibilityCallback mActionBarVisibilityCallback;
+
+    private final int ACTION_BAR_ANIMATE_DELAY = 600; // ms
+
+    private ScrollerCompat mFlingEstimator;
+
+    private ViewPropertyAnimatorCompat mCurrentActionBarTopAnimator;
+    private ViewPropertyAnimatorCompat mCurrentActionBarBottomAnimator;
+
+    private final ViewPropertyAnimatorListener mTopAnimatorListener
+            = new ViewPropertyAnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(View view) {
+            mCurrentActionBarTopAnimator = null;
+            mAnimatingForFling = false;
+        }
+
+        @Override
+        public void onAnimationCancel(View view) {
+            mCurrentActionBarTopAnimator = null;
+            mAnimatingForFling = false;
+        }
+    };
+
+    private final ViewPropertyAnimatorListener mBottomAnimatorListener =
+            new ViewPropertyAnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(View view) {
+            mCurrentActionBarBottomAnimator = null;
+            mAnimatingForFling = false;
+        }
+
+        @Override
+        public void onAnimationCancel(View view) {
+            mCurrentActionBarBottomAnimator = null;
+            mAnimatingForFling = false;
+        }
+    };
+
+    private final Runnable mRemoveActionBarHideOffset = new Runnable() {
+        public void run() {
+            haltActionBarHideOffsetAnimations();
+            mCurrentActionBarTopAnimator = ViewCompat.animate(mActionBarTop).translationY(0)
+                    .setListener(mTopAnimatorListener);
+            if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
+                mCurrentActionBarBottomAnimator = ViewCompat.animate(mActionBarBottom).translationY(0)
+                        .setListener(mBottomAnimatorListener);
+            }
+        }
+    };
+
+    private final Runnable mAddActionBarHideOffset = new Runnable() {
+        public void run() {
+            haltActionBarHideOffsetAnimations();
+            mCurrentActionBarTopAnimator = ViewCompat.animate(mActionBarTop)
+                    .translationY(-mActionBarTop.getHeight())
+                    .setListener(mTopAnimatorListener);
+            if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
+                mCurrentActionBarBottomAnimator =  ViewCompat.animate(mActionBarBottom)
+                        .translationY(mActionBarBottom.getHeight())
+                        .setListener(mBottomAnimatorListener);
+            }
+        }
+    };
+
+//    public static final Property<ActionBarOverlayLayout, Integer> ACTION_BAR_HIDE_OFFSET =
+//            new IntProperty<ActionBarOverlayLayout>("actionBarHideOffset") {
+//
+//                @Override
+//                public void setValue(ActionBarOverlayLayout object, int value) {
+//                    object.setActionBarHideOffset(value);
+//                }
+//
+//                @Override
+//                public Integer get(ActionBarOverlayLayout object) {
+//                    return object.getActionBarHideOffset();
+//                }
+//            };
+
+    static final int[] ATTRS = new int [] {
+            R.attr.actionBarSize,
+            android.R.attr.windowContentOverlay
     };
 
     public ActionBarOverlayLayout(Context context) {
@@ -57,19 +174,124 @@
     }
 
     private void init(Context context) {
-        TypedArray ta = getContext().getTheme().obtainStyledAttributes(mActionBarSizeAttr);
+        TypedArray ta = getContext().getTheme().obtainStyledAttributes(ATTRS);
         mActionBarHeight = ta.getDimensionPixelSize(0, 0);
+        mWindowContentOverlay = ta.getDrawable(1);
+        setWillNotDraw(mWindowContentOverlay == null);
         ta.recycle();
+
+        mIgnoreWindowContentOverlay = context.getApplicationInfo().targetSdkVersion <
+                Build.VERSION_CODES.KITKAT;
+
+        mFlingEstimator = ScrollerCompat.create(context);
     }
 
-    public void setActionBar(ActionBar impl) {
-        mActionBar = impl;
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        haltActionBarHideOffsetAnimations();
+    }
+
+    public void setActionBarVisibilityCallback(ActionBarVisibilityCallback cb) {
+        mActionBarVisibilityCallback = cb;
+        if (getWindowToken() != null) {
+            // This is being initialized after being added to a window;
+            // make sure to update all state now.
+            mActionBarVisibilityCallback.onWindowVisibilityChanged(mWindowVisibility);
+            if (mLastSystemUiVisibility != 0) {
+                int newVis = mLastSystemUiVisibility;
+                onWindowSystemUiVisibilityChanged(newVis);
+                ViewCompat.requestApplyInsets(this);
+            }
+        }
+    }
+
+    public void setOverlayMode(boolean overlayMode) {
+        mOverlayMode = overlayMode;
+
+        /*
+         * Drawing the window content overlay was broken before K so starting to draw it
+         * again unexpectedly will cause artifacts in some apps. They should fix it.
+         */
+        mIgnoreWindowContentOverlay = overlayMode &&
+                getContext().getApplicationInfo().targetSdkVersion <
+                        Build.VERSION_CODES.KITKAT;
+    }
+
+    public boolean isInOverlayMode() {
+        return mOverlayMode;
+    }
+
+    public void setHasNonEmbeddedTabs(boolean hasNonEmbeddedTabs) {
+        mHasNonEmbeddedTabs = hasNonEmbeddedTabs;
+    }
+
+    public void setShowingForActionMode(boolean showing) {
+        // TODO: Add workaround for this
+//        if (showing) {
+//            // Here's a fun hack: if the status bar is currently being hidden,
+//            // and the application has asked for stable content insets, then
+//            // we will end up with the action mode action bar being shown
+//            // without the status bar, but moved below where the status bar
+//            // would be.  Not nice.  Trying to have this be positioned
+//            // correctly is not easy (basically we need yet *another* content
+//            // inset from the window manager to know where to put it), so
+//            // instead we will just temporarily force the status bar to be shown.
+//            if ((getWindowSystemUiVisibility() & (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+//                    | SYSTEM_UI_FLAG_LAYOUT_STABLE))
+//                    == (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE)) {
+//                setDisabledSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN);
+//            }
+//        } else {
+//            setDisabledSystemUiVisibility(0);
+//        }
+    }
+
+    protected void onConfigurationChanged(Configuration newConfig) {
+        if (Build.VERSION.SDK_INT >= 8) {
+            super.onConfigurationChanged(newConfig);
+        }
+        init(getContext());
+        ViewCompat.requestApplyInsets(this);
+    }
+
+    public void onWindowSystemUiVisibilityChanged(int visible) {
+        if (Build.VERSION.SDK_INT >= 16) {
+            super.onWindowSystemUiVisibilityChanged(visible);
+        }
+        pullChildren();
+        final int diff = mLastSystemUiVisibility ^ visible;
+        mLastSystemUiVisibility = visible;
+        final boolean barVisible = (visible & SYSTEM_UI_FLAG_FULLSCREEN) == 0;
+        final boolean stable = (visible & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
+        if (mActionBarVisibilityCallback != null) {
+            // We want the bar to be visible if it is not being hidden,
+            // or the app has not turned on a stable UI mode (meaning they
+            // are performing explicit layout around the action bar).
+            mActionBarVisibilityCallback.enableContentAnimations(!stable);
+            if (barVisible || !stable) mActionBarVisibilityCallback.showForSystem();
+            else mActionBarVisibilityCallback.hideForSystem();
+        }
+        if ((diff & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
+            if (mActionBarVisibilityCallback != null) {
+                ViewCompat.requestApplyInsets(this);
+            }
+        }
+    }
+
+    @Override
+    protected void onWindowVisibilityChanged(int visibility) {
+        super.onWindowVisibilityChanged(visibility);
+        mWindowVisibility = visibility;
+        if (mActionBarVisibilityCallback != null) {
+            mActionBarVisibilityCallback.onWindowVisibilityChanged(visibility);
+        }
     }
 
     private boolean applyInsets(View view, Rect insets, boolean left, boolean top,
             boolean bottom, boolean right) {
         boolean changed = false;
-        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) view.getLayoutParams();
+        LayoutParams lp = (LayoutParams)view.getLayoutParams();
         if (left && lp.leftMargin != insets.left) {
             changed = true;
             lp.leftMargin = insets.left;
@@ -89,16 +311,535 @@
         return changed;
     }
 
+// TODO
+//    @Override
+//    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+//        pullChildren();
+//
+//        final int vis = getWindowSystemUiVisibility();
+//        final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
+//        final Rect systemInsets = insets.getSystemWindowInsets();
+//
+//        // The top and bottom action bars are always within the content area.
+//        boolean changed = applyInsets(mActionBarTop, systemInsets, true, true, false, true);
+//        if (mActionBarBottom != null) {
+//            changed |= applyInsets(mActionBarBottom, systemInsets, true, false, true, true);
+//        }
+//
+//        mBaseInnerInsets.set(systemInsets);
+//        computeFitSystemWindows(mBaseInnerInsets, mBaseContentInsets);
+//        if (!mLastBaseContentInsets.equals(mBaseContentInsets)) {
+//            changed = true;
+//            mLastBaseContentInsets.set(mBaseContentInsets);
+//        }
+//
+//        if (changed) {
+//            requestLayout();
+//        }
+//
+//        // We don't do any more at this point.  To correctly compute the content/inner
+//        // insets in all cases, we need to know the measured size of the various action
+//        // bar elements.  onApplyWindowInsets() happens before the measure pass, so we can't
+//        // do that here.  Instead we will take this up in onMeasure().
+//        return WindowInsets.CONSUMED;
+//    }
+
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new LayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return new LayoutParams(p);
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof LayoutParams;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        pullChildren();
+
+        int maxHeight = 0;
+        int maxWidth = 0;
+        int childState = 0;
+
+        int topInset = 0;
+        int bottomInset = 0;
+
+        measureChildWithMargins(mActionBarTop, widthMeasureSpec, 0, heightMeasureSpec, 0);
+        LayoutParams lp = (LayoutParams) mActionBarTop.getLayoutParams();
+        maxWidth = Math.max(maxWidth,
+                mActionBarTop.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
+        maxHeight = Math.max(maxHeight,
+                mActionBarTop.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
+        childState = ViewUtils.combineMeasuredStates(childState,
+                ViewCompat.getMeasuredState(mActionBarTop));
+
+        // xlarge screen layout doesn't have bottom action bar.
+        if (mActionBarBottom != null) {
+            measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0);
+            lp = (LayoutParams) mActionBarBottom.getLayoutParams();
+            maxWidth = Math.max(maxWidth,
+                    mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
+            maxHeight = Math.max(maxHeight,
+                    mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
+            childState = ViewUtils.combineMeasuredStates(childState,
+                    ViewCompat.getMeasuredState(mActionBarBottom));
+        }
+
+        final int vis = ViewCompat.getWindowSystemUiVisibility(this);
+        final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
+
+        if (stable) {
+            // This is the standard space needed for the action bar.  For stable measurement,
+            // we can't depend on the size currently reported by it -- this must remain constant.
+            topInset = mActionBarHeight;
+            if (mHasNonEmbeddedTabs) {
+                final View tabs = mActionBarTop.getTabContainer();
+                if (tabs != null) {
+                    // If tabs are not embedded, increase space on top to account for them.
+                    topInset += mActionBarHeight;
+                }
+            }
+        } else if (mActionBarTop.getVisibility() != GONE) {
+            // This is the space needed on top of the window for all of the action bar
+            // and tabs.
+            topInset = mActionBarTop.getMeasuredHeight();
+        }
+
+        if (mDecorToolbar.isSplit()) {
+            // If action bar is split, adjust bottom insets for it.
+            if (mActionBarBottom != null) {
+                if (stable) {
+                    bottomInset = mActionBarHeight;
+                } else {
+                    bottomInset = mActionBarBottom.getMeasuredHeight();
+                }
+            }
+        }
+
+        // If the window has not requested system UI layout flags, we need to
+        // make sure its content is not being covered by system UI...  though it
+        // will still be covered by the action bar if they have requested it to
+        // overlay.
+        mContentInsets.set(mBaseContentInsets);
+        mInnerInsets.set(mBaseInnerInsets);
+        if (!mOverlayMode && !stable) {
+            mContentInsets.top += topInset;
+            mContentInsets.bottom += bottomInset;
+        } else {
+            mInnerInsets.top += topInset;
+            mInnerInsets.bottom += bottomInset;
+        }
+        applyInsets(mContent, mContentInsets, true, true, true, true);
+
+        if (!mLastInnerInsets.equals(mInnerInsets)) {
+            // If the inner insets have changed, we need to dispatch this down to
+            // the app's fitSystemWindows().  We do this before measuring the content
+            // view to keep the same semantics as the normal fitSystemWindows() call.
+            mLastInnerInsets.set(mInnerInsets);
+
+            mContent.dispatchFitSystemWindows(mInnerInsets);
+        }
+
+        measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0);
+        lp = (LayoutParams) mContent.getLayoutParams();
+        maxWidth = Math.max(maxWidth,
+                mContent.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
+        maxHeight = Math.max(maxHeight,
+                mContent.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
+        childState = ViewUtils.combineMeasuredStates(childState,
+                ViewCompat.getMeasuredState(mContent));
+
+        // Account for padding too
+        maxWidth += getPaddingLeft() + getPaddingRight();
+        maxHeight += getPaddingTop() + getPaddingBottom();
+
+        // Check against our minimum height and width
+        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
+        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
+
+        setMeasuredDimension(
+                ViewCompat.resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
+                ViewCompat.resolveSizeAndState(maxHeight, heightMeasureSpec,
+                        childState << MEASURED_HEIGHT_STATE_SHIFT));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        final int count = getChildCount();
+
+        final int parentLeft = getPaddingLeft();
+        final int parentRight = right - left - getPaddingRight();
+
+        final int parentTop = getPaddingTop();
+        final int parentBottom = bottom - top - getPaddingBottom();
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+                final int width = child.getMeasuredWidth();
+                final int height = child.getMeasuredHeight();
+
+                int childLeft = parentLeft + lp.leftMargin;
+                int childTop;
+                if (child == mActionBarBottom) {
+                    childTop = parentBottom - height - lp.bottomMargin;
+                } else {
+                    childTop = parentTop + lp.topMargin;
+                }
+
+                child.layout(childLeft, childTop, childLeft + width, childTop + height);
+            }
+        }
+    }
+
+    @Override
+    public void draw(Canvas c) {
+        super.draw(c);
+        if (mWindowContentOverlay != null && !mIgnoreWindowContentOverlay) {
+            final int top = mActionBarTop.getVisibility() == VISIBLE ?
+                    (int) (mActionBarTop.getBottom() + ViewCompat.getTranslationY(mActionBarTop) + 0.5f)
+                    : 0;
+            mWindowContentOverlay.setBounds(0, top, getWidth(),
+                    top + mWindowContentOverlay.getIntrinsicHeight());
+            mWindowContentOverlay.draw(c);
+        }
+    }
+
+    @Override
+    public boolean shouldDelayChildPressedState() {
+        return false;
+    }
+
+    @Override
+    public boolean onStartNestedScroll(View child, View target, int axes) {
+        if ((axes & SCROLL_AXIS_VERTICAL) == 0 || mActionBarTop.getVisibility() != VISIBLE) {
+            return false;
+        }
+        return mHideOnContentScroll;
+    }
+
+    @Override
+    public void onNestedScrollAccepted(View child, View target, int axes) {
+        super.onNestedScrollAccepted(child, target, axes);
+        mHideOnContentScrollReference = getActionBarHideOffset();
+        haltActionBarHideOffsetAnimations();
+        if (mActionBarVisibilityCallback != null) {
+            mActionBarVisibilityCallback.onContentScrollStarted();
+        }
+    }
+
+    @Override
+    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+            int dxUnconsumed, int dyUnconsumed) {
+        mHideOnContentScrollReference += dyConsumed;
+        setActionBarHideOffset(mHideOnContentScrollReference);
+    }
+
+    @Override
+    public void onStopNestedScroll(View target) {
+        super.onStopNestedScroll(target);
+        if (mHideOnContentScroll && !mAnimatingForFling) {
+            if (mHideOnContentScrollReference <= mActionBarTop.getHeight()) {
+                postRemoveActionBarHideOffset();
+            } else {
+                postAddActionBarHideOffset();
+            }
+        }
+        if (mActionBarVisibilityCallback != null) {
+            mActionBarVisibilityCallback.onContentScrollStopped();
+        }
+    }
+
+    @Override
+    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+        if (!mHideOnContentScroll || !consumed) {
+            return false;
+        }
+        if (shouldHideActionBarOnFling(velocityX, velocityY)) {
+            addActionBarHideOffset();
+        } else {
+            removeActionBarHideOffset();
+        }
+        mAnimatingForFling = true;
+        return true;
+    }
+
     void pullChildren() {
         if (mContent == null) {
-            mContent = findViewById(R.id.action_bar_activity_content);
-            if (mContent == null) {
-                mContent = findViewById(android.R.id.content);
-            }
-            mActionBarTop = findViewById(R.id.top_action_bar);
-            mContainerView = (ActionBarContainer) findViewById(R.id.action_bar_container);
-            mActionView = (ActionBarView) findViewById(R.id.action_bar);
-            mActionBarBottom = findViewById(R.id.split_action_bar);
+            mContent = (ContentFrameLayout) findViewById(R.id.action_bar_activity_content);
+            mActionBarTop = (ActionBarContainer) findViewById(R.id.action_bar_container);
+            mDecorToolbar = getDecorToolbar(findViewById(R.id.action_bar));
+            mActionBarBottom = (ActionBarContainer) findViewById(R.id.split_action_bar);
         }
     }
+
+    private DecorToolbar getDecorToolbar(View view) {
+        if (view instanceof DecorToolbar) {
+            return (DecorToolbar) view;
+        } else if (view instanceof Toolbar) {
+            return ((Toolbar) view).getWrapper();
+        } else {
+            throw new IllegalStateException("Can't make a decor toolbar out of " +
+                    view.getClass().getSimpleName());
+        }
+    }
+
+    public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) {
+        if (hideOnContentScroll != mHideOnContentScroll) {
+            mHideOnContentScroll = hideOnContentScroll;
+            if (!hideOnContentScroll) {
+                stopNestedScroll();
+                haltActionBarHideOffsetAnimations();
+                setActionBarHideOffset(0);
+            }
+        }
+    }
+
+    public boolean isHideOnContentScrollEnabled() {
+        return mHideOnContentScroll;
+    }
+
+    public int getActionBarHideOffset() {
+        return mActionBarTop != null ? -((int) ViewCompat.getTranslationY(mActionBarTop)) : 0;
+    }
+
+    public void setActionBarHideOffset(int offset) {
+        haltActionBarHideOffsetAnimations();
+        final int topHeight = mActionBarTop.getHeight();
+        offset = Math.max(0, Math.min(offset, topHeight));
+        ViewCompat.setTranslationY(mActionBarTop, -offset);
+        if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
+            // Match the hide offset proportionally for a split bar
+            final float fOffset = (float) offset / topHeight;
+            final int bOffset = (int) (mActionBarBottom.getHeight() * fOffset);
+            ViewCompat.setTranslationY(mActionBarBottom, bOffset);
+        }
+    }
+
+    private void haltActionBarHideOffsetAnimations() {
+        removeCallbacks(mRemoveActionBarHideOffset);
+        removeCallbacks(mAddActionBarHideOffset);
+        if (mCurrentActionBarTopAnimator != null) {
+            mCurrentActionBarTopAnimator.cancel();
+        }
+        if (mCurrentActionBarBottomAnimator != null) {
+            mCurrentActionBarBottomAnimator.cancel();
+        }
+    }
+
+    private void postRemoveActionBarHideOffset() {
+        haltActionBarHideOffsetAnimations();
+        postDelayed(mRemoveActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY);
+    }
+
+    private void postAddActionBarHideOffset() {
+        haltActionBarHideOffsetAnimations();
+        postDelayed(mAddActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY);
+    }
+
+    private void removeActionBarHideOffset() {
+        haltActionBarHideOffsetAnimations();
+        mRemoveActionBarHideOffset.run();
+    }
+
+    private void addActionBarHideOffset() {
+        haltActionBarHideOffsetAnimations();
+        mAddActionBarHideOffset.run();
+    }
+
+    private boolean shouldHideActionBarOnFling(float velocityX, float velocityY) {
+        mFlingEstimator.fling(0, 0, 0, (int) velocityY, 0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);
+        final int finalY = mFlingEstimator.getFinalY();
+        return finalY > mActionBarTop.getHeight();
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (super.dispatchKeyEvent(event)) {
+            return true;
+        }
+
+        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+            final int action = event.getAction();
+
+            // Collapse any expanded action views.
+            if (mDecorToolbar != null && mDecorToolbar.hasExpandedActionView()) {
+                if (action == KeyEvent.ACTION_UP) {
+                    mDecorToolbar.collapseActionView();
+                }
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public void setWindowCallback(WindowCallback cb) {
+        pullChildren();
+        mDecorToolbar.setWindowCallback(cb);
+    }
+
+    @Override
+    public void setWindowTitle(CharSequence title) {
+        pullChildren();
+        mDecorToolbar.setWindowTitle(title);
+    }
+
+    @Override
+    public CharSequence getTitle() {
+        pullChildren();
+        return mDecorToolbar.getTitle();
+    }
+
+    @Override
+    public void initFeature(int windowFeature) {
+        pullChildren();
+        switch (windowFeature) {
+            case Window.FEATURE_PROGRESS:
+                mDecorToolbar.initProgress();
+                break;
+            case Window.FEATURE_INDETERMINATE_PROGRESS:
+                mDecorToolbar.initIndeterminateProgress();
+                break;
+            case Window.FEATURE_ACTION_BAR_OVERLAY:
+                setOverlayMode(true);
+                break;
+        }
+    }
+
+    @Override
+    public void setUiOptions(int uiOptions) {
+        // Split Action Bar not included.
+    }
+
+    @Override
+    public boolean hasIcon() {
+        pullChildren();
+        return mDecorToolbar.hasIcon();
+    }
+
+    @Override
+    public boolean hasLogo() {
+        pullChildren();
+        return mDecorToolbar.hasLogo();
+    }
+
+    @Override
+    public void setIcon(int resId) {
+        pullChildren();
+        mDecorToolbar.setIcon(resId);
+    }
+
+    @Override
+    public void setIcon(Drawable d) {
+        pullChildren();
+        mDecorToolbar.setIcon(d);
+    }
+
+    @Override
+    public void setLogo(int resId) {
+        pullChildren();
+        mDecorToolbar.setLogo(resId);
+    }
+
+    @Override
+    public boolean canShowOverflowMenu() {
+        pullChildren();
+        return mDecorToolbar.canShowOverflowMenu();
+    }
+
+    @Override
+    public boolean isOverflowMenuShowing() {
+        pullChildren();
+        return mDecorToolbar.isOverflowMenuShowing();
+    }
+
+    @Override
+    public boolean isOverflowMenuShowPending() {
+        pullChildren();
+        return mDecorToolbar.isOverflowMenuShowPending();
+    }
+
+    @Override
+    public boolean showOverflowMenu() {
+        pullChildren();
+        return mDecorToolbar.showOverflowMenu();
+    }
+
+    @Override
+    public boolean hideOverflowMenu() {
+        pullChildren();
+        return mDecorToolbar.hideOverflowMenu();
+    }
+
+    @Override
+    public void setMenuPrepared() {
+        pullChildren();
+        mDecorToolbar.setMenuPrepared();
+    }
+
+    @Override
+    public void setMenu(Menu menu, MenuPresenter.Callback cb) {
+        pullChildren();
+        mDecorToolbar.setMenu(menu, cb);
+    }
+
+    @Override
+    public void saveToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) {
+        pullChildren();
+        mDecorToolbar.saveHierarchyState(toolbarStates);
+    }
+
+    @Override
+    public void restoreToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) {
+        pullChildren();
+        mDecorToolbar.restoreHierarchyState(toolbarStates);
+    }
+
+    @Override
+    public void dismissPopups() {
+        pullChildren();
+        mDecorToolbar.dismissPopupMenus();
+    }
+
+    public static class LayoutParams extends MarginLayoutParams {
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+        }
+
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        public LayoutParams(ViewGroup.LayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(ViewGroup.MarginLayoutParams source) {
+            super(source);
+        }
+    }
+
+    public interface ActionBarVisibilityCallback {
+        void onWindowVisibilityChanged(int visibility);
+        void showForSystem();
+        void hideForSystem();
+        void enableContentAnimations(boolean enable);
+        void onContentScrollStarted();
+        void onContentScrollStopped();
+    }
 }
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ActionBarView.java b/v7/appcompat/src/android/support/v7/internal/widget/ActionBarView.java
index 111372a..7eca30c 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/ActionBarView.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ActionBarView.java
@@ -20,32 +20,37 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.internal.view.SupportMenuItem;
+import android.support.v4.view.*;
 import android.support.v7.app.ActionBar;
-import android.support.v7.app.ActionBar.OnNavigationListener;
 import android.support.v7.appcompat.R;
-import android.support.v7.view.CollapsibleActionView;
+import android.support.v7.internal.app.WindowCallback;
+import android.support.v7.internal.transition.ActionBarTransition;
 import android.support.v7.internal.view.menu.ActionMenuItem;
-import android.support.v7.internal.view.menu.ActionMenuPresenter;
-import android.support.v7.internal.view.menu.ActionMenuView;
+import android.support.v7.widget.ActionMenuPresenter;
+import android.support.v7.widget.ActionMenuView;
 import android.support.v7.internal.view.menu.MenuBuilder;
 import android.support.v7.internal.view.menu.MenuItemImpl;
 import android.support.v7.internal.view.menu.MenuPresenter;
 import android.support.v7.internal.view.menu.MenuView;
 import android.support.v7.internal.view.menu.SubMenuBuilder;
-import android.support.v4.internal.view.SupportMenu;
-import android.support.v4.internal.view.SupportMenuItem;
+import android.support.v7.view.CollapsibleActionView;
+import android.support.v7.widget.LinearLayoutCompat;
+import android.text.Layout;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewParent;
@@ -60,8 +65,7 @@
 /**
  * @hide
  */
-public class ActionBarView extends AbsActionBarView {
-
+public class ActionBarView extends AbsActionBarView implements DecorToolbar {
     private static final String TAG = "ActionBarView";
 
     /**
@@ -74,12 +78,12 @@
      */
     private static final int DISPLAY_RELAYOUT_MASK =
             ActionBar.DISPLAY_SHOW_HOME |
-                    ActionBar.DISPLAY_USE_LOGO |
-                    ActionBar.DISPLAY_HOME_AS_UP |
-                    ActionBar.DISPLAY_SHOW_CUSTOM |
-                    ActionBar.DISPLAY_SHOW_TITLE;
+            ActionBar.DISPLAY_USE_LOGO |
+            ActionBar.DISPLAY_HOME_AS_UP |
+            ActionBar.DISPLAY_SHOW_CUSTOM |
+            ActionBar.DISPLAY_SHOW_TITLE;
 
-    private static final int DEFAULT_CUSTOM_GRAVITY = Gravity.LEFT | Gravity.CENTER_VERTICAL;
+    private static final int DEFAULT_CUSTOM_GRAVITY = GravityCompat.START | Gravity.CENTER_VERTICAL;
 
     private int mNavigationMode;
     private int mDisplayOptions = -1;
@@ -87,6 +91,8 @@
     private CharSequence mSubtitle;
     private Drawable mIcon;
     private Drawable mLogo;
+    private CharSequence mHomeDescription;
+    private int mHomeDescriptionRes;
 
     private Context mContext;
     private HomeView mHomeLayout;
@@ -94,14 +100,14 @@
     private LinearLayout mTitleLayout;
     private TextView mTitleView;
     private TextView mSubtitleView;
-    private View mTitleUpView;
+    private ViewGroup mUpGoerFive;
 
-    private SpinnerICS mSpinner;
-    private LinearLayout mListNavLayout;
+    private SpinnerCompat mSpinner;
+    private LinearLayoutCompat mListNavLayout;
     private ScrollingTabContainerView mTabScrollView;
     private View mCustomNavView;
-    private ProgressBarICS mProgressView;
-    private ProgressBarICS mIndeterminateProgressView;
+    private ProgressBarCompat mProgressView;
+    private ProgressBarCompat mIndeterminateProgressView;
 
     private int mProgressBarPadding;
     private int mItemPadding;
@@ -113,38 +119,25 @@
 
     private boolean mUserTitle;
     private boolean mIncludeTabs;
-    private boolean mIsCollapsable;
-    private boolean mIsCollapsed;
+    private boolean mIsCollapsible;
+    private boolean mWasHomeEnabled; // Was it enabled before action view expansion?
 
     private MenuBuilder mOptionsMenu;
+    private boolean mMenuPrepared;
 
     private ActionBarContextView mContextView;
 
     private ActionMenuItem mLogoNavItem;
 
     private SpinnerAdapter mSpinnerAdapter;
-    private OnNavigationListener mCallback;
+    private AdapterViewCompat.OnItemSelectedListener mNavItemSelectedListener;
 
     private Runnable mTabSelector;
 
     private ExpandedActionViewMenuPresenter mExpandedMenuPresenter;
     View mExpandedActionView;
 
-    Window.Callback mWindowCallback;
-
-    private final AdapterViewICS.OnItemSelectedListener mNavItemSelectedListener =
-            new AdapterViewICS.OnItemSelectedListener() {
-                public void onItemSelected(AdapterViewICS<?> parent, View view, int position,
-                        long id) {
-                    if (mCallback != null) {
-                        mCallback.onNavigationItemSelected(position, id);
-                    }
-                }
-
-                public void onNothingSelected(AdapterViewICS<?> parent) {
-                    // Do nothing
-                }
-            };
+    WindowCallback mWindowCallback;
 
     private final OnClickListener mExpandedActionViewUpListener = new OnClickListener() {
         @Override
@@ -158,7 +151,10 @@
 
     private final OnClickListener mUpClickListener = new OnClickListener() {
         public void onClick(View v) {
-            mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mLogoNavItem);
+            if (mMenuPrepared) {
+                // Only invoke the window callback if the options menu has been initialized.
+                mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mLogoNavItem);
+            }
         }
     };
 
@@ -172,39 +168,42 @@
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionBar,
                 R.attr.actionBarStyle, 0);
 
-        ApplicationInfo appInfo = context.getApplicationInfo();
-        PackageManager pm = context.getPackageManager();
         mNavigationMode = a.getInt(R.styleable.ActionBar_navigationMode,
                 ActionBar.NAVIGATION_MODE_STANDARD);
         mTitle = a.getText(R.styleable.ActionBar_title);
         mSubtitle = a.getText(R.styleable.ActionBar_subtitle);
         mLogo = a.getDrawable(R.styleable.ActionBar_logo);
+        mIcon = a.getDrawable(R.styleable.ActionBar_icon);
+
+        ApplicationInfo appInfo = context.getApplicationInfo();
+        PackageManager pm = context.getPackageManager();
         if (mLogo == null) {
             if (Build.VERSION.SDK_INT >= 9) {
                 if (context instanceof Activity) {
                     try {
+                        // Try fetching the Activity's logo first...
                         mLogo = pm.getActivityLogo(((Activity) context).getComponentName());
-                    } catch (NameNotFoundException e) {
+                    } catch (PackageManager.NameNotFoundException e) {
                         Log.e(TAG, "Activity component name not found!", e);
                     }
                 }
                 if (mLogo == null) {
+                    // If there is not an Activity logo set, retrieve the application's
                     mLogo = appInfo.loadLogo(pm);
                 }
             }
         }
-
-        // TODO(trevorjohns): Should these use the android namespace
-        mIcon = a.getDrawable(R.styleable.ActionBar_icon);
         if (mIcon == null) {
             if (context instanceof Activity) {
                 try {
+                    // Try fetching the Activity's icon first...
                     mIcon = pm.getActivityIcon(((Activity) context).getComponentName());
-                } catch (NameNotFoundException e) {
+                } catch (PackageManager.NameNotFoundException e) {
                     Log.e(TAG, "Activity component name not found!", e);
                 }
             }
             if (mIcon == null) {
+                // If there is not an Activity icon set, retrieve the application's
                 mIcon = appInfo.loadIcon(pm);
             }
         }
@@ -215,40 +214,59 @@
                 R.styleable.ActionBar_homeLayout,
                 R.layout.abc_action_bar_home);
 
-        mHomeLayout = (HomeView) inflater.inflate(homeResId, this, false);
+        mUpGoerFive = (ViewGroup) inflater.inflate(
+                R.layout.abc_action_bar_up_container, this, false);
+        mHomeLayout = (HomeView) inflater.inflate(homeResId, mUpGoerFive, false);
 
-        mExpandedHomeLayout = (HomeView) inflater.inflate(homeResId, this, false);
-        mExpandedHomeLayout.setUp(true);
+        mExpandedHomeLayout = (HomeView) inflater.inflate(homeResId, mUpGoerFive, false);
+        mExpandedHomeLayout.setShowUp(true);
         mExpandedHomeLayout.setOnClickListener(mExpandedActionViewUpListener);
         mExpandedHomeLayout.setContentDescription(getResources().getText(
                 R.string.abc_action_bar_up_description));
 
+        // This needs to highlight/be focusable on its own.
+        // TODO: Clean up the handoff between expanded/normal.
+        final Drawable upBackground = mUpGoerFive.getBackground();
+        if (upBackground != null) {
+            mExpandedHomeLayout.setBackgroundDrawable(upBackground
+                    .getConstantState().newDrawable());
+        }
+        mExpandedHomeLayout.setEnabled(true);
+        mExpandedHomeLayout.setFocusable(true);
+
         mTitleStyleRes = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0);
         mSubtitleStyleRes = a.getResourceId(R.styleable.ActionBar_subtitleTextStyle, 0);
         mProgressStyle = a.getResourceId(R.styleable.ActionBar_progressBarStyle, 0);
         mIndeterminateProgressStyle = a.getResourceId(
                 R.styleable.ActionBar_indeterminateProgressStyle, 0);
 
-        mProgressBarPadding = a
-                .getDimensionPixelOffset(R.styleable.ActionBar_progressBarPadding, 0);
+        mProgressBarPadding = a.getDimensionPixelOffset(R.styleable.ActionBar_progressBarPadding, 0);
         mItemPadding = a.getDimensionPixelOffset(R.styleable.ActionBar_itemPadding, 0);
 
         setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, DISPLAY_DEFAULT));
 
         final int customNavId = a.getResourceId(R.styleable.ActionBar_customNavigationLayout, 0);
         if (customNavId != 0) {
-            mCustomNavView = (View) inflater.inflate(customNavId, this, false);
+            mCustomNavView = inflater.inflate(customNavId, this, false);
             mNavigationMode = ActionBar.NAVIGATION_MODE_STANDARD;
             setDisplayOptions(mDisplayOptions | ActionBar.DISPLAY_SHOW_CUSTOM);
         }
 
         mContentHeight = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
-        a.recycle();
-        mLogoNavItem = new ActionMenuItem(context, 0, android.R.id.home, 0, 0, mTitle);
-        mHomeLayout.setOnClickListener(mUpClickListener);
-        mHomeLayout.setClickable(true);
-        mHomeLayout.setFocusable(true);
 
+        a.recycle();
+
+        mLogoNavItem = new ActionMenuItem(context, 0, android.R.id.home, 0, 0, mTitle);
+
+        mUpGoerFive.setOnClickListener(mUpClickListener);
+        mUpGoerFive.setClickable(true);
+        mUpGoerFive.setFocusable(true);
+
+        if (ViewCompat.getImportantForAccessibility(this)
+                == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+            ViewCompat.setImportantForAccessibility(this,
+                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+        }
     }
 
     @Override
@@ -257,40 +275,33 @@
 
         mTitleView = null;
         mSubtitleView = null;
-        mTitleUpView = null;
-        if (mTitleLayout != null && mTitleLayout.getParent() == this) {
-            removeView(mTitleLayout);
+        if (mTitleLayout != null && mTitleLayout.getParent() == mUpGoerFive) {
+            mUpGoerFive.removeView(mTitleLayout);
         }
         mTitleLayout = null;
         if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
             initTitle();
         }
 
+        if (mHomeDescriptionRes != 0) {
+            setNavigationContentDescription(mHomeDescriptionRes);
+        }
+
         if (mTabScrollView != null && mIncludeTabs) {
             ViewGroup.LayoutParams lp = mTabScrollView.getLayoutParams();
             if (lp != null) {
                 lp.width = LayoutParams.WRAP_CONTENT;
-                lp.height = LayoutParams.FILL_PARENT;
+                lp.height = LayoutParams.MATCH_PARENT;
             }
             mTabScrollView.setAllowCollapse(true);
         }
-
-        if (mProgressView != null) {
-            removeView(mProgressView);
-            initProgress();
-        }
-        if (mIndeterminateProgressView != null) {
-            removeView(mIndeterminateProgressView);
-            initIndeterminateProgress();
-        }
     }
 
     /**
-     * Set the view callback used to invoke menu items; used for dispatching home button presses.
-     *
-     * @param cb View callback to dispatch to
+     * Set the window callback used to invoke menu items; used for dispatching home button presses.
+     * @param cb Window callback to dispatch to
      */
-    public void setWindowCallback(Window.Callback cb) {
+    public void setWindowCallback(WindowCallback cb) {
         mWindowCallback = cb;
     }
 
@@ -309,7 +320,7 @@
     }
 
     public void initProgress() {
-        mProgressView = new ProgressBarICS(mContext, null, 0, mProgressStyle);
+        mProgressView = new ProgressBarCompat(mContext, null, 0, mProgressStyle);
         mProgressView.setId(R.id.progress_horizontal);
         mProgressView.setMax(10000);
         mProgressView.setVisibility(GONE);
@@ -317,7 +328,7 @@
     }
 
     public void initIndeterminateProgress() {
-        mIndeterminateProgressView = new ProgressBarICS(mContext, null, 0,
+        mIndeterminateProgressView = new ProgressBarCompat(mContext, null, 0,
                 mIndeterminateProgressStyle);
         mIndeterminateProgressView.setId(R.id.progress_circular);
         mIndeterminateProgressView.setVisibility(GONE);
@@ -325,7 +336,7 @@
     }
 
     @Override
-    public void setSplitActionBar(boolean splitActionBar) {
+    public void setSplitToolbar(boolean splitActionBar) {
         if (mSplitActionBar != splitActionBar) {
             if (mMenuView != null) {
                 final ViewGroup oldParent = (ViewGroup) mMenuView.getParent();
@@ -336,7 +347,7 @@
                     if (mSplitView != null) {
                         mSplitView.addView(mMenuView);
                     }
-                    mMenuView.getLayoutParams().width = LayoutParams.FILL_PARENT;
+                    mMenuView.getLayoutParams().width = LayoutParams.MATCH_PARENT;
                 } else {
                     addView(mMenuView);
                     mMenuView.getLayoutParams().width = LayoutParams.WRAP_CONTENT;
@@ -361,18 +372,23 @@
                     mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
                 }
             }
-            super.setSplitActionBar(splitActionBar);
+            super.setSplitToolbar(splitActionBar);
         }
     }
 
-    public boolean isSplitActionBar() {
+    public boolean isSplit() {
         return mSplitActionBar;
     }
 
+    public boolean canSplit() {
+        return true;
+    }
+
     public boolean hasEmbeddedTabs() {
         return mIncludeTabs;
     }
 
+    @Override
     public void setEmbeddedTabView(ScrollingTabContainerView tabs) {
         if (mTabScrollView != null) {
             removeView(mTabScrollView);
@@ -383,19 +399,17 @@
             addView(mTabScrollView);
             ViewGroup.LayoutParams lp = mTabScrollView.getLayoutParams();
             lp.width = LayoutParams.WRAP_CONTENT;
-            lp.height = LayoutParams.FILL_PARENT;
+            lp.height = LayoutParams.MATCH_PARENT;
             tabs.setAllowCollapse(true);
         }
     }
 
-    public void setCallback(OnNavigationListener callback) {
-        mCallback = callback;
+    public void setMenuPrepared() {
+        mMenuPrepared = true;
     }
 
-    public void setMenu(SupportMenu menu, MenuPresenter.Callback cb) {
-        if (menu == mOptionsMenu) {
-            return;
-        }
+    public void setMenu(Menu menu, MenuPresenter.Callback cb) {
+        if (menu == mOptionsMenu) return;
 
         if (mOptionsMenu != null) {
             mOptionsMenu.removeMenuPresenter(mActionMenuPresenter);
@@ -419,14 +433,13 @@
 
         ActionMenuView menuView;
         final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
-                LayoutParams.FILL_PARENT);
+                LayoutParams.MATCH_PARENT);
         if (!mSplitActionBar) {
             mActionMenuPresenter.setExpandedActionViewsExclusive(
                     getResources().getBoolean(
                             R.bool.abc_action_bar_expanded_action_views_exclusive));
             configPresenters(builder);
             menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
-            menuView.initialize(builder);
             final ViewGroup oldParent = (ViewGroup) menuView.getParent();
             if (oldParent != null && oldParent != this) {
                 oldParent.removeView(menuView);
@@ -440,7 +453,8 @@
             // No limit to the item count; use whatever will fit.
             mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE);
             // Span the whole width
-            layoutParams.width = LayoutParams.FILL_PARENT;
+            layoutParams.width = LayoutParams.MATCH_PARENT;
+            layoutParams.height = LayoutParams.WRAP_CONTENT;
             configPresenters(builder);
             menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
             if (mSplitView != null) {
@@ -460,16 +474,14 @@
 
     private void configPresenters(MenuBuilder builder) {
         if (builder != null) {
-            builder.addMenuPresenter(mActionMenuPresenter);
-            builder.addMenuPresenter(mExpandedMenuPresenter);
+            builder.addMenuPresenter(mActionMenuPresenter, mPopupContext);
+            builder.addMenuPresenter(mExpandedMenuPresenter, mPopupContext);
         } else {
-            mActionMenuPresenter.initForMenu(mContext, null);
-            mExpandedMenuPresenter.initForMenu(mContext, null);
+            mActionMenuPresenter.initForMenu(mPopupContext, null);
+            mExpandedMenuPresenter.initForMenu(mPopupContext, null);
+            mActionMenuPresenter.updateMenuView(true);
+            mExpandedMenuPresenter.updateMenuView(true);
         }
-
-        // Make sure the Presenter's View is updated
-        mActionMenuPresenter.updateMenuView(true);
-        mExpandedMenuPresenter.updateMenuView(true);
     }
 
     public boolean hasExpandedActionView() {
@@ -485,8 +497,11 @@
         }
     }
 
-    public void setCustomNavigationView(View view) {
+    public void setCustomView(View view) {
         final boolean showCustom = (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0;
+        if (showCustom) {
+            ActionBarTransition.beginDelayedTransition(this);
+        }
         if (mCustomNavView != null && showCustom) {
             removeView(mCustomNavView);
         }
@@ -502,8 +517,8 @@
 
     /**
      * Set the action bar title. This will always replace or override window titles.
-     *
      * @param title Title to set
+     *
      * @see #setWindowTitle(CharSequence)
      */
     public void setTitle(CharSequence title) {
@@ -513,8 +528,8 @@
 
     /**
      * Set the window title. A window title will always be replaced or overridden by a user title.
-     *
      * @param title Title to set
+     *
      * @see #setTitle(CharSequence)
      */
     public void setWindowTitle(CharSequence title) {
@@ -524,6 +539,7 @@
     }
 
     private void setTitleImpl(CharSequence title) {
+        ActionBarTransition.beginDelayedTransition(this);
         mTitle = title;
         if (mTitleView != null) {
             mTitleView.setText(title);
@@ -535,6 +551,7 @@
         if (mLogoNavItem != null) {
             mLogoNavItem.setTitle(title);
         }
+        updateHomeAccessibility(mUpGoerFive.isEnabled());
     }
 
     public CharSequence getSubtitle() {
@@ -542,6 +559,7 @@
     }
 
     public void setSubtitle(CharSequence subtitle) {
+        ActionBarTransition.beginDelayedTransition(this);
         mSubtitle = subtitle;
         if (mSubtitleView != null) {
             mSubtitleView.setText(subtitle);
@@ -551,21 +569,76 @@
                     (!TextUtils.isEmpty(mTitle) || !TextUtils.isEmpty(mSubtitle));
             mTitleLayout.setVisibility(visible ? VISIBLE : GONE);
         }
+        updateHomeAccessibility(mUpGoerFive.isEnabled());
     }
 
     public void setHomeButtonEnabled(boolean enable) {
-        mHomeLayout.setEnabled(enable);
-        mHomeLayout.setFocusable(enable);
-        // Make sure the home button has an accurate content description for accessibility.
-        if (!enable) {
-            mHomeLayout.setContentDescription(null);
-        } else if ((mDisplayOptions & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
-            mHomeLayout.setContentDescription(mContext.getResources().getText(
-                    R.string.abc_action_bar_up_description));
-        } else {
-            mHomeLayout.setContentDescription(mContext.getResources().getText(
-                    R.string.abc_action_bar_home_description));
+        setHomeButtonEnabled(enable, true);
+    }
+
+    private void setHomeButtonEnabled(boolean enable, boolean recordState) {
+        if (recordState) {
+            mWasHomeEnabled = enable;
         }
+
+        if (mExpandedActionView != null) {
+            // There's an action view currently showing and we want to keep the state
+            // configured for the action view at the moment. If we needed to record the
+            // new state for later we will have done so above.
+            return;
+        }
+
+        mUpGoerFive.setEnabled(enable);
+        mUpGoerFive.setFocusable(enable);
+        // Make sure the home button has an accurate content description for accessibility.
+        updateHomeAccessibility(enable);
+    }
+
+    private void updateHomeAccessibility(boolean homeEnabled) {
+        if (!homeEnabled) {
+            mUpGoerFive.setContentDescription(null);
+            ViewCompat.setImportantForAccessibility(mUpGoerFive,
+                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
+        } else {
+            ViewCompat.setImportantForAccessibility(mUpGoerFive,
+                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+            mUpGoerFive.setContentDescription(buildHomeContentDescription());
+        }
+    }
+
+    /**
+     * Compose a content description for the Home/Up affordance.
+     *
+     * <p>As this encompasses the icon/logo, title and subtitle all in one, we need
+     * a description for the whole wad of stuff that can be localized properly.</p>
+     */
+    private CharSequence buildHomeContentDescription() {
+        final CharSequence homeDesc;
+        if (mHomeDescription != null) {
+            homeDesc = mHomeDescription;
+        } else {
+            if ((mDisplayOptions & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
+                homeDesc = mContext.getResources().getText(R.string.abc_action_bar_up_description);
+            } else {
+                homeDesc = mContext.getResources().getText(R.string.abc_action_bar_home_description);
+            }
+        }
+
+        final CharSequence title = getTitle();
+        final CharSequence subtitle = getSubtitle();
+        if (!TextUtils.isEmpty(title)) {
+            final String result;
+            if (!TextUtils.isEmpty(subtitle)) {
+                result = getResources().getString(
+                        R.string.abc_action_bar_home_subtitle_description_format,
+                        title, subtitle, homeDesc);
+            } else {
+                result = getResources().getString(R.string.abc_action_bar_home_description_format,
+                        title, homeDesc);
+            }
+            return result;
+        }
+        return homeDesc;
     }
 
     public void setDisplayOptions(int options) {
@@ -573,13 +646,11 @@
         mDisplayOptions = options;
 
         if ((flagsChanged & DISPLAY_RELAYOUT_MASK) != 0) {
-            final boolean showHome = (options & ActionBar.DISPLAY_SHOW_HOME) != 0;
-            final int vis = showHome && mExpandedActionView == null ? VISIBLE : GONE;
-            mHomeLayout.setVisibility(vis);
+            ActionBarTransition.beginDelayedTransition(this);
 
             if ((flagsChanged & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
                 final boolean setUp = (options & ActionBar.DISPLAY_HOME_AS_UP) != 0;
-                mHomeLayout.setUp(setUp);
+                mHomeLayout.setShowUp(setUp);
 
                 // Showing home as up implicitly enables interaction with it.
                 // In honeycomb it was always enabled, so make this transition
@@ -591,8 +662,7 @@
             }
 
             if ((flagsChanged & ActionBar.DISPLAY_USE_LOGO) != 0) {
-                final boolean logoVis = mLogo != null
-                        && (options & ActionBar.DISPLAY_USE_LOGO) != 0;
+                final boolean logoVis = mLogo != null && (options & ActionBar.DISPLAY_USE_LOGO) != 0;
                 mHomeLayout.setIcon(logoVis ? mLogo : mIcon);
             }
 
@@ -600,16 +670,18 @@
                 if ((options & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
                     initTitle();
                 } else {
-                    removeView(mTitleLayout);
+                    mUpGoerFive.removeView(mTitleLayout);
                 }
             }
 
-            if (mTitleLayout != null && (flagsChanged &
-                    (ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_HOME)) != 0) {
-                final boolean homeAsUp = (mDisplayOptions & ActionBar.DISPLAY_HOME_AS_UP) != 0;
-                mTitleUpView.setVisibility(!showHome ? (homeAsUp ? VISIBLE : INVISIBLE) : GONE);
-                mTitleLayout.setEnabled(!showHome && homeAsUp);
-            }
+            final boolean showHome = (options & ActionBar.DISPLAY_SHOW_HOME) != 0;
+            final boolean homeAsUp = (mDisplayOptions & ActionBar.DISPLAY_HOME_AS_UP) != 0;
+            final boolean titleUp = !showHome && homeAsUp;
+            mHomeLayout.setShowIcon(showHome);
+
+            final int homeVis = (showHome || titleUp) && mExpandedActionView == null ?
+                    VISIBLE : GONE;
+            mHomeLayout.setVisibility(homeVis);
 
             if ((flagsChanged & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomNavView != null) {
                 if ((options & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
@@ -625,15 +697,7 @@
         }
 
         // Make sure the home button has an accurate content description for accessibility.
-        if (!mHomeLayout.isEnabled()) {
-            mHomeLayout.setContentDescription(null);
-        } else if ((options & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
-            mHomeLayout.setContentDescription(mContext.getResources().getText(
-                    R.string.abc_action_bar_up_description));
-        } else {
-            mHomeLayout.setContentDescription(mContext.getResources().getText(
-                    R.string.abc_action_bar_home_description));
-        }
+        updateHomeAccessibility(mUpGoerFive.isEnabled());
     }
 
     public void setIcon(Drawable icon) {
@@ -648,7 +712,11 @@
     }
 
     public void setIcon(int resId) {
-        setIcon(mContext.getResources().getDrawable(resId));
+        setIcon(resId != 0 ? ContextCompat.getDrawable(mContext, resId) : null);
+    }
+
+    public boolean hasIcon() {
+        return mIcon != null;
     }
 
     public void setLogo(Drawable logo) {
@@ -659,62 +727,70 @@
     }
 
     public void setLogo(int resId) {
-        setLogo(mContext.getResources().getDrawable(resId));
+        setLogo(resId != 0 ? ContextCompat.getDrawable(mContext, resId) : null);
+    }
+
+    public boolean hasLogo() {
+        return mLogo != null;
     }
 
     public void setNavigationMode(int mode) {
         final int oldMode = mNavigationMode;
         if (mode != oldMode) {
+            ActionBarTransition.beginDelayedTransition(this);
             switch (oldMode) {
-                case ActionBar.NAVIGATION_MODE_LIST:
-                    if (mListNavLayout != null) {
-                        removeView(mListNavLayout);
-                    }
-                    break;
-                case ActionBar.NAVIGATION_MODE_TABS:
-                    if (mTabScrollView != null && mIncludeTabs) {
-                        removeView(mTabScrollView);
-                    }
+            case ActionBar.NAVIGATION_MODE_LIST:
+                if (mListNavLayout != null) {
+                    removeView(mListNavLayout);
+                }
+                break;
+            case ActionBar.NAVIGATION_MODE_TABS:
+                if (mTabScrollView != null && mIncludeTabs) {
+                    removeView(mTabScrollView);
+                }
             }
 
             switch (mode) {
-                case ActionBar.NAVIGATION_MODE_LIST:
-                    if (mSpinner == null) {
-                        mSpinner = new SpinnerICS(mContext, null,
-                                R.attr.actionDropDownStyle);
-                        mListNavLayout = (LinearLayout) LayoutInflater.from(mContext).inflate(
-                                R.layout.abc_action_bar_view_list_nav_layout, null);
-                        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
-                                LayoutParams.WRAP_CONTENT, LayoutParams.FILL_PARENT);
-                        params.gravity = Gravity.CENTER;
-                        mListNavLayout.addView(mSpinner, params);
-                    }
-                    if (mSpinner.getAdapter() != mSpinnerAdapter) {
-                        mSpinner.setAdapter(mSpinnerAdapter);
-                    }
-                    mSpinner.setOnItemSelectedListener(mNavItemSelectedListener);
-                    addView(mListNavLayout);
-                    break;
-                case ActionBar.NAVIGATION_MODE_TABS:
-                    if (mTabScrollView != null && mIncludeTabs) {
-                        addView(mTabScrollView);
-                    }
-                    break;
+            case ActionBar.NAVIGATION_MODE_LIST:
+                if (mSpinner == null) {
+                    mSpinner = new SpinnerCompat(mContext, null,
+                            R.attr.actionDropDownStyle);
+                    mSpinner.setId(R.id.action_bar_spinner);
+                    mListNavLayout = new LinearLayoutCompat(mContext, null,
+                            R.attr.actionBarTabBarStyle);
+                    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+                            LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+                    params.gravity = Gravity.CENTER;
+                    mListNavLayout.addView(mSpinner, params);
+                }
+                if (mSpinner.getAdapter() != mSpinnerAdapter) {
+                    mSpinner.setAdapter(mSpinnerAdapter);
+                }
+                mSpinner.setOnItemSelectedListener(mNavItemSelectedListener);
+                addView(mListNavLayout);
+                break;
+            case ActionBar.NAVIGATION_MODE_TABS:
+                if (mTabScrollView != null && mIncludeTabs) {
+                    addView(mTabScrollView);
+                }
+                break;
             }
             mNavigationMode = mode;
             requestLayout();
         }
     }
 
-    public void setDropdownAdapter(SpinnerAdapter adapter) {
+    public void setDropdownParams(SpinnerAdapter adapter, AdapterViewCompat.OnItemSelectedListener l) {
         mSpinnerAdapter = adapter;
+        mNavItemSelectedListener = l;
         if (mSpinner != null) {
             mSpinner.setAdapter(adapter);
+            mSpinner.setOnItemSelectedListener(l);
         }
     }
 
-    public SpinnerAdapter getDropdownAdapter() {
-        return mSpinnerAdapter;
+    public int getDropdownItemCount() {
+        return mSpinnerAdapter != null ? mSpinnerAdapter.getCount() : 0;
     }
 
     public void setDropdownSelectedPosition(int position) {
@@ -725,7 +801,7 @@
         return mSpinner.getSelectedItemPosition();
     }
 
-    public View getCustomNavigationView() {
+    public View getCustomView() {
         return mCustomNavView;
     }
 
@@ -738,6 +814,11 @@
     }
 
     @Override
+    public ViewGroup getViewGroup() {
+        return this;
+    }
+
+    @Override
     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
         // Used by custom nav views if they don't supply layout params. Everything else
         // added to an ActionBarView should have them already.
@@ -748,7 +829,8 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        addView(mHomeLayout);
+        mUpGoerFive.addView(mHomeLayout, 0);
+        addView(mUpGoerFive);
 
         if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
             final ViewParent parent = mCustomNavView.getParent();
@@ -768,9 +850,6 @@
                     this, false);
             mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title);
             mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle);
-            mTitleUpView = (View) mTitleLayout.findViewById(R.id.up);
-
-            mTitleLayout.setOnClickListener(mUpClickListener);
 
             if (mTitleStyleRes != 0) {
                 mTitleView.setTextAppearance(mContext, mTitleStyleRes);
@@ -786,18 +865,16 @@
                 mSubtitleView.setText(mSubtitle);
                 mSubtitleView.setVisibility(VISIBLE);
             }
-
-            final boolean homeAsUp = (mDisplayOptions & ActionBar.DISPLAY_HOME_AS_UP) != 0;
-            final boolean showHome = (mDisplayOptions & ActionBar.DISPLAY_SHOW_HOME) != 0;
-            mTitleUpView.setVisibility(!showHome ? (homeAsUp ? VISIBLE : INVISIBLE) : GONE);
-            mTitleLayout.setEnabled(homeAsUp && !showHome);
         }
 
-        addView(mTitleLayout);
+        ActionBarTransition.beginDelayedTransition(this);
+        mUpGoerFive.addView(mTitleLayout);
         if (mExpandedActionView != null ||
                 (TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mSubtitle))) {
             // Don't show while in expanded mode or with empty text
             mTitleLayout.setVisibility(GONE);
+        } else {
+            mTitleLayout.setVisibility(VISIBLE);
         }
     }
 
@@ -805,23 +882,50 @@
         mContextView = view;
     }
 
-    public void setCollapsable(boolean collapsable) {
-        mIsCollapsable = collapsable;
+    public void setCollapsible(boolean collapsible) {
+        mIsCollapsible = collapsible;
     }
 
-    public boolean isCollapsed() {
-        return mIsCollapsed;
+    /**
+     * @return True if any characters in the title were truncated
+     */
+    public boolean isTitleTruncated() {
+        if (mTitleView == null) {
+            return false;
+        }
+
+        final Layout titleLayout = mTitleView.getLayout();
+        if (titleLayout == null) {
+            return false;
+        }
+
+        final int lineCount = titleLayout.getLineCount();
+        for (int i = 0; i < lineCount; i++) {
+            if (titleLayout.getEllipsisCount(i) > 0) {
+                return true;
+            }
+        }
+        return false;
     }
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         final int childCount = getChildCount();
-        if (mIsCollapsable) {
+        if (mIsCollapsible) {
             int visibleChildren = 0;
             for (int i = 0; i < childCount; i++) {
                 final View child = getChildAt(i);
                 if (child.getVisibility() != GONE &&
-                        !(child == mMenuView && mMenuView.getChildCount() == 0)) {
+                        !(child == mMenuView && mMenuView.getChildCount() == 0) &&
+                        child != mUpGoerFive) {
+                    visibleChildren++;
+                }
+            }
+
+            final int upChildCount = mUpGoerFive.getChildCount();
+            for (int i = 0; i < upChildCount; i++) {
+                final View child = mUpGoerFive.getChildAt(i);
+                if (child.getVisibility() != GONE) {
                     visibleChildren++;
                 }
             }
@@ -829,16 +933,14 @@
             if (visibleChildren == 0) {
                 // No size for an empty action bar when collapsable.
                 setMeasuredDimension(0, 0);
-                mIsCollapsed = true;
                 return;
             }
         }
-        mIsCollapsed = false;
 
         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
         if (widthMode != MeasureSpec.EXACTLY) {
             throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
-                    "with android:layout_width=\"MATCH_PARENT\" (or fill_parent)");
+                    "with android:layout_width=\"match_parent\" (or fill_parent)");
         }
 
         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
@@ -849,7 +951,7 @@
 
         int contentWidth = MeasureSpec.getSize(widthMeasureSpec);
 
-        int maxHeight = mContentHeight > 0 ?
+        int maxHeight = mContentHeight >= 0 ?
                 mContentHeight : MeasureSpec.getSize(heightMeasureSpec);
 
         final int verticalPadding = getPaddingTop() + getPaddingBottom();
@@ -857,31 +959,46 @@
         final int paddingRight = getPaddingRight();
         final int height = maxHeight - verticalPadding;
         final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
+        final int exactHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
 
         int availableWidth = contentWidth - paddingLeft - paddingRight;
         int leftOfCenter = availableWidth / 2;
         int rightOfCenter = leftOfCenter;
 
+        final boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
+                (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
+
         HomeView homeLayout = mExpandedActionView != null ? mExpandedHomeLayout : mHomeLayout;
 
-        if (homeLayout.getVisibility() != GONE) {
-            final ViewGroup.LayoutParams lp = homeLayout.getLayoutParams();
-            int homeWidthSpec;
-            if (lp.width < 0) {
-                homeWidthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST);
-            } else {
-                homeWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
-            }
-            homeLayout.measure(homeWidthSpec,
-                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
-            final int homeWidth = homeLayout.getMeasuredWidth() + homeLayout.getLeftOffset();
-            availableWidth = Math.max(0, availableWidth - homeWidth);
-            leftOfCenter = Math.max(0, availableWidth - homeWidth);
+        final ViewGroup.LayoutParams homeLp = homeLayout.getLayoutParams();
+        int homeWidthSpec;
+        if (homeLp.width < 0) {
+            homeWidthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST);
+        } else {
+            homeWidthSpec = MeasureSpec.makeMeasureSpec(homeLp.width, MeasureSpec.EXACTLY);
+        }
+
+        /*
+         * This is a little weird.
+         * We're only measuring the *home* affordance within the Up container here
+         * on purpose, because we want to give the available space to all other views before
+         * the title text. We'll remeasure the whole up container again later.
+         * We need to measure this container so we know the right offset for the up affordance
+         * no matter what.
+         */
+        homeLayout.measure(homeWidthSpec, exactHeightSpec);
+
+        int homeWidth = 0;
+        if ((homeLayout.getVisibility() != GONE && homeLayout.getParent() == mUpGoerFive)
+                || showTitle) {
+            homeWidth = homeLayout.getMeasuredWidth();
+            final int homeOffsetWidth = homeWidth + homeLayout.getStartOffset();
+            availableWidth = Math.max(0, availableWidth - homeOffsetWidth);
+            leftOfCenter = Math.max(0, availableWidth - homeOffsetWidth);
         }
 
         if (mMenuView != null && mMenuView.getParent() == this) {
-            availableWidth = measureChildView(mMenuView, availableWidth,
-                    childSpecHeight, 0);
+            availableWidth = measureChildView(mMenuView, availableWidth, exactHeightSpec, 0);
             rightOfCenter = Math.max(0, rightOfCenter - mMenuView.getMeasuredWidth());
         }
 
@@ -893,9 +1010,6 @@
                     rightOfCenter - mIndeterminateProgressView.getMeasuredWidth());
         }
 
-        final boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
-                (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
-
         if (mExpandedActionView == null) {
             switch (mNavigationMode) {
                 case ActionBar.NAVIGATION_MODE_LIST:
@@ -948,7 +1062,7 @@
             }
 
             // If the action bar is wrapping to its content height, don't allow a custom
-            // view to FILL_PARENT.
+            // view to MATCH_PARENT.
             int customNavHeightMode;
             if (mContentHeight <= 0) {
                 customNavHeightMode = MeasureSpec.AT_MOST;
@@ -963,13 +1077,13 @@
                     MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
             int customNavWidth = Math.max(0,
                     (lp.width >= 0 ? Math.min(lp.width, availableWidth) : availableWidth)
-                            - horizontalMargin);
+                    - horizontalMargin);
             final int hgrav = (ablp != null ? ablp.gravity : DEFAULT_CUSTOM_GRAVITY) &
                     Gravity.HORIZONTAL_GRAVITY_MASK;
 
             // Centering a custom view is treated specially; we try to center within the whole
             // action bar rather than in the available space.
-            if (hgrav == Gravity.CENTER_HORIZONTAL && lp.width == LayoutParams.FILL_PARENT) {
+            if (hgrav == Gravity.CENTER_HORIZONTAL && lp.width == LayoutParams.MATCH_PARENT) {
                 customNavWidth = Math.min(leftOfCenter, rightOfCenter) * 2;
             }
 
@@ -979,9 +1093,13 @@
             availableWidth -= horizontalMargin + customView.getMeasuredWidth();
         }
 
-        if (mExpandedActionView == null && showTitle) {
-            availableWidth = measureChildView(mTitleLayout, availableWidth,
-                    MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.EXACTLY), 0);
+        /*
+         * Measure the whole up container now, allowing for the full home+title sections.
+         * (This will re-measure the home view.)
+         */
+        availableWidth = measureChildView(mUpGoerFive, availableWidth + homeWidth,
+                MeasureSpec.makeMeasureSpec(mContentHeight, MeasureSpec.EXACTLY), 0);
+        if (mTitleLayout != null) {
             leftOfCenter = Math.max(0, leftOfCenter - mTitleLayout.getMeasuredWidth());
         }
 
@@ -1012,8 +1130,6 @@
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        int x = getPaddingLeft();
-        final int y = getPaddingTop();
         final int contentHeight = b - t - getPaddingTop() - getPaddingBottom();
 
         if (contentHeight <= 0) {
@@ -1021,52 +1137,63 @@
             return;
         }
 
+        final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this);
+        final int direction = isLayoutRtl ? 1 : -1;
+        int menuStart = isLayoutRtl ? getPaddingLeft() : r - l - getPaddingRight();
+        // In LTR mode, we start from left padding and go to the right; in RTL mode, we start
+        // from the padding right and go to the left (in reverse way)
+        int x = isLayoutRtl ? r - l - getPaddingRight() : getPaddingLeft();
+        final int y = getPaddingTop();
+
         HomeView homeLayout = mExpandedActionView != null ? mExpandedHomeLayout : mHomeLayout;
-        if (homeLayout.getVisibility() != GONE) {
-            final int leftOffset = homeLayout.getLeftOffset();
-            x += positionChild(homeLayout, x + leftOffset, y, contentHeight) + leftOffset;
+        final boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
+                (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
+        int startOffset = 0;
+        if (homeLayout.getParent() == mUpGoerFive) {
+            if (homeLayout.getVisibility() != GONE) {
+                startOffset = homeLayout.getStartOffset();
+            } else if (showTitle) {
+                startOffset = homeLayout.getUpWidth();
+            }
         }
 
-        if (mExpandedActionView == null) {
-            final boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE
-                    &&
-                    (mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
-            if (showTitle) {
-                x += positionChild(mTitleLayout, x, y, contentHeight);
-            }
+        // Position the up container based on where the edge of the home layout should go.
+        x += positionChild(mUpGoerFive,
+                next(x, startOffset, isLayoutRtl), y, contentHeight, isLayoutRtl);
+        x = next(x, startOffset, isLayoutRtl);
 
+        if (mExpandedActionView == null) {
             switch (mNavigationMode) {
                 case ActionBar.NAVIGATION_MODE_STANDARD:
                     break;
                 case ActionBar.NAVIGATION_MODE_LIST:
                     if (mListNavLayout != null) {
                         if (showTitle) {
-                            x += mItemPadding;
+                            x = next(x, mItemPadding, isLayoutRtl);
                         }
-                        x += positionChild(mListNavLayout, x, y, contentHeight) + mItemPadding;
+                        x += positionChild(mListNavLayout, x, y, contentHeight, isLayoutRtl);
+                        x = next(x, mItemPadding, isLayoutRtl);
                     }
                     break;
                 case ActionBar.NAVIGATION_MODE_TABS:
                     if (mTabScrollView != null) {
-                        if (showTitle) {
-                            x += mItemPadding;
-                        }
-                        x += positionChild(mTabScrollView, x, y, contentHeight) + mItemPadding;
+                        if (showTitle) x = next(x, mItemPadding, isLayoutRtl);
+                        x += positionChild(mTabScrollView, x, y, contentHeight, isLayoutRtl);
+                        x = next(x, mItemPadding, isLayoutRtl);
                     }
                     break;
             }
         }
 
-        int menuLeft = r - l - getPaddingRight();
         if (mMenuView != null && mMenuView.getParent() == this) {
-            positionChildInverse(mMenuView, menuLeft, y, contentHeight);
-            menuLeft -= mMenuView.getMeasuredWidth();
+            positionChild(mMenuView, menuStart, y, contentHeight, !isLayoutRtl);
+            menuStart += direction * mMenuView.getMeasuredWidth();
         }
 
         if (mIndeterminateProgressView != null &&
                 mIndeterminateProgressView.getVisibility() != GONE) {
-            positionChildInverse(mIndeterminateProgressView, menuLeft, y, contentHeight);
-            menuLeft -= mIndeterminateProgressView.getMeasuredWidth();
+            positionChild(mIndeterminateProgressView, menuStart, y, contentHeight, !isLayoutRtl);
+            menuStart += direction * mIndeterminateProgressView.getMeasuredWidth();
         }
 
         View customView = null;
@@ -1077,51 +1204,63 @@
             customView = mCustomNavView;
         }
         if (customView != null) {
+            final int layoutDirection = ViewCompat.getLayoutDirection(this);
             ViewGroup.LayoutParams lp = customView.getLayoutParams();
             final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ?
                     (ActionBar.LayoutParams) lp : null;
-
             final int gravity = ablp != null ? ablp.gravity : DEFAULT_CUSTOM_GRAVITY;
             final int navWidth = customView.getMeasuredWidth();
 
             int topMargin = 0;
             int bottomMargin = 0;
             if (ablp != null) {
-                x += ablp.leftMargin;
-                menuLeft -= ablp.rightMargin;
+                x = next(x, MarginLayoutParamsCompat.getMarginStart(ablp), isLayoutRtl);
+                menuStart += direction * MarginLayoutParamsCompat.getMarginEnd(ablp);
                 topMargin = ablp.topMargin;
                 bottomMargin = ablp.bottomMargin;
             }
 
-            int hgravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+            int hgravity = gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
             // See if we actually have room to truly center; if not push against left or right.
             if (hgravity == Gravity.CENTER_HORIZONTAL) {
-                final int centeredLeft = (getWidth() - navWidth) / 2;
-                if (centeredLeft < x) {
-                    hgravity = Gravity.LEFT;
-                } else if (centeredLeft + navWidth > menuLeft) {
-                    hgravity = Gravity.RIGHT;
+                final int centeredLeft = ((getRight() - getLeft()) - navWidth) / 2;
+                if (isLayoutRtl) {
+                    final int centeredStart = centeredLeft + navWidth;
+                    final int centeredEnd = centeredLeft;
+                    if (centeredStart > x) {
+                        hgravity = Gravity.RIGHT;
+                    } else if (centeredEnd < menuStart) {
+                        hgravity = Gravity.LEFT;
+                    }
+                } else {
+                    final int centeredStart = centeredLeft;
+                    final int centeredEnd = centeredLeft + navWidth;
+                    if (centeredStart < x) {
+                        hgravity = Gravity.LEFT;
+                    } else if (centeredEnd > menuStart) {
+                        hgravity = Gravity.RIGHT;
+                    }
                 }
-            } else if (gravity == -1) {
-                hgravity = Gravity.LEFT;
+            } else if (gravity == Gravity.NO_GRAVITY) {
+                hgravity = Gravity.START;
             }
 
             int xpos = 0;
-            switch (hgravity) {
+            switch (GravityCompat.getAbsoluteGravity(hgravity, layoutDirection)) {
                 case Gravity.CENTER_HORIZONTAL:
-                    xpos = (getWidth() - navWidth) / 2;
+                    xpos = ((getRight() - getLeft()) - navWidth) / 2;
                     break;
                 case Gravity.LEFT:
-                    xpos = x;
+                    xpos = isLayoutRtl ? menuStart : x;
                     break;
                 case Gravity.RIGHT:
-                    xpos = menuLeft - navWidth;
+                    xpos = isLayoutRtl ? x - navWidth : menuStart - navWidth;
                     break;
             }
 
             int vgravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
 
-            if (gravity == -1) {
+            if (gravity == Gravity.NO_GRAVITY) {
                 vgravity = Gravity.CENTER_VERTICAL;
             }
 
@@ -1129,7 +1268,7 @@
             switch (vgravity) {
                 case Gravity.CENTER_VERTICAL:
                     final int paddedTop = getPaddingTop();
-                    final int paddedBottom = getHeight() - getPaddingBottom();
+                    final int paddedBottom = getBottom() - getTop() - getPaddingBottom();
                     ypos = ((paddedBottom - paddedTop) - customView.getMeasuredHeight()) / 2;
                     break;
                 case Gravity.TOP:
@@ -1143,7 +1282,7 @@
             final int customWidth = customView.getMeasuredWidth();
             customView.layout(xpos, ypos, xpos + customWidth,
                     ypos + customView.getMeasuredHeight());
-            x += customWidth;
+            x = next(x, customWidth, isLayoutRtl);
         }
 
         if (mProgressView != null) {
@@ -1201,16 +1340,26 @@
         }
     }
 
-    public void setHomeAsUpIndicator(Drawable indicator) {
+    public void setNavigationIcon(Drawable indicator) {
         mHomeLayout.setUpIndicator(indicator);
     }
 
-    public void setHomeAsUpIndicator(int resId) {
+    public void setNavigationIcon(int resId) {
         mHomeLayout.setUpIndicator(resId);
     }
 
-    static class SavedState extends BaseSavedState {
+    public void setNavigationContentDescription(CharSequence description) {
+        mHomeDescription = description;
+        updateHomeAccessibility(mUpGoerFive.isEnabled());
+    }
 
+    public void setNavigationContentDescription(int resId) {
+        mHomeDescriptionRes = resId;
+        mHomeDescription = resId != 0 ? getResources().getText(resId) : null;
+        updateHomeAccessibility(mUpGoerFive.isEnabled());
+    }
+
+    static class SavedState extends BaseSavedState {
         int expandedMenuItemId;
         boolean isOverflowOpen;
 
@@ -1233,35 +1382,48 @@
 
         public static final Parcelable.Creator<SavedState> CREATOR =
                 new Parcelable.Creator<SavedState>() {
-                    public SavedState createFromParcel(Parcel in) {
-                        return new SavedState(in);
-                    }
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
 
-                    public SavedState[] newArray(int size) {
-                        return new SavedState[size];
-                    }
-                };
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
     }
 
     private static class HomeView extends FrameLayout {
         private ImageView mUpView;
         private ImageView mIconView;
         private int mUpWidth;
+        private int mStartOffset;
         private int mUpIndicatorRes;
         private Drawable mDefaultUpIndicator;
 
+        private static final long DEFAULT_TRANSITION_DURATION = 150;
+
         public HomeView(Context context) {
             this(context, null);
         }
 
         public HomeView(Context context, AttributeSet attrs) {
             super(context, attrs);
+            // TODO:
+//            LayoutTransition t = getLayoutTransition();
+//            if (t != null) {
+//                // Set a lower duration than the default
+//                t.setDuration(DEFAULT_TRANSITION_DURATION);
+//            }
         }
 
-        public void setUp(boolean isUp) {
+        public void setShowUp(boolean isUp) {
             mUpView.setVisibility(isUp ? VISIBLE : GONE);
         }
 
+        public void setShowIcon(boolean showIcon) {
+            mIconView.setVisibility(showIcon ? VISIBLE : GONE);
+        }
+
         public void setIcon(Drawable icon) {
             mIconView.setImageDrawable(icon);
         }
@@ -1273,13 +1435,15 @@
 
         public void setUpIndicator(int resId) {
             mUpIndicatorRes = resId;
-            mUpView.setImageDrawable(resId != 0 ? getResources().getDrawable(resId)
-                    : mDefaultUpIndicator);
+            mUpView.setImageDrawable(resId != 0
+                    ? ContextCompat.getDrawable(getContext(), resId)
+                    : null);
         }
 
-        @Override
         protected void onConfigurationChanged(Configuration newConfig) {
-            super.onConfigurationChanged(newConfig);
+            if (Build.VERSION.SDK_INT >= 8) {
+                super.onConfigurationChanged(newConfig);
+            }
             if (mUpIndicatorRes != 0) {
                 // Reload for config change
                 setUpIndicator(mUpIndicatorRes);
@@ -1288,11 +1452,26 @@
 
         @Override
         public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+            onPopulateAccessibilityEvent(event);
+            return true;
+        }
+
+        public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+            if (Build.VERSION.SDK_INT >= 14) {
+                super.onPopulateAccessibilityEvent(event);
+            }
             final CharSequence cdesc = getContentDescription();
             if (!TextUtils.isEmpty(cdesc)) {
                 event.getText().add(cdesc);
             }
-            return true;
+        }
+
+        public boolean dispatchHoverEvent(MotionEvent event) {
+            if (Build.VERSION.SDK_INT >= 14) {
+                // Don't allow children to hover; we want this to be treated as a single component.
+                return onHoverEvent(event);
+            }
+            return false;
         }
 
         @Override
@@ -1302,22 +1481,34 @@
             mDefaultUpIndicator = mUpView.getDrawable();
         }
 
-        public int getLeftOffset() {
-            return mUpView.getVisibility() == GONE ? mUpWidth : 0;
+        public int getStartOffset() {
+            return mUpView.getVisibility() == GONE ? mStartOffset : 0;
+        }
+
+        public int getUpWidth() {
+            return mUpWidth;
         }
 
         @Override
         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
             measureChildWithMargins(mUpView, widthMeasureSpec, 0, heightMeasureSpec, 0);
             final LayoutParams upLp = (LayoutParams) mUpView.getLayoutParams();
-            mUpWidth = upLp.leftMargin + mUpView.getMeasuredWidth() + upLp.rightMargin;
-            int width = mUpView.getVisibility() == GONE ? 0 : mUpWidth;
+            final int upMargins = upLp.leftMargin + upLp.rightMargin;
+            mUpWidth = mUpView.getMeasuredWidth();
+            mStartOffset = mUpWidth + upMargins;
+            int width = mUpView.getVisibility() == GONE ? 0 : mStartOffset;
             int height = upLp.topMargin + mUpView.getMeasuredHeight() + upLp.bottomMargin;
-            measureChildWithMargins(mIconView, widthMeasureSpec, width, heightMeasureSpec, 0);
-            final LayoutParams iconLp = (LayoutParams) mIconView.getLayoutParams();
-            width += iconLp.leftMargin + mIconView.getMeasuredWidth() + iconLp.rightMargin;
-            height = Math.max(height,
-                    iconLp.topMargin + mIconView.getMeasuredHeight() + iconLp.bottomMargin);
+
+            if (mIconView.getVisibility() != GONE) {
+                measureChildWithMargins(mIconView, widthMeasureSpec, width, heightMeasureSpec, 0);
+                final LayoutParams iconLp = (LayoutParams) mIconView.getLayoutParams();
+                width += iconLp.leftMargin + mIconView.getMeasuredWidth() + iconLp.rightMargin;
+                height = Math.max(height,
+                        iconLp.topMargin + mIconView.getMeasuredHeight() + iconLp.bottomMargin);
+            } else if (upMargins < 0) {
+                // Remove the measurement effects of negative margins used for offsets
+                width -= upMargins;
+            }
 
             final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
             final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
@@ -1352,30 +1543,52 @@
         @Override
         protected void onLayout(boolean changed, int l, int t, int r, int b) {
             final int vCenter = (b - t) / 2;
-            int width = r - l;
+            final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this);
+            final int width = getWidth();
             int upOffset = 0;
             if (mUpView.getVisibility() != GONE) {
                 final LayoutParams upLp = (LayoutParams) mUpView.getLayoutParams();
                 final int upHeight = mUpView.getMeasuredHeight();
                 final int upWidth = mUpView.getMeasuredWidth();
-                final int upTop = vCenter - upHeight / 2;
-                mUpView.layout(0, upTop, upWidth, upTop + upHeight);
                 upOffset = upLp.leftMargin + upWidth + upLp.rightMargin;
-                width -= upOffset;
-                l += upOffset;
+                final int upTop = vCenter - upHeight / 2;
+                final int upBottom = upTop + upHeight;
+                final int upRight;
+                final int upLeft;
+                if (isLayoutRtl) {
+                    upRight = width;
+                    upLeft = upRight - upWidth;
+                    r -= upOffset;
+                } else {
+                    upRight = upWidth;
+                    upLeft = 0;
+                    l += upOffset;
+                }
+                mUpView.layout(upLeft, upTop, upRight, upBottom);
             }
+
             final LayoutParams iconLp = (LayoutParams) mIconView.getLayoutParams();
             final int iconHeight = mIconView.getMeasuredHeight();
             final int iconWidth = mIconView.getMeasuredWidth();
             final int hCenter = (r - l) / 2;
-            final int iconLeft = upOffset + Math.max(iconLp.leftMargin, hCenter - iconWidth / 2);
             final int iconTop = Math.max(iconLp.topMargin, vCenter - iconHeight / 2);
-            mIconView.layout(iconLeft, iconTop, iconLeft + iconWidth, iconTop + iconHeight);
+            final int iconBottom = iconTop + iconHeight;
+            final int iconLeft;
+            final int iconRight;
+            int marginStart = MarginLayoutParamsCompat.getMarginStart(iconLp);
+            final int delta = Math.max(marginStart, hCenter - iconWidth / 2);
+            if (isLayoutRtl) {
+                iconRight = width - upOffset - delta;
+                iconLeft = iconRight - iconWidth;
+            } else {
+                iconLeft = upOffset + delta;
+                iconRight = iconLeft + iconWidth;
+            }
+            mIconView.layout(iconLeft, iconTop, iconRight, iconBottom);
         }
     }
 
     private class ExpandedActionViewMenuPresenter implements MenuPresenter {
-
         MenuBuilder mMenu;
         MenuItemImpl mCurrentExpandedItem;
 
@@ -1437,28 +1650,23 @@
 
         @Override
         public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+            ActionBarTransition.beginDelayedTransition(ActionBarView.this);
+
             mExpandedActionView = item.getActionView();
             mExpandedHomeLayout.setIcon(mIcon.getConstantState().newDrawable(getResources()));
             mCurrentExpandedItem = item;
             if (mExpandedActionView.getParent() != ActionBarView.this) {
                 addView(mExpandedActionView);
             }
-            if (mExpandedHomeLayout.getParent() != ActionBarView.this) {
-                addView(mExpandedHomeLayout);
+            if (mExpandedHomeLayout.getParent() != mUpGoerFive) {
+                mUpGoerFive.addView(mExpandedHomeLayout);
             }
             mHomeLayout.setVisibility(GONE);
-            if (mTitleLayout != null) {
-                mTitleLayout.setVisibility(GONE);
-            }
-            if (mTabScrollView != null) {
-                mTabScrollView.setVisibility(GONE);
-            }
-            if (mSpinner != null) {
-                mSpinner.setVisibility(GONE);
-            }
-            if (mCustomNavView != null) {
-                mCustomNavView.setVisibility(GONE);
-            }
+            if (mTitleLayout != null) mTitleLayout.setVisibility(GONE);
+            if (mTabScrollView != null) mTabScrollView.setVisibility(GONE);
+            if (mSpinner != null) mSpinner.setVisibility(GONE);
+            if (mCustomNavView != null) mCustomNavView.setVisibility(GONE);
+            setHomeButtonEnabled(false, false);
             requestLayout();
             item.setActionViewExpanded(true);
 
@@ -1471,6 +1679,8 @@
 
         @Override
         public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+            ActionBarTransition.beginDelayedTransition(ActionBarView.this);
+
             // Do this before detaching the actionview from the hierarchy, in case
             // it needs to dismiss the soft keyboard, etc.
             if (mExpandedActionView instanceof CollapsibleActionView) {
@@ -1478,7 +1688,7 @@
             }
 
             removeView(mExpandedActionView);
-            removeView(mExpandedHomeLayout);
+            mUpGoerFive.removeView(mExpandedHomeLayout);
             mExpandedActionView = null;
             if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_HOME) != 0) {
                 mHomeLayout.setVisibility(VISIBLE);
@@ -1490,17 +1700,13 @@
                     mTitleLayout.setVisibility(VISIBLE);
                 }
             }
-            if (mTabScrollView != null && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
-                mTabScrollView.setVisibility(VISIBLE);
-            }
-            if (mSpinner != null && mNavigationMode == ActionBar.NAVIGATION_MODE_LIST) {
-                mSpinner.setVisibility(VISIBLE);
-            }
-            if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
-                mCustomNavView.setVisibility(VISIBLE);
-            }
+            if (mTabScrollView != null) mTabScrollView.setVisibility(VISIBLE);
+            if (mSpinner != null) mSpinner.setVisibility(VISIBLE);
+            if (mCustomNavView != null) mCustomNavView.setVisibility(VISIBLE);
+
             mExpandedHomeLayout.setIcon(null);
             mCurrentExpandedItem = null;
+            setHomeButtonEnabled(mWasHomeEnabled); // Set by expandItemActionView above
             requestLayout();
             item.setActionViewExpanded(false);
 
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ActivityChooserModel.java b/v7/appcompat/src/android/support/v7/internal/widget/ActivityChooserModel.java
index 5bcafe8..d97f8e7 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/ActivityChooserModel.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ActivityChooserModel.java
@@ -23,6 +23,7 @@
 import android.database.DataSetObservable;
 import android.os.AsyncTask;
 import android.os.Build;
+import android.support.v4.os.AsyncTaskCompat;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Xml;
@@ -574,24 +575,11 @@
         }
         mHistoricalRecordsChanged = false;
         if (!TextUtils.isEmpty(mHistoryFileName)) {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
-                executePersistHistoryAsyncTaskSDK11();
-            } else {
-                executePersistHistoryAsyncTaskBase();
-            }
+            AsyncTaskCompat.executeParallel(new PersistHistoryAsyncTask(),
+                    mHistoricalRecords, mHistoryFileName);
         }
     }
 
-    private void executePersistHistoryAsyncTaskBase() {
-        new PersistHistoryAsyncTask().execute(new ArrayList<HistoricalRecord>(mHistoricalRecords),
-                mHistoryFileName);
-    }
-
-    private void executePersistHistoryAsyncTaskSDK11() {
-        new PersistHistoryAsyncTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
-                new ArrayList<HistoricalRecord>(mHistoricalRecords), mHistoryFileName);
-    }
-
     /**
      * Sets the sorter for ordering activities based on historical data and an intent.
      *
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ActivityChooserView.java b/v7/appcompat/src/android/support/v7/internal/widget/ActivityChooserView.java
index cccdb2c..4d751b8 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/ActivityChooserView.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ActivityChooserView.java
@@ -26,6 +26,7 @@
 import android.graphics.drawable.Drawable;
 import android.support.v4.view.ActionProvider;
 import android.support.v7.appcompat.R;
+import android.support.v7.widget.ListPopupWindow;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/AdapterViewCompat.java b/v7/appcompat/src/android/support/v7/internal/widget/AdapterViewCompat.java
new file mode 100644
index 0000000..bdce3c8
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/AdapterViewCompat.java
@@ -0,0 +1,1150 @@
+package android.support.v7.internal.widget;
+
+/*
+ * Copyright (C) 2006 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.
+ */
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.Adapter;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+
+/**
+ * An AdapterView is a view whose children are determined by an {@link android.widget.Adapter}.
+ *
+ * <p>
+ * See {@link ListView}, {@link android.widget.GridView}, {@link android.widget.Spinner} and
+ *      {@link android.widget.Gallery} for commonly used subclasses of AdapterView.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using AdapterView, read the
+ * <a href="{@docRoot}guide/topics/ui/binding.html">Binding to Data with AdapterView</a>
+ * developer guide.</p></div>
+ *
+ * @hide
+ */
+public abstract class AdapterViewCompat<T extends Adapter> extends ViewGroup {
+
+    /**
+     * The item view type returned by {@link Adapter#getItemViewType(int)} when
+     * the adapter does not want the item's view recycled.
+     */
+    static final int ITEM_VIEW_TYPE_IGNORE = -1;
+
+    /**
+     * The item view type returned by {@link Adapter#getItemViewType(int)} when
+     * the item is a header or footer.
+     */
+    static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2;
+
+    /**
+     * The position of the first child displayed
+     */
+    @ViewDebug.ExportedProperty(category = "scrolling")
+    int mFirstPosition = 0;
+
+    /**
+     * The offset in pixels from the top of the AdapterView to the top
+     * of the view to select during the next layout.
+     */
+    int mSpecificTop;
+
+    /**
+     * Position from which to start looking for mSyncRowId
+     */
+    int mSyncPosition;
+
+    /**
+     * Row id to look for when data has changed
+     */
+    long mSyncRowId = INVALID_ROW_ID;
+
+    /**
+     * Height of the view when mSyncPosition and mSyncRowId where set
+     */
+    long mSyncHeight;
+
+    /**
+     * True if we need to sync to mSyncRowId
+     */
+    boolean mNeedSync = false;
+
+    /**
+     * Indicates whether to sync based on the selection or position. Possible
+     * values are {@link #SYNC_SELECTED_POSITION} or
+     * {@link #SYNC_FIRST_POSITION}.
+     */
+    int mSyncMode;
+
+    /**
+     * Our height after the last layout
+     */
+    private int mLayoutHeight;
+
+    /**
+     * Sync based on the selected child
+     */
+    static final int SYNC_SELECTED_POSITION = 0;
+
+    /**
+     * Sync based on the first child displayed
+     */
+    static final int SYNC_FIRST_POSITION = 1;
+
+    /**
+     * Maximum amount of time to spend in {@link #findSyncPosition()}
+     */
+    static final int SYNC_MAX_DURATION_MILLIS = 100;
+
+    /**
+     * Indicates that this view is currently being laid out.
+     */
+    boolean mInLayout = false;
+
+    /**
+     * The listener that receives notifications when an item is selected.
+     */
+    OnItemSelectedListener mOnItemSelectedListener;
+
+    /**
+     * The listener that receives notifications when an item is clicked.
+     */
+    OnItemClickListener mOnItemClickListener;
+
+    /**
+     * The listener that receives notifications when an item is long clicked.
+     */
+    OnItemLongClickListener mOnItemLongClickListener;
+
+    /**
+     * True if the data has changed since the last layout
+     */
+    boolean mDataChanged;
+
+    /**
+     * The position within the adapter's data set of the item to select
+     * during the next layout.
+     */
+    @ViewDebug.ExportedProperty(category = "list")
+    int mNextSelectedPosition = INVALID_POSITION;
+
+    /**
+     * The item id of the item to select during the next layout.
+     */
+    long mNextSelectedRowId = INVALID_ROW_ID;
+
+    /**
+     * The position within the adapter's data set of the currently selected item.
+     */
+    @ViewDebug.ExportedProperty(category = "list")
+    int mSelectedPosition = INVALID_POSITION;
+
+    /**
+     * The item id of the currently selected item.
+     */
+    long mSelectedRowId = INVALID_ROW_ID;
+
+    /**
+     * View to show if there are no items to show.
+     */
+    private View mEmptyView;
+
+    /**
+     * The number of items in the current adapter.
+     */
+    @ViewDebug.ExportedProperty(category = "list")
+    int mItemCount;
+
+    /**
+     * The number of items in the adapter before a data changed event occurred.
+     */
+    int mOldItemCount;
+
+    /**
+     * Represents an invalid position. All valid positions are in the range 0 to 1 less than the
+     * number of items in the current adapter.
+     */
+    public static final int INVALID_POSITION = -1;
+
+    /**
+     * Represents an empty or invalid row id
+     */
+    public static final long INVALID_ROW_ID = Long.MIN_VALUE;
+
+    /**
+     * The last selected position we used when notifying
+     */
+    int mOldSelectedPosition = INVALID_POSITION;
+
+    /**
+     * The id of the last selected position we used when notifying
+     */
+    long mOldSelectedRowId = INVALID_ROW_ID;
+
+    /**
+     * Indicates what focusable state is requested when calling setFocusable().
+     * In addition to this, this view has other criteria for actually
+     * determining the focusable state (such as whether its empty or the text
+     * filter is shown).
+     *
+     * @see #setFocusable(boolean)
+     * @see #checkFocus()
+     */
+    private boolean mDesiredFocusableState;
+    private boolean mDesiredFocusableInTouchModeState;
+
+    private SelectionNotifier mSelectionNotifier;
+    /**
+     * When set to true, calls to requestLayout() will not propagate up the parent hierarchy.
+     * This is used to layout the children during a layout pass.
+     */
+    boolean mBlockLayoutRequests = false;
+
+    AdapterViewCompat(Context context) {
+        super(context);
+    }
+
+    AdapterViewCompat(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    AdapterViewCompat(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when an item in this
+     * AdapterView has been clicked.
+     */
+    public interface OnItemClickListener {
+
+        /**
+         * Callback method to be invoked when an item in this AdapterView has
+         * been clicked.
+         * <p>
+         * Implementers can call getItemAtPosition(position) if they need
+         * to access the data associated with the selected item.
+         *
+         * @param parent The AdapterView where the click happened.
+         * @param view The view within the AdapterView that was clicked (this
+         *            will be a view provided by the adapter)
+         * @param position The position of the view in the adapter.
+         * @param id The row id of the item that was clicked.
+         */
+        void onItemClick(AdapterViewCompat<?> parent, View view, int position, long id);
+    }
+
+    class OnItemClickListenerWrapper implements AdapterView.OnItemClickListener {
+
+        private final OnItemClickListener mWrappedListener;
+
+        public OnItemClickListenerWrapper(OnItemClickListener listener) {
+            mWrappedListener = listener;
+        }
+
+        @Override
+        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+            mWrappedListener.onItemClick(AdapterViewCompat.this, view, position, id);
+        }
+    }
+
+    /**
+     * Register a callback to be invoked when an item in this AdapterView has
+     * been clicked.
+     *
+     * @param listener The callback that will be invoked.
+     */
+    public void setOnItemClickListener(OnItemClickListener listener) {
+        mOnItemClickListener = listener;
+    }
+
+    /**
+     * @return The callback to be invoked with an item in this AdapterView has
+     *         been clicked, or null id no callback has been set.
+     */
+    public final OnItemClickListener getOnItemClickListener() {
+        return mOnItemClickListener;
+    }
+
+    /**
+     * Call the OnItemClickListener, if it is defined.
+     *
+     * @param view The view within the AdapterView that was clicked.
+     * @param position The position of the view in the adapter.
+     * @param id The row id of the item that was clicked.
+     * @return True if there was an assigned OnItemClickListener that was
+     *         called, false otherwise is returned.
+     */
+    public boolean performItemClick(View view, int position, long id) {
+        if (mOnItemClickListener != null) {
+            playSoundEffect(SoundEffectConstants.CLICK);
+            if (view != null) {
+                view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
+            }
+            mOnItemClickListener.onItemClick(this, view, position, id);
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when an item in this
+     * view has been clicked and held.
+     */
+    public interface OnItemLongClickListener {
+        /**
+         * Callback method to be invoked when an item in this view has been
+         * clicked and held.
+         *
+         * Implementers can call getItemAtPosition(position) if they need to access
+         * the data associated with the selected item.
+         *
+         * @param parent The AbsListView where the click happened
+         * @param view The view within the AbsListView that was clicked
+         * @param position The position of the view in the list
+         * @param id The row id of the item that was clicked
+         *
+         * @return true if the callback consumed the long click, false otherwise
+         */
+        boolean onItemLongClick(AdapterViewCompat<?> parent, View view, int position, long id);
+    }
+
+
+    /**
+     * Register a callback to be invoked when an item in this AdapterView has
+     * been clicked and held
+     *
+     * @param listener The callback that will run
+     */
+    public void setOnItemLongClickListener(OnItemLongClickListener listener) {
+        if (!isLongClickable()) {
+            setLongClickable(true);
+        }
+        mOnItemLongClickListener = listener;
+    }
+
+    /**
+     * @return The callback to be invoked with an item in this AdapterView has
+     *         been clicked and held, or null id no callback as been set.
+     */
+    public final OnItemLongClickListener getOnItemLongClickListener() {
+        return mOnItemLongClickListener;
+    }
+
+    /**
+     * Interface definition for a callback to be invoked when
+     * an item in this view has been selected.
+     */
+    public interface OnItemSelectedListener {
+        /**
+         * <p>Callback method to be invoked when an item in this view has been
+         * selected. This callback is invoked only when the newly selected
+         * position is different from the previously selected position or if
+         * there was no selected item.</p>
+         *
+         * Impelmenters can call getItemAtPosition(position) if they need to access the
+         * data associated with the selected item.
+         *
+         * @param parent The AdapterView where the selection happened
+         * @param view The view within the AdapterView that was clicked
+         * @param position The position of the view in the adapter
+         * @param id The row id of the item that is selected
+         */
+        void onItemSelected(AdapterViewCompat<?> parent, View view, int position, long id);
+
+        /**
+         * Callback method to be invoked when the selection disappears from this
+         * view. The selection can disappear for instance when touch is activated
+         * or when the adapter becomes empty.
+         *
+         * @param parent The AdapterView that now contains no selected item.
+         */
+        void onNothingSelected(AdapterViewCompat<?> parent);
+    }
+
+
+    /**
+     * Register a callback to be invoked when an item in this AdapterView has
+     * been selected.
+     *
+     * @param listener The callback that will run
+     */
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mOnItemSelectedListener = listener;
+    }
+
+    public final OnItemSelectedListener getOnItemSelectedListener() {
+        return mOnItemSelectedListener;
+    }
+
+    /**
+     * Extra menu information provided to the
+     * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
+     * callback when a context menu is brought up for this AdapterView.
+     *
+     */
+    public static class AdapterContextMenuInfo implements ContextMenu.ContextMenuInfo {
+
+        public AdapterContextMenuInfo(View targetView, int position, long id) {
+            this.targetView = targetView;
+            this.position = position;
+            this.id = id;
+        }
+
+        /**
+         * The child view for which the context menu is being displayed. This
+         * will be one of the children of this AdapterView.
+         */
+        public View targetView;
+
+        /**
+         * The position in the adapter for which the context menu is being
+         * displayed.
+         */
+        public int position;
+
+        /**
+         * The row id of the item for which the context menu is being displayed.
+         */
+        public long id;
+    }
+
+    /**
+     * Returns the adapter currently associated with this widget.
+     *
+     * @return The adapter used to provide this view's content.
+     */
+    public abstract T getAdapter();
+
+    /**
+     * Sets the adapter that provides the data and the views to represent the data
+     * in this widget.
+     *
+     * @param adapter The adapter to use to create this view's content.
+     */
+    public abstract void setAdapter(T adapter);
+
+    /**
+     * This method is not supported and throws an UnsupportedOperationException when called.
+     *
+     * @param child Ignored.
+     *
+     * @throws UnsupportedOperationException Every time this method is invoked.
+     */
+    @Override
+    public void addView(View child) {
+        throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
+    }
+
+    /**
+     * This method is not supported and throws an UnsupportedOperationException when called.
+     *
+     * @param child Ignored.
+     * @param index Ignored.
+     *
+     * @throws UnsupportedOperationException Every time this method is invoked.
+     */
+    @Override
+    public void addView(View child, int index) {
+        throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
+    }
+
+    /**
+     * This method is not supported and throws an UnsupportedOperationException when called.
+     *
+     * @param child Ignored.
+     * @param params Ignored.
+     *
+     * @throws UnsupportedOperationException Every time this method is invoked.
+     */
+    @Override
+    public void addView(View child, LayoutParams params) {
+        throw new UnsupportedOperationException("addView(View, LayoutParams) "
+                + "is not supported in AdapterView");
+    }
+
+    /**
+     * This method is not supported and throws an UnsupportedOperationException when called.
+     *
+     * @param child Ignored.
+     * @param index Ignored.
+     * @param params Ignored.
+     *
+     * @throws UnsupportedOperationException Every time this method is invoked.
+     */
+    @Override
+    public void addView(View child, int index, LayoutParams params) {
+        throw new UnsupportedOperationException("addView(View, int, LayoutParams) "
+                + "is not supported in AdapterView");
+    }
+
+    /**
+     * This method is not supported and throws an UnsupportedOperationException when called.
+     *
+     * @param child Ignored.
+     *
+     * @throws UnsupportedOperationException Every time this method is invoked.
+     */
+    @Override
+    public void removeView(View child) {
+        throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView");
+    }
+
+    /**
+     * This method is not supported and throws an UnsupportedOperationException when called.
+     *
+     * @param index Ignored.
+     *
+     * @throws UnsupportedOperationException Every time this method is invoked.
+     */
+    @Override
+    public void removeViewAt(int index) {
+        throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView");
+    }
+
+    /**
+     * This method is not supported and throws an UnsupportedOperationException when called.
+     *
+     * @throws UnsupportedOperationException Every time this method is invoked.
+     */
+    @Override
+    public void removeAllViews() {
+        throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView");
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        mLayoutHeight = getHeight();
+    }
+
+    /**
+     * Return the position of the currently selected item within the adapter's data set
+     *
+     * @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected.
+     */
+    @ViewDebug.CapturedViewProperty
+    public int getSelectedItemPosition() {
+        return mNextSelectedPosition;
+    }
+
+    /**
+     * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID}
+     * if nothing is selected.
+     */
+    @ViewDebug.CapturedViewProperty
+    public long getSelectedItemId() {
+        return mNextSelectedRowId;
+    }
+
+    /**
+     * @return The view corresponding to the currently selected item, or null
+     * if nothing is selected
+     */
+    public abstract View getSelectedView();
+
+    /**
+     * @return The data corresponding to the currently selected item, or
+     * null if there is nothing selected.
+     */
+    public Object getSelectedItem() {
+        T adapter = getAdapter();
+        int selection = getSelectedItemPosition();
+        if (adapter != null && adapter.getCount() > 0 && selection >= 0) {
+            return adapter.getItem(selection);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * @return The number of items owned by the Adapter associated with this
+     *         AdapterView. (This is the number of data items, which may be
+     *         larger than the number of visible views.)
+     */
+    @ViewDebug.CapturedViewProperty
+    public int getCount() {
+        return mItemCount;
+    }
+
+    /**
+     * Get the position within the adapter's data set for the view, where view is a an adapter item
+     * or a descendant of an adapter item.
+     *
+     * @param view an adapter item, or a descendant of an adapter item. This must be visible in this
+     *        AdapterView at the time of the call.
+     * @return the position within the adapter's data set of the view, or {@link #INVALID_POSITION}
+     *         if the view does not correspond to a list item (or it is not currently visible).
+     */
+    public int getPositionForView(View view) {
+        View listItem = view;
+        try {
+            View v;
+            while (!(v = (View) listItem.getParent()).equals(this)) {
+                listItem = v;
+            }
+        } catch (ClassCastException e) {
+            // We made it up to the window without find this list view
+            return INVALID_POSITION;
+        }
+
+        // Search the children for the list item
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            if (getChildAt(i).equals(listItem)) {
+                return mFirstPosition + i;
+            }
+        }
+
+        // Child not found!
+        return INVALID_POSITION;
+    }
+
+    /**
+     * Returns the position within the adapter's data set for the first item
+     * displayed on screen.
+     *
+     * @return The position within the adapter's data set
+     */
+    public int getFirstVisiblePosition() {
+        return mFirstPosition;
+    }
+
+    /**
+     * Returns the position within the adapter's data set for the last item
+     * displayed on screen.
+     *
+     * @return The position within the adapter's data set
+     */
+    public int getLastVisiblePosition() {
+        return mFirstPosition + getChildCount() - 1;
+    }
+
+    /**
+     * Sets the currently selected item. To support accessibility subclasses that
+     * override this method must invoke the overriden super method first.
+     *
+     * @param position Index (starting at 0) of the data item to be selected.
+     */
+    public abstract void setSelection(int position);
+
+    /**
+     * Sets the view to show if the adapter is empty
+     */
+    public void setEmptyView(View emptyView) {
+        mEmptyView = emptyView;
+
+        final T adapter = getAdapter();
+        final boolean empty = ((adapter == null) || adapter.isEmpty());
+        updateEmptyStatus(empty);
+    }
+
+    /**
+     * When the current adapter is empty, the AdapterView can display a special view
+     * call the empty view. The empty view is used to provide feedback to the user
+     * that no data is available in this AdapterView.
+     *
+     * @return The view to show if the adapter is empty.
+     */
+    public View getEmptyView() {
+        return mEmptyView;
+    }
+
+    /**
+     * Indicates whether this view is in filter mode. Filter mode can for instance
+     * be enabled by a user when typing on the keyboard.
+     *
+     * @return True if the view is in filter mode, false otherwise.
+     */
+    boolean isInFilterMode() {
+        return false;
+    }
+
+    @Override
+    public void setFocusable(boolean focusable) {
+        final T adapter = getAdapter();
+        final boolean empty = adapter == null || adapter.getCount() == 0;
+
+        mDesiredFocusableState = focusable;
+        if (!focusable) {
+            mDesiredFocusableInTouchModeState = false;
+        }
+
+        super.setFocusable(focusable && (!empty || isInFilterMode()));
+    }
+
+    @Override
+    public void setFocusableInTouchMode(boolean focusable) {
+        final T adapter = getAdapter();
+        final boolean empty = adapter == null || adapter.getCount() == 0;
+
+        mDesiredFocusableInTouchModeState = focusable;
+        if (focusable) {
+            mDesiredFocusableState = true;
+        }
+
+        super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode()));
+    }
+
+    void checkFocus() {
+        final T adapter = getAdapter();
+        final boolean empty = adapter == null || adapter.getCount() == 0;
+        final boolean focusable = !empty || isInFilterMode();
+        // The order in which we set focusable in touch mode/focusable may matter
+        // for the client, see View.setFocusableInTouchMode() comments for more
+        // details
+        super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
+        super.setFocusable(focusable && mDesiredFocusableState);
+        if (mEmptyView != null) {
+            updateEmptyStatus((adapter == null) || adapter.isEmpty());
+        }
+    }
+
+    /**
+     * Update the status of the list based on the empty parameter.  If empty is true and
+     * we have an empty view, display it.  In all the other cases, make sure that the listview
+     * is VISIBLE and that the empty view is GONE (if it's not null).
+     */
+    private void updateEmptyStatus(boolean empty) {
+        if (isInFilterMode()) {
+            empty = false;
+        }
+
+        if (empty) {
+            if (mEmptyView != null) {
+                mEmptyView.setVisibility(View.VISIBLE);
+                setVisibility(View.GONE);
+            } else {
+                // If the caller just removed our empty view, make sure the list view is visible
+                setVisibility(View.VISIBLE);
+            }
+
+            // We are now GONE, so pending layouts will not be dispatched.
+            // Force one here to make sure that the state of the list matches
+            // the state of the adapter.
+            if (mDataChanged) {
+                this.onLayout(false, getLeft(), getTop(), getRight(), getBottom());
+            }
+        } else {
+            if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
+            setVisibility(View.VISIBLE);
+        }
+    }
+
+    /**
+     * Gets the data associated with the specified position in the list.
+     *
+     * @param position Which data to get
+     * @return The data associated with the specified position in the list
+     */
+    public Object getItemAtPosition(int position) {
+        T adapter = getAdapter();
+        return (adapter == null || position < 0) ? null : adapter.getItem(position);
+    }
+
+    public long getItemIdAtPosition(int position) {
+        T adapter = getAdapter();
+        return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position);
+    }
+
+    @Override
+    public void setOnClickListener(OnClickListener l) {
+        throw new RuntimeException("Don't call setOnClickListener for an AdapterView. "
+                + "You probably want setOnItemClickListener instead");
+    }
+
+    /**
+     * Override to prevent freezing of any views created by the adapter.
+     */
+    @Override
+    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
+        dispatchFreezeSelfOnly(container);
+    }
+
+    /**
+     * Override to prevent thawing of any views created by the adapter.
+     */
+    @Override
+    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
+        dispatchThawSelfOnly(container);
+    }
+
+    class AdapterDataSetObserver extends DataSetObserver {
+
+        private Parcelable mInstanceState = null;
+
+        @Override
+        public void onChanged() {
+            mDataChanged = true;
+            mOldItemCount = mItemCount;
+            mItemCount = getAdapter().getCount();
+
+            // Detect the case where a cursor that was previously invalidated has
+            // been repopulated with new data.
+            if (AdapterViewCompat.this.getAdapter().hasStableIds() && mInstanceState != null
+                    && mOldItemCount == 0 && mItemCount > 0) {
+                AdapterViewCompat.this.onRestoreInstanceState(mInstanceState);
+                mInstanceState = null;
+            } else {
+                rememberSyncState();
+            }
+            checkFocus();
+            requestLayout();
+        }
+
+        @Override
+        public void onInvalidated() {
+            mDataChanged = true;
+
+            if (AdapterViewCompat.this.getAdapter().hasStableIds()) {
+                // Remember the current state for the case where our hosting activity is being
+                // stopped and later restarted
+                mInstanceState = AdapterViewCompat.this.onSaveInstanceState();
+            }
+
+            // Data is invalid so we should reset our state
+            mOldItemCount = mItemCount;
+            mItemCount = 0;
+            mSelectedPosition = INVALID_POSITION;
+            mSelectedRowId = INVALID_ROW_ID;
+            mNextSelectedPosition = INVALID_POSITION;
+            mNextSelectedRowId = INVALID_ROW_ID;
+            mNeedSync = false;
+
+            checkFocus();
+            requestLayout();
+        }
+
+        public void clearSavedState() {
+            mInstanceState = null;
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        removeCallbacks(mSelectionNotifier);
+    }
+
+    private class SelectionNotifier implements Runnable {
+        public void run() {
+            if (mDataChanged) {
+                // Data has changed between when this SelectionNotifier
+                // was posted and now. We need to wait until the AdapterView
+                // has been synched to the new data.
+                if (getAdapter() != null) {
+                    post(this);
+                }
+            } else {
+                fireOnSelected();
+            }
+        }
+    }
+
+    void selectionChanged() {
+        if (mOnItemSelectedListener != null) {
+            if (mInLayout || mBlockLayoutRequests) {
+                // If we are in a layout traversal, defer notification
+                // by posting. This ensures that the view tree is
+                // in a consistent state and is able to accomodate
+                // new layout or invalidate requests.
+                if (mSelectionNotifier == null) {
+                    mSelectionNotifier = new SelectionNotifier();
+                }
+                post(mSelectionNotifier);
+            } else {
+                fireOnSelected();
+            }
+        }
+
+        // we fire selection events here not in View
+        if (mSelectedPosition != ListView.INVALID_POSITION && isShown() && !isInTouchMode()) {
+            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+        }
+    }
+
+    private void fireOnSelected() {
+        if (mOnItemSelectedListener == null)
+            return;
+
+        int selection = this.getSelectedItemPosition();
+        if (selection >= 0) {
+            View v = getSelectedView();
+            mOnItemSelectedListener.onItemSelected(this, v, selection,
+                    getAdapter().getItemId(selection));
+        } else {
+            mOnItemSelectedListener.onNothingSelected(this);
+        }
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        View selectedView = getSelectedView();
+        if (selectedView != null && selectedView.getVisibility() == VISIBLE
+                && selectedView.dispatchPopulateAccessibilityEvent(event)) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected boolean canAnimate() {
+        return super.canAnimate() && mItemCount > 0;
+    }
+
+    void handleDataChanged() {
+        final int count = mItemCount;
+        boolean found = false;
+
+        if (count > 0) {
+
+            int newPos;
+
+            // Find the row we are supposed to sync to
+            if (mNeedSync) {
+                // Update this first, since setNextSelectedPositionInt inspects
+                // it
+                mNeedSync = false;
+
+                // See if we can find a position in the new data with the same
+                // id as the old selection
+                newPos = findSyncPosition();
+                if (newPos >= 0) {
+                    // Verify that new selection is selectable
+                    int selectablePos = lookForSelectablePosition(newPos, true);
+                    if (selectablePos == newPos) {
+                        // Same row id is selected
+                        setNextSelectedPositionInt(newPos);
+                        found = true;
+                    }
+                }
+            }
+            if (!found) {
+                // Try to use the same position if we can't find matching data
+                newPos = getSelectedItemPosition();
+
+                // Pin position to the available range
+                if (newPos >= count) {
+                    newPos = count - 1;
+                }
+                if (newPos < 0) {
+                    newPos = 0;
+                }
+
+                // Make sure we select something selectable -- first look down
+                int selectablePos = lookForSelectablePosition(newPos, true);
+                if (selectablePos < 0) {
+                    // Looking down didn't work -- try looking up
+                    selectablePos = lookForSelectablePosition(newPos, false);
+                }
+                if (selectablePos >= 0) {
+                    setNextSelectedPositionInt(selectablePos);
+                    checkSelectionChanged();
+                    found = true;
+                }
+            }
+        }
+        if (!found) {
+            // Nothing is selected
+            mSelectedPosition = INVALID_POSITION;
+            mSelectedRowId = INVALID_ROW_ID;
+            mNextSelectedPosition = INVALID_POSITION;
+            mNextSelectedRowId = INVALID_ROW_ID;
+            mNeedSync = false;
+            checkSelectionChanged();
+        }
+    }
+
+    void checkSelectionChanged() {
+        if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
+            selectionChanged();
+            mOldSelectedPosition = mSelectedPosition;
+            mOldSelectedRowId = mSelectedRowId;
+        }
+    }
+
+    /**
+     * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition
+     * and then alternates between moving up and moving down until 1) we find the right position, or
+     * 2) we run out of time, or 3) we have looked at every position
+     *
+     * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't
+     *         be found
+     */
+    int findSyncPosition() {
+        int count = mItemCount;
+
+        if (count == 0) {
+            return INVALID_POSITION;
+        }
+
+        long idToMatch = mSyncRowId;
+        int seed = mSyncPosition;
+
+        // If there isn't a selection don't hunt for it
+        if (idToMatch == INVALID_ROW_ID) {
+            return INVALID_POSITION;
+        }
+
+        // Pin seed to reasonable values
+        seed = Math.max(0, seed);
+        seed = Math.min(count - 1, seed);
+
+        long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;
+
+        long rowId;
+
+        // first position scanned so far
+        int first = seed;
+
+        // last position scanned so far
+        int last = seed;
+
+        // True if we should move down on the next iteration
+        boolean next = false;
+
+        // True when we have looked at the first item in the data
+        boolean hitFirst;
+
+        // True when we have looked at the last item in the data
+        boolean hitLast;
+
+        // Get the item ID locally (instead of getItemIdAtPosition), so
+        // we need the adapter
+        T adapter = getAdapter();
+        if (adapter == null) {
+            return INVALID_POSITION;
+        }
+
+        while (SystemClock.uptimeMillis() <= endTime) {
+            rowId = adapter.getItemId(seed);
+            if (rowId == idToMatch) {
+                // Found it!
+                return seed;
+            }
+
+            hitLast = last == count - 1;
+            hitFirst = first == 0;
+
+            if (hitLast && hitFirst) {
+                // Looked at everything
+                break;
+            }
+
+            if (hitFirst || (next && !hitLast)) {
+                // Either we hit the top, or we are trying to move down
+                last++;
+                seed = last;
+                // Try going up next time
+                next = false;
+            } else if (hitLast || (!next && !hitFirst)) {
+                // Either we hit the bottom, or we are trying to move up
+                first--;
+                seed = first;
+                // Try going down next time
+                next = true;
+            }
+
+        }
+
+        return INVALID_POSITION;
+    }
+
+    /**
+     * Find a position that can be selected (i.e., is not a separator).
+     *
+     * @param position The starting position to look at.
+     * @param lookDown Whether to look down for other positions.
+     * @return The next selectable position starting at position and then searching either up or
+     *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
+     */
+    int lookForSelectablePosition(int position, boolean lookDown) {
+        return position;
+    }
+
+    /**
+     * Utility to keep mSelectedPosition and mSelectedRowId in sync
+     * @param position Our current position
+     */
+    void setSelectedPositionInt(int position) {
+        mSelectedPosition = position;
+        mSelectedRowId = getItemIdAtPosition(position);
+    }
+
+    /**
+     * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync
+     * @param position Intended value for mSelectedPosition the next time we go
+     * through layout
+     */
+    void setNextSelectedPositionInt(int position) {
+        mNextSelectedPosition = position;
+        mNextSelectedRowId = getItemIdAtPosition(position);
+        // If we are trying to sync to the selection, update that too
+        if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
+            mSyncPosition = position;
+            mSyncRowId = mNextSelectedRowId;
+        }
+    }
+
+    /**
+     * Remember enough information to restore the screen state when the data has
+     * changed.
+     *
+     */
+    void rememberSyncState() {
+        if (getChildCount() > 0) {
+            mNeedSync = true;
+            mSyncHeight = mLayoutHeight;
+            if (mSelectedPosition >= 0) {
+                // Sync the selection state
+                View v = getChildAt(mSelectedPosition - mFirstPosition);
+                mSyncRowId = mNextSelectedRowId;
+                mSyncPosition = mNextSelectedPosition;
+                if (v != null) {
+                    mSpecificTop = v.getTop();
+                }
+                mSyncMode = SYNC_SELECTED_POSITION;
+            } else {
+                // Sync the based on the offset of the first view
+                View v = getChildAt(0);
+                T adapter = getAdapter();
+                if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
+                    mSyncRowId = adapter.getItemId(mFirstPosition);
+                } else {
+                    mSyncRowId = NO_ID;
+                }
+                mSyncPosition = mFirstPosition;
+                if (v != null) {
+                    mSpecificTop = v.getTop();
+                }
+                mSyncMode = SYNC_FIRST_POSITION;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/AdapterViewICS.java b/v7/appcompat/src/android/support/v7/internal/widget/AdapterViewICS.java
deleted file mode 100644
index b586e69..0000000
--- a/v7/appcompat/src/android/support/v7/internal/widget/AdapterViewICS.java
+++ /dev/null
@@ -1,1148 +0,0 @@
-package android.support.v7.internal.widget;
-
-/*
- * Copyright (C) 2006 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.
- */
-
-import android.content.Context;
-import android.database.DataSetObserver;
-import android.os.Parcelable;
-import android.os.SystemClock;
-import android.util.AttributeSet;
-import android.util.SparseArray;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.SoundEffectConstants;
-import android.view.View;
-import android.view.ViewDebug;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.Adapter;
-import android.widget.AdapterView;
-import android.widget.ListView;
-
-
-/**
- * An AdapterView is a view whose children are determined by an {@link android.widget.Adapter}.
- *
- * <p>
- * See {@link ListView}, {@link android.widget.GridView}, {@link android.widget.Spinner} and
- *      {@link android.widget.Gallery} for commonly used subclasses of AdapterView.
- *
- * <div class="special reference">
- * <h3>Developer Guides</h3>
- * <p>For more information about using AdapterView, read the
- * <a href="{@docRoot}guide/topics/ui/binding.html">Binding to Data with AdapterView</a>
- * developer guide.</p></div>
- */
-abstract class AdapterViewICS<T extends Adapter> extends ViewGroup {
-
-    /**
-     * The item view type returned by {@link Adapter#getItemViewType(int)} when
-     * the adapter does not want the item's view recycled.
-     */
-    static final int ITEM_VIEW_TYPE_IGNORE = -1;
-
-    /**
-     * The item view type returned by {@link Adapter#getItemViewType(int)} when
-     * the item is a header or footer.
-     */
-    static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2;
-
-    /**
-     * The position of the first child displayed
-     */
-    @ViewDebug.ExportedProperty(category = "scrolling")
-    int mFirstPosition = 0;
-
-    /**
-     * The offset in pixels from the top of the AdapterView to the top
-     * of the view to select during the next layout.
-     */
-    int mSpecificTop;
-
-    /**
-     * Position from which to start looking for mSyncRowId
-     */
-    int mSyncPosition;
-
-    /**
-     * Row id to look for when data has changed
-     */
-    long mSyncRowId = INVALID_ROW_ID;
-
-    /**
-     * Height of the view when mSyncPosition and mSyncRowId where set
-     */
-    long mSyncHeight;
-
-    /**
-     * True if we need to sync to mSyncRowId
-     */
-    boolean mNeedSync = false;
-
-    /**
-     * Indicates whether to sync based on the selection or position. Possible
-     * values are {@link #SYNC_SELECTED_POSITION} or
-     * {@link #SYNC_FIRST_POSITION}.
-     */
-    int mSyncMode;
-
-    /**
-     * Our height after the last layout
-     */
-    private int mLayoutHeight;
-
-    /**
-     * Sync based on the selected child
-     */
-    static final int SYNC_SELECTED_POSITION = 0;
-
-    /**
-     * Sync based on the first child displayed
-     */
-    static final int SYNC_FIRST_POSITION = 1;
-
-    /**
-     * Maximum amount of time to spend in {@link #findSyncPosition()}
-     */
-    static final int SYNC_MAX_DURATION_MILLIS = 100;
-
-    /**
-     * Indicates that this view is currently being laid out.
-     */
-    boolean mInLayout = false;
-
-    /**
-     * The listener that receives notifications when an item is selected.
-     */
-    OnItemSelectedListener mOnItemSelectedListener;
-
-    /**
-     * The listener that receives notifications when an item is clicked.
-     */
-    OnItemClickListener mOnItemClickListener;
-
-    /**
-     * The listener that receives notifications when an item is long clicked.
-     */
-    OnItemLongClickListener mOnItemLongClickListener;
-
-    /**
-     * True if the data has changed since the last layout
-     */
-    boolean mDataChanged;
-
-    /**
-     * The position within the adapter's data set of the item to select
-     * during the next layout.
-     */
-    @ViewDebug.ExportedProperty(category = "list")
-    int mNextSelectedPosition = INVALID_POSITION;
-
-    /**
-     * The item id of the item to select during the next layout.
-     */
-    long mNextSelectedRowId = INVALID_ROW_ID;
-
-    /**
-     * The position within the adapter's data set of the currently selected item.
-     */
-    @ViewDebug.ExportedProperty(category = "list")
-    int mSelectedPosition = INVALID_POSITION;
-
-    /**
-     * The item id of the currently selected item.
-     */
-    long mSelectedRowId = INVALID_ROW_ID;
-
-    /**
-     * View to show if there are no items to show.
-     */
-    private View mEmptyView;
-
-    /**
-     * The number of items in the current adapter.
-     */
-    @ViewDebug.ExportedProperty(category = "list")
-    int mItemCount;
-
-    /**
-     * The number of items in the adapter before a data changed event occurred.
-     */
-    int mOldItemCount;
-
-    /**
-     * Represents an invalid position. All valid positions are in the range 0 to 1 less than the
-     * number of items in the current adapter.
-     */
-    public static final int INVALID_POSITION = -1;
-
-    /**
-     * Represents an empty or invalid row id
-     */
-    public static final long INVALID_ROW_ID = Long.MIN_VALUE;
-
-    /**
-     * The last selected position we used when notifying
-     */
-    int mOldSelectedPosition = INVALID_POSITION;
-
-    /**
-     * The id of the last selected position we used when notifying
-     */
-    long mOldSelectedRowId = INVALID_ROW_ID;
-
-    /**
-     * Indicates what focusable state is requested when calling setFocusable().
-     * In addition to this, this view has other criteria for actually
-     * determining the focusable state (such as whether its empty or the text
-     * filter is shown).
-     *
-     * @see #setFocusable(boolean)
-     * @see #checkFocus()
-     */
-    private boolean mDesiredFocusableState;
-    private boolean mDesiredFocusableInTouchModeState;
-
-    private SelectionNotifier mSelectionNotifier;
-    /**
-     * When set to true, calls to requestLayout() will not propagate up the parent hierarchy.
-     * This is used to layout the children during a layout pass.
-     */
-    boolean mBlockLayoutRequests = false;
-
-    AdapterViewICS(Context context) {
-        super(context);
-    }
-
-    AdapterViewICS(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    AdapterViewICS(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    /**
-     * Interface definition for a callback to be invoked when an item in this
-     * AdapterView has been clicked.
-     */
-    public interface OnItemClickListener {
-
-        /**
-         * Callback method to be invoked when an item in this AdapterView has
-         * been clicked.
-         * <p>
-         * Implementers can call getItemAtPosition(position) if they need
-         * to access the data associated with the selected item.
-         *
-         * @param parent The AdapterView where the click happened.
-         * @param view The view within the AdapterView that was clicked (this
-         *            will be a view provided by the adapter)
-         * @param position The position of the view in the adapter.
-         * @param id The row id of the item that was clicked.
-         */
-        void onItemClick(AdapterViewICS<?> parent, View view, int position, long id);
-    }
-
-    class OnItemClickListenerWrapper implements AdapterView.OnItemClickListener {
-
-        private final OnItemClickListener mWrappedListener;
-
-        public OnItemClickListenerWrapper(OnItemClickListener listener) {
-            mWrappedListener = listener;
-        }
-
-        @Override
-        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-            mWrappedListener.onItemClick(AdapterViewICS.this, view, position, id);
-        }
-    }
-
-    /**
-     * Register a callback to be invoked when an item in this AdapterView has
-     * been clicked.
-     *
-     * @param listener The callback that will be invoked.
-     */
-    public void setOnItemClickListener(OnItemClickListener listener) {
-        mOnItemClickListener = listener;
-    }
-
-    /**
-     * @return The callback to be invoked with an item in this AdapterView has
-     *         been clicked, or null id no callback has been set.
-     */
-    public final OnItemClickListener getOnItemClickListener() {
-        return mOnItemClickListener;
-    }
-
-    /**
-     * Call the OnItemClickListener, if it is defined.
-     *
-     * @param view The view within the AdapterView that was clicked.
-     * @param position The position of the view in the adapter.
-     * @param id The row id of the item that was clicked.
-     * @return True if there was an assigned OnItemClickListener that was
-     *         called, false otherwise is returned.
-     */
-    public boolean performItemClick(View view, int position, long id) {
-        if (mOnItemClickListener != null) {
-            playSoundEffect(SoundEffectConstants.CLICK);
-            if (view != null) {
-                view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
-            }
-            mOnItemClickListener.onItemClick(this, view, position, id);
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * Interface definition for a callback to be invoked when an item in this
-     * view has been clicked and held.
-     */
-    public interface OnItemLongClickListener {
-        /**
-         * Callback method to be invoked when an item in this view has been
-         * clicked and held.
-         *
-         * Implementers can call getItemAtPosition(position) if they need to access
-         * the data associated with the selected item.
-         *
-         * @param parent The AbsListView where the click happened
-         * @param view The view within the AbsListView that was clicked
-         * @param position The position of the view in the list
-         * @param id The row id of the item that was clicked
-         *
-         * @return true if the callback consumed the long click, false otherwise
-         */
-        boolean onItemLongClick(AdapterViewICS<?> parent, View view, int position, long id);
-    }
-
-
-    /**
-     * Register a callback to be invoked when an item in this AdapterView has
-     * been clicked and held
-     *
-     * @param listener The callback that will run
-     */
-    public void setOnItemLongClickListener(OnItemLongClickListener listener) {
-        if (!isLongClickable()) {
-            setLongClickable(true);
-        }
-        mOnItemLongClickListener = listener;
-    }
-
-    /**
-     * @return The callback to be invoked with an item in this AdapterView has
-     *         been clicked and held, or null id no callback as been set.
-     */
-    public final OnItemLongClickListener getOnItemLongClickListener() {
-        return mOnItemLongClickListener;
-    }
-
-    /**
-     * Interface definition for a callback to be invoked when
-     * an item in this view has been selected.
-     */
-    public interface OnItemSelectedListener {
-        /**
-         * <p>Callback method to be invoked when an item in this view has been
-         * selected. This callback is invoked only when the newly selected
-         * position is different from the previously selected position or if
-         * there was no selected item.</p>
-         *
-         * Impelmenters can call getItemAtPosition(position) if they need to access the
-         * data associated with the selected item.
-         *
-         * @param parent The AdapterView where the selection happened
-         * @param view The view within the AdapterView that was clicked
-         * @param position The position of the view in the adapter
-         * @param id The row id of the item that is selected
-         */
-        void onItemSelected(AdapterViewICS<?> parent, View view, int position, long id);
-
-        /**
-         * Callback method to be invoked when the selection disappears from this
-         * view. The selection can disappear for instance when touch is activated
-         * or when the adapter becomes empty.
-         *
-         * @param parent The AdapterView that now contains no selected item.
-         */
-        void onNothingSelected(AdapterViewICS<?> parent);
-    }
-
-
-    /**
-     * Register a callback to be invoked when an item in this AdapterView has
-     * been selected.
-     *
-     * @param listener The callback that will run
-     */
-    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
-        mOnItemSelectedListener = listener;
-    }
-
-    public final OnItemSelectedListener getOnItemSelectedListener() {
-        return mOnItemSelectedListener;
-    }
-
-    /**
-     * Extra menu information provided to the
-     * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) }
-     * callback when a context menu is brought up for this AdapterView.
-     *
-     */
-    public static class AdapterContextMenuInfo implements ContextMenu.ContextMenuInfo {
-
-        public AdapterContextMenuInfo(View targetView, int position, long id) {
-            this.targetView = targetView;
-            this.position = position;
-            this.id = id;
-        }
-
-        /**
-         * The child view for which the context menu is being displayed. This
-         * will be one of the children of this AdapterView.
-         */
-        public View targetView;
-
-        /**
-         * The position in the adapter for which the context menu is being
-         * displayed.
-         */
-        public int position;
-
-        /**
-         * The row id of the item for which the context menu is being displayed.
-         */
-        public long id;
-    }
-
-    /**
-     * Returns the adapter currently associated with this widget.
-     *
-     * @return The adapter used to provide this view's content.
-     */
-    public abstract T getAdapter();
-
-    /**
-     * Sets the adapter that provides the data and the views to represent the data
-     * in this widget.
-     *
-     * @param adapter The adapter to use to create this view's content.
-     */
-    public abstract void setAdapter(T adapter);
-
-    /**
-     * This method is not supported and throws an UnsupportedOperationException when called.
-     *
-     * @param child Ignored.
-     *
-     * @throws UnsupportedOperationException Every time this method is invoked.
-     */
-    @Override
-    public void addView(View child) {
-        throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
-    }
-
-    /**
-     * This method is not supported and throws an UnsupportedOperationException when called.
-     *
-     * @param child Ignored.
-     * @param index Ignored.
-     *
-     * @throws UnsupportedOperationException Every time this method is invoked.
-     */
-    @Override
-    public void addView(View child, int index) {
-        throw new UnsupportedOperationException("addView(View, int) is not supported in AdapterView");
-    }
-
-    /**
-     * This method is not supported and throws an UnsupportedOperationException when called.
-     *
-     * @param child Ignored.
-     * @param params Ignored.
-     *
-     * @throws UnsupportedOperationException Every time this method is invoked.
-     */
-    @Override
-    public void addView(View child, LayoutParams params) {
-        throw new UnsupportedOperationException("addView(View, LayoutParams) "
-                + "is not supported in AdapterView");
-    }
-
-    /**
-     * This method is not supported and throws an UnsupportedOperationException when called.
-     *
-     * @param child Ignored.
-     * @param index Ignored.
-     * @param params Ignored.
-     *
-     * @throws UnsupportedOperationException Every time this method is invoked.
-     */
-    @Override
-    public void addView(View child, int index, LayoutParams params) {
-        throw new UnsupportedOperationException("addView(View, int, LayoutParams) "
-                + "is not supported in AdapterView");
-    }
-
-    /**
-     * This method is not supported and throws an UnsupportedOperationException when called.
-     *
-     * @param child Ignored.
-     *
-     * @throws UnsupportedOperationException Every time this method is invoked.
-     */
-    @Override
-    public void removeView(View child) {
-        throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView");
-    }
-
-    /**
-     * This method is not supported and throws an UnsupportedOperationException when called.
-     *
-     * @param index Ignored.
-     *
-     * @throws UnsupportedOperationException Every time this method is invoked.
-     */
-    @Override
-    public void removeViewAt(int index) {
-        throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView");
-    }
-
-    /**
-     * This method is not supported and throws an UnsupportedOperationException when called.
-     *
-     * @throws UnsupportedOperationException Every time this method is invoked.
-     */
-    @Override
-    public void removeAllViews() {
-        throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView");
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        mLayoutHeight = getHeight();
-    }
-
-    /**
-     * Return the position of the currently selected item within the adapter's data set
-     *
-     * @return int Position (starting at 0), or {@link #INVALID_POSITION} if there is nothing selected.
-     */
-    @ViewDebug.CapturedViewProperty
-    public int getSelectedItemPosition() {
-        return mNextSelectedPosition;
-    }
-
-    /**
-     * @return The id corresponding to the currently selected item, or {@link #INVALID_ROW_ID}
-     * if nothing is selected.
-     */
-    @ViewDebug.CapturedViewProperty
-    public long getSelectedItemId() {
-        return mNextSelectedRowId;
-    }
-
-    /**
-     * @return The view corresponding to the currently selected item, or null
-     * if nothing is selected
-     */
-    public abstract View getSelectedView();
-
-    /**
-     * @return The data corresponding to the currently selected item, or
-     * null if there is nothing selected.
-     */
-    public Object getSelectedItem() {
-        T adapter = getAdapter();
-        int selection = getSelectedItemPosition();
-        if (adapter != null && adapter.getCount() > 0 && selection >= 0) {
-            return adapter.getItem(selection);
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * @return The number of items owned by the Adapter associated with this
-     *         AdapterView. (This is the number of data items, which may be
-     *         larger than the number of visible views.)
-     */
-    @ViewDebug.CapturedViewProperty
-    public int getCount() {
-        return mItemCount;
-    }
-
-    /**
-     * Get the position within the adapter's data set for the view, where view is a an adapter item
-     * or a descendant of an adapter item.
-     *
-     * @param view an adapter item, or a descendant of an adapter item. This must be visible in this
-     *        AdapterView at the time of the call.
-     * @return the position within the adapter's data set of the view, or {@link #INVALID_POSITION}
-     *         if the view does not correspond to a list item (or it is not currently visible).
-     */
-    public int getPositionForView(View view) {
-        View listItem = view;
-        try {
-            View v;
-            while (!(v = (View) listItem.getParent()).equals(this)) {
-                listItem = v;
-            }
-        } catch (ClassCastException e) {
-            // We made it up to the window without find this list view
-            return INVALID_POSITION;
-        }
-
-        // Search the children for the list item
-        final int childCount = getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            if (getChildAt(i).equals(listItem)) {
-                return mFirstPosition + i;
-            }
-        }
-
-        // Child not found!
-        return INVALID_POSITION;
-    }
-
-    /**
-     * Returns the position within the adapter's data set for the first item
-     * displayed on screen.
-     *
-     * @return The position within the adapter's data set
-     */
-    public int getFirstVisiblePosition() {
-        return mFirstPosition;
-    }
-
-    /**
-     * Returns the position within the adapter's data set for the last item
-     * displayed on screen.
-     *
-     * @return The position within the adapter's data set
-     */
-    public int getLastVisiblePosition() {
-        return mFirstPosition + getChildCount() - 1;
-    }
-
-    /**
-     * Sets the currently selected item. To support accessibility subclasses that
-     * override this method must invoke the overriden super method first.
-     *
-     * @param position Index (starting at 0) of the data item to be selected.
-     */
-    public abstract void setSelection(int position);
-
-    /**
-     * Sets the view to show if the adapter is empty
-     */
-    public void setEmptyView(View emptyView) {
-        mEmptyView = emptyView;
-
-        final T adapter = getAdapter();
-        final boolean empty = ((adapter == null) || adapter.isEmpty());
-        updateEmptyStatus(empty);
-    }
-
-    /**
-     * When the current adapter is empty, the AdapterView can display a special view
-     * call the empty view. The empty view is used to provide feedback to the user
-     * that no data is available in this AdapterView.
-     *
-     * @return The view to show if the adapter is empty.
-     */
-    public View getEmptyView() {
-        return mEmptyView;
-    }
-
-    /**
-     * Indicates whether this view is in filter mode. Filter mode can for instance
-     * be enabled by a user when typing on the keyboard.
-     *
-     * @return True if the view is in filter mode, false otherwise.
-     */
-    boolean isInFilterMode() {
-        return false;
-    }
-
-    @Override
-    public void setFocusable(boolean focusable) {
-        final T adapter = getAdapter();
-        final boolean empty = adapter == null || adapter.getCount() == 0;
-
-        mDesiredFocusableState = focusable;
-        if (!focusable) {
-            mDesiredFocusableInTouchModeState = false;
-        }
-
-        super.setFocusable(focusable && (!empty || isInFilterMode()));
-    }
-
-    @Override
-    public void setFocusableInTouchMode(boolean focusable) {
-        final T adapter = getAdapter();
-        final boolean empty = adapter == null || adapter.getCount() == 0;
-
-        mDesiredFocusableInTouchModeState = focusable;
-        if (focusable) {
-            mDesiredFocusableState = true;
-        }
-
-        super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode()));
-    }
-
-    void checkFocus() {
-        final T adapter = getAdapter();
-        final boolean empty = adapter == null || adapter.getCount() == 0;
-        final boolean focusable = !empty || isInFilterMode();
-        // The order in which we set focusable in touch mode/focusable may matter
-        // for the client, see View.setFocusableInTouchMode() comments for more
-        // details
-        super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
-        super.setFocusable(focusable && mDesiredFocusableState);
-        if (mEmptyView != null) {
-            updateEmptyStatus((adapter == null) || adapter.isEmpty());
-        }
-    }
-
-    /**
-     * Update the status of the list based on the empty parameter.  If empty is true and
-     * we have an empty view, display it.  In all the other cases, make sure that the listview
-     * is VISIBLE and that the empty view is GONE (if it's not null).
-     */
-    private void updateEmptyStatus(boolean empty) {
-        if (isInFilterMode()) {
-            empty = false;
-        }
-
-        if (empty) {
-            if (mEmptyView != null) {
-                mEmptyView.setVisibility(View.VISIBLE);
-                setVisibility(View.GONE);
-            } else {
-                // If the caller just removed our empty view, make sure the list view is visible
-                setVisibility(View.VISIBLE);
-            }
-
-            // We are now GONE, so pending layouts will not be dispatched.
-            // Force one here to make sure that the state of the list matches
-            // the state of the adapter.
-            if (mDataChanged) {
-                this.onLayout(false, getLeft(), getTop(), getRight(), getBottom());
-            }
-        } else {
-            if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
-            setVisibility(View.VISIBLE);
-        }
-    }
-
-    /**
-     * Gets the data associated with the specified position in the list.
-     *
-     * @param position Which data to get
-     * @return The data associated with the specified position in the list
-     */
-    public Object getItemAtPosition(int position) {
-        T adapter = getAdapter();
-        return (adapter == null || position < 0) ? null : adapter.getItem(position);
-    }
-
-    public long getItemIdAtPosition(int position) {
-        T adapter = getAdapter();
-        return (adapter == null || position < 0) ? INVALID_ROW_ID : adapter.getItemId(position);
-    }
-
-    @Override
-    public void setOnClickListener(OnClickListener l) {
-        throw new RuntimeException("Don't call setOnClickListener for an AdapterView. "
-                + "You probably want setOnItemClickListener instead");
-    }
-
-    /**
-     * Override to prevent freezing of any views created by the adapter.
-     */
-    @Override
-    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
-        dispatchFreezeSelfOnly(container);
-    }
-
-    /**
-     * Override to prevent thawing of any views created by the adapter.
-     */
-    @Override
-    protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
-        dispatchThawSelfOnly(container);
-    }
-
-    class AdapterDataSetObserver extends DataSetObserver {
-
-        private Parcelable mInstanceState = null;
-
-        @Override
-        public void onChanged() {
-            mDataChanged = true;
-            mOldItemCount = mItemCount;
-            mItemCount = getAdapter().getCount();
-
-            // Detect the case where a cursor that was previously invalidated has
-            // been repopulated with new data.
-            if (AdapterViewICS.this.getAdapter().hasStableIds() && mInstanceState != null
-                    && mOldItemCount == 0 && mItemCount > 0) {
-                AdapterViewICS.this.onRestoreInstanceState(mInstanceState);
-                mInstanceState = null;
-            } else {
-                rememberSyncState();
-            }
-            checkFocus();
-            requestLayout();
-        }
-
-        @Override
-        public void onInvalidated() {
-            mDataChanged = true;
-
-            if (AdapterViewICS.this.getAdapter().hasStableIds()) {
-                // Remember the current state for the case where our hosting activity is being
-                // stopped and later restarted
-                mInstanceState = AdapterViewICS.this.onSaveInstanceState();
-            }
-
-            // Data is invalid so we should reset our state
-            mOldItemCount = mItemCount;
-            mItemCount = 0;
-            mSelectedPosition = INVALID_POSITION;
-            mSelectedRowId = INVALID_ROW_ID;
-            mNextSelectedPosition = INVALID_POSITION;
-            mNextSelectedRowId = INVALID_ROW_ID;
-            mNeedSync = false;
-
-            checkFocus();
-            requestLayout();
-        }
-
-        public void clearSavedState() {
-            mInstanceState = null;
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        removeCallbacks(mSelectionNotifier);
-    }
-
-    private class SelectionNotifier implements Runnable {
-        public void run() {
-            if (mDataChanged) {
-                // Data has changed between when this SelectionNotifier
-                // was posted and now. We need to wait until the AdapterView
-                // has been synched to the new data.
-                if (getAdapter() != null) {
-                    post(this);
-                }
-            } else {
-                fireOnSelected();
-            }
-        }
-    }
-
-    void selectionChanged() {
-        if (mOnItemSelectedListener != null) {
-            if (mInLayout || mBlockLayoutRequests) {
-                // If we are in a layout traversal, defer notification
-                // by posting. This ensures that the view tree is
-                // in a consistent state and is able to accomodate
-                // new layout or invalidate requests.
-                if (mSelectionNotifier == null) {
-                    mSelectionNotifier = new SelectionNotifier();
-                }
-                post(mSelectionNotifier);
-            } else {
-                fireOnSelected();
-            }
-        }
-
-        // we fire selection events here not in View
-        if (mSelectedPosition != ListView.INVALID_POSITION && isShown() && !isInTouchMode()) {
-            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
-        }
-    }
-
-    private void fireOnSelected() {
-        if (mOnItemSelectedListener == null)
-            return;
-
-        int selection = this.getSelectedItemPosition();
-        if (selection >= 0) {
-            View v = getSelectedView();
-            mOnItemSelectedListener.onItemSelected(this, v, selection,
-                    getAdapter().getItemId(selection));
-        } else {
-            mOnItemSelectedListener.onNothingSelected(this);
-        }
-    }
-
-    @Override
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        View selectedView = getSelectedView();
-        if (selectedView != null && selectedView.getVisibility() == VISIBLE
-                && selectedView.dispatchPopulateAccessibilityEvent(event)) {
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    protected boolean canAnimate() {
-        return super.canAnimate() && mItemCount > 0;
-    }
-
-    void handleDataChanged() {
-        final int count = mItemCount;
-        boolean found = false;
-
-        if (count > 0) {
-
-            int newPos;
-
-            // Find the row we are supposed to sync to
-            if (mNeedSync) {
-                // Update this first, since setNextSelectedPositionInt inspects
-                // it
-                mNeedSync = false;
-
-                // See if we can find a position in the new data with the same
-                // id as the old selection
-                newPos = findSyncPosition();
-                if (newPos >= 0) {
-                    // Verify that new selection is selectable
-                    int selectablePos = lookForSelectablePosition(newPos, true);
-                    if (selectablePos == newPos) {
-                        // Same row id is selected
-                        setNextSelectedPositionInt(newPos);
-                        found = true;
-                    }
-                }
-            }
-            if (!found) {
-                // Try to use the same position if we can't find matching data
-                newPos = getSelectedItemPosition();
-
-                // Pin position to the available range
-                if (newPos >= count) {
-                    newPos = count - 1;
-                }
-                if (newPos < 0) {
-                    newPos = 0;
-                }
-
-                // Make sure we select something selectable -- first look down
-                int selectablePos = lookForSelectablePosition(newPos, true);
-                if (selectablePos < 0) {
-                    // Looking down didn't work -- try looking up
-                    selectablePos = lookForSelectablePosition(newPos, false);
-                }
-                if (selectablePos >= 0) {
-                    setNextSelectedPositionInt(selectablePos);
-                    checkSelectionChanged();
-                    found = true;
-                }
-            }
-        }
-        if (!found) {
-            // Nothing is selected
-            mSelectedPosition = INVALID_POSITION;
-            mSelectedRowId = INVALID_ROW_ID;
-            mNextSelectedPosition = INVALID_POSITION;
-            mNextSelectedRowId = INVALID_ROW_ID;
-            mNeedSync = false;
-            checkSelectionChanged();
-        }
-    }
-
-    void checkSelectionChanged() {
-        if ((mSelectedPosition != mOldSelectedPosition) || (mSelectedRowId != mOldSelectedRowId)) {
-            selectionChanged();
-            mOldSelectedPosition = mSelectedPosition;
-            mOldSelectedRowId = mSelectedRowId;
-        }
-    }
-
-    /**
-     * Searches the adapter for a position matching mSyncRowId. The search starts at mSyncPosition
-     * and then alternates between moving up and moving down until 1) we find the right position, or
-     * 2) we run out of time, or 3) we have looked at every position
-     *
-     * @return Position of the row that matches mSyncRowId, or {@link #INVALID_POSITION} if it can't
-     *         be found
-     */
-    int findSyncPosition() {
-        int count = mItemCount;
-
-        if (count == 0) {
-            return INVALID_POSITION;
-        }
-
-        long idToMatch = mSyncRowId;
-        int seed = mSyncPosition;
-
-        // If there isn't a selection don't hunt for it
-        if (idToMatch == INVALID_ROW_ID) {
-            return INVALID_POSITION;
-        }
-
-        // Pin seed to reasonable values
-        seed = Math.max(0, seed);
-        seed = Math.min(count - 1, seed);
-
-        long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS;
-
-        long rowId;
-
-        // first position scanned so far
-        int first = seed;
-
-        // last position scanned so far
-        int last = seed;
-
-        // True if we should move down on the next iteration
-        boolean next = false;
-
-        // True when we have looked at the first item in the data
-        boolean hitFirst;
-
-        // True when we have looked at the last item in the data
-        boolean hitLast;
-
-        // Get the item ID locally (instead of getItemIdAtPosition), so
-        // we need the adapter
-        T adapter = getAdapter();
-        if (adapter == null) {
-            return INVALID_POSITION;
-        }
-
-        while (SystemClock.uptimeMillis() <= endTime) {
-            rowId = adapter.getItemId(seed);
-            if (rowId == idToMatch) {
-                // Found it!
-                return seed;
-            }
-
-            hitLast = last == count - 1;
-            hitFirst = first == 0;
-
-            if (hitLast && hitFirst) {
-                // Looked at everything
-                break;
-            }
-
-            if (hitFirst || (next && !hitLast)) {
-                // Either we hit the top, or we are trying to move down
-                last++;
-                seed = last;
-                // Try going up next time
-                next = false;
-            } else if (hitLast || (!next && !hitFirst)) {
-                // Either we hit the bottom, or we are trying to move up
-                first--;
-                seed = first;
-                // Try going down next time
-                next = true;
-            }
-
-        }
-
-        return INVALID_POSITION;
-    }
-
-    /**
-     * Find a position that can be selected (i.e., is not a separator).
-     *
-     * @param position The starting position to look at.
-     * @param lookDown Whether to look down for other positions.
-     * @return The next selectable position starting at position and then searching either up or
-     *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
-     */
-    int lookForSelectablePosition(int position, boolean lookDown) {
-        return position;
-    }
-
-    /**
-     * Utility to keep mSelectedPosition and mSelectedRowId in sync
-     * @param position Our current position
-     */
-    void setSelectedPositionInt(int position) {
-        mSelectedPosition = position;
-        mSelectedRowId = getItemIdAtPosition(position);
-    }
-
-    /**
-     * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync
-     * @param position Intended value for mSelectedPosition the next time we go
-     * through layout
-     */
-    void setNextSelectedPositionInt(int position) {
-        mNextSelectedPosition = position;
-        mNextSelectedRowId = getItemIdAtPosition(position);
-        // If we are trying to sync to the selection, update that too
-        if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) {
-            mSyncPosition = position;
-            mSyncRowId = mNextSelectedRowId;
-        }
-    }
-
-    /**
-     * Remember enough information to restore the screen state when the data has
-     * changed.
-     *
-     */
-    void rememberSyncState() {
-        if (getChildCount() > 0) {
-            mNeedSync = true;
-            mSyncHeight = mLayoutHeight;
-            if (mSelectedPosition >= 0) {
-                // Sync the selection state
-                View v = getChildAt(mSelectedPosition - mFirstPosition);
-                mSyncRowId = mNextSelectedRowId;
-                mSyncPosition = mNextSelectedPosition;
-                if (v != null) {
-                    mSpecificTop = v.getTop();
-                }
-                mSyncMode = SYNC_SELECTED_POSITION;
-            } else {
-                // Sync the based on the offset of the first view
-                View v = getChildAt(0);
-                T adapter = getAdapter();
-                if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
-                    mSyncRowId = adapter.getItemId(mFirstPosition);
-                } else {
-                    mSyncRowId = NO_ID;
-                }
-                mSyncPosition = mFirstPosition;
-                if (v != null) {
-                    mSpecificTop = v.getTop();
-                }
-                mSyncMode = SYNC_FIRST_POSITION;
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ContentFrameLayout.java b/v7/appcompat/src/android/support/v7/internal/widget/ContentFrameLayout.java
new file mode 100644
index 0000000..1f140d1
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ContentFrameLayout.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+/**
+ * @hide
+ */
+public class ContentFrameLayout extends FrameLayout {
+
+    public ContentFrameLayout(Context context) {
+        this(context, null);
+    }
+
+    public ContentFrameLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ContentFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    /**
+     * @hide
+     */
+    public void dispatchFitSystemWindows(Rect insets) {
+        fitSystemWindows(insets);
+    }
+
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/DecorContentParent.java b/v7/appcompat/src/android/support/v7/internal/widget/DecorContentParent.java
new file mode 100644
index 0000000..121938a
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/DecorContentParent.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v7.internal.widget;
+
+import android.graphics.drawable.Drawable;
+import android.os.Parcelable;
+import android.support.v7.internal.app.WindowCallback;
+import android.support.v7.internal.view.menu.MenuPresenter;
+import android.util.SparseArray;
+import android.view.Menu;
+
+/**
+ * Implemented by the top-level decor layout for a window. DecorContentParent offers
+ * entry points for a number of title/window decor features.
+ */
+public interface DecorContentParent {
+    void setWindowCallback(WindowCallback cb);
+    void setWindowTitle(CharSequence title);
+    CharSequence getTitle();
+    void initFeature(int windowFeature);
+    void setUiOptions(int uiOptions);
+    boolean hasIcon();
+    boolean hasLogo();
+    void setIcon(int resId);
+    void setIcon(Drawable d);
+    void setLogo(int resId);
+    boolean canShowOverflowMenu();
+    boolean isOverflowMenuShowing();
+    boolean isOverflowMenuShowPending();
+    boolean showOverflowMenu();
+    boolean hideOverflowMenu();
+    void setMenuPrepared();
+    void setMenu(Menu menu, MenuPresenter.Callback cb);
+    void saveToolbarHierarchyState(SparseArray<Parcelable> toolbarStates);
+    void restoreToolbarHierarchyState(SparseArray<Parcelable> toolbarStates);
+    void dismissPopups();
+
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/DecorToolbar.java b/v7/appcompat/src/android/support/v7/internal/widget/DecorToolbar.java
new file mode 100644
index 0000000..a5f395b
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/DecorToolbar.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v7.internal.widget;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Parcelable;
+import android.support.v7.internal.app.WindowCallback;
+import android.support.v7.internal.view.menu.MenuPresenter;
+import android.util.SparseArray;
+import android.view.Menu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.SpinnerAdapter;
+
+/**
+ * Common interface for a toolbar that sits as part of the window decor.
+ * Layouts that control window decor use this as a point of interaction with different
+ * bar implementations.
+ *
+ * @hide
+ */
+public interface DecorToolbar {
+    ViewGroup getViewGroup();
+    Context getContext();
+    boolean isSplit();
+    boolean hasExpandedActionView();
+    void collapseActionView();
+    void setWindowCallback(WindowCallback cb);
+    void setWindowTitle(CharSequence title);
+    CharSequence getTitle();
+    void setTitle(CharSequence title);
+    CharSequence getSubtitle();
+    void setSubtitle(CharSequence subtitle);
+    void initProgress();
+    void initIndeterminateProgress();
+    boolean canSplit();
+    void setSplitView(ViewGroup splitView);
+    void setSplitToolbar(boolean split);
+    void setSplitWhenNarrow(boolean splitWhenNarrow);
+    boolean hasIcon();
+    boolean hasLogo();
+    void setIcon(int resId);
+    void setIcon(Drawable d);
+    void setLogo(int resId);
+    void setLogo(Drawable d);
+    boolean canShowOverflowMenu();
+    boolean isOverflowMenuShowing();
+    boolean isOverflowMenuShowPending();
+    boolean showOverflowMenu();
+    boolean hideOverflowMenu();
+    void setMenuPrepared();
+    void setMenu(Menu menu, MenuPresenter.Callback cb);
+    void dismissPopupMenus();
+
+    int getDisplayOptions();
+    void setDisplayOptions(int opts);
+    void setEmbeddedTabView(ScrollingTabContainerView tabView);
+    boolean hasEmbeddedTabs();
+    boolean isTitleTruncated();
+    void setCollapsible(boolean collapsible);
+    void setHomeButtonEnabled(boolean enable);
+    int getNavigationMode();
+    void setNavigationMode(int mode);
+    void setDropdownParams(SpinnerAdapter adapter, AdapterViewCompat.OnItemSelectedListener listener);
+    void setDropdownSelectedPosition(int position);
+    int getDropdownSelectedPosition();
+    int getDropdownItemCount();
+    void setCustomView(View view);
+    View getCustomView();
+    void animateToVisibility(int visibility);
+    void setNavigationIcon(Drawable icon);
+    void setNavigationIcon(int resId);
+    void setNavigationContentDescription(CharSequence description);
+    void setNavigationContentDescription(int resId);
+    void saveHierarchyState(SparseArray<Parcelable> toolbarStates);
+    void restoreHierarchyState(SparseArray<Parcelable> toolbarStates);
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/LinearLayoutICS.java b/v7/appcompat/src/android/support/v7/internal/widget/LinearLayoutICS.java
deleted file mode 100644
index 0f80420..0000000
--- a/v7/appcompat/src/android/support/v7/internal/widget/LinearLayoutICS.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2013 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.internal.widget;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.support.v7.appcompat.R;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-
-/**
- * @hide
- */
-public class LinearLayoutICS extends LinearLayout {
-
-    private static final int SHOW_DIVIDER_NONE = 0;
-    private static final int SHOW_DIVIDER_BEGINNING = 1;
-    private static final int SHOW_DIVIDER_MIDDLE = 2;
-    private static final int SHOW_DIVIDER_END = 4;
-
-    private final Drawable mDivider;
-    private final int mDividerWidth, mDividerHeight;
-    private final int mShowDividers;
-    private final int mDividerPadding;
-
-    public LinearLayoutICS(Context context, AttributeSet attrs) {
-        super(context, attrs);
-
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LinearLayoutICS);
-
-        mDivider = a.getDrawable(R.styleable.LinearLayoutICS_divider);
-        if (mDivider != null) {
-            mDividerWidth = mDivider.getIntrinsicWidth();
-            mDividerHeight = mDivider.getIntrinsicHeight();
-        } else {
-            mDividerHeight = mDividerWidth = 0;
-        }
-
-        mShowDividers = a.getInt(R.styleable.LinearLayoutICS_showDividers, SHOW_DIVIDER_NONE);
-        mDividerPadding = a.getDimensionPixelSize(R.styleable.LinearLayoutICS_dividerPadding, 0);
-
-        a.recycle();
-
-        setWillNotDraw(mDivider == null);
-    }
-
-    public int getSupportDividerWidth() {
-        return mDividerWidth;
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        if (mDivider == null) {
-            return;
-        }
-
-        if (getOrientation() == VERTICAL) {
-            drawSupportDividersVertical(canvas);
-        } else {
-            drawSupportDividersHorizontal(canvas);
-        }
-    }
-
-    @Override
-    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
-            int parentHeightMeasureSpec, int heightUsed) {
-
-        if (mDivider != null) {
-            final int childIndex = indexOfChild(child);
-            final int count = getChildCount();
-            final LayoutParams params = (LayoutParams) child.getLayoutParams();
-
-            // To display the dividers in-between the child views, we modify their margins
-            // to create space.
-            if (getOrientation() == VERTICAL) {
-                if (hasSupportDividerBeforeChildAt(childIndex)) {
-                    params.topMargin = mDividerHeight;
-                } else if (childIndex == count - 1 && hasSupportDividerBeforeChildAt(count)) {
-                    params.bottomMargin = mDividerHeight;
-                }
-            } else {
-                if (hasSupportDividerBeforeChildAt(childIndex)) {
-                    params.leftMargin = mDividerWidth;
-                } else if (childIndex == count - 1 && hasSupportDividerBeforeChildAt(count)) {
-                    params.rightMargin = mDividerWidth;
-                }
-            }
-        }
-
-        super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
-                parentHeightMeasureSpec, heightUsed);
-    }
-
-    void drawSupportDividersVertical(Canvas canvas) {
-        final int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            final View child = getChildAt(i);
-            if (child != null && child.getVisibility() != GONE &&
-                    hasSupportDividerBeforeChildAt(i)) {
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                drawSupportHorizontalDivider(canvas, child.getTop() - lp.topMargin);
-            }
-        }
-
-        if (hasSupportDividerBeforeChildAt(count)) {
-            final View child = getChildAt(count - 1);
-            int bottom = 0;
-            if (child == null) {
-                bottom = getHeight() - getPaddingBottom() - mDividerHeight;
-            } else {
-                bottom = child.getBottom();
-            }
-            drawSupportHorizontalDivider(canvas, bottom);
-        }
-    }
-
-    void drawSupportDividersHorizontal(Canvas canvas) {
-        final int count = getChildCount();
-        for (int i = 0; i < count; i++) {
-            final View child = getChildAt(i);
-            if (child != null && child.getVisibility() != GONE &&
-                    hasSupportDividerBeforeChildAt(i)) {
-                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                drawSupportVerticalDivider(canvas, child.getLeft() - lp.leftMargin);
-            }
-        }
-
-        if (hasSupportDividerBeforeChildAt(count)) {
-            final View child = getChildAt(count - 1);
-            int right = 0;
-            if (child == null) {
-                right = getWidth() - getPaddingRight() - mDividerWidth;
-            } else {
-                right = child.getRight();
-            }
-            drawSupportVerticalDivider(canvas, right);
-        }
-    }
-
-    void drawSupportHorizontalDivider(Canvas canvas, int top) {
-        mDivider.setBounds(getPaddingLeft() + mDividerPadding, top,
-                getWidth() - getPaddingRight() - mDividerPadding, top + mDividerHeight);
-        mDivider.draw(canvas);
-    }
-
-    void drawSupportVerticalDivider(Canvas canvas, int left) {
-        mDivider.setBounds(left, getPaddingTop() + mDividerPadding,
-                left + mDividerWidth, getHeight() - getPaddingBottom() - mDividerPadding);
-        mDivider.draw(canvas);
-    }
-
-    /**
-     * Determines where to position dividers between children.
-     *
-     * @param childIndex Index of child to check for preceding divider
-     * @return true if there should be a divider before the child at childIndex
-     */
-    protected boolean hasSupportDividerBeforeChildAt(int childIndex) {
-        if (childIndex == 0) {
-            return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0;
-        } else if (childIndex == getChildCount()) {
-            return (mShowDividers & SHOW_DIVIDER_END) != 0;
-        } else if ((mShowDividers & SHOW_DIVIDER_MIDDLE) != 0) {
-            boolean hasVisibleViewBefore = false;
-            for (int i = childIndex - 1; i >= 0; i--) {
-                if (getChildAt(i).getVisibility() != GONE) {
-                    hasVisibleViewBefore = true;
-                    break;
-                }
-            }
-            return hasVisibleViewBefore;
-        }
-        return false;
-    }
-
-}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ListPopupWindow.java b/v7/appcompat/src/android/support/v7/internal/widget/ListPopupWindow.java
deleted file mode 100644
index 35299f8..0000000
--- a/v7/appcompat/src/android/support/v7/internal/widget/ListPopupWindow.java
+++ /dev/null
@@ -1,1422 +0,0 @@
-/*
- * Copyright (C) 2010 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.internal.widget;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.DataSetObserver;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.support.v7.appcompat.R;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.View.OnTouchListener;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.widget.*;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * A ListPopupWindow anchors itself to a host view and displays a list of choices.
- *
- * <p>ListPopupWindow contains a number of tricky behaviors surrounding positioning, scrolling
- * parents to fit the dropdown, interacting sanely with the IME if present, and others.
- *
- * @see android.widget.AutoCompleteTextView
- * @see android.widget.Spinner
- *
- * @hide
- */
-public class ListPopupWindow {
-
-    private static final String TAG = "ListPopupWindow";
-    private static final boolean DEBUG = false;
-
-    /**
-     * This value controls the length of time that the user must leave a pointer down without
-     * scrolling to expand the autocomplete dropdown list to cover the IME.
-     */
-    private static final int EXPAND_LIST_TIMEOUT = 250;
-
-    private Context mContext;
-    private PopupWindow mPopup;
-    private ListAdapter mAdapter;
-    private DropDownListView mDropDownList;
-
-    private int mDropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
-    private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
-    private int mDropDownHorizontalOffset;
-    private int mDropDownVerticalOffset;
-    private boolean mDropDownVerticalOffsetSet;
-
-    private boolean mDropDownAlwaysVisible = false;
-    private boolean mForceIgnoreOutsideTouch = false;
-    int mListItemExpandMaximum = Integer.MAX_VALUE;
-
-    private View mPromptView;
-    private int mPromptPosition = POSITION_PROMPT_ABOVE;
-
-    private DataSetObserver mObserver;
-
-    private View mDropDownAnchorView;
-
-    private Drawable mDropDownListHighlight;
-
-    private AdapterView.OnItemClickListener mItemClickListener;
-    private AdapterView.OnItemSelectedListener mItemSelectedListener;
-
-    private final ResizePopupRunnable mResizePopupRunnable = new ResizePopupRunnable();
-    private final PopupTouchInterceptor mTouchInterceptor = new PopupTouchInterceptor();
-    private final PopupScrollListener mScrollListener = new PopupScrollListener();
-    private final ListSelectorHider mHideSelector = new ListSelectorHider();
-    private Runnable mShowDropDownRunnable;
-
-    private Handler mHandler = new Handler();
-
-    private Rect mTempRect = new Rect();
-
-    private boolean mModal;
-
-    private int mLayoutDirection;
-
-    /**
-     * The provided prompt view should appear above list content.
-     *
-     * @see #setPromptPosition(int)
-     * @see #getPromptPosition()
-     * @see #setPromptView(View)
-     */
-    public static final int POSITION_PROMPT_ABOVE = 0;
-
-    /**
-     * The provided prompt view should appear below list content.
-     *
-     * @see #setPromptPosition(int)
-     * @see #getPromptPosition()
-     * @see #setPromptView(View)
-     */
-    public static final int POSITION_PROMPT_BELOW = 1;
-
-    /**
-     * Alias for {@link ViewGroup.LayoutParams#FILL_PARENT}. If used to specify a popup width, the
-     * popup will match the width of the anchor view. If used to specify a popup height, the popup
-     * will fill available space.
-     */
-    public static final int FILL_PARENT = ViewGroup.LayoutParams.FILL_PARENT;
-
-    /**
-     * Alias for {@link ViewGroup.LayoutParams#WRAP_CONTENT}. If used to specify a popup width, the
-     * popup will use the width of its content.
-     */
-    public static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT;
-
-    /**
-     * Mode for {@link #setInputMethodMode(int)}: the requirements for the input method should be
-     * based on the focusability of the popup.  That is if it is focusable than it needs to work
-     * with the input method, else it doesn't.
-     */
-    public static final int INPUT_METHOD_FROM_FOCUSABLE = PopupWindow.INPUT_METHOD_FROM_FOCUSABLE;
-
-    /**
-     * Mode for {@link #setInputMethodMode(int)}: this popup always needs to work with an input
-     * method, regardless of whether it is focusable.  This means that it will always be displayed
-     * so that the user can also operate the input method while it is shown.
-     */
-    public static final int INPUT_METHOD_NEEDED = PopupWindow.INPUT_METHOD_NEEDED;
-
-    /**
-     * Mode for {@link #setInputMethodMode(int)}: this popup never needs to work with an input
-     * method, regardless of whether it is focusable.  This means that it will always be displayed
-     * to use as much space on the screen as needed, regardless of whether this covers the input
-     * method.
-     */
-    public static final int INPUT_METHOD_NOT_NEEDED = PopupWindow.INPUT_METHOD_NOT_NEEDED;
-
-    /**
-     * Create a new, empty popup window capable of displaying items from a ListAdapter. Backgrounds
-     * should be set using {@link #setBackgroundDrawable(Drawable)}.
-     *
-     * @param context Context used for contained views.
-     */
-    public ListPopupWindow(Context context) {
-        this(context, null, R.attr.listPopupWindowStyle);
-    }
-
-    /**
-     * Create a new, empty popup window capable of displaying items from a ListAdapter. Backgrounds
-     * should be set using {@link #setBackgroundDrawable(Drawable)}.
-     *
-     * @param context Context used for contained views.
-     * @param attrs   Attributes from inflating parent views used to style the popup.
-     */
-    public ListPopupWindow(Context context, AttributeSet attrs) {
-        this(context, attrs, R.attr.listPopupWindowStyle);
-    }
-
-    /**
-     * Create a new, empty popup window capable of displaying items from a ListAdapter. Backgrounds
-     * should be set using {@link #setBackgroundDrawable(Drawable)}.
-     *
-     * @param context      Context used for contained views.
-     * @param attrs        Attributes from inflating parent views used to style the popup.
-     * @param defStyleAttr Default style attribute to use for popup content.
-     */
-    public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
-        mContext = context;
-        mPopup = new PopupWindow(context, attrs, defStyleAttr);
-        mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
-        // Set the default layout direction to match the default locale one
-        final Locale locale = mContext.getResources().getConfiguration().locale;
-
-    }
-
-    /**
-     * Sets the adapter that provides the data and the views to represent the data in this popup
-     * window.
-     *
-     * @param adapter The adapter to use to create this window's content.
-     */
-    public void setAdapter(ListAdapter adapter) {
-        if (mObserver == null) {
-            mObserver = new PopupDataSetObserver();
-        } else if (mAdapter != null) {
-            mAdapter.unregisterDataSetObserver(mObserver);
-        }
-        mAdapter = adapter;
-        if (mAdapter != null) {
-            adapter.registerDataSetObserver(mObserver);
-        }
-
-        if (mDropDownList != null) {
-            mDropDownList.setAdapter(mAdapter);
-        }
-    }
-
-    /**
-     * Set where the optional prompt view should appear. The default is {@link
-     * #POSITION_PROMPT_ABOVE}.
-     *
-     * @param position A position constant declaring where the prompt should be displayed.
-     * @see #POSITION_PROMPT_ABOVE
-     * @see #POSITION_PROMPT_BELOW
-     */
-    public void setPromptPosition(int position) {
-        mPromptPosition = position;
-    }
-
-    /**
-     * @return Where the optional prompt view should appear.
-     * @see #POSITION_PROMPT_ABOVE
-     * @see #POSITION_PROMPT_BELOW
-     */
-    public int getPromptPosition() {
-        return mPromptPosition;
-    }
-
-    /**
-     * Set whether this window should be modal when shown.
-     *
-     * <p>If a popup window is modal, it will receive all touch and key input. If the user touches
-     * outside the popup window's content area the popup window will be dismissed.
-     *
-     * @param modal {@code true} if the popup window should be modal, {@code false} otherwise.
-     */
-    public void setModal(boolean modal) {
-        mModal = true;
-        mPopup.setFocusable(modal);
-    }
-
-    /**
-     * Returns whether the popup window will be modal when shown.
-     *
-     * @return {@code true} if the popup window will be modal, {@code false} otherwise.
-     */
-    public boolean isModal() {
-        return mModal;
-    }
-
-    /**
-     * Forces outside touches to be ignored. Normally if {@link #isDropDownAlwaysVisible()} is
-     * false, we allow outside touch to dismiss the dropdown. If this is set to true, then we ignore
-     * outside touch even when the drop down is not set to always visible.
-     *
-     * @hide Used only by AutoCompleteTextView to handle some internal special cases.
-     */
-    public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) {
-        mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch;
-    }
-
-    /**
-     * Sets whether the drop-down should remain visible under certain conditions.
-     *
-     * The drop-down will occupy the entire screen below {@link #getAnchorView} regardless of the
-     * size or content of the list.  {@link #getBackground()} will fill any space that is not used
-     * by the list.
-     *
-     * @param dropDownAlwaysVisible Whether to keep the drop-down visible.
-     * @hide Only used by AutoCompleteTextView under special conditions.
-     */
-    public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
-        mDropDownAlwaysVisible = dropDownAlwaysVisible;
-    }
-
-    /**
-     * @return Whether the drop-down is visible under special conditions.
-     * @hide Only used by AutoCompleteTextView under special conditions.
-     */
-    public boolean isDropDownAlwaysVisible() {
-        return mDropDownAlwaysVisible;
-    }
-
-    /**
-     * Sets the operating mode for the soft input area.
-     *
-     * @param mode The desired mode, see {@link android.view.WindowManager.LayoutParams#softInputMode}
-     *             for the full list
-     * @see android.view.WindowManager.LayoutParams#softInputMode
-     * @see #getSoftInputMode()
-     */
-    public void setSoftInputMode(int mode) {
-        mPopup.setSoftInputMode(mode);
-    }
-
-    /**
-     * Returns the current value in {@link #setSoftInputMode(int)}.
-     *
-     * @see #setSoftInputMode(int)
-     * @see android.view.WindowManager.LayoutParams#softInputMode
-     */
-    public int getSoftInputMode() {
-        return mPopup.getSoftInputMode();
-    }
-
-    /**
-     * Sets a drawable to use as the list item selector.
-     *
-     * @param selector List selector drawable to use in the popup.
-     */
-    public void setListSelector(Drawable selector) {
-        mDropDownListHighlight = selector;
-    }
-
-    /**
-     * @return The background drawable for the popup window.
-     */
-    public Drawable getBackground() {
-        return mPopup.getBackground();
-    }
-
-    /**
-     * Sets a drawable to be the background for the popup window.
-     *
-     * @param d A drawable to set as the background.
-     */
-    public void setBackgroundDrawable(Drawable d) {
-        mPopup.setBackgroundDrawable(d);
-    }
-
-    /**
-     * Set an animation style to use when the popup window is shown or dismissed.
-     *
-     * @param animationStyle Animation style to use.
-     */
-    public void setAnimationStyle(int animationStyle) {
-        mPopup.setAnimationStyle(animationStyle);
-    }
-
-    /**
-     * Returns the animation style that will be used when the popup window is shown or dismissed.
-     *
-     * @return Animation style that will be used.
-     */
-    public int getAnimationStyle() {
-        return mPopup.getAnimationStyle();
-    }
-
-    /**
-     * Returns the view that will be used to anchor this popup.
-     *
-     * @return The popup's anchor view
-     */
-    public View getAnchorView() {
-        return mDropDownAnchorView;
-    }
-
-    /**
-     * Sets the popup's anchor view. This popup will always be positioned relative to the anchor
-     * view when shown.
-     *
-     * @param anchor The view to use as an anchor.
-     */
-    public void setAnchorView(View anchor) {
-        mDropDownAnchorView = anchor;
-    }
-
-    /**
-     * @return The horizontal offset of the popup from its anchor in pixels.
-     */
-    public int getHorizontalOffset() {
-        return mDropDownHorizontalOffset;
-    }
-
-    /**
-     * Set the horizontal offset of this popup from its anchor view in pixels.
-     *
-     * @param offset The horizontal offset of the popup from its anchor.
-     */
-    public void setHorizontalOffset(int offset) {
-        mDropDownHorizontalOffset = offset;
-    }
-
-    /**
-     * @return The vertical offset of the popup from its anchor in pixels.
-     */
-    public int getVerticalOffset() {
-        if (!mDropDownVerticalOffsetSet) {
-            return 0;
-        }
-        return mDropDownVerticalOffset;
-    }
-
-    /**
-     * Set the vertical offset of this popup from its anchor view in pixels.
-     *
-     * @param offset The vertical offset of the popup from its anchor.
-     */
-    public void setVerticalOffset(int offset) {
-        mDropDownVerticalOffset = offset;
-        mDropDownVerticalOffsetSet = true;
-    }
-
-    /**
-     * @return The width of the popup window in pixels.
-     */
-    public int getWidth() {
-        return mDropDownWidth;
-    }
-
-    /**
-     * Sets the width of the popup window in pixels. Can also be {@link #FILL_PARENT} or {@link
-     * #WRAP_CONTENT}.
-     *
-     * @param width Width of the popup window.
-     */
-    public void setWidth(int width) {
-        mDropDownWidth = width;
-    }
-
-    /**
-     * Sets the width of the popup window by the size of its content. The final width may be larger
-     * to accommodate styled window dressing.
-     *
-     * @param width Desired width of content in pixels.
-     */
-    public void setContentWidth(int width) {
-        Drawable popupBackground = mPopup.getBackground();
-        if (popupBackground != null) {
-            popupBackground.getPadding(mTempRect);
-            mDropDownWidth = mTempRect.left + mTempRect.right + width;
-        } else {
-            setWidth(width);
-        }
-    }
-
-    /**
-     * @return The height of the popup window in pixels.
-     */
-    public int getHeight() {
-        return mDropDownHeight;
-    }
-
-    /**
-     * Sets the height of the popup window in pixels. Can also be {@link #FILL_PARENT}.
-     *
-     * @param height Height of the popup window.
-     */
-    public void setHeight(int height) {
-        mDropDownHeight = height;
-    }
-
-    /**
-     * Sets a listener to receive events when a list item is clicked.
-     *
-     * @param clickListener Listener to register
-     * @see ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener)
-     */
-    public void setOnItemClickListener(AdapterView.OnItemClickListener clickListener) {
-        mItemClickListener = clickListener;
-    }
-
-    /**
-     * Sets a listener to receive events when a list item is selected.
-     *
-     * @param selectedListener Listener to register.
-     * @see ListView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener)
-     */
-    public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener selectedListener) {
-        mItemSelectedListener = selectedListener;
-    }
-
-    /**
-     * Set a view to act as a user prompt for this popup window. Where the prompt view will appear
-     * is controlled by {@link #setPromptPosition(int)}.
-     *
-     * @param prompt View to use as an informational prompt.
-     */
-    public void setPromptView(View prompt) {
-        boolean showing = isShowing();
-        if (showing) {
-            removePromptView();
-        }
-        mPromptView = prompt;
-        if (showing) {
-            show();
-        }
-    }
-
-    /**
-     * Post a {@link #show()} call to the UI thread.
-     */
-    public void postShow() {
-        mHandler.post(mShowDropDownRunnable);
-    }
-
-    /**
-     * Show the popup list. If the list is already showing, this method will recalculate the popup's
-     * size and position.
-     */
-    public void show() {
-        int height = buildDropDown();
-
-        int widthSpec = 0;
-        int heightSpec = 0;
-
-        boolean noInputMethod = isInputMethodNotNeeded();
-
-        if (mPopup.isShowing()) {
-            if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
-                // The call to PopupWindow's update method below can accept -1 for any
-                // value you do not want to update.
-                widthSpec = -1;
-            } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
-                widthSpec = getAnchorView().getWidth();
-            } else {
-                widthSpec = mDropDownWidth;
-            }
-
-            if (mDropDownHeight == ViewGroup.LayoutParams.FILL_PARENT) {
-                // The call to PopupWindow's update method below can accept -1 for any
-                // value you do not want to update.
-                heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.FILL_PARENT;
-                if (noInputMethod) {
-                    mPopup.setWindowLayoutMode(
-                            mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT ?
-                                    ViewGroup.LayoutParams.FILL_PARENT : 0, 0);
-                } else {
-                    mPopup.setWindowLayoutMode(
-                            mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT ?
-                                    ViewGroup.LayoutParams.FILL_PARENT : 0,
-                            ViewGroup.LayoutParams.FILL_PARENT);
-                }
-            } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
-                heightSpec = height;
-            } else {
-                heightSpec = mDropDownHeight;
-            }
-
-            mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
-
-            mPopup.update(getAnchorView(), mDropDownHorizontalOffset,
-                    mDropDownVerticalOffset, widthSpec, heightSpec);
-        } else {
-            if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
-                widthSpec = ViewGroup.LayoutParams.FILL_PARENT;
-            } else {
-                if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
-                    mPopup.setWidth(getAnchorView().getWidth());
-                } else {
-                    mPopup.setWidth(mDropDownWidth);
-                }
-            }
-
-            if (mDropDownHeight == ViewGroup.LayoutParams.FILL_PARENT) {
-                heightSpec = ViewGroup.LayoutParams.FILL_PARENT;
-            } else {
-                if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
-                    mPopup.setHeight(height);
-                } else {
-                    mPopup.setHeight(mDropDownHeight);
-                }
-            }
-
-            mPopup.setWindowLayoutMode(widthSpec, heightSpec);
-
-            // use outside touchable to dismiss drop down when touching outside of it, so
-            // only set this if the dropdown is not always visible
-            mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
-            mPopup.setTouchInterceptor(mTouchInterceptor);
-            mPopup.showAsDropDown(getAnchorView(),
-                    mDropDownHorizontalOffset, mDropDownVerticalOffset);
-            mDropDownList.setSelection(ListView.INVALID_POSITION);
-
-            if (!mModal || mDropDownList.isInTouchMode()) {
-                clearListSelection();
-            }
-            if (!mModal) {
-                mHandler.post(mHideSelector);
-            }
-        }
-    }
-
-    /**
-     * Dismiss the popup window.
-     */
-    public void dismiss() {
-        mPopup.dismiss();
-        removePromptView();
-        mPopup.setContentView(null);
-        mDropDownList = null;
-        mHandler.removeCallbacks(mResizePopupRunnable);
-    }
-
-    /**
-     * Set a listener to receive a callback when the popup is dismissed.
-     *
-     * @param listener Listener that will be notified when the popup is dismissed.
-     */
-    public void setOnDismissListener(PopupWindow.OnDismissListener listener) {
-        mPopup.setOnDismissListener(listener);
-    }
-
-    private void removePromptView() {
-        if (mPromptView != null) {
-            final ViewParent parent = mPromptView.getParent();
-            if (parent instanceof ViewGroup) {
-                final ViewGroup group = (ViewGroup) parent;
-                group.removeView(mPromptView);
-            }
-        }
-    }
-
-    /**
-     * Control how the popup operates with an input method: one of {@link
-     * #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED}, or {@link
-     * #INPUT_METHOD_NOT_NEEDED}.
-     *
-     * <p>If the popup is showing, calling this method will take effect only the next time the popup
-     * is shown or through a manual call to the {@link #show()} method.</p>
-     *
-     * @see #getInputMethodMode()
-     * @see #show()
-     */
-    public void setInputMethodMode(int mode) {
-        mPopup.setInputMethodMode(mode);
-    }
-
-    /**
-     * Return the current value in {@link #setInputMethodMode(int)}.
-     *
-     * @see #setInputMethodMode(int)
-     */
-    public int getInputMethodMode() {
-        return mPopup.getInputMethodMode();
-    }
-
-    /**
-     * Set the selected position of the list. Only valid when {@link #isShowing()} == {@code true}.
-     *
-     * @param position List position to set as selected.
-     */
-    public void setSelection(int position) {
-        DropDownListView list = mDropDownList;
-        if (isShowing() && list != null) {
-            list.mListSelectionHidden = false;
-            list.setSelection(position);
-            if (list.getChoiceMode() != ListView.CHOICE_MODE_NONE) {
-                list.setItemChecked(position, true);
-            }
-        }
-    }
-
-    /**
-     * Clear any current list selection. Only valid when {@link #isShowing()} == {@code true}.
-     */
-    public void clearListSelection() {
-        final DropDownListView list = mDropDownList;
-        if (list != null) {
-            // WARNING: Please read the comment where mListSelectionHidden is declared
-            list.mListSelectionHidden = true;
-            //list.hideSelector();
-            list.requestLayout();
-        }
-    }
-
-    /**
-     * @return {@code true} if the popup is currently showing, {@code false} otherwise.
-     */
-    public boolean isShowing() {
-        return mPopup.isShowing();
-    }
-
-    /**
-     * @return {@code true} if this popup is configured to assume the user does not need to interact
-     *         with the IME while it is showing, {@code false} otherwise.
-     */
-    public boolean isInputMethodNotNeeded() {
-        return mPopup.getInputMethodMode() == INPUT_METHOD_NOT_NEEDED;
-    }
-
-    /**
-     * Perform an item click operation on the specified list adapter position.
-     *
-     * @param position Adapter position for performing the click
-     * @return true if the click action could be performed, false if not. (e.g. if the popup was not
-     *         showing, this method would return false.)
-     */
-    public boolean performItemClick(int position) {
-        if (isShowing()) {
-            if (mItemClickListener != null) {
-                final DropDownListView list = mDropDownList;
-                final View child = list.getChildAt(position - list.getFirstVisiblePosition());
-                final ListAdapter adapter = list.getAdapter();
-                mItemClickListener.onItemClick(list, child, position, adapter.getItemId(position));
-            }
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * @return The currently selected item or null if the popup is not showing.
-     */
-    public Object getSelectedItem() {
-        if (!isShowing()) {
-            return null;
-        }
-        return mDropDownList.getSelectedItem();
-    }
-
-    /**
-     * @return The position of the currently selected item or {@link ListView#INVALID_POSITION} if
-     *         {@link #isShowing()} == {@code false}.
-     * @see ListView#getSelectedItemPosition()
-     */
-    public int getSelectedItemPosition() {
-        if (!isShowing()) {
-            return ListView.INVALID_POSITION;
-        }
-        return mDropDownList.getSelectedItemPosition();
-    }
-
-    /**
-     * @return The ID of the currently selected item or {@link ListView#INVALID_ROW_ID} if {@link
-     *         #isShowing()} == {@code false}.
-     * @see ListView#getSelectedItemId()
-     */
-    public long getSelectedItemId() {
-        if (!isShowing()) {
-            return ListView.INVALID_ROW_ID;
-        }
-        return mDropDownList.getSelectedItemId();
-    }
-
-    /**
-     * @return The View for the currently selected item or null if {@link #isShowing()} == {@code
-     *         false}.
-     * @see ListView#getSelectedView()
-     */
-    public View getSelectedView() {
-        if (!isShowing()) {
-            return null;
-        }
-        return mDropDownList.getSelectedView();
-    }
-
-    /**
-     * @return The {@link ListView} displayed within the popup window. Only valid when {@link
-     *         #isShowing()} == {@code true}.
-     */
-    public ListView getListView() {
-        return mDropDownList;
-    }
-
-    /**
-     * The maximum number of list items that can be visible and still have the list expand when
-     * touched.
-     *
-     * @param max Max number of items that can be visible and still allow the list to expand.
-     */
-    void setListItemExpandMax(int max) {
-        mListItemExpandMaximum = max;
-    }
-
-    /**
-     * Filter key down events. By forwarding key down events to this function, views using non-modal
-     * ListPopupWindow can have it handle key selection of items.
-     *
-     * @param keyCode keyCode param passed to the host view's onKeyDown
-     * @param event   event param passed to the host view's onKeyDown
-     * @return true if the event was handled, false if it was ignored.
-     * @see #setModal(boolean)
-     */
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        // when the drop down is shown, we drive it directly
-        if (isShowing()) {
-            // the key events are forwarded to the list in the drop down view
-            // note that ListView handles space but we don't want that to happen
-            // also if selection is not currently in the drop down, then don't
-            // let center or enter presses go there since that would cause it
-            // to select one of its items
-            if (keyCode != KeyEvent.KEYCODE_SPACE
-                    && (mDropDownList.getSelectedItemPosition() >= 0
-                    || (keyCode != KeyEvent.KEYCODE_ENTER
-                    && keyCode != KeyEvent.KEYCODE_DPAD_CENTER))) {
-                int curIndex = mDropDownList.getSelectedItemPosition();
-                boolean consumed;
-
-                final boolean below = !mPopup.isAboveAnchor();
-
-                final ListAdapter adapter = mAdapter;
-
-                boolean allEnabled;
-                int firstItem = Integer.MAX_VALUE;
-                int lastItem = Integer.MIN_VALUE;
-
-                if (adapter != null) {
-                    allEnabled = adapter.areAllItemsEnabled();
-                    firstItem = allEnabled ? 0 :
-                            mDropDownList.lookForSelectablePosition(0, true);
-                    lastItem = allEnabled ? adapter.getCount() - 1 :
-                            mDropDownList.lookForSelectablePosition(adapter.getCount() - 1, false);
-                }
-
-                if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= firstItem) ||
-                        (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= lastItem)) {
-                    // When the selection is at the top, we block the key
-                    // event to prevent focus from moving.
-                    clearListSelection();
-                    mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
-                    show();
-                    return true;
-                } else {
-                    // WARNING: Please read the comment where mListSelectionHidden
-                    //          is declared
-                    mDropDownList.mListSelectionHidden = false;
-                }
-
-                consumed = mDropDownList.onKeyDown(keyCode, event);
-                if (DEBUG) {
-                    Log.v(TAG, "Key down: code=" + keyCode + " list consumed=" + consumed);
-                }
-
-                if (consumed) {
-                    // If it handled the key event, then the user is
-                    // navigating in the list, so we should put it in front.
-                    mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
-                    // Here's a little trick we need to do to make sure that
-                    // the list view is actually showing its focus indicator,
-                    // by ensuring it has focus and getting its window out
-                    // of touch mode.
-                    mDropDownList.requestFocusFromTouch();
-                    show();
-
-                    switch (keyCode) {
-                        // avoid passing the focus from the text view to the
-                        // next component
-                        case KeyEvent.KEYCODE_ENTER:
-                        case KeyEvent.KEYCODE_DPAD_CENTER:
-                        case KeyEvent.KEYCODE_DPAD_DOWN:
-                        case KeyEvent.KEYCODE_DPAD_UP:
-                            return true;
-                    }
-                } else {
-                    if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
-                        // when the selection is at the bottom, we block the
-                        // event to avoid going to the next focusable widget
-                        if (curIndex == lastItem) {
-                            return true;
-                        }
-                    } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP &&
-                            curIndex == firstItem) {
-                        return true;
-                    }
-                }
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Filter key down events. By forwarding key up events to this function, views using non-modal
-     * ListPopupWindow can have it handle key selection of items.
-     *
-     * @param keyCode keyCode param passed to the host view's onKeyUp
-     * @param event   event param passed to the host view's onKeyUp
-     * @return true if the event was handled, false if it was ignored.
-     * @see #setModal(boolean)
-     */
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
-            boolean consumed = mDropDownList.onKeyUp(keyCode, event);
-            if (consumed) {
-                switch (keyCode) {
-                    // if the list accepts the key events and the key event
-                    // was a click, the text view gets the selected item
-                    // from the drop down as its content
-                    case KeyEvent.KEYCODE_ENTER:
-                    case KeyEvent.KEYCODE_DPAD_CENTER:
-                        dismiss();
-                        break;
-                }
-            }
-            return consumed;
-        }
-        return false;
-    }
-
-    /**
-     * <p>Builds the popup window's content and returns the height the popup should have. Returns -1
-     * when the content already exists.</p>
-     *
-     * @return the content's height or -1 if content already exists
-     */
-    private int buildDropDown() {
-        ViewGroup dropDownView;
-        int otherHeights = 0;
-
-        if (mDropDownList == null) {
-            Context context = mContext;
-
-            /**
-             * This Runnable exists for the sole purpose of checking if the view layout has got
-             * completed and if so call showDropDown to display the drop down. This is used to show
-             * the drop down as soon as possible after user opens up the search dialog, without
-             * waiting for the normal UI pipeline to do it's job which is slower than this method.
-             */
-            mShowDropDownRunnable = new Runnable() {
-                public void run() {
-                    // View layout should be all done before displaying the drop down.
-                    View view = getAnchorView();
-                    if (view != null && view.getWindowToken() != null) {
-                        show();
-                    }
-                }
-            };
-
-            mDropDownList = new DropDownListView(context, !mModal);
-            if (mDropDownListHighlight != null) {
-                mDropDownList.setSelector(mDropDownListHighlight);
-            }
-            mDropDownList.setAdapter(mAdapter);
-            mDropDownList.setOnItemClickListener(mItemClickListener);
-            mDropDownList.setFocusable(true);
-            mDropDownList.setFocusableInTouchMode(true);
-            mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
-                public void onItemSelected(AdapterView<?> parent, View view,
-                        int position, long id) {
-
-                    if (position != -1) {
-                        DropDownListView dropDownList = mDropDownList;
-
-                        if (dropDownList != null) {
-                            dropDownList.mListSelectionHidden = false;
-                        }
-                    }
-                }
-
-                public void onNothingSelected(AdapterView<?> parent) {
-                }
-            });
-            mDropDownList.setOnScrollListener(mScrollListener);
-
-            if (mItemSelectedListener != null) {
-                mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
-            }
-
-            dropDownView = mDropDownList;
-
-            View hintView = mPromptView;
-            if (hintView != null) {
-                // if a hint has been specified, we accomodate more space for it and
-                // add a text view in the drop down menu, at the bottom of the list
-                LinearLayout hintContainer = new LinearLayout(context);
-                hintContainer.setOrientation(LinearLayout.VERTICAL);
-
-                LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
-                        ViewGroup.LayoutParams.FILL_PARENT, 0, 1.0f
-                );
-
-                switch (mPromptPosition) {
-                    case POSITION_PROMPT_BELOW:
-                        hintContainer.addView(dropDownView, hintParams);
-                        hintContainer.addView(hintView);
-                        break;
-
-                    case POSITION_PROMPT_ABOVE:
-                        hintContainer.addView(hintView);
-                        hintContainer.addView(dropDownView, hintParams);
-                        break;
-
-                    default:
-                        Log.e(TAG, "Invalid hint position " + mPromptPosition);
-                        break;
-                }
-
-                // measure the hint's height to find how much more vertical space
-                // we need to add to the drop down's height
-                int widthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.AT_MOST);
-                int heightSpec = MeasureSpec.UNSPECIFIED;
-                hintView.measure(widthSpec, heightSpec);
-
-                hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams();
-                otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin
-                        + hintParams.bottomMargin;
-
-                dropDownView = hintContainer;
-            }
-
-            mPopup.setContentView(dropDownView);
-        } else {
-            dropDownView = (ViewGroup) mPopup.getContentView();
-            final View view = mPromptView;
-            if (view != null) {
-                LinearLayout.LayoutParams hintParams =
-                        (LinearLayout.LayoutParams) view.getLayoutParams();
-                otherHeights = view.getMeasuredHeight() + hintParams.topMargin
-                        + hintParams.bottomMargin;
-            }
-        }
-
-        // getMaxAvailableHeight() subtracts the padding, so we put it back
-        // to get the available height for the whole window
-        int padding = 0;
-        Drawable background = mPopup.getBackground();
-        if (background != null) {
-            background.getPadding(mTempRect);
-            padding = mTempRect.top + mTempRect.bottom;
-
-            // If we don't have an explicit vertical offset, determine one from the window
-            // background so that content will line up.
-            if (!mDropDownVerticalOffsetSet) {
-                mDropDownVerticalOffset = -mTempRect.top;
-            }
-        } else {
-            mTempRect.setEmpty();
-        }
-
-        // Max height available on the screen for a popup.
-        boolean ignoreBottomDecorations =
-                mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
-        final int maxHeight = getMaxAvailableHeight(
-                getAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations);
-
-        if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.FILL_PARENT) {
-            return maxHeight + padding;
-        }
-
-        final int childWidthSpec;
-        switch (mDropDownWidth) {
-            case ViewGroup.LayoutParams.WRAP_CONTENT:
-                childWidthSpec = MeasureSpec.makeMeasureSpec(
-                        mContext.getResources().getDisplayMetrics().widthPixels -
-                                (mTempRect.left + mTempRect.right),
-                        MeasureSpec.AT_MOST);
-                break;
-            case ViewGroup.LayoutParams.FILL_PARENT:
-                childWidthSpec = MeasureSpec.makeMeasureSpec(
-                        mContext.getResources().getDisplayMetrics().widthPixels -
-                                (mTempRect.left + mTempRect.right),
-                        MeasureSpec.EXACTLY);
-                break;
-            default:
-                childWidthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.EXACTLY);
-                break;
-        }
-
-        final int listContent = mDropDownList.measureHeightOfChildrenCompat(childWidthSpec,
-                0, DropDownListView.NO_POSITION, maxHeight - otherHeights, -1);
-        // add padding only if the list has items in it, that way we don't show
-        // the popup if it is not needed
-        if (listContent > 0) {
-            otherHeights += padding;
-        }
-
-        return listContent + otherHeights;
-    }
-
-    /**
-     * Copied from PopupWindow.java of JB
-     *
-     * Returns the maximum height that is available for the popup to be completely shown, optionally
-     * ignoring any bottom decorations such as the input method. It is recommended that this height
-     * be the maximum for the popup's height, otherwise it is possible that the popup will be
-     * clipped.
-     *
-     * @param anchor                  The view on which the popup window must be anchored.
-     * @param yOffset                 y offset from the view's bottom edge
-     * @param ignoreBottomDecorations if true, the height returned will be all the way to the bottom
-     *                                of the display, ignoring any bottom decorations
-     * @return The maximum available height for the popup to be completely shown.
-     * @hide Pending API council approval.
-     */
-    public int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) {
-        final Rect displayFrame = new Rect();
-        anchor.getWindowVisibleDisplayFrame(displayFrame);
-
-        int[] mDrawingLocation = new int[2];
-        final int[] anchorPos = mDrawingLocation;
-        anchor.getLocationOnScreen(anchorPos);
-
-        int bottomEdge = displayFrame.bottom;
-        if (ignoreBottomDecorations) {
-            Resources res = anchor.getContext().getResources();
-            bottomEdge = res.getDisplayMetrics().heightPixels;
-        }
-        final int distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset;
-        final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset;
-
-        // anchorPos[1] is distance from anchor to top of screen
-        int returnedHeight = Math.max(distanceToBottom, distanceToTop);
-        if (mPopup.getBackground() != null) {
-            mPopup.getBackground().getPadding(mTempRect);
-            returnedHeight -= mTempRect.top + mTempRect.bottom;
-        }
-
-        return returnedHeight;
-    }
-
-    /**
-     * <p>Wrapper class for a ListView. This wrapper can hijack the focus to make sure the list uses
-     * the appropriate drawables and states when displayed on screen within a drop down. The focus
-     * is never actually passed to the drop down in this mode; the list only looks focused.</p>
-     */
-    private static class DropDownListView extends ListView {
-
-        private static final String TAG = ListPopupWindow.TAG + ".DropDownListView";
-
-        /*
-        * WARNING: This is a workaround for a touch mode issue.
-        *
-        * Touch mode is propagated lazily to windows. This causes problems in
-        * the following scenario:
-        * - Type something in the AutoCompleteTextView and get some results
-        * - Move down with the d-pad to select an item in the list
-        * - Move up with the d-pad until the selection disappears
-        * - Type more text in the AutoCompleteTextView *using the soft keyboard*
-        *   and get new results; you are now in touch mode
-        * - The selection comes back on the first item in the list, even though
-        *   the list is supposed to be in touch mode
-        *
-        * Using the soft keyboard triggers the touch mode change but that change
-        * is propagated to our window only after the first list layout, therefore
-        * after the list attempts to resurrect the selection.
-        *
-        * The trick to work around this issue is to pretend the list is in touch
-        * mode when we know that the selection should not appear, that is when
-        * we know the user moved the selection away from the list.
-        *
-        * This boolean is set to true whenever we explicitly hide the list's
-        * selection and reset to false whenever we know the user moved the
-        * selection back to the list.
-        *
-        * When this boolean is true, isInTouchMode() returns true, otherwise it
-        * returns super.isInTouchMode().
-        */
-        private boolean mListSelectionHidden;
-
-
-        public static final int INVALID_POSITION = -1;
-
-        static final int NO_POSITION = -1;
-
-
-        /**
-         * True if this wrapper should fake focus.
-         */
-        private boolean mHijackFocus;
-
-        /**
-         * <p>Creates a new list view wrapper.</p>
-         *
-         * @param context this view's context
-         */
-        public DropDownListView(Context context, boolean hijackFocus) {
-            super(context, null, R.attr.dropDownListViewStyle);
-            mHijackFocus = hijackFocus;
-            setCacheColorHint(0); // Transparent, since the background drawable could be anything.
-        }
-
-        /**
-         * Find a position that can be selected (i.e., is not a separator).
-         *
-         * @param position The starting position to look at.
-         * @param lookDown Whether to look down for other positions.
-         * @return The next selectable position starting at position and then searching either up or
-         *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
-         */
-        private int lookForSelectablePosition(int position, boolean lookDown) {
-            final ListAdapter adapter = getAdapter();
-            if (adapter == null || isInTouchMode()) {
-                return INVALID_POSITION;
-            }
-
-            final int count = adapter.getCount();
-            if (!getAdapter().areAllItemsEnabled()) {
-                if (lookDown) {
-                    position = Math.max(0, position);
-                    while (position < count && !adapter.isEnabled(position)) {
-                        position++;
-                    }
-                } else {
-                    position = Math.min(position, count - 1);
-                    while (position >= 0 && !adapter.isEnabled(position)) {
-                        position--;
-                    }
-                }
-
-                if (position < 0 || position >= count) {
-                    return INVALID_POSITION;
-                }
-                return position;
-            } else {
-                if (position < 0 || position >= count) {
-                    return INVALID_POSITION;
-                }
-                return position;
-            }
-        }
-
-        @Override
-        public boolean isInTouchMode() {
-            // WARNING: Please read the comment where mListSelectionHidden is declared
-            return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode();
-        }
-
-        /**
-         * <p>Returns the focus state in the drop down.</p>
-         *
-         * @return true always if hijacking focus
-         */
-        @Override
-        public boolean hasWindowFocus() {
-            return mHijackFocus || super.hasWindowFocus();
-        }
-
-        /**
-         * <p>Returns the focus state in the drop down.</p>
-         *
-         * @return true always if hijacking focus
-         */
-        @Override
-        public boolean isFocused() {
-            return mHijackFocus || super.isFocused();
-        }
-
-        /**
-         * <p>Returns the focus state in the drop down.</p>
-         *
-         * @return true always if hijacking focus
-         */
-        @Override
-        public boolean hasFocus() {
-            return mHijackFocus || super.hasFocus();
-        }
-
-        /**
-         * Measures the height of the given range of children (inclusive) and returns the height
-         * with this ListView's padding and divider heights included. If maxHeight is provided, the
-         * measuring will stop when the current height reaches maxHeight.
-         *
-         * @param widthMeasureSpec             The width measure spec to be given to a child's
-         *                                     {@link View#measure(int, int)}.
-         * @param startPosition                The position of the first child to be shown.
-         * @param endPosition                  The (inclusive) position of the last child to be
-         *                                     shown. Specify {@link #NO_POSITION} if the last child
-         *                                     should be the last available child from the adapter.
-         * @param maxHeight                    The maximum height that will be returned (if all the
-         *                                     children don't fit in this value, this value will be
-         *                                     returned).
-         * @param disallowPartialChildPosition In general, whether the returned height should only
-         *                                     contain entire children. This is more powerful--it is
-         *                                     the first inclusive position at which partial
-         *                                     children will not be allowed. Example: it looks nice
-         *                                     to have at least 3 completely visible children, and
-         *                                     in portrait this will most likely fit; but in
-         *                                     landscape there could be times when even 2 children
-         *                                     can not be completely shown, so a value of 2
-         *                                     (remember, inclusive) would be good (assuming
-         *                                     startPosition is 0).
-         * @return The height of this ListView with the given children.
-         */
-        final int measureHeightOfChildrenCompat(int widthMeasureSpec, int startPosition,
-                int endPosition, final int maxHeight,
-                int disallowPartialChildPosition) {
-
-            final int paddingTop = getListPaddingTop();
-            final int paddingBottom = getListPaddingBottom();
-            final int paddingLeft = getListPaddingLeft();
-            final int paddingRight = getListPaddingRight();
-            final int reportedDividerHeight = getDividerHeight();
-            final Drawable divider = getDivider();
-
-            final ListAdapter adapter = getAdapter();
-
-            if (adapter == null) {
-                return paddingTop + paddingBottom;
-            }
-
-            // Include the padding of the list
-            int returnedHeight = paddingTop + paddingBottom;
-            final int dividerHeight = ((reportedDividerHeight > 0) && divider != null)
-                    ? reportedDividerHeight : 0;
-
-            // The previous height value that was less than maxHeight and contained
-            // no partial children
-            int prevHeightWithoutPartialChild = 0;
-
-            View child = null;
-            int viewType = 0;
-            int count = adapter.getCount();
-            for (int i = 0; i < count; i++) {
-                int newType = adapter.getItemViewType(i);
-                if (newType != viewType) {
-                    child = null;
-                    viewType = newType;
-                }
-                child = adapter.getView(i, child, this);
-
-                // Compute child height spec
-                int heightMeasureSpec;
-                final ViewGroup.LayoutParams childLp = child.getLayoutParams();
-                if (childLp != null && childLp.height > 0) {
-                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(childLp.height,
-                            MeasureSpec.EXACTLY);
-                } else {
-                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
-                }
-                child.measure(widthMeasureSpec, heightMeasureSpec);
-
-                if (i > 0) {
-                    // Count the divider for all but one child
-                    returnedHeight += dividerHeight;
-                }
-
-                returnedHeight += child.getMeasuredHeight();
-
-                if (returnedHeight >= maxHeight) {
-                    // We went over, figure out which height to return.  If returnedHeight >
-                    // maxHeight, then the i'th position did not fit completely.
-                    return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
-                            && (i > disallowPartialChildPosition) // We've past the min pos
-                            && (prevHeightWithoutPartialChild > 0) // We have a prev height
-                            && (returnedHeight != maxHeight) // i'th child did not fit completely
-                            ? prevHeightWithoutPartialChild
-                            : maxHeight;
-                }
-
-                if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
-                    prevHeightWithoutPartialChild = returnedHeight;
-                }
-            }
-
-            // At this point, we went through the range of children, and they each
-            // completely fit, so return the returnedHeight
-            return returnedHeight;
-        }
-
-    }
-
-    private class PopupDataSetObserver extends DataSetObserver {
-
-        @Override
-        public void onChanged() {
-            if (isShowing()) {
-                // Resize the popup to fit new content
-                show();
-            }
-        }
-
-        @Override
-        public void onInvalidated() {
-            dismiss();
-        }
-    }
-
-    private class ListSelectorHider implements Runnable {
-
-        public void run() {
-            clearListSelection();
-        }
-    }
-
-    private class ResizePopupRunnable implements Runnable {
-
-        public void run() {
-            if (mDropDownList != null && mDropDownList.getCount() > mDropDownList.getChildCount() &&
-                    mDropDownList.getChildCount() <= mListItemExpandMaximum) {
-                mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
-                show();
-            }
-        }
-    }
-
-    private class PopupTouchInterceptor implements OnTouchListener {
-
-        public boolean onTouch(View v, MotionEvent event) {
-            final int action = event.getAction();
-            final int x = (int) event.getX();
-            final int y = (int) event.getY();
-
-            if (action == MotionEvent.ACTION_DOWN &&
-                    mPopup != null && mPopup.isShowing() &&
-                    (x >= 0 && x < mPopup.getWidth() && y >= 0 && y < mPopup.getHeight())) {
-                mHandler.postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT);
-            } else if (action == MotionEvent.ACTION_UP) {
-                mHandler.removeCallbacks(mResizePopupRunnable);
-            }
-            return false;
-        }
-    }
-
-    private class PopupScrollListener implements ListView.OnScrollListener {
-
-        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
-                int totalItemCount) {
-
-        }
-
-        public void onScrollStateChanged(AbsListView view, int scrollState) {
-            if (scrollState == SCROLL_STATE_TOUCH_SCROLL &&
-                    !isInputMethodNotNeeded() && mPopup.getContentView() != null) {
-                mHandler.removeCallbacks(mResizePopupRunnable);
-                mResizePopupRunnable.run();
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ListViewCompat.java b/v7/appcompat/src/android/support/v7/internal/widget/ListViewCompat.java
new file mode 100644
index 0000000..5930822
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ListViewCompat.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+import java.lang.reflect.Field;
+
+/**
+ * This class contains a number of useful things for ListView. Mainly used by
+ * {@link android.support.v7.widget.ListPopupWindow}.
+ *
+ * @hide
+ */
+public class ListViewCompat extends ListView {
+
+    public static final int INVALID_POSITION = -1;
+    public static final int NO_POSITION = -1;
+
+    private static final int[] STATE_SET_NOTHING = new int[] { 0 };
+
+    final Rect mSelectorRect = new Rect();
+    int mSelectionLeftPadding = 0;
+    int mSelectionTopPadding = 0;
+    int mSelectionRightPadding = 0;
+    int mSelectionBottomPadding = 0;
+
+    private Field mIsChildViewEnabled;
+
+    public ListViewCompat(Context context) {
+        this(context, null);
+    }
+
+    public ListViewCompat(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ListViewCompat(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        try {
+            mIsChildViewEnabled = AbsListView.class.getDeclaredField("mIsChildViewEnabled");
+            mIsChildViewEnabled.setAccessible(true);
+        } catch (NoSuchFieldException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void setSelector(Drawable sel) {
+        super.setSelector(sel);
+
+        Rect padding = new Rect();
+        sel.getPadding(padding);
+        mSelectionLeftPadding = padding.left;
+        mSelectionTopPadding = padding.top;
+        mSelectionRightPadding = padding.right;
+        mSelectionBottomPadding = padding.bottom;
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        updateSelectorStateCompat();
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        final boolean drawSelectorOnTop = false;
+        if (!drawSelectorOnTop) {
+            drawSelectorCompat(canvas);
+        }
+
+        super.dispatchDraw(canvas);
+
+        if (drawSelectorOnTop) {
+            drawSelectorCompat(canvas);
+        }
+    }
+
+    protected void updateSelectorStateCompat() {
+        Drawable selector = getSelector();
+        if (selector != null) {
+            if (shouldShowSelectorCompat()) {
+                selector.setState(getDrawableState());
+            } else {
+                selector.setState(STATE_SET_NOTHING);
+            }
+        }
+    }
+
+    protected boolean shouldShowSelectorCompat() {
+        return !isInTouchMode() || (touchModeDrawsInPressedStateCompat() && isPressed());
+    }
+
+    protected boolean touchModeDrawsInPressedStateCompat() {
+        return false;
+    }
+
+    protected void drawSelectorCompat(Canvas canvas) {
+        if (!mSelectorRect.isEmpty()) {
+            final Drawable selector = getSelector();
+            selector.setBounds(mSelectorRect);
+            selector.draw(canvas);
+        }
+    }
+
+    /**
+     * Find a position that can be selected (i.e., is not a separator).
+     *
+     * @param position The starting position to look at.
+     * @param lookDown Whether to look down for other positions.
+     * @return The next selectable position starting at position and then searching either up or
+     *         down. Returns {@link #INVALID_POSITION} if nothing can be found.
+     */
+    public int lookForSelectablePosition(int position, boolean lookDown) {
+        final ListAdapter adapter = getAdapter();
+        if (adapter == null || isInTouchMode()) {
+            return INVALID_POSITION;
+        }
+
+        final int count = adapter.getCount();
+        if (!getAdapter().areAllItemsEnabled()) {
+            if (lookDown) {
+                position = Math.max(0, position);
+                while (position < count && !adapter.isEnabled(position)) {
+                    position++;
+                }
+            } else {
+                position = Math.min(position, count - 1);
+                while (position >= 0 && !adapter.isEnabled(position)) {
+                    position--;
+                }
+            }
+
+            if (position < 0 || position >= count) {
+                return INVALID_POSITION;
+            }
+            return position;
+        } else {
+            if (position < 0 || position >= count) {
+                return INVALID_POSITION;
+            }
+            return position;
+        }
+    }
+
+    protected void positionSelectorLikeTouchCompat(int position, View sel, float x, float y) {
+        positionSelectorLikeFocusCompat(position, sel);
+
+        Drawable selector = getSelector();
+        if (selector != null && position != INVALID_POSITION) {
+            DrawableCompat.setHotspot(selector, x, y);
+        }
+    }
+
+    protected void positionSelectorLikeFocusCompat(int position, View sel) {
+        // If we're changing position, update the visibility since the selector
+        // is technically being detached from the previous selection.
+        final Drawable selector = getSelector();
+        final boolean manageState = selector != null && position != INVALID_POSITION;
+        if (manageState) {
+            selector.setVisible(false, false);
+        }
+
+        positionSelectorCompat(position, sel);
+
+        if (manageState) {
+            final Rect bounds = mSelectorRect;
+            final float x = bounds.exactCenterX();
+            final float y = bounds.exactCenterY();
+            selector.setVisible(getVisibility() == VISIBLE, false);
+            DrawableCompat.setHotspot(selector, x, y);
+        }
+    }
+
+    protected void positionSelectorCompat(int position, View sel) {
+        final Rect selectorRect = mSelectorRect;
+        selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom());
+
+        // Adjust for selection padding.
+        selectorRect.left -= mSelectionLeftPadding;
+        selectorRect.top -= mSelectionTopPadding;
+        selectorRect.right += mSelectionRightPadding;
+        selectorRect.bottom += mSelectionBottomPadding;
+
+        try {
+            // AbsListView.mIsChildViewEnabled controls the selector's state so we need to
+            // modify it's value
+            final boolean isChildViewEnabled = mIsChildViewEnabled.getBoolean(this);
+            if (sel.isEnabled() != isChildViewEnabled) {
+                mIsChildViewEnabled.set(this, !isChildViewEnabled);
+                if (position != INVALID_POSITION) {
+                    refreshDrawableState();
+                }
+            }
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Measures the height of the given range of children (inclusive) and returns the height
+     * with this ListView's padding and divider heights included. If maxHeight is provided, the
+     * measuring will stop when the current height reaches maxHeight.
+     *
+     * @param widthMeasureSpec             The width measure spec to be given to a child's
+     *                                     {@link View#measure(int, int)}.
+     * @param startPosition                The position of the first child to be shown.
+     * @param endPosition                  The (inclusive) position of the last child to be
+     *                                     shown. Specify {@link #NO_POSITION} if the last child
+     *                                     should be the last available child from the adapter.
+     * @param maxHeight                    The maximum height that will be returned (if all the
+     *                                     children don't fit in this value, this value will be
+     *                                     returned).
+     * @param disallowPartialChildPosition In general, whether the returned height should only
+     *                                     contain entire children. This is more powerful--it is
+     *                                     the first inclusive position at which partial
+     *                                     children will not be allowed. Example: it looks nice
+     *                                     to have at least 3 completely visible children, and
+     *                                     in portrait this will most likely fit; but in
+     *                                     landscape there could be times when even 2 children
+     *                                     can not be completely shown, so a value of 2
+     *                                     (remember, inclusive) would be good (assuming
+     *                                     startPosition is 0).
+     * @return The height of this ListView with the given children.
+     */
+    public int measureHeightOfChildrenCompat(int widthMeasureSpec, int startPosition,
+            int endPosition, final int maxHeight,
+            int disallowPartialChildPosition) {
+
+        final int paddingTop = getListPaddingTop();
+        final int paddingBottom = getListPaddingBottom();
+        final int paddingLeft = getListPaddingLeft();
+        final int paddingRight = getListPaddingRight();
+        final int reportedDividerHeight = getDividerHeight();
+        final Drawable divider = getDivider();
+
+        final ListAdapter adapter = getAdapter();
+
+        if (adapter == null) {
+            return paddingTop + paddingBottom;
+        }
+
+        // Include the padding of the list
+        int returnedHeight = paddingTop + paddingBottom;
+        final int dividerHeight = ((reportedDividerHeight > 0) && divider != null)
+                ? reportedDividerHeight : 0;
+
+        // The previous height value that was less than maxHeight and contained
+        // no partial children
+        int prevHeightWithoutPartialChild = 0;
+
+        View child = null;
+        int viewType = 0;
+        int count = adapter.getCount();
+        for (int i = 0; i < count; i++) {
+            int newType = adapter.getItemViewType(i);
+            if (newType != viewType) {
+                child = null;
+                viewType = newType;
+            }
+            child = adapter.getView(i, child, this);
+
+            // Compute child height spec
+            int heightMeasureSpec;
+            final ViewGroup.LayoutParams childLp = child.getLayoutParams();
+            if (childLp != null && childLp.height > 0) {
+                heightMeasureSpec = MeasureSpec.makeMeasureSpec(childLp.height,
+                        MeasureSpec.EXACTLY);
+            } else {
+                heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+            }
+            child.measure(widthMeasureSpec, heightMeasureSpec);
+
+            if (i > 0) {
+                // Count the divider for all but one child
+                returnedHeight += dividerHeight;
+            }
+
+            returnedHeight += child.getMeasuredHeight();
+
+            if (returnedHeight >= maxHeight) {
+                // We went over, figure out which height to return.  If returnedHeight >
+                // maxHeight, then the i'th position did not fit completely.
+                return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
+                        && (i > disallowPartialChildPosition) // We've past the min pos
+                        && (prevHeightWithoutPartialChild > 0) // We have a prev height
+                        && (returnedHeight != maxHeight) // i'th child did not fit completely
+                        ? prevHeightWithoutPartialChild
+                        : maxHeight;
+            }
+
+            if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
+                prevHeightWithoutPartialChild = returnedHeight;
+            }
+        }
+
+        // At this point, we went through the range of children, and they each
+        // completely fit, so return the returnedHeight
+        return returnedHeight;
+    }
+
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/NativeActionModeAwareLayout.java b/v7/appcompat/src/android/support/v7/internal/widget/NativeActionModeAwareLayout.java
index 1027983..d19fff9 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/NativeActionModeAwareLayout.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/NativeActionModeAwareLayout.java
@@ -16,16 +16,18 @@
 
 package android.support.v7.internal.widget;
 
+import android.annotation.TargetApi;
 import android.content.Context;
+import android.os.Build;
 import android.util.AttributeSet;
 import android.view.ActionMode;
 import android.view.View;
-import android.widget.LinearLayout;
 
 /**
  * @hide
  */
-public class NativeActionModeAwareLayout extends LinearLayout {
+@TargetApi(Build.VERSION_CODES.HONEYCOMB)
+public class NativeActionModeAwareLayout extends ContentFrameLayout {
 
     private OnActionModeForChildListener mActionModeForChildListener;
 
@@ -37,10 +39,9 @@
         mActionModeForChildListener = listener;
     }
 
-    @Override
     public ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback) {
         if (mActionModeForChildListener != null) {
-            callback = mActionModeForChildListener.onActionModeForChild(callback);
+            return mActionModeForChildListener.startActionModeForChild(originalView, callback);
         }
         return super.startActionModeForChild(originalView, callback);
     }
@@ -49,6 +50,6 @@
      * @hide
      */
     public interface OnActionModeForChildListener {
-        ActionMode.Callback onActionModeForChild(ActionMode.Callback callback);
+        ActionMode startActionModeForChild(View originalView, ActionMode.Callback callback);
     }
 }
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ProgressBarCompat.java b/v7/appcompat/src/android/support/v7/internal/widget/ProgressBarCompat.java
new file mode 100644
index 0000000..3d83367
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ProgressBarCompat.java
@@ -0,0 +1,923 @@
+package android.support.v7.internal.widget;
+
+/*
+ * Copyright (C) 2013 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.
+ */
+
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ClipDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RoundRectShape;
+import android.graphics.drawable.shapes.Shape;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.Transformation;
+
+/**
+ * @hide
+ */
+public class ProgressBarCompat extends View {
+
+    private static final int MAX_LEVEL = 10000;
+    private static final int ANIMATION_RESOLUTION = 200;
+
+    /**
+     * android.R.styleable.ProgressBar is internalised, so we need to create it ourselves.
+      */
+    private static final int[] android_R_styleable_ProgressBar = new int[]{
+            android.R.attr.max,
+            android.R.attr.progress,
+            android.R.attr.secondaryProgress,
+            android.R.attr.indeterminate,
+            android.R.attr.indeterminateOnly,
+            android.R.attr.indeterminateDrawable,
+            android.R.attr.progressDrawable,
+            android.R.attr.indeterminateDuration,
+            android.R.attr.indeterminateBehavior,
+            android.R.attr.minWidth,
+            android.R.attr.maxWidth,
+            android.R.attr.minHeight,
+            android.R.attr.maxHeight,
+            android.R.attr.interpolator,
+    };
+
+    int mMinWidth;
+    int mMaxWidth;
+    int mMinHeight;
+    int mMaxHeight;
+
+    private int mProgress;
+    private int mSecondaryProgress;
+    private int mMax;
+
+    private int mBehavior;
+    private int mDuration;
+    private boolean mIndeterminate;
+    private boolean mOnlyIndeterminate;
+    private Transformation mTransformation;
+    private AlphaAnimation mAnimation;
+    private Drawable mIndeterminateDrawable;
+    private Drawable mProgressDrawable;
+    private Drawable mCurrentDrawable;
+    Bitmap mSampleTile;
+    private boolean mNoInvalidate;
+    private Interpolator mInterpolator;
+    private RefreshProgressRunnable mRefreshProgressRunnable;
+    private long mUiThreadId;
+    private boolean mShouldStartAnimationDrawable;
+    private long mLastDrawTime;
+
+    private boolean mInDrawing;
+
+    /**
+     * @hide
+     */
+    public ProgressBarCompat(Context context, AttributeSet attrs, int defStyle, int styleRes) {
+        super(context, attrs, defStyle);
+        mUiThreadId = Thread.currentThread().getId();
+        initProgressBar();
+
+        TypedArray a = context.obtainStyledAttributes(attrs, android_R_styleable_ProgressBar,
+                defStyle, styleRes);
+
+        mNoInvalidate = true;
+
+        setMax(a.getInt(0, mMax));
+        setProgress(a.getInt(1, mProgress));
+        setSecondaryProgress(a.getInt(2, mSecondaryProgress));
+
+        final boolean indeterminate = a.getBoolean(3, mIndeterminate);
+        mOnlyIndeterminate = a.getBoolean(4, mOnlyIndeterminate);
+
+        Drawable drawable = a.getDrawable(5);
+        if (drawable != null) {
+            drawable = tileifyIndeterminate(drawable);
+            setIndeterminateDrawable(drawable);
+        }
+
+        drawable = a.getDrawable(6);
+        if (drawable != null) {
+            drawable = tileify(drawable, false);
+            // Calling this method can set mMaxHeight, make sure the corresponding
+            // XML attribute for mMaxHeight is read after calling this method
+            setProgressDrawable(drawable);
+        }
+
+        mDuration = a.getInt(7, mDuration);
+        mBehavior = a.getInt(8, mBehavior);
+        mMinWidth = a.getDimensionPixelSize(9, mMinWidth);
+        mMaxWidth = a.getDimensionPixelSize(10, mMaxWidth);
+        mMinHeight = a.getDimensionPixelSize(11, mMinHeight);
+        mMaxHeight = a.getDimensionPixelSize(12, mMaxHeight);
+
+        final int resID = a.getResourceId(13, android.R.anim.linear_interpolator);
+        if (resID > 0) {
+            setInterpolator(context, resID);
+        }
+
+        a.recycle();
+
+        mNoInvalidate = false;
+        setIndeterminate(mOnlyIndeterminate || indeterminate);
+    }
+
+    /**
+     * Converts a drawable to a tiled version of itself. It will recursively
+     * traverse layer and state list drawables.
+     */
+    private Drawable tileify(Drawable drawable, boolean clip) {
+
+        if (drawable instanceof LayerDrawable) {
+            LayerDrawable background = (LayerDrawable) drawable;
+            final int N = background.getNumberOfLayers();
+            Drawable[] outDrawables = new Drawable[N];
+
+            for (int i = 0; i < N; i++) {
+                int id = background.getId(i);
+                outDrawables[i] = tileify(background.getDrawable(i),
+                        (id == android.R.id.progress || id == android.R.id.secondaryProgress));
+            }
+
+            LayerDrawable newBg = new LayerDrawable(outDrawables);
+
+            for (int i = 0; i < N; i++) {
+                newBg.setId(i, background.getId(i));
+            }
+
+            return newBg;
+
+        } else if (drawable instanceof BitmapDrawable) {
+            final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
+            if (mSampleTile == null) {
+                mSampleTile = tileBitmap;
+            }
+
+            final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
+
+            final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
+                    Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
+            shapeDrawable.getPaint().setShader(bitmapShader);
+
+            return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
+                    ClipDrawable.HORIZONTAL) : shapeDrawable;
+        }
+
+        return drawable;
+    }
+
+    Shape getDrawableShape() {
+        final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
+        return new RoundRectShape(roundedCorners, null, null);
+    }
+
+    /**
+     * Convert a AnimationDrawable for use as a barberpole animation.
+     * Each frame of the animation is wrapped in a ClipDrawable and
+     * given a tiling BitmapShader.
+     */
+    private Drawable tileifyIndeterminate(Drawable drawable) {
+        if (drawable instanceof AnimationDrawable) {
+            AnimationDrawable background = (AnimationDrawable) drawable;
+            final int N = background.getNumberOfFrames();
+            AnimationDrawable newBg = new AnimationDrawable();
+            newBg.setOneShot(background.isOneShot());
+
+            for (int i = 0; i < N; i++) {
+                Drawable frame = tileify(background.getFrame(i), true);
+                frame.setLevel(10000);
+                newBg.addFrame(frame, background.getDuration(i));
+            }
+            newBg.setLevel(10000);
+            drawable = newBg;
+        }
+        return drawable;
+    }
+
+    /**
+     * <p>
+     * Initialize the progress bar's default values:
+     * </p>
+     * <ul>
+     * <li>progress = 0</li>
+     * <li>max = 100</li>
+     * <li>animation duration = 4000 ms</li>
+     * <li>indeterminate = false</li>
+     * <li>behavior = repeat</li>
+     * </ul>
+     */
+    private void initProgressBar() {
+        mMax = 100;
+        mProgress = 0;
+        mSecondaryProgress = 0;
+        mIndeterminate = false;
+        mOnlyIndeterminate = false;
+        mDuration = 4000;
+        mBehavior = AlphaAnimation.RESTART;
+        mMinWidth = 24;
+        mMaxWidth = 48;
+        mMinHeight = 24;
+        mMaxHeight = 48;
+    }
+
+    /**
+     * <p>Indicate whether this progress bar is in indeterminate mode.</p>
+     *
+     * @return true if the progress bar is in indeterminate mode
+     */
+    public synchronized boolean isIndeterminate() {
+        return mIndeterminate;
+    }
+
+    /**
+     * <p>Change the indeterminate mode for this progress bar. In indeterminate
+     * mode, the progress is ignored and the progress bar shows an infinite
+     * animation instead.</p>
+     *
+     * If this progress bar's style only supports indeterminate mode (such as the circular
+     * progress bars), then this will be ignored.
+     *
+     * @param indeterminate true to enable the indeterminate mode
+     */
+    public synchronized void setIndeterminate(boolean indeterminate) {
+        if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
+            mIndeterminate = indeterminate;
+
+            if (indeterminate) {
+                // swap between indeterminate and regular backgrounds
+                mCurrentDrawable = mIndeterminateDrawable;
+                startAnimation();
+            } else {
+                mCurrentDrawable = mProgressDrawable;
+                stopAnimation();
+            }
+        }
+    }
+
+    /**
+     * <p>Get the drawable used to draw the progress bar in
+     * indeterminate mode.</p>
+     *
+     * @return a {@link android.graphics.drawable.Drawable} instance
+     *
+     * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable)
+     * @see #setIndeterminate(boolean)
+     */
+    public Drawable getIndeterminateDrawable() {
+        return mIndeterminateDrawable;
+    }
+
+    /**
+     * <p>Define the drawable used to draw the progress bar in
+     * indeterminate mode.</p>
+     *
+     * @param d the new drawable
+     *
+     * @see #getIndeterminateDrawable()
+     * @see #setIndeterminate(boolean)
+     */
+    public void setIndeterminateDrawable(Drawable d) {
+        if (d != null) {
+            d.setCallback(this);
+        }
+        mIndeterminateDrawable = d;
+        if (mIndeterminate) {
+            mCurrentDrawable = d;
+            postInvalidate();
+        }
+    }
+
+    /**
+     * <p>Get the drawable used to draw the progress bar in
+     * progress mode.</p>
+     *
+     * @return a {@link android.graphics.drawable.Drawable} instance
+     *
+     * @see #setProgressDrawable(android.graphics.drawable.Drawable)
+     * @see #setIndeterminate(boolean)
+     */
+    public Drawable getProgressDrawable() {
+        return mProgressDrawable;
+    }
+
+    /**
+     * <p>Define the drawable used to draw the progress bar in
+     * progress mode.</p>
+     *
+     * @param d the new drawable
+     *
+     * @see #getProgressDrawable()
+     * @see #setIndeterminate(boolean)
+     */
+    public void setProgressDrawable(Drawable d) {
+        boolean needUpdate;
+        if (mProgressDrawable != null && d != mProgressDrawable) {
+            mProgressDrawable.setCallback(null);
+            needUpdate = true;
+        } else {
+            needUpdate = false;
+        }
+
+        if (d != null) {
+            d.setCallback(this);
+
+            // Make sure the android_R_styleable_ProgressBar is always tall enough
+            int drawableHeight = d.getMinimumHeight();
+            if (mMaxHeight < drawableHeight) {
+                mMaxHeight = drawableHeight;
+                requestLayout();
+            }
+        }
+        mProgressDrawable = d;
+        if (!mIndeterminate) {
+            mCurrentDrawable = d;
+            postInvalidate();
+        }
+
+        if (needUpdate) {
+            updateDrawableBounds(getWidth(), getHeight());
+            updateDrawableState();
+            doRefreshProgress(android.R.id.progress, mProgress, false, false);
+            doRefreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false, false);
+        }
+    }
+
+    @Override
+    protected boolean verifyDrawable(Drawable who) {
+        return who == mProgressDrawable || who == mIndeterminateDrawable
+                || super.verifyDrawable(who);
+    }
+
+    @Override
+    public void postInvalidate() {
+        if (!mNoInvalidate) {
+            super.postInvalidate();
+        }
+    }
+
+    private class RefreshProgressRunnable implements Runnable {
+
+        private int mId;
+        private int mProgress;
+        private boolean mFromUser;
+
+        RefreshProgressRunnable(int id, int progress, boolean fromUser) {
+            mId = id;
+            mProgress = progress;
+            mFromUser = fromUser;
+        }
+
+        public void run() {
+            doRefreshProgress(mId, mProgress, mFromUser, true);
+            // Put ourselves back in the cache when we are done
+            mRefreshProgressRunnable = this;
+        }
+
+        public void setup(int id, int progress, boolean fromUser) {
+            mId = id;
+            mProgress = progress;
+            mFromUser = fromUser;
+        }
+
+    }
+
+    private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
+            boolean callBackToApp) {
+        float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
+        final Drawable d = mCurrentDrawable;
+        if (d != null) {
+            Drawable progressDrawable = null;
+
+            if (d instanceof LayerDrawable) {
+                progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id);
+            }
+
+            final int level = (int) (scale * MAX_LEVEL);
+            (progressDrawable != null ? progressDrawable : d).setLevel(level);
+        } else {
+            invalidate();
+        }
+    }
+
+    private synchronized void refreshProgress(int id, int progress, boolean fromUser) {
+        if (mUiThreadId == Thread.currentThread().getId()) {
+            doRefreshProgress(id, progress, fromUser, true);
+        } else {
+            RefreshProgressRunnable r;
+            if (mRefreshProgressRunnable != null) {
+                // Use cached RefreshProgressRunnable if available
+                r = mRefreshProgressRunnable;
+                // Uncache it
+                mRefreshProgressRunnable = null;
+                r.setup(id, progress, fromUser);
+            } else {
+                // Make a new one
+                r = new RefreshProgressRunnable(id, progress, fromUser);
+            }
+            post(r);
+        }
+    }
+
+    /**
+     * <p>Set the current progress to the specified value. Does not do anything
+     * if the progress bar is in indeterminate mode.</p>
+     *
+     * @param progress the new progress, between 0 and {@link #getMax()}
+     *
+     * @see #setIndeterminate(boolean)
+     * @see #isIndeterminate()
+     * @see #getProgress()
+     * @see #incrementProgressBy(int)
+     */
+    public synchronized void setProgress(int progress) {
+        setProgress(progress, false);
+    }
+
+    synchronized void setProgress(int progress, boolean fromUser) {
+        if (mIndeterminate) {
+            return;
+        }
+
+        if (progress < 0) {
+            progress = 0;
+        }
+
+        if (progress > mMax) {
+            progress = mMax;
+        }
+
+        if (progress != mProgress) {
+            mProgress = progress;
+            refreshProgress(android.R.id.progress, mProgress, fromUser);
+        }
+    }
+
+    /**
+     * <p>
+     * Set the current secondary progress to the specified value. Does not do
+     * anything if the progress bar is in indeterminate mode.
+     * </p>
+     *
+     * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
+     * @see #setIndeterminate(boolean)
+     * @see #isIndeterminate()
+     * @see #getSecondaryProgress()
+     * @see #incrementSecondaryProgressBy(int)
+     */
+    public synchronized void setSecondaryProgress(int secondaryProgress) {
+        if (mIndeterminate) {
+            return;
+        }
+
+        if (secondaryProgress < 0) {
+            secondaryProgress = 0;
+        }
+
+        if (secondaryProgress > mMax) {
+            secondaryProgress = mMax;
+        }
+
+        if (secondaryProgress != mSecondaryProgress) {
+            mSecondaryProgress = secondaryProgress;
+            refreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false);
+        }
+    }
+
+    /**
+     * <p>Get the progress bar's current level of progress. Return 0 when the
+     * progress bar is in indeterminate mode.</p>
+     *
+     * @return the current progress, between 0 and {@link #getMax()}
+     *
+     * @see #setIndeterminate(boolean)
+     * @see #isIndeterminate()
+     * @see #setProgress(int)
+     * @see #setMax(int)
+     * @see #getMax()
+     */
+    public synchronized int getProgress() {
+        return mIndeterminate ? 0 : mProgress;
+    }
+
+    /**
+     * <p>Get the progress bar's current level of secondary progress. Return 0 when the
+     * progress bar is in indeterminate mode.</p>
+     *
+     * @return the current secondary progress, between 0 and {@link #getMax()}
+     *
+     * @see #setIndeterminate(boolean)
+     * @see #isIndeterminate()
+     * @see #setSecondaryProgress(int)
+     * @see #setMax(int)
+     * @see #getMax()
+     */
+    public synchronized int getSecondaryProgress() {
+        return mIndeterminate ? 0 : mSecondaryProgress;
+    }
+
+    /**
+     * <p>Return the upper limit of this progress bar's range.</p>
+     *
+     * @return a positive integer
+     *
+     * @see #setMax(int)
+     * @see #getProgress()
+     * @see #getSecondaryProgress()
+     */
+    public synchronized int getMax() {
+        return mMax;
+    }
+
+    /**
+     * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p>
+     *
+     * @param max the upper range of this progress bar
+     *
+     * @see #getMax()
+     * @see #setProgress(int)
+     * @see #setSecondaryProgress(int)
+     */
+    public synchronized void setMax(int max) {
+        if (max < 0) {
+            max = 0;
+        }
+        if (max != mMax) {
+            mMax = max;
+            postInvalidate();
+
+            if (mProgress > max) {
+                mProgress = max;
+            }
+            refreshProgress(android.R.id.progress, mProgress, false);
+        }
+    }
+
+    /**
+     * <p>Increase the progress bar's progress by the specified amount.</p>
+     *
+     * @param diff the amount by which the progress must be increased
+     *
+     * @see #setProgress(int)
+     */
+    public synchronized final void incrementProgressBy(int diff) {
+        setProgress(mProgress + diff);
+    }
+
+    /**
+     * <p>Increase the progress bar's secondary progress by the specified amount.</p>
+     *
+     * @param diff the amount by which the secondary progress must be increased
+     *
+     * @see #setSecondaryProgress(int)
+     */
+    public synchronized final void incrementSecondaryProgressBy(int diff) {
+        setSecondaryProgress(mSecondaryProgress + diff);
+    }
+
+    /**
+     * <p>Start the indeterminate progress animation.</p>
+     */
+    void startAnimation() {
+        if (getVisibility() != VISIBLE) {
+            return;
+        }
+
+        if (mIndeterminateDrawable instanceof Animatable) {
+            mShouldStartAnimationDrawable = true;
+            mAnimation = null;
+        } else {
+            if (mInterpolator == null) {
+                mInterpolator = new LinearInterpolator();
+            }
+
+            mTransformation = new Transformation();
+            mAnimation = new AlphaAnimation(0.0f, 1.0f);
+            mAnimation.setRepeatMode(mBehavior);
+            mAnimation.setRepeatCount(Animation.INFINITE);
+            mAnimation.setDuration(mDuration);
+            mAnimation.setInterpolator(mInterpolator);
+            mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
+        }
+        postInvalidate();
+    }
+
+    /**
+     * <p>Stop the indeterminate progress animation.</p>
+     */
+    void stopAnimation() {
+        mAnimation = null;
+        mTransformation = null;
+        if (mIndeterminateDrawable instanceof Animatable) {
+            ((Animatable) mIndeterminateDrawable).stop();
+            mShouldStartAnimationDrawable = false;
+        }
+        postInvalidate();
+    }
+
+    /**
+     * Sets the acceleration curve for the indeterminate animation.
+     * The interpolator is loaded as a resource from the specified context.
+     *
+     * @param context The application environment
+     * @param resID The resource identifier of the interpolator to load
+     */
+    public void setInterpolator(Context context, int resID) {
+        setInterpolator(AnimationUtils.loadInterpolator(context, resID));
+    }
+
+    /**
+     * Sets the acceleration curve for the indeterminate animation.
+     * Defaults to a linear interpolation.
+     *
+     * @param interpolator The interpolator which defines the acceleration curve
+     */
+    public void setInterpolator(Interpolator interpolator) {
+        mInterpolator = interpolator;
+    }
+
+    /**
+     * Gets the acceleration curve type for the indeterminate animation.
+     *
+     * @return the {@link Interpolator} associated to this animation
+     */
+    public Interpolator getInterpolator() {
+        return mInterpolator;
+    }
+
+    @Override
+    public void setVisibility(int v) {
+        if (getVisibility() != v) {
+            super.setVisibility(v);
+
+            if (mIndeterminate) {
+                // let's be nice with the UI thread
+                if (v == GONE || v == INVISIBLE) {
+                    stopAnimation();
+                } else {
+                    startAnimation();
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void onVisibilityChanged(View changedView, int visibility) {
+        if (Build.VERSION.SDK_INT >= 8) {
+            super.onVisibilityChanged(changedView, visibility);
+        }
+
+        if (mIndeterminate) {
+            // let's be nice with the UI thread
+            if (visibility == GONE || visibility == INVISIBLE) {
+                stopAnimation();
+            } else {
+                startAnimation();
+            }
+        }
+    }
+
+    @Override
+    public void invalidateDrawable(Drawable dr) {
+        if (!mInDrawing) {
+            if (verifyDrawable(dr)) {
+                final Rect dirty = dr.getBounds();
+                final int scrollX = getScrollX() + getPaddingLeft();
+                final int scrollY = getScrollY() + getPaddingTop();
+
+                invalidate(dirty.left + scrollX, dirty.top + scrollY,
+                        dirty.right + scrollX, dirty.bottom + scrollY);
+            } else {
+                super.invalidateDrawable(dr);
+            }
+        }
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        updateDrawableBounds(w, h);
+    }
+
+    private void updateDrawableBounds(int w, int h) {
+        // onDraw will translate the canvas so we draw starting at 0,0
+        int right = w - getPaddingRight() - getPaddingLeft();
+        int bottom = h - getPaddingBottom() - getPaddingTop();
+        int top = 0;
+        int left = 0;
+
+        if (mIndeterminateDrawable != null) {
+            // Aspect ratio logic does not apply to AnimationDrawables
+            if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) {
+                // Maintain aspect ratio. Certain kinds of animated drawables
+                // get very confused otherwise.
+                final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth();
+                final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight();
+                final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
+                final float boundAspect = (float) w / h;
+                if (intrinsicAspect != boundAspect) {
+                    if (boundAspect > intrinsicAspect) {
+                        // New width is larger. Make it smaller to match height.
+                        final int width = (int) (h * intrinsicAspect);
+                        left = (w - width) / 2;
+                        right = left + width;
+                    } else {
+                        // New height is larger. Make it smaller to match width.
+                        final int height = (int) (w * (1 / intrinsicAspect));
+                        top = (h - height) / 2;
+                        bottom = top + height;
+                    }
+                }
+            }
+            mIndeterminateDrawable.setBounds(left, top, right, bottom);
+        }
+
+        if (mProgressDrawable != null) {
+            mProgressDrawable.setBounds(0, 0, right, bottom);
+        }
+    }
+
+    @Override
+    protected synchronized void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        Drawable d = mCurrentDrawable;
+        if (d != null) {
+            // Translate canvas so a indeterminate circular progress bar with padding
+            // rotates properly in its animation
+            canvas.save();
+            canvas.translate(getPaddingLeft(), getPaddingTop());
+            long time = getDrawingTime();
+            if (mAnimation != null) {
+                mAnimation.getTransformation(time, mTransformation);
+                float scale = mTransformation.getAlpha();
+                try {
+                    mInDrawing = true;
+                    d.setLevel((int) (scale * MAX_LEVEL));
+                } finally {
+                    mInDrawing = false;
+                }
+                if (SystemClock.uptimeMillis() - mLastDrawTime >= ANIMATION_RESOLUTION) {
+                    mLastDrawTime = SystemClock.uptimeMillis();
+                    postInvalidateDelayed(ANIMATION_RESOLUTION);
+                }
+            }
+            d.draw(canvas);
+            canvas.restore();
+            if (mShouldStartAnimationDrawable && d instanceof Animatable) {
+                ((Animatable) d).start();
+                mShouldStartAnimationDrawable = false;
+            }
+        }
+    }
+
+    @Override
+    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        Drawable d = mCurrentDrawable;
+
+        int dw = 0;
+        int dh = 0;
+        if (d != null) {
+            dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
+            dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
+        }
+        updateDrawableState();
+        dw += getPaddingLeft() + getPaddingRight();
+        dh += getPaddingTop() + getPaddingBottom();
+
+        setMeasuredDimension(resolveSize(dw, widthMeasureSpec),
+                resolveSize(dh, heightMeasureSpec));
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        updateDrawableState();
+    }
+
+    private void updateDrawableState() {
+        int[] state = getDrawableState();
+
+        if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
+            mProgressDrawable.setState(state);
+        }
+
+        if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
+            mIndeterminateDrawable.setState(state);
+        }
+    }
+
+    static class SavedState extends BaseSavedState {
+        int progress;
+        int secondaryProgress;
+
+        /**
+         * Constructor called from {@link ProgressBarCompat#onSaveInstanceState()}
+         */
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        /**
+         * Constructor called from {@link #CREATOR}
+         */
+        private SavedState(Parcel in) {
+            super(in);
+            progress = in.readInt();
+            secondaryProgress = in.readInt();
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeInt(progress);
+            out.writeInt(secondaryProgress);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        // Force our ancestor class to save its state
+        Parcelable superState = super.onSaveInstanceState();
+        SavedState ss = new SavedState(superState);
+
+        ss.progress = mProgress;
+        ss.secondaryProgress = mSecondaryProgress;
+
+        return ss;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+        super.onRestoreInstanceState(ss.getSuperState());
+
+        setProgress(ss.progress);
+        setSecondaryProgress(ss.secondaryProgress);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (mIndeterminate) {
+            startAnimation();
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        if (mIndeterminate) {
+            stopAnimation();
+        }
+        if(mRefreshProgressRunnable != null) {
+            removeCallbacks(mRefreshProgressRunnable);
+        }
+
+        // This should come after stopAnimation(), otherwise an invalidate message remains in the
+        // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
+        super.onDetachedFromWindow();
+    }
+
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ProgressBarICS.java b/v7/appcompat/src/android/support/v7/internal/widget/ProgressBarICS.java
deleted file mode 100644
index d272b5a..0000000
--- a/v7/appcompat/src/android/support/v7/internal/widget/ProgressBarICS.java
+++ /dev/null
@@ -1,920 +0,0 @@
-package android.support.v7.internal.widget;
-
-/*
- * Copyright (C) 2013 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.
- */
-
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.Shader;
-import android.graphics.drawable.Animatable;
-import android.graphics.drawable.AnimationDrawable;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ClipDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.RoundRectShape;
-import android.graphics.drawable.shapes.Shape;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.SystemClock;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
-import android.view.animation.Transformation;
-
-/**
- * @hide
- */
-public class ProgressBarICS extends View {
-
-    private static final int MAX_LEVEL = 10000;
-    private static final int ANIMATION_RESOLUTION = 200;
-
-    /**
-     * android.R.styleable.ProgressBar is internalised, so we need to create it ourselves.
-      */
-    private static final int[] android_R_styleable_ProgressBar = new int[]{
-            android.R.attr.max,
-            android.R.attr.progress,
-            android.R.attr.secondaryProgress,
-            android.R.attr.indeterminate,
-            android.R.attr.indeterminateOnly,
-            android.R.attr.indeterminateDrawable,
-            android.R.attr.progressDrawable,
-            android.R.attr.indeterminateDuration,
-            android.R.attr.indeterminateBehavior,
-            android.R.attr.minWidth,
-            android.R.attr.maxWidth,
-            android.R.attr.minHeight,
-            android.R.attr.maxHeight,
-            android.R.attr.interpolator,
-    };
-
-    int mMinWidth;
-    int mMaxWidth;
-    int mMinHeight;
-    int mMaxHeight;
-
-    private int mProgress;
-    private int mSecondaryProgress;
-    private int mMax;
-
-    private int mBehavior;
-    private int mDuration;
-    private boolean mIndeterminate;
-    private boolean mOnlyIndeterminate;
-    private Transformation mTransformation;
-    private AlphaAnimation mAnimation;
-    private Drawable mIndeterminateDrawable;
-    private Drawable mProgressDrawable;
-    private Drawable mCurrentDrawable;
-    Bitmap mSampleTile;
-    private boolean mNoInvalidate;
-    private Interpolator mInterpolator;
-    private RefreshProgressRunnable mRefreshProgressRunnable;
-    private long mUiThreadId;
-    private boolean mShouldStartAnimationDrawable;
-    private long mLastDrawTime;
-
-    private boolean mInDrawing;
-
-    /**
-     * @hide
-     */
-    public ProgressBarICS(Context context, AttributeSet attrs, int defStyle, int styleRes) {
-        super(context, attrs, defStyle);
-        mUiThreadId = Thread.currentThread().getId();
-        initProgressBar();
-
-        TypedArray a = context.obtainStyledAttributes(attrs, android_R_styleable_ProgressBar,
-                defStyle, styleRes);
-
-        mNoInvalidate = true;
-
-        setMax(a.getInt(0, mMax));
-        setProgress(a.getInt(1, mProgress));
-        setSecondaryProgress(a.getInt(2, mSecondaryProgress));
-
-        final boolean indeterminate = a.getBoolean(3, mIndeterminate);
-        mOnlyIndeterminate = a.getBoolean(4, mOnlyIndeterminate);
-
-        Drawable drawable = a.getDrawable(5);
-        if (drawable != null) {
-            drawable = tileifyIndeterminate(drawable);
-            setIndeterminateDrawable(drawable);
-        }
-
-        drawable = a.getDrawable(6);
-        if (drawable != null) {
-            drawable = tileify(drawable, false);
-            // Calling this method can set mMaxHeight, make sure the corresponding
-            // XML attribute for mMaxHeight is read after calling this method
-            setProgressDrawable(drawable);
-        }
-
-        mDuration = a.getInt(7, mDuration);
-        mBehavior = a.getInt(8, mBehavior);
-        mMinWidth = a.getDimensionPixelSize(9, mMinWidth);
-        mMaxWidth = a.getDimensionPixelSize(10, mMaxWidth);
-        mMinHeight = a.getDimensionPixelSize(11, mMinHeight);
-        mMaxHeight = a.getDimensionPixelSize(12, mMaxHeight);
-
-        final int resID = a.getResourceId(13, android.R.anim.linear_interpolator);
-        if (resID > 0) {
-            setInterpolator(context, resID);
-        }
-
-        a.recycle();
-
-        mNoInvalidate = false;
-        setIndeterminate(mOnlyIndeterminate || indeterminate);
-    }
-
-    /**
-     * Converts a drawable to a tiled version of itself. It will recursively
-     * traverse layer and state list drawables.
-     */
-    private Drawable tileify(Drawable drawable, boolean clip) {
-
-        if (drawable instanceof LayerDrawable) {
-            LayerDrawable background = (LayerDrawable) drawable;
-            final int N = background.getNumberOfLayers();
-            Drawable[] outDrawables = new Drawable[N];
-
-            for (int i = 0; i < N; i++) {
-                int id = background.getId(i);
-                outDrawables[i] = tileify(background.getDrawable(i),
-                        (id == android.R.id.progress || id == android.R.id.secondaryProgress));
-            }
-
-            LayerDrawable newBg = new LayerDrawable(outDrawables);
-
-            for (int i = 0; i < N; i++) {
-                newBg.setId(i, background.getId(i));
-            }
-
-            return newBg;
-
-        } else if (drawable instanceof BitmapDrawable) {
-            final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
-            if (mSampleTile == null) {
-                mSampleTile = tileBitmap;
-            }
-
-            final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
-
-            final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
-                    Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
-            shapeDrawable.getPaint().setShader(bitmapShader);
-
-            return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
-                    ClipDrawable.HORIZONTAL) : shapeDrawable;
-        }
-
-        return drawable;
-    }
-
-    Shape getDrawableShape() {
-        final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
-        return new RoundRectShape(roundedCorners, null, null);
-    }
-
-    /**
-     * Convert a AnimationDrawable for use as a barberpole animation.
-     * Each frame of the animation is wrapped in a ClipDrawable and
-     * given a tiling BitmapShader.
-     */
-    private Drawable tileifyIndeterminate(Drawable drawable) {
-        if (drawable instanceof AnimationDrawable) {
-            AnimationDrawable background = (AnimationDrawable) drawable;
-            final int N = background.getNumberOfFrames();
-            AnimationDrawable newBg = new AnimationDrawable();
-            newBg.setOneShot(background.isOneShot());
-
-            for (int i = 0; i < N; i++) {
-                Drawable frame = tileify(background.getFrame(i), true);
-                frame.setLevel(10000);
-                newBg.addFrame(frame, background.getDuration(i));
-            }
-            newBg.setLevel(10000);
-            drawable = newBg;
-        }
-        return drawable;
-    }
-
-    /**
-     * <p>
-     * Initialize the progress bar's default values:
-     * </p>
-     * <ul>
-     * <li>progress = 0</li>
-     * <li>max = 100</li>
-     * <li>animation duration = 4000 ms</li>
-     * <li>indeterminate = false</li>
-     * <li>behavior = repeat</li>
-     * </ul>
-     */
-    private void initProgressBar() {
-        mMax = 100;
-        mProgress = 0;
-        mSecondaryProgress = 0;
-        mIndeterminate = false;
-        mOnlyIndeterminate = false;
-        mDuration = 4000;
-        mBehavior = AlphaAnimation.RESTART;
-        mMinWidth = 24;
-        mMaxWidth = 48;
-        mMinHeight = 24;
-        mMaxHeight = 48;
-    }
-
-    /**
-     * <p>Indicate whether this progress bar is in indeterminate mode.</p>
-     *
-     * @return true if the progress bar is in indeterminate mode
-     */
-    public synchronized boolean isIndeterminate() {
-        return mIndeterminate;
-    }
-
-    /**
-     * <p>Change the indeterminate mode for this progress bar. In indeterminate
-     * mode, the progress is ignored and the progress bar shows an infinite
-     * animation instead.</p>
-     *
-     * If this progress bar's style only supports indeterminate mode (such as the circular
-     * progress bars), then this will be ignored.
-     *
-     * @param indeterminate true to enable the indeterminate mode
-     */
-    public synchronized void setIndeterminate(boolean indeterminate) {
-        if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
-            mIndeterminate = indeterminate;
-
-            if (indeterminate) {
-                // swap between indeterminate and regular backgrounds
-                mCurrentDrawable = mIndeterminateDrawable;
-                startAnimation();
-            } else {
-                mCurrentDrawable = mProgressDrawable;
-                stopAnimation();
-            }
-        }
-    }
-
-    /**
-     * <p>Get the drawable used to draw the progress bar in
-     * indeterminate mode.</p>
-     *
-     * @return a {@link android.graphics.drawable.Drawable} instance
-     *
-     * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable)
-     * @see #setIndeterminate(boolean)
-     */
-    public Drawable getIndeterminateDrawable() {
-        return mIndeterminateDrawable;
-    }
-
-    /**
-     * <p>Define the drawable used to draw the progress bar in
-     * indeterminate mode.</p>
-     *
-     * @param d the new drawable
-     *
-     * @see #getIndeterminateDrawable()
-     * @see #setIndeterminate(boolean)
-     */
-    public void setIndeterminateDrawable(Drawable d) {
-        if (d != null) {
-            d.setCallback(this);
-        }
-        mIndeterminateDrawable = d;
-        if (mIndeterminate) {
-            mCurrentDrawable = d;
-            postInvalidate();
-        }
-    }
-
-    /**
-     * <p>Get the drawable used to draw the progress bar in
-     * progress mode.</p>
-     *
-     * @return a {@link android.graphics.drawable.Drawable} instance
-     *
-     * @see #setProgressDrawable(android.graphics.drawable.Drawable)
-     * @see #setIndeterminate(boolean)
-     */
-    public Drawable getProgressDrawable() {
-        return mProgressDrawable;
-    }
-
-    /**
-     * <p>Define the drawable used to draw the progress bar in
-     * progress mode.</p>
-     *
-     * @param d the new drawable
-     *
-     * @see #getProgressDrawable()
-     * @see #setIndeterminate(boolean)
-     */
-    public void setProgressDrawable(Drawable d) {
-        boolean needUpdate;
-        if (mProgressDrawable != null && d != mProgressDrawable) {
-            mProgressDrawable.setCallback(null);
-            needUpdate = true;
-        } else {
-            needUpdate = false;
-        }
-
-        if (d != null) {
-            d.setCallback(this);
-
-            // Make sure the android_R_styleable_ProgressBar is always tall enough
-            int drawableHeight = d.getMinimumHeight();
-            if (mMaxHeight < drawableHeight) {
-                mMaxHeight = drawableHeight;
-                requestLayout();
-            }
-        }
-        mProgressDrawable = d;
-        if (!mIndeterminate) {
-            mCurrentDrawable = d;
-            postInvalidate();
-        }
-
-        if (needUpdate) {
-            updateDrawableBounds(getWidth(), getHeight());
-            updateDrawableState();
-            doRefreshProgress(android.R.id.progress, mProgress, false, false);
-            doRefreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false, false);
-        }
-    }
-
-    @Override
-    protected boolean verifyDrawable(Drawable who) {
-        return who == mProgressDrawable || who == mIndeterminateDrawable
-                || super.verifyDrawable(who);
-    }
-
-    @Override
-    public void postInvalidate() {
-        if (!mNoInvalidate) {
-            super.postInvalidate();
-        }
-    }
-
-    private class RefreshProgressRunnable implements Runnable {
-
-        private int mId;
-        private int mProgress;
-        private boolean mFromUser;
-
-        RefreshProgressRunnable(int id, int progress, boolean fromUser) {
-            mId = id;
-            mProgress = progress;
-            mFromUser = fromUser;
-        }
-
-        public void run() {
-            doRefreshProgress(mId, mProgress, mFromUser, true);
-            // Put ourselves back in the cache when we are done
-            mRefreshProgressRunnable = this;
-        }
-
-        public void setup(int id, int progress, boolean fromUser) {
-            mId = id;
-            mProgress = progress;
-            mFromUser = fromUser;
-        }
-
-    }
-
-    private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
-            boolean callBackToApp) {
-        float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
-        final Drawable d = mCurrentDrawable;
-        if (d != null) {
-            Drawable progressDrawable = null;
-
-            if (d instanceof LayerDrawable) {
-                progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id);
-            }
-
-            final int level = (int) (scale * MAX_LEVEL);
-            (progressDrawable != null ? progressDrawable : d).setLevel(level);
-        } else {
-            invalidate();
-        }
-    }
-
-    private synchronized void refreshProgress(int id, int progress, boolean fromUser) {
-        if (mUiThreadId == Thread.currentThread().getId()) {
-            doRefreshProgress(id, progress, fromUser, true);
-        } else {
-            RefreshProgressRunnable r;
-            if (mRefreshProgressRunnable != null) {
-                // Use cached RefreshProgressRunnable if available
-                r = mRefreshProgressRunnable;
-                // Uncache it
-                mRefreshProgressRunnable = null;
-                r.setup(id, progress, fromUser);
-            } else {
-                // Make a new one
-                r = new RefreshProgressRunnable(id, progress, fromUser);
-            }
-            post(r);
-        }
-    }
-
-    /**
-     * <p>Set the current progress to the specified value. Does not do anything
-     * if the progress bar is in indeterminate mode.</p>
-     *
-     * @param progress the new progress, between 0 and {@link #getMax()}
-     *
-     * @see #setIndeterminate(boolean)
-     * @see #isIndeterminate()
-     * @see #getProgress()
-     * @see #incrementProgressBy(int)
-     */
-    public synchronized void setProgress(int progress) {
-        setProgress(progress, false);
-    }
-
-    synchronized void setProgress(int progress, boolean fromUser) {
-        if (mIndeterminate) {
-            return;
-        }
-
-        if (progress < 0) {
-            progress = 0;
-        }
-
-        if (progress > mMax) {
-            progress = mMax;
-        }
-
-        if (progress != mProgress) {
-            mProgress = progress;
-            refreshProgress(android.R.id.progress, mProgress, fromUser);
-        }
-    }
-
-    /**
-     * <p>
-     * Set the current secondary progress to the specified value. Does not do
-     * anything if the progress bar is in indeterminate mode.
-     * </p>
-     *
-     * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
-     * @see #setIndeterminate(boolean)
-     * @see #isIndeterminate()
-     * @see #getSecondaryProgress()
-     * @see #incrementSecondaryProgressBy(int)
-     */
-    public synchronized void setSecondaryProgress(int secondaryProgress) {
-        if (mIndeterminate) {
-            return;
-        }
-
-        if (secondaryProgress < 0) {
-            secondaryProgress = 0;
-        }
-
-        if (secondaryProgress > mMax) {
-            secondaryProgress = mMax;
-        }
-
-        if (secondaryProgress != mSecondaryProgress) {
-            mSecondaryProgress = secondaryProgress;
-            refreshProgress(android.R.id.secondaryProgress, mSecondaryProgress, false);
-        }
-    }
-
-    /**
-     * <p>Get the progress bar's current level of progress. Return 0 when the
-     * progress bar is in indeterminate mode.</p>
-     *
-     * @return the current progress, between 0 and {@link #getMax()}
-     *
-     * @see #setIndeterminate(boolean)
-     * @see #isIndeterminate()
-     * @see #setProgress(int)
-     * @see #setMax(int)
-     * @see #getMax()
-     */
-    public synchronized int getProgress() {
-        return mIndeterminate ? 0 : mProgress;
-    }
-
-    /**
-     * <p>Get the progress bar's current level of secondary progress. Return 0 when the
-     * progress bar is in indeterminate mode.</p>
-     *
-     * @return the current secondary progress, between 0 and {@link #getMax()}
-     *
-     * @see #setIndeterminate(boolean)
-     * @see #isIndeterminate()
-     * @see #setSecondaryProgress(int)
-     * @see #setMax(int)
-     * @see #getMax()
-     */
-    public synchronized int getSecondaryProgress() {
-        return mIndeterminate ? 0 : mSecondaryProgress;
-    }
-
-    /**
-     * <p>Return the upper limit of this progress bar's range.</p>
-     *
-     * @return a positive integer
-     *
-     * @see #setMax(int)
-     * @see #getProgress()
-     * @see #getSecondaryProgress()
-     */
-    public synchronized int getMax() {
-        return mMax;
-    }
-
-    /**
-     * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p>
-     *
-     * @param max the upper range of this progress bar
-     *
-     * @see #getMax()
-     * @see #setProgress(int)
-     * @see #setSecondaryProgress(int)
-     */
-    public synchronized void setMax(int max) {
-        if (max < 0) {
-            max = 0;
-        }
-        if (max != mMax) {
-            mMax = max;
-            postInvalidate();
-
-            if (mProgress > max) {
-                mProgress = max;
-            }
-            refreshProgress(android.R.id.progress, mProgress, false);
-        }
-    }
-
-    /**
-     * <p>Increase the progress bar's progress by the specified amount.</p>
-     *
-     * @param diff the amount by which the progress must be increased
-     *
-     * @see #setProgress(int)
-     */
-    public synchronized final void incrementProgressBy(int diff) {
-        setProgress(mProgress + diff);
-    }
-
-    /**
-     * <p>Increase the progress bar's secondary progress by the specified amount.</p>
-     *
-     * @param diff the amount by which the secondary progress must be increased
-     *
-     * @see #setSecondaryProgress(int)
-     */
-    public synchronized final void incrementSecondaryProgressBy(int diff) {
-        setSecondaryProgress(mSecondaryProgress + diff);
-    }
-
-    /**
-     * <p>Start the indeterminate progress animation.</p>
-     */
-    void startAnimation() {
-        if (getVisibility() != VISIBLE) {
-            return;
-        }
-
-        if (mIndeterminateDrawable instanceof Animatable) {
-            mShouldStartAnimationDrawable = true;
-            mAnimation = null;
-        } else {
-            if (mInterpolator == null) {
-                mInterpolator = new LinearInterpolator();
-            }
-
-            mTransformation = new Transformation();
-            mAnimation = new AlphaAnimation(0.0f, 1.0f);
-            mAnimation.setRepeatMode(mBehavior);
-            mAnimation.setRepeatCount(Animation.INFINITE);
-            mAnimation.setDuration(mDuration);
-            mAnimation.setInterpolator(mInterpolator);
-            mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
-        }
-        postInvalidate();
-    }
-
-    /**
-     * <p>Stop the indeterminate progress animation.</p>
-     */
-    void stopAnimation() {
-        mAnimation = null;
-        mTransformation = null;
-        if (mIndeterminateDrawable instanceof Animatable) {
-            ((Animatable) mIndeterminateDrawable).stop();
-            mShouldStartAnimationDrawable = false;
-        }
-        postInvalidate();
-    }
-
-    /**
-     * Sets the acceleration curve for the indeterminate animation.
-     * The interpolator is loaded as a resource from the specified context.
-     *
-     * @param context The application environment
-     * @param resID The resource identifier of the interpolator to load
-     */
-    public void setInterpolator(Context context, int resID) {
-        setInterpolator(AnimationUtils.loadInterpolator(context, resID));
-    }
-
-    /**
-     * Sets the acceleration curve for the indeterminate animation.
-     * Defaults to a linear interpolation.
-     *
-     * @param interpolator The interpolator which defines the acceleration curve
-     */
-    public void setInterpolator(Interpolator interpolator) {
-        mInterpolator = interpolator;
-    }
-
-    /**
-     * Gets the acceleration curve type for the indeterminate animation.
-     *
-     * @return the {@link Interpolator} associated to this animation
-     */
-    public Interpolator getInterpolator() {
-        return mInterpolator;
-    }
-
-    @Override
-    public void setVisibility(int v) {
-        if (getVisibility() != v) {
-            super.setVisibility(v);
-
-            if (mIndeterminate) {
-                // let's be nice with the UI thread
-                if (v == GONE || v == INVISIBLE) {
-                    stopAnimation();
-                } else {
-                    startAnimation();
-                }
-            }
-        }
-    }
-
-    @Override
-    protected void onVisibilityChanged(View changedView, int visibility) {
-        super.onVisibilityChanged(changedView, visibility);
-
-        if (mIndeterminate) {
-            // let's be nice with the UI thread
-            if (visibility == GONE || visibility == INVISIBLE) {
-                stopAnimation();
-            } else {
-                startAnimation();
-            }
-        }
-    }
-
-    @Override
-    public void invalidateDrawable(Drawable dr) {
-        if (!mInDrawing) {
-            if (verifyDrawable(dr)) {
-                final Rect dirty = dr.getBounds();
-                final int scrollX = getScrollX() + getPaddingLeft();
-                final int scrollY = getScrollY() + getPaddingTop();
-
-                invalidate(dirty.left + scrollX, dirty.top + scrollY,
-                        dirty.right + scrollX, dirty.bottom + scrollY);
-            } else {
-                super.invalidateDrawable(dr);
-            }
-        }
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        updateDrawableBounds(w, h);
-    }
-
-    private void updateDrawableBounds(int w, int h) {
-        // onDraw will translate the canvas so we draw starting at 0,0
-        int right = w - getPaddingRight() - getPaddingLeft();
-        int bottom = h - getPaddingBottom() - getPaddingTop();
-        int top = 0;
-        int left = 0;
-
-        if (mIndeterminateDrawable != null) {
-            // Aspect ratio logic does not apply to AnimationDrawables
-            if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) {
-                // Maintain aspect ratio. Certain kinds of animated drawables
-                // get very confused otherwise.
-                final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth();
-                final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight();
-                final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight;
-                final float boundAspect = (float) w / h;
-                if (intrinsicAspect != boundAspect) {
-                    if (boundAspect > intrinsicAspect) {
-                        // New width is larger. Make it smaller to match height.
-                        final int width = (int) (h * intrinsicAspect);
-                        left = (w - width) / 2;
-                        right = left + width;
-                    } else {
-                        // New height is larger. Make it smaller to match width.
-                        final int height = (int) (w * (1 / intrinsicAspect));
-                        top = (h - height) / 2;
-                        bottom = top + height;
-                    }
-                }
-            }
-            mIndeterminateDrawable.setBounds(left, top, right, bottom);
-        }
-
-        if (mProgressDrawable != null) {
-            mProgressDrawable.setBounds(0, 0, right, bottom);
-        }
-    }
-
-    @Override
-    protected synchronized void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-
-        Drawable d = mCurrentDrawable;
-        if (d != null) {
-            // Translate canvas so a indeterminate circular progress bar with padding
-            // rotates properly in its animation
-            canvas.save();
-            canvas.translate(getPaddingLeft(), getPaddingTop());
-            long time = getDrawingTime();
-            if (mAnimation != null) {
-                mAnimation.getTransformation(time, mTransformation);
-                float scale = mTransformation.getAlpha();
-                try {
-                    mInDrawing = true;
-                    d.setLevel((int) (scale * MAX_LEVEL));
-                } finally {
-                    mInDrawing = false;
-                }
-                if (SystemClock.uptimeMillis() - mLastDrawTime >= ANIMATION_RESOLUTION) {
-                    mLastDrawTime = SystemClock.uptimeMillis();
-                    postInvalidateDelayed(ANIMATION_RESOLUTION);
-                }
-            }
-            d.draw(canvas);
-            canvas.restore();
-            if (mShouldStartAnimationDrawable && d instanceof Animatable) {
-                ((Animatable) d).start();
-                mShouldStartAnimationDrawable = false;
-            }
-        }
-    }
-
-    @Override
-    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        Drawable d = mCurrentDrawable;
-
-        int dw = 0;
-        int dh = 0;
-        if (d != null) {
-            dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
-            dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
-        }
-        updateDrawableState();
-        dw += getPaddingLeft() + getPaddingRight();
-        dh += getPaddingTop() + getPaddingBottom();
-
-        setMeasuredDimension(resolveSize(dw, widthMeasureSpec),
-                resolveSize(dh, heightMeasureSpec));
-    }
-
-    @Override
-    protected void drawableStateChanged() {
-        super.drawableStateChanged();
-        updateDrawableState();
-    }
-
-    private void updateDrawableState() {
-        int[] state = getDrawableState();
-
-        if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
-            mProgressDrawable.setState(state);
-        }
-
-        if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
-            mIndeterminateDrawable.setState(state);
-        }
-    }
-
-    static class SavedState extends BaseSavedState {
-        int progress;
-        int secondaryProgress;
-
-        /**
-         * Constructor called from {@link ProgressBarICS#onSaveInstanceState()}
-         */
-        SavedState(Parcelable superState) {
-            super(superState);
-        }
-
-        /**
-         * Constructor called from {@link #CREATOR}
-         */
-        private SavedState(Parcel in) {
-            super(in);
-            progress = in.readInt();
-            secondaryProgress = in.readInt();
-        }
-
-        @Override
-        public void writeToParcel(Parcel out, int flags) {
-            super.writeToParcel(out, flags);
-            out.writeInt(progress);
-            out.writeInt(secondaryProgress);
-        }
-
-        public static final Parcelable.Creator<SavedState> CREATOR
-                = new Parcelable.Creator<SavedState>() {
-            public SavedState createFromParcel(Parcel in) {
-                return new SavedState(in);
-            }
-
-            public SavedState[] newArray(int size) {
-                return new SavedState[size];
-            }
-        };
-    }
-
-    @Override
-    public Parcelable onSaveInstanceState() {
-        // Force our ancestor class to save its state
-        Parcelable superState = super.onSaveInstanceState();
-        SavedState ss = new SavedState(superState);
-
-        ss.progress = mProgress;
-        ss.secondaryProgress = mSecondaryProgress;
-
-        return ss;
-    }
-
-    @Override
-    public void onRestoreInstanceState(Parcelable state) {
-        SavedState ss = (SavedState) state;
-        super.onRestoreInstanceState(ss.getSuperState());
-
-        setProgress(ss.progress);
-        setSecondaryProgress(ss.secondaryProgress);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        if (mIndeterminate) {
-            startAnimation();
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        if (mIndeterminate) {
-            stopAnimation();
-        }
-        if(mRefreshProgressRunnable != null) {
-            removeCallbacks(mRefreshProgressRunnable);
-        }
-
-        // This should come after stopAnimation(), otherwise an invalidate message remains in the
-        // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
-        super.onDetachedFromWindow();
-    }
-
-}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/RtlSpacingHelper.java b/v7/appcompat/src/android/support/v7/internal/widget/RtlSpacingHelper.java
new file mode 100644
index 0000000..e6c80d1
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/RtlSpacingHelper.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v7.internal.widget;
+
+/**
+ * RtlSpacingHelper manages the relationship between left/right and start/end for views
+ * that need to maintain both absolute and relative settings for a form of spacing similar
+ * to view padding.
+ *
+ * @hide
+ */
+public class RtlSpacingHelper {
+    public static final int UNDEFINED = Integer.MIN_VALUE;
+
+    private int mLeft = 0;
+    private int mRight = 0;
+    private int mStart = UNDEFINED;
+    private int mEnd = UNDEFINED;
+    private int mExplicitLeft = 0;
+    private int mExplicitRight = 0;
+
+    private boolean mIsRtl = false;
+    private boolean mIsRelative = false;
+
+    public int getLeft() {
+        return mLeft;
+    }
+
+    public int getRight() {
+        return mRight;
+    }
+
+    public int getStart() {
+        return mIsRtl ? mRight : mLeft;
+    }
+
+    public int getEnd() {
+        return mIsRtl ? mLeft : mRight;
+    }
+
+    public void setRelative(int start, int end) {
+        mStart = start;
+        mEnd = end;
+        mIsRelative = true;
+        if (mIsRtl) {
+            if (end != UNDEFINED) mLeft = end;
+            if (start != UNDEFINED) mRight = start;
+        } else {
+            if (start != UNDEFINED) mLeft = start;
+            if (end != UNDEFINED) mRight = end;
+        }
+    }
+
+    public void setAbsolute(int left, int right) {
+        mIsRelative = false;
+        if (left != UNDEFINED) mLeft = mExplicitLeft = left;
+        if (right != UNDEFINED) mRight = mExplicitRight = right;
+    }
+
+    public void setDirection(boolean isRtl) {
+        if (isRtl == mIsRtl) {
+            return;
+        }
+        mIsRtl = isRtl;
+        if (mIsRelative) {
+            if (isRtl) {
+                mLeft = mEnd != UNDEFINED ? mEnd : mExplicitLeft;
+                mRight = mStart != UNDEFINED ? mStart : mExplicitRight;
+            } else {
+                mLeft = mStart != UNDEFINED ? mStart : mExplicitLeft;
+                mRight = mEnd != UNDEFINED ? mEnd : mExplicitRight;
+            }
+        } else {
+            mLeft = mExplicitLeft;
+            mRight = mExplicitRight;
+        }
+    }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ScrollingTabContainerView.java b/v7/appcompat/src/android/support/v7/internal/widget/ScrollingTabContainerView.java
index 4a3550c..27ab53a 100644
--- a/v7/appcompat/src/android/support/v7/internal/widget/ScrollingTabContainerView.java
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ScrollingTabContainerView.java
@@ -18,22 +18,33 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewPropertyAnimatorCompat;
+import android.support.v4.view.ViewPropertyAnimatorListener;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.support.v7.app.ActionBar;
 import android.support.v7.appcompat.R;
 import android.support.v7.internal.view.ActionBarPolicy;
+import android.support.v7.widget.LinearLayoutCompat;
+import android.text.TextUtils;
 import android.text.TextUtils.TruncateAt;
-import android.util.AttributeSet;
 import android.view.Gravity;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
 import android.widget.BaseAdapter;
 import android.widget.HorizontalScrollView;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.TextView;
+import android.widget.Toast;
 
 /**
  * This widget implements the dynamic action bar tab behavior that can change across different
@@ -42,26 +53,30 @@
  * @hide
  */
 public class ScrollingTabContainerView extends HorizontalScrollView
-        implements AdapterViewICS.OnItemClickListener {
+        implements AdapterViewCompat.OnItemClickListener {
 
     private static final String TAG = "ScrollingTabContainerView";
     Runnable mTabSelector;
     private TabClickListener mTabClickListener;
 
-    private LinearLayout mTabLayout;
-    private SpinnerICS mTabSpinner;
+    private LinearLayoutCompat mTabLayout;
+    private SpinnerCompat mTabSpinner;
     private boolean mAllowCollapse;
 
-    private final LayoutInflater mInflater;
-
     int mMaxTabWidth;
     int mStackedTabMaxWidth;
     private int mContentHeight;
     private int mSelectedTabIndex;
 
+    protected ViewPropertyAnimatorCompat mVisibilityAnim;
+    protected final VisibilityAnimListener mVisAnimListener = new VisibilityAnimListener();
+
+    private static final Interpolator sAlphaInterpolator = new DecelerateInterpolator();
+
+    private static final int FADE_DURATION = 200;
+
     public ScrollingTabContainerView(Context context) {
         super(context);
-        mInflater = LayoutInflater.from(context);
 
         setHorizontalScrollBarEnabled(false);
 
@@ -69,9 +84,9 @@
         setContentHeight(abp.getTabContainerHeight());
         mStackedTabMaxWidth = abp.getStackedTabMaxWidth();
 
-        mTabLayout = (LinearLayout) mInflater.inflate(R.layout.abc_action_bar_tabbar, this, false);
+        mTabLayout = createTabLayout();
         addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
-                ViewGroup.LayoutParams.FILL_PARENT));
+                ViewGroup.LayoutParams.MATCH_PARENT));
     }
 
     @Override
@@ -120,8 +135,8 @@
     }
 
     /**
-     * Indicates whether this view is collapsed into a dropdown menu instead of traditional tabs.
-     *
+     * Indicates whether this view is collapsed into a dropdown menu instead
+     * of traditional tabs.
      * @return true if showing as a spinner
      */
     private boolean isCollapsed() {
@@ -133,16 +148,14 @@
     }
 
     private void performCollapse() {
-        if (isCollapsed()) {
-            return;
-        }
+        if (isCollapsed()) return;
 
         if (mTabSpinner == null) {
             mTabSpinner = createSpinner();
         }
         removeView(mTabLayout);
         addView(mTabSpinner, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
-                ViewGroup.LayoutParams.FILL_PARENT));
+                ViewGroup.LayoutParams.MATCH_PARENT));
         if (mTabSpinner.getAdapter() == null) {
             mTabSpinner.setAdapter(new TabAdapter());
         }
@@ -154,13 +167,11 @@
     }
 
     private boolean performExpand() {
-        if (!isCollapsed()) {
-            return false;
-        }
+        if (!isCollapsed()) return false;
 
         removeView(mTabSpinner);
         addView(mTabLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
-                ViewGroup.LayoutParams.FILL_PARENT));
+                ViewGroup.LayoutParams.MATCH_PARENT));
         setTabSelected(mTabSpinner.getSelectedItemPosition());
         return false;
     }
@@ -186,16 +197,30 @@
         requestLayout();
     }
 
-    private SpinnerICS createSpinner() {
-        final SpinnerICS spinner = new SpinnerICS(getContext(), null,
+    private LinearLayoutCompat createTabLayout() {
+        final LinearLayoutCompat tabLayout = new LinearLayoutCompat(getContext(), null,
+                R.attr.actionBarTabBarStyle);
+        tabLayout.setMeasureWithLargestChildEnabled(true);
+        tabLayout.setGravity(Gravity.CENTER);
+        tabLayout.setLayoutParams(new LinearLayoutCompat.LayoutParams(
+                LinearLayoutCompat.LayoutParams.WRAP_CONTENT, LinearLayoutCompat.LayoutParams.MATCH_PARENT));
+        return tabLayout;
+    }
+
+    private SpinnerCompat createSpinner() {
+        final SpinnerCompat spinner = new SpinnerCompat(getContext(), null,
                 R.attr.actionDropDownStyle);
-        spinner.setLayoutParams(new LinearLayout.LayoutParams(
-                LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.FILL_PARENT));
+        spinner.setLayoutParams(new LinearLayoutCompat.LayoutParams(
+                LinearLayoutCompat.LayoutParams.WRAP_CONTENT, LinearLayoutCompat.LayoutParams.MATCH_PARENT));
         spinner.setOnItemClickListenerInt(this);
         return spinner;
     }
 
     protected void onConfigurationChanged(Configuration newConfig) {
+        if (Build.VERSION.SDK_INT >= 8) {
+            super.onConfigurationChanged(newConfig);
+        }
+
         ActionBarPolicy abp = ActionBarPolicy.get(getContext());
         // Action bar can change size on configuration changes.
         // Reread the desired height from the theme-specified style.
@@ -203,6 +228,31 @@
         mStackedTabMaxWidth = abp.getStackedTabMaxWidth();
     }
 
+    public void animateToVisibility(int visibility) {
+        if (mVisibilityAnim != null) {
+            mVisibilityAnim.cancel();
+        }
+        if (visibility == VISIBLE) {
+            if (getVisibility() != VISIBLE) {
+                ViewCompat.setAlpha(this, 0f);
+            }
+
+            ViewPropertyAnimatorCompat anim = ViewCompat.animate(this).alpha(1f);
+            anim.setDuration(FADE_DURATION);
+
+            anim.setInterpolator(sAlphaInterpolator);
+            anim.setListener(mVisAnimListener.withFinalVisibility(anim, visibility));
+            anim.start();
+        } else {
+            ViewPropertyAnimatorCompat anim = ViewCompat.animate(this).alpha(0f);
+            anim.setDuration(FADE_DURATION);
+
+            anim.setInterpolator(sAlphaInterpolator);
+            anim.setListener(mVisAnimListener.withFinalVisibility(anim, visibility));
+            anim.start();
+        }
+    }
+
     public void animateToTab(final int position) {
         final View tabView = mTabLayout.getChildAt(position);
         if (mTabSelector != null) {
@@ -236,13 +286,10 @@
     }
 
     private TabView createTabView(ActionBar.Tab tab, boolean forAdapter) {
-        final TabView tabView = (TabView) mInflater.inflate(R.layout.abc_action_bar_tab, mTabLayout,
-                false);
-        tabView.attach(this, tab, forAdapter);
-
+        final TabView tabView = new TabView(getContext(), tab, forAdapter);
         if (forAdapter) {
             tabView.setBackgroundDrawable(null);
-            tabView.setLayoutParams(new ListView.LayoutParams(ListView.LayoutParams.FILL_PARENT,
+            tabView.setLayoutParams(new ListView.LayoutParams(ListView.LayoutParams.MATCH_PARENT,
                     mContentHeight));
         } else {
             tabView.setFocusable(true);
@@ -257,8 +304,8 @@
 
     public void addTab(ActionBar.Tab tab, boolean setSelected) {
         TabView tabView = createTabView(tab, false);
-        mTabLayout.addView(tabView, new LinearLayout.LayoutParams(0,
-                LayoutParams.FILL_PARENT, 1));
+        mTabLayout.addView(tabView, new LinearLayoutCompat.LayoutParams(0,
+                LayoutParams.MATCH_PARENT, 1));
         if (mTabSpinner != null) {
             ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
         }
@@ -272,8 +319,8 @@
 
     public void addTab(ActionBar.Tab tab, int position, boolean setSelected) {
         final TabView tabView = createTabView(tab, false);
-        mTabLayout.addView(tabView, position, new LinearLayout.LayoutParams(
-                0, LayoutParams.FILL_PARENT, 1));
+        mTabLayout.addView(tabView, position, new LinearLayoutCompat.LayoutParams(
+                0, LayoutParams.MATCH_PARENT, 1));
         if (mTabSpinner != null) {
             ((TabAdapter) mTabSpinner.getAdapter()).notifyDataSetChanged();
         }
@@ -316,32 +363,23 @@
     }
 
     @Override
-    public void onItemClick(AdapterViewICS<?> parent, View view, int position, long id) {
+    public void onItemClick(AdapterViewCompat<?> parent, View view, int position, long id) {
         TabView tabView = (TabView) view;
         tabView.getTab().select();
     }
 
-    /**
-     * @hide
-     */
-    public static class TabView extends LinearLayout {
-
+    private class TabView extends LinearLayoutCompat implements OnLongClickListener {
         private ActionBar.Tab mTab;
         private TextView mTextView;
         private ImageView mIconView;
         private View mCustomView;
-        private ScrollingTabContainerView mParent;
 
-        public TabView(Context context, AttributeSet attrs) {
-            super(context, attrs);
-        }
-
-        void attach(ScrollingTabContainerView parent, ActionBar.Tab tab, boolean forList) {
-            mParent = parent;
+        public TabView(Context context, ActionBar.Tab tab, boolean forList) {
+            super(context, null, R.attr.actionBarTabStyle);
             mTab = tab;
 
             if (forList) {
-                setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
+                setGravity(GravityCompat.START | Gravity.CENTER_VERTICAL);
             }
 
             update();
@@ -353,14 +391,38 @@
         }
 
         @Override
+        public void setSelected(boolean selected) {
+            final boolean changed = (isSelected() != selected);
+            super.setSelected(selected);
+            if (changed && selected) {
+                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+            }
+        }
+
+        @Override
+        public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+            super.onInitializeAccessibilityEvent(event);
+            // This view masquerades as an action bar tab.
+            event.setClassName(ActionBar.Tab.class.getName());
+        }
+
+        @Override
+        public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+            super.onInitializeAccessibilityNodeInfo(info);
+
+            if (Build.VERSION.SDK_INT >= 14) {
+                // This view masquerades as an action bar tab.
+                info.setClassName(ActionBar.Tab.class.getName());
+            }
+        }
+
+        @Override
         public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
-            int maxTabWidth = mParent != null ? mParent.mMaxTabWidth : 0;
-
             // Re-measure if we went beyond our maximum size.
-            if (maxTabWidth > 0 && getMeasuredWidth() > maxTabWidth) {
-                super.onMeasure(MeasureSpec.makeMeasureSpec(maxTabWidth, MeasureSpec.EXACTLY),
+            if (mMaxTabWidth > 0 && getMeasuredWidth() > mMaxTabWidth) {
+                super.onMeasure(MeasureSpec.makeMeasureSpec(mMaxTabWidth, MeasureSpec.EXACTLY),
                         heightMeasureSpec);
             }
         }
@@ -371,15 +433,11 @@
             if (custom != null) {
                 final ViewParent customParent = custom.getParent();
                 if (customParent != this) {
-                    if (customParent != null) {
-                        ((ViewGroup) customParent).removeView(custom);
-                    }
+                    if (customParent != null) ((ViewGroup) customParent).removeView(custom);
                     addView(custom);
                 }
                 mCustomView = custom;
-                if (mTextView != null) {
-                    mTextView.setVisibility(GONE);
-                }
+                if (mTextView != null) mTextView.setVisibility(GONE);
                 if (mIconView != null) {
                     mIconView.setVisibility(GONE);
                     mIconView.setImageDrawable(null);
@@ -410,7 +468,8 @@
                     mIconView.setImageDrawable(null);
                 }
 
-                if (text != null) {
+                final boolean hasText = !TextUtils.isEmpty(text);
+                if (hasText) {
                     if (mTextView == null) {
                         TextView textView = new CompatTextView(getContext(), null,
                                 R.attr.actionBarTabTextStyle);
@@ -432,16 +491,41 @@
                 if (mIconView != null) {
                     mIconView.setContentDescription(tab.getContentDescription());
                 }
+
+                if (!hasText && !TextUtils.isEmpty(tab.getContentDescription())) {
+                    setOnLongClickListener(this);
+                } else {
+                    setOnLongClickListener(null);
+                    setLongClickable(false);
+                }
             }
         }
 
+        public boolean onLongClick(View v) {
+            final int[] screenPos = new int[2];
+            getLocationOnScreen(screenPos);
+
+            final Context context = getContext();
+            final int width = getWidth();
+            final int height = getHeight();
+            final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
+
+            Toast cheatSheet = Toast.makeText(context, mTab.getContentDescription(),
+                    Toast.LENGTH_SHORT);
+            // Show under the tab
+            cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
+                    (screenPos[0] + width / 2) - screenWidth / 2, height);
+
+            cheatSheet.show();
+            return true;
+        }
+
         public ActionBar.Tab getTab() {
             return mTab;
         }
     }
 
     private class TabAdapter extends BaseAdapter {
-
         @Override
         public int getCount() {
             return mTabLayout.getChildCount();
@@ -469,7 +553,6 @@
     }
 
     private class TabClickListener implements OnClickListener {
-
         public void onClick(View view) {
             TabView tabView = (TabView) view;
             tabView.getTab().select();
@@ -481,5 +564,35 @@
         }
     }
 
+    protected class VisibilityAnimListener implements ViewPropertyAnimatorListener {
+        private boolean mCanceled = false;
+        private int mFinalVisibility;
+
+        public VisibilityAnimListener withFinalVisibility(ViewPropertyAnimatorCompat animation,
+                int visibility) {
+            mFinalVisibility = visibility;
+            mVisibilityAnim = animation;
+            return this;
+        }
+
+        @Override
+        public void onAnimationStart(View view) {
+            setVisibility(VISIBLE);
+            mCanceled = false;
+        }
+
+        @Override
+        public void onAnimationEnd(View view) {
+            if (mCanceled) return;
+
+            mVisibilityAnim = null;
+            setVisibility(mFinalVisibility);
+        }
+
+        @Override
+        public void onAnimationCancel(View view) {
+            mCanceled = true;
+        }
+    }
 }
 
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/SpinnerCompat.java b/v7/appcompat/src/android/support/v7/internal/widget/SpinnerCompat.java
new file mode 100644
index 0000000..f1d12f1
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/SpinnerCompat.java
@@ -0,0 +1,1106 @@
+/*
+ * Copyright (C) 2007 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.internal.widget;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.appcompat.R;
+import android.support.v7.widget.ListPopupWindow;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.AdapterView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.PopupWindow;
+import android.widget.SpinnerAdapter;
+
+
+/**
+ * A view that displays one child at a time and lets the user pick among them. The items in the
+ * Spinner come from the {@link android.widget.Adapter} associated with this view.
+ *
+ * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-spinner.html">Spinner
+ * tutorial</a>.</p>
+ */
+class SpinnerCompat extends AbsSpinnerCompat implements DialogInterface.OnClickListener {
+    private static final String TAG = "Spinner";
+
+    // Only measure this many items to get a decent max width.
+    private static final int MAX_ITEMS_MEASURED = 15;
+
+    /**
+     * Use a dialog window for selecting spinner options.
+     */
+    public static final int MODE_DIALOG = 0;
+
+    /**
+     * Use a dropdown anchored to the Spinner for selecting spinner options.
+     */
+    public static final int MODE_DROPDOWN = 1;
+
+    /**
+     * Use the theme-supplied value to select the dropdown mode.
+     */
+    private static final int MODE_THEME = -1;
+
+    /**
+     * Forwarding listener used to implement drag-to-open.
+     */
+    private ListPopupWindow.ForwardingListener mForwardingListener;
+
+    private SpinnerPopup mPopup;
+
+    private DropDownAdapter mTempAdapter;
+
+    int mDropDownWidth;
+
+    private int mGravity;
+
+    private boolean mDisableChildrenWhenDisabled;
+
+    private Rect mTempRect = new Rect();
+
+    /**
+     * Construct a new spinner with the given context's theme.
+     *
+     * @param context The Context the view is running in, through which it can access the current
+     *                theme, resources, etc.
+     */
+    SpinnerCompat(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * Construct a new spinner with the given context's theme and the supplied mode of displaying
+     * choices. <code>mode</code> may be one of {@link #MODE_DIALOG} or {@link #MODE_DROPDOWN}.
+     *
+     * @param context The Context the view is running in, through which it can access the current
+     *                theme, resources, etc.
+     * @param mode    Constant describing how the user will select choices from the spinner.
+     * @see #MODE_DIALOG
+     * @see #MODE_DROPDOWN
+     */
+    SpinnerCompat(Context context, int mode) {
+        this(context, null, R.attr.spinnerStyle, mode);
+    }
+
+    /**
+     * Construct a new spinner with the given context's theme and the supplied attribute set.
+     *
+     * @param context The Context the view is running in, through which it can access the current
+     *                theme, resources, etc.
+     * @param attrs   The attributes of the XML tag that is inflating the view.
+     */
+    SpinnerCompat(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.spinnerStyle);
+    }
+
+    /**
+     * Construct a new spinner with the given context's theme, the supplied attribute set, and
+     * default style.
+     *
+     * @param context  The Context the view is running in, through which it can access the current
+     *                 theme, resources, etc.
+     * @param attrs    The attributes of the XML tag that is inflating the view.
+     * @param defStyle The default style to apply to this view. If 0, no style will be applied
+     *                 (beyond what is included in the theme). This may either be an attribute
+     *                 resource, whose value will be retrieved from the current theme, or an
+     *                 explicit style resource.
+     */
+    SpinnerCompat(Context context, AttributeSet attrs, int defStyle) {
+        this(context, attrs, defStyle, MODE_THEME);
+    }
+
+    /**
+     * Construct a new spinner with the given context's theme, the supplied attribute set, and
+     * default style. <code>mode</code> may be one of {@link #MODE_DIALOG} or {@link #MODE_DROPDOWN}
+     * and determines how the user will select choices from the spinner.
+     *
+     * @param context  The Context the view is running in, through which it can access the current
+     *                 theme, resources, etc.
+     * @param attrs    The attributes of the XML tag that is inflating the view.
+     * @param defStyle The default style to apply to this view. If 0, no style will be applied
+     *                 (beyond what is included in the theme). This may either be an attribute
+     *                 resource, whose value will be retrieved from the current theme, or an
+     *                 explicit style resource.
+     * @param mode     Constant describing how the user will select choices from the spinner.
+     * @see #MODE_DIALOG
+     * @see #MODE_DROPDOWN
+     */
+    SpinnerCompat(Context context, AttributeSet attrs, int defStyle, int mode) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.Spinner, defStyle, 0);
+
+        if (mode == MODE_THEME) {
+            mode = a.getInt(R.styleable.Spinner_spinnerMode, MODE_DIALOG);
+        }
+
+        switch (mode) {
+            case MODE_DIALOG: {
+                mPopup = new DialogPopup();
+                break;
+            }
+
+            case MODE_DROPDOWN: {
+                final DropdownPopup popup = new DropdownPopup(context, attrs, defStyle);
+
+                mDropDownWidth = a.getLayoutDimension(R.styleable.Spinner_android_dropDownWidth,
+                        ViewGroup.LayoutParams.WRAP_CONTENT);
+
+                popup.setBackgroundDrawable(
+                        a.getDrawable(R.styleable.Spinner_android_popupBackground));
+
+                final int verticalOffset = a.getDimensionPixelOffset(
+                        R.styleable.Spinner_android_dropDownVerticalOffset, 0);
+                if (verticalOffset != 0) {
+                    popup.setVerticalOffset(verticalOffset);
+                }
+
+                final int horizontalOffset = a.getDimensionPixelOffset(
+                        R.styleable.Spinner_android_dropDownHorizontalOffset, 0);
+                if (horizontalOffset != 0) {
+                    popup.setHorizontalOffset(horizontalOffset);
+                }
+
+                mPopup = popup;
+                mForwardingListener = new ListPopupWindow.ForwardingListener(this) {
+                    @Override
+                    public ListPopupWindow getPopup() {
+                        return popup;
+                    }
+
+                    @Override
+                    public boolean onForwardingStarted() {
+                        if (!mPopup.isShowing()) {
+                            mPopup.show();
+                        }
+                        return true;
+                    }
+                };
+                break;
+            }
+        }
+
+        mGravity = a.getInt(R.styleable.Spinner_android_gravity, Gravity.CENTER);
+
+        mPopup.setPromptText(a.getString(R.styleable.Spinner_prompt));
+
+        mDisableChildrenWhenDisabled = a.getBoolean(
+                R.styleable.Spinner_disableChildrenWhenDisabled, false);
+
+        a.recycle();
+
+        // Base constructor can call setAdapter before we initialize mPopup.
+        // Finish setting things up if this happened.
+        if (mTempAdapter != null) {
+            mPopup.setAdapter(mTempAdapter);
+            mTempAdapter = null;
+        }
+    }
+
+    /**
+     * Set the background drawable for the spinner's popup window of choices. Only valid in {@link
+     * #MODE_DROPDOWN}; this method is a no-op in other modes.
+     *
+     * @param background Background drawable
+     */
+    public void setPopupBackgroundDrawable(Drawable background) {
+        if (!(mPopup instanceof DropdownPopup)) {
+            Log.e(TAG, "setPopupBackgroundDrawable: incompatible spinner mode; ignoring...");
+            return;
+        }
+        ((DropdownPopup) mPopup).setBackgroundDrawable(background);
+    }
+
+    /**
+     * Set the background drawable for the spinner's popup window of choices. Only valid in {@link
+     * #MODE_DROPDOWN}; this method is a no-op in other modes.
+     *
+     * @param resId Resource ID of a background drawable
+     */
+    public void setPopupBackgroundResource(int resId) {
+        setPopupBackgroundDrawable(ContextCompat.getDrawable(getContext(), resId));
+    }
+
+    /**
+     * Get the background drawable for the spinner's popup window of choices. Only valid in {@link
+     * #MODE_DROPDOWN}; other modes will return null.
+     *
+     * @return background Background drawable
+     */
+    public Drawable getPopupBackground() {
+        return mPopup.getBackground();
+    }
+
+    /**
+     * Set a vertical offset in pixels for the spinner's popup window of choices. Only valid in
+     * {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
+     *
+     * @param pixels Vertical offset in pixels
+     */
+    public void setDropDownVerticalOffset(int pixels) {
+        mPopup.setVerticalOffset(pixels);
+    }
+
+    /**
+     * Get the configured vertical offset in pixels for the spinner's popup window of choices. Only
+     * valid in {@link #MODE_DROPDOWN}; other modes will return 0.
+     *
+     * @return Vertical offset in pixels
+     */
+    public int getDropDownVerticalOffset() {
+        return mPopup.getVerticalOffset();
+    }
+
+    /**
+     * Set a horizontal offset in pixels for the spinner's popup window of choices. Only valid in
+     * {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
+     *
+     * @param pixels Horizontal offset in pixels
+     */
+    public void setDropDownHorizontalOffset(int pixels) {
+        mPopup.setHorizontalOffset(pixels);
+    }
+
+    /**
+     * Get the configured horizontal offset in pixels for the spinner's popup window of choices.
+     * Only valid in {@link #MODE_DROPDOWN}; other modes will return 0.
+     *
+     * @return Horizontal offset in pixels
+     */
+    public int getDropDownHorizontalOffset() {
+        return mPopup.getHorizontalOffset();
+    }
+
+    /**
+     * Set the width of the spinner's popup window of choices in pixels. This value may also be set
+     * to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} to match the width of the Spinner
+     * itself, or {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured
+     * size of contained dropdown list items.
+     *
+     * <p>Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.</p>
+     *
+     * @param pixels Width in pixels, WRAP_CONTENT, or MATCH_PARENT
+     */
+    public void setDropDownWidth(int pixels) {
+        if (!(mPopup instanceof DropdownPopup)) {
+            Log.e(TAG, "Cannot set dropdown width for MODE_DIALOG, ignoring");
+            return;
+        }
+        mDropDownWidth = pixels;
+    }
+
+    /**
+     * Get the configured width of the spinner's popup window of choices in pixels. The returned
+     * value may also be {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} meaning the popup
+     * window will match the width of the Spinner itself, or {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
+     * to wrap to the measured size of contained dropdown list items.
+     *
+     * @return Width in pixels, WRAP_CONTENT, or MATCH_PARENT
+     */
+    public int getDropDownWidth() {
+        return mDropDownWidth;
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        if (mDisableChildrenWhenDisabled) {
+            final int count = getChildCount();
+            for (int i = 0; i < count; i++) {
+                getChildAt(i).setEnabled(enabled);
+            }
+        }
+    }
+
+    /**
+     * Describes how the selected item view is positioned. Currently only the horizontal component
+     * is used. The default is determined by the current theme.
+     *
+     * @param gravity See {@link android.view.Gravity}
+     */
+    public void setGravity(int gravity) {
+        if (mGravity != gravity) {
+            if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
+                gravity |= GravityCompat.START;
+            }
+            mGravity = gravity;
+            requestLayout();
+        }
+    }
+
+    @Override
+    public void setAdapter(SpinnerAdapter adapter) {
+        super.setAdapter(adapter);
+
+        mRecycler.clear();
+
+        final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
+        if (targetSdkVersion >= Build.VERSION_CODES.L
+                && adapter != null && adapter.getViewTypeCount() != 1) {
+            throw new IllegalArgumentException("Spinner adapter view type count must be 1");
+        }
+        if (mPopup != null) {
+            mPopup.setAdapter(new DropDownAdapter(adapter));
+        } else {
+            mTempAdapter = new DropDownAdapter(adapter);
+        }
+    }
+
+    @Override
+    public int getBaseline() {
+        View child = null;
+
+        if (getChildCount() > 0) {
+            child = getChildAt(0);
+        } else if (mAdapter != null && mAdapter.getCount() > 0) {
+            child = makeView(0, false);
+            mRecycler.put(0, child);
+        }
+
+        if (child != null) {
+            final int childBaseline = child.getBaseline();
+            return childBaseline >= 0 ? child.getTop() + childBaseline : -1;
+        } else {
+            return -1;
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        if (mPopup != null && mPopup.isShowing()) {
+            mPopup.dismiss();
+        }
+    }
+
+    /**
+     * <p>A spinner does not support item click events. Calling this method will raise an
+     * exception.</p>
+     *
+     * @param l this listener will be ignored
+     */
+    @Override
+    public void setOnItemClickListener(OnItemClickListener l) {
+        throw new RuntimeException("setOnItemClickListener cannot be used with a spinner.");
+    }
+
+    void setOnItemClickListenerInt(OnItemClickListener l) {
+        super.setOnItemClickListener(l);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mForwardingListener != null && mForwardingListener.onTouch(this, event)) {
+            return true;
+        }
+
+        return super.onTouchEvent(event);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        if (mPopup != null && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
+            final int measuredWidth = getMeasuredWidth();
+            setMeasuredDimension(Math.min(Math.max(measuredWidth,
+                    measureContentWidth(getAdapter(), getBackground())),
+                    MeasureSpec.getSize(widthMeasureSpec)),
+                    getMeasuredHeight());
+        }
+    }
+
+    /**
+     * @see android.view.View#onLayout(boolean, int, int, int, int)
+     *
+     * Creates and positions all views
+     */
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        mInLayout = true;
+        layout(0, false);
+        mInLayout = false;
+    }
+
+    /**
+     * Creates and positions all views for this Spinner.
+     *
+     * @param delta Change in the selected position. +1 means selection is moving to the right, so
+     *              views are scrolling to the left. -1 means selection is moving to the left.
+     */
+    @Override
+    void layout(int delta, boolean animate) {
+        int childrenLeft = mSpinnerPadding.left;
+        int childrenWidth = getRight() - getLeft() - mSpinnerPadding.left - mSpinnerPadding.right;
+
+        if (mDataChanged) {
+            handleDataChanged();
+        }
+
+        // Handle the empty set by removing all views
+        if (mItemCount == 0) {
+            resetList();
+            return;
+        }
+
+        if (mNextSelectedPosition >= 0) {
+            setSelectedPositionInt(mNextSelectedPosition);
+        }
+
+        recycleAllViews();
+
+        // Clear out old views
+        removeAllViewsInLayout();
+
+        // Make selected view and position it
+        mFirstPosition = mSelectedPosition;
+        if (mAdapter != null) {
+            View sel = makeView(mSelectedPosition, true);
+            int width = sel.getMeasuredWidth();
+            int selectedOffset = childrenLeft;
+            final int layoutDirection = ViewCompat.getLayoutDirection(this);
+            final int absoluteGravity = GravityCompat.getAbsoluteGravity(mGravity, layoutDirection);
+            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+                case Gravity.CENTER_HORIZONTAL:
+                    selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2);
+                    break;
+                case Gravity.RIGHT:
+                    selectedOffset = childrenLeft + childrenWidth - width;
+                    break;
+            }
+            sel.offsetLeftAndRight(selectedOffset);
+        }
+
+        // Flush any cached views that did not get reused above
+        mRecycler.clear();
+
+        invalidate();
+
+        checkSelectionChanged();
+
+        mDataChanged = false;
+        mNeedSync = false;
+        setNextSelectedPositionInt(mSelectedPosition);
+    }
+
+    /**
+     * Obtain a view, either by pulling an existing view from the recycler or by getting a new one
+     * from the adapter. If we are animating, make sure there is enough information in the view's
+     * layout parameters to animate from the old to new positions.
+     *
+     * @param position Position in the spinner for the view to obtain
+     * @param addChild true to add the child to the spinner, false to obtain and configure only.
+     * @return A view for the given position
+     */
+    private View makeView(int position, boolean addChild) {
+
+        View child;
+
+        if (!mDataChanged) {
+            child = mRecycler.get(position);
+            if (child != null) {
+                // Position the view
+                setUpChild(child, addChild);
+
+                return child;
+            }
+        }
+
+        // Nothing found in the recycler -- ask the adapter for a view
+        child = mAdapter.getView(position, null, this);
+
+        // Position the view
+        setUpChild(child, addChild);
+
+        return child;
+    }
+
+    /**
+     * Helper for makeAndAddView to set the position of a view and fill out its layout paramters.
+     *
+     * @param child    The view to position
+     * @param addChild true if the child should be added to the Spinner during setup
+     */
+    private void setUpChild(View child, boolean addChild) {
+
+        // Respect layout params that are already in the view. Otherwise
+        // make some up...
+        ViewGroup.LayoutParams lp = child.getLayoutParams();
+        if (lp == null) {
+            lp = generateDefaultLayoutParams();
+        }
+
+        if (addChild) {
+            addViewInLayout(child, 0, lp);
+        }
+
+        child.setSelected(hasFocus());
+        if (mDisableChildrenWhenDisabled) {
+            child.setEnabled(isEnabled());
+        }
+
+        // Get measure specs
+        int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
+                mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
+        int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
+                mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
+
+        // Measure child
+        child.measure(childWidthSpec, childHeightSpec);
+
+        int childLeft;
+        int childRight;
+
+        // Position vertically based on gravity setting
+        int childTop = mSpinnerPadding.top
+                + ((getMeasuredHeight() - mSpinnerPadding.bottom -
+                mSpinnerPadding.top - child.getMeasuredHeight()) / 2);
+        int childBottom = childTop + child.getMeasuredHeight();
+
+        int width = child.getMeasuredWidth();
+        childLeft = 0;
+        childRight = childLeft + width;
+
+        child.layout(childLeft, childTop, childRight, childBottom);
+    }
+
+    @Override
+    public boolean performClick() {
+        boolean handled = super.performClick();
+
+        if (!handled) {
+            handled = true;
+
+            if (!mPopup.isShowing()) {
+                mPopup.show();
+            }
+        }
+
+        return handled;
+    }
+
+    public void onClick(DialogInterface dialog, int which) {
+        setSelection(which);
+        dialog.dismiss();
+    }
+
+    /**
+     * Sets the prompt to display when the dialog is shown.
+     * @param prompt the prompt to set
+     */
+    public void setPrompt(CharSequence prompt) {
+        mPopup.setPromptText(prompt);
+    }
+
+    /**
+     * Sets the prompt to display when the dialog is shown.
+     * @param promptId the resource ID of the prompt to display when the dialog is shown
+     */
+    public void setPromptId(int promptId) {
+        setPrompt(getContext().getText(promptId));
+    }
+
+    /**
+     * @return The prompt to display when the dialog is shown
+     */
+    public CharSequence getPrompt() {
+        return mPopup.getHintText();
+    }
+
+    int measureContentWidth(SpinnerAdapter adapter, Drawable background) {
+        if (adapter == null) {
+            return 0;
+        }
+
+        int width = 0;
+        View itemView = null;
+        int itemType = 0;
+        final int widthMeasureSpec =
+                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        final int heightMeasureSpec =
+                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+        // Make sure the number of items we'll measure is capped. If it's a huge data set
+        // with wildly varying sizes, oh well.
+        int start = Math.max(0, getSelectedItemPosition());
+        final int end = Math.min(adapter.getCount(), start + MAX_ITEMS_MEASURED);
+        final int count = end - start;
+        start = Math.max(0, start - (MAX_ITEMS_MEASURED - count));
+        for (int i = start; i < end; i++) {
+            final int positionType = adapter.getItemViewType(i);
+            if (positionType != itemType) {
+                itemType = positionType;
+                itemView = null;
+            }
+            itemView = adapter.getView(i, itemView, this);
+            if (itemView.getLayoutParams() == null) {
+                itemView.setLayoutParams(new ViewGroup.LayoutParams(
+                        ViewGroup.LayoutParams.WRAP_CONTENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT));
+            }
+            itemView.measure(widthMeasureSpec, heightMeasureSpec);
+            width = Math.max(width, itemView.getMeasuredWidth());
+        }
+
+        // Add background padding to measured width
+        if (background != null) {
+            background.getPadding(mTempRect);
+            width += mTempRect.left + mTempRect.right;
+        }
+
+        return width;
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        final SavedState ss = new SavedState(super.onSaveInstanceState());
+        ss.showDropdown = mPopup != null && mPopup.isShowing();
+        return ss;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+
+        super.onRestoreInstanceState(ss.getSuperState());
+
+        if (ss.showDropdown) {
+            ViewTreeObserver vto = getViewTreeObserver();
+            if (vto != null) {
+                final ViewTreeObserver.OnGlobalLayoutListener listener
+                        = new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        if (!mPopup.isShowing()) {
+                            mPopup.show();
+                        }
+                        final ViewTreeObserver vto = getViewTreeObserver();
+                        if (vto != null) {
+                            vto.removeGlobalOnLayoutListener(this);
+                        }
+                    }
+                };
+                vto.addOnGlobalLayoutListener(listener);
+            }
+        }
+    }
+
+    static class SavedState extends AbsSpinnerCompat.SavedState {
+
+        boolean showDropdown;
+
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        private SavedState(Parcel in) {
+            super(in);
+            showDropdown = in.readByte() != 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeByte((byte) (showDropdown ? 1 : 0));
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+                    public SavedState createFromParcel(Parcel in) {
+                        return new SavedState(in);
+                    }
+
+                    public SavedState[] newArray(int size) {
+                        return new SavedState[size];
+                    }
+                };
+    }
+
+    /**
+     * <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance into a
+     * ListAdapter.</p>
+     */
+    private static class DropDownAdapter implements ListAdapter, SpinnerAdapter {
+
+        private SpinnerAdapter mAdapter;
+
+        private ListAdapter mListAdapter;
+
+        /**
+         * <p>Creates a new ListAdapter wrapper for the specified adapter.</p>
+         *
+         * @param adapter the Adapter to transform into a ListAdapter
+         */
+        public DropDownAdapter(SpinnerAdapter adapter) {
+            this.mAdapter = adapter;
+            if (adapter instanceof ListAdapter) {
+                this.mListAdapter = (ListAdapter) adapter;
+            }
+        }
+
+        public int getCount() {
+            return mAdapter == null ? 0 : mAdapter.getCount();
+        }
+
+        public Object getItem(int position) {
+            return mAdapter == null ? null : mAdapter.getItem(position);
+        }
+
+        public long getItemId(int position) {
+            return mAdapter == null ? -1 : mAdapter.getItemId(position);
+        }
+
+        public View getView(int position, View convertView, ViewGroup parent) {
+            return getDropDownView(position, convertView, parent);
+        }
+
+        public View getDropDownView(int position, View convertView, ViewGroup parent) {
+            return (mAdapter == null) ? null
+                    : mAdapter.getDropDownView(position, convertView, parent);
+        }
+
+        public boolean hasStableIds() {
+            return mAdapter != null && mAdapter.hasStableIds();
+        }
+
+        public void registerDataSetObserver(DataSetObserver observer) {
+            if (mAdapter != null) {
+                mAdapter.registerDataSetObserver(observer);
+            }
+        }
+
+        public void unregisterDataSetObserver(DataSetObserver observer) {
+            if (mAdapter != null) {
+                mAdapter.unregisterDataSetObserver(observer);
+            }
+        }
+
+        /**
+         * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call. Otherwise,
+         * return true.
+         */
+        public boolean areAllItemsEnabled() {
+            final ListAdapter adapter = mListAdapter;
+            if (adapter != null) {
+                return adapter.areAllItemsEnabled();
+            } else {
+                return true;
+            }
+        }
+
+        /**
+         * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call. Otherwise,
+         * return true.
+         */
+        public boolean isEnabled(int position) {
+            final ListAdapter adapter = mListAdapter;
+            if (adapter != null) {
+                return adapter.isEnabled(position);
+            } else {
+                return true;
+            }
+        }
+
+        public int getItemViewType(int position) {
+            return 0;
+        }
+
+        public int getViewTypeCount() {
+            return 1;
+        }
+
+        public boolean isEmpty() {
+            return getCount() == 0;
+        }
+    }
+
+    /**
+     * Implements some sort of popup selection interface for selecting a spinner option. Allows for
+     * different spinner modes.
+     */
+    private interface SpinnerPopup {
+
+        public void setAdapter(ListAdapter adapter);
+
+        /**
+         * Show the popup
+         */
+        public void show();
+
+        /**
+         * Dismiss the popup
+         */
+        public void dismiss();
+
+        /**
+         * @return true if the popup is showing, false otherwise.
+         */
+        public boolean isShowing();
+
+        /**
+         * Set hint text to be displayed to the user. This should provide a description of the
+         * choice being made.
+         *
+         * @param hintText Hint text to set.
+         */
+        public void setPromptText(CharSequence hintText);
+
+        public CharSequence getHintText();
+
+        public void setBackgroundDrawable(Drawable bg);
+
+        public void setVerticalOffset(int px);
+
+        public void setHorizontalOffset(int px);
+
+        public Drawable getBackground();
+
+        public int getVerticalOffset();
+
+        public int getHorizontalOffset();
+    }
+
+    private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener {
+
+        private AlertDialog mPopup;
+
+        private ListAdapter mListAdapter;
+
+        private CharSequence mPrompt;
+
+        public void dismiss() {
+            if (mPopup != null) {
+                mPopup.dismiss();
+                mPopup = null;
+            }
+        }
+
+        public boolean isShowing() {
+            return mPopup != null ? mPopup.isShowing() : false;
+        }
+
+        public void setAdapter(ListAdapter adapter) {
+            mListAdapter = adapter;
+        }
+
+        public void setPromptText(CharSequence hintText) {
+            mPrompt = hintText;
+        }
+
+        public CharSequence getHintText() {
+            return mPrompt;
+        }
+
+        public void show() {
+            if (mListAdapter == null) {
+                return;
+            }
+            AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+            if (mPrompt != null) {
+                builder.setTitle(mPrompt);
+            }
+            mPopup = builder.setSingleChoiceItems(mListAdapter,
+                    getSelectedItemPosition(), this).create();
+            mPopup.show();
+        }
+
+        public void onClick(DialogInterface dialog, int which) {
+            setSelection(which);
+            if (mOnItemClickListener != null) {
+                performItemClick(null, which, mListAdapter.getItemId(which));
+            }
+            dismiss();
+        }
+
+        @Override
+        public void setBackgroundDrawable(Drawable bg) {
+            Log.e(TAG, "Cannot set popup background for MODE_DIALOG, ignoring");
+        }
+
+        @Override
+        public void setVerticalOffset(int px) {
+            Log.e(TAG, "Cannot set vertical offset for MODE_DIALOG, ignoring");
+        }
+
+        @Override
+        public void setHorizontalOffset(int px) {
+            Log.e(TAG, "Cannot set horizontal offset for MODE_DIALOG, ignoring");
+        }
+
+        @Override
+        public Drawable getBackground() {
+            return null;
+        }
+
+        @Override
+        public int getVerticalOffset() {
+            return 0;
+        }
+
+        @Override
+        public int getHorizontalOffset() {
+            return 0;
+        }
+    }
+
+    private class DropdownPopup extends ListPopupWindow implements SpinnerPopup {
+
+        private CharSequence mHintText;
+
+        private ListAdapter mAdapter;
+
+        public DropdownPopup(
+                Context context, AttributeSet attrs, int defStyleAttr) {
+            super(context, attrs, defStyleAttr);
+
+            setAnchorView(SpinnerCompat.this);
+            setModal(true);
+            setPromptPosition(POSITION_PROMPT_ABOVE);
+
+            setOnItemClickListener(new AdapterView.OnItemClickListener() {
+                @Override
+                public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+                    SpinnerCompat.this.setSelection(position);
+                    if (mOnItemClickListener != null) {
+                        SpinnerCompat.this
+                                .performItemClick(v, position, mAdapter.getItemId(position));
+                    }
+                    dismiss();
+                }
+            });
+        }
+
+        @Override
+        public void setAdapter(ListAdapter adapter) {
+            super.setAdapter(adapter);
+            mAdapter = adapter;
+        }
+
+        public CharSequence getHintText() {
+            return mHintText;
+        }
+
+        public void setPromptText(CharSequence hintText) {
+            // Hint text is ignored for dropdowns, but maintain it here.
+            mHintText = hintText;
+        }
+
+        void computeContentWidth() {
+            final Drawable background = getBackground();
+            int hOffset = 0;
+            if (background != null) {
+                background.getPadding(mTempRect);
+                hOffset = ViewUtils.isLayoutRtl(SpinnerCompat.this) ? mTempRect.right
+                        : -mTempRect.left;
+            } else {
+                mTempRect.left = mTempRect.right = 0;
+            }
+
+            final int spinnerPaddingLeft = SpinnerCompat.this.getPaddingLeft();
+            final int spinnerPaddingRight = SpinnerCompat.this.getPaddingRight();
+            final int spinnerWidth = SpinnerCompat.this.getWidth();
+            if (mDropDownWidth == WRAP_CONTENT) {
+                int contentWidth = measureContentWidth(
+                        (SpinnerAdapter) mAdapter, getBackground());
+                final int contentWidthLimit = getContext().getResources()
+                        .getDisplayMetrics().widthPixels - mTempRect.left - mTempRect.right;
+                if (contentWidth > contentWidthLimit) {
+                    contentWidth = contentWidthLimit;
+                }
+                setContentWidth(Math.max(
+                        contentWidth, spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight));
+            } else if (mDropDownWidth == MATCH_PARENT) {
+                setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight);
+            } else {
+                setContentWidth(mDropDownWidth);
+            }
+            if (ViewUtils.isLayoutRtl(SpinnerCompat.this)) {
+                hOffset += spinnerWidth - spinnerPaddingRight - getWidth();
+            } else {
+                hOffset += spinnerPaddingLeft;
+            }
+            setHorizontalOffset(hOffset);
+        }
+
+        public void show(int textDirection, int textAlignment) {
+            final boolean wasShowing = isShowing();
+
+            computeContentWidth();
+            setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
+            super.show();
+            final ListView listView = getListView();
+            listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+            //listView.setTextDirection(textDirection);
+            //listView.setTextAlignment(textAlignment);
+            setSelection(SpinnerCompat.this.getSelectedItemPosition());
+
+            if (wasShowing) {
+                // Skip setting up the layout/dismiss listener below. If we were previously
+                // showing it will still stick around.
+                return;
+            }
+
+            // Make sure we hide if our anchor goes away.
+            // TODO: This might be appropriate to push all the way down to PopupWindow,
+            // but it may have other side effects to investigate first. (Text editing handles, etc.)
+            final ViewTreeObserver vto = getViewTreeObserver();
+            if (vto != null) {
+                final ViewTreeObserver.OnGlobalLayoutListener layoutListener
+                        = new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        computeContentWidth();
+
+                        // Use super.show here to update; we don't want to move the selected
+                        // position or adjust other things that would be reset otherwise.
+                        DropdownPopup.super.show();
+                    }
+                };
+                vto.addOnGlobalLayoutListener(layoutListener);
+                setOnDismissListener(new PopupWindow.OnDismissListener() {
+                    @Override
+                    public void onDismiss() {
+                        final ViewTreeObserver vto = getViewTreeObserver();
+                        if (vto != null) {
+                            vto.removeGlobalOnLayoutListener(layoutListener);
+                        }
+                    }
+                });
+            }
+        }
+    }
+}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/SpinnerICS.java b/v7/appcompat/src/android/support/v7/internal/widget/SpinnerICS.java
deleted file mode 100644
index e8629f3..0000000
--- a/v7/appcompat/src/android/support/v7/internal/widget/SpinnerICS.java
+++ /dev/null
@@ -1,763 +0,0 @@
-/*
- * Copyright (C) 2007 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.internal.widget;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.content.res.TypedArray;
-import android.database.DataSetObserver;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.support.v7.appcompat.R;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.ListAdapter;
-import android.widget.ListView;
-import android.widget.SpinnerAdapter;
-
-
-/**
- * A view that displays one child at a time and lets the user pick among them.
- * The items in the Spinner come from the {@link android.widget.Adapter} associated with
- * this view.
- *
- * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-spinner.html">Spinner
- * tutorial</a>.</p>
- *
- * @attr ref android.support.v7.appcompat.R.styleable#Spinner_prompt
- */
-class SpinnerICS extends AbsSpinnerICS implements OnClickListener {
-    private static final String TAG = "Spinner";
-
-    // Only measure this many items to get a decent max width.
-    private static final int MAX_ITEMS_MEASURED = 15;
-
-    /**
-     * Use a dialog window for selecting spinner options.
-     */
-    static final int MODE_DIALOG = 0;
-
-    /**
-     * Use a dropdown anchored to the Spinner for selecting spinner options.
-     */
-    static final int MODE_DROPDOWN = 1;
-
-    /**
-     * Use the theme-supplied value to select the dropdown mode.
-     */
-    private static final int MODE_THEME = -1;
-
-    private SpinnerPopup mPopup;
-    private DropDownAdapter mTempAdapter;
-    int mDropDownWidth;
-
-    private int mGravity;
-
-    private Rect mTempRect = new Rect();
-
-    /**
-     * Construct a new spinner with the given context's theme.
-     *
-     * @param context The Context the view is running in, through which it can
-     *        access the current theme, resources, etc.
-     */
-    SpinnerICS(Context context) {
-        this(context, null);
-    }
-
-    /**
-     * Construct a new spinner with the given context's theme and the supplied
-     * mode of displaying choices. <code>mode</code> may be one of
-     * {@link #MODE_DIALOG} or {@link #MODE_DROPDOWN}.
-     *
-     * @param context The Context the view is running in, through which it can
-     *        access the current theme, resources, etc.
-     * @param mode Constant describing how the user will select choices from the spinner.
-     *
-     * @see #MODE_DIALOG
-     * @see #MODE_DROPDOWN
-     */
-    SpinnerICS(Context context, int mode) {
-        this(context, null, R.attr.spinnerStyle, mode);
-    }
-
-    /**
-     * Construct a new spinner with the given context's theme and the supplied attribute set.
-     *
-     * @param context The Context the view is running in, through which it can
-     *        access the current theme, resources, etc.
-     * @param attrs The attributes of the XML tag that is inflating the view.
-     */
-    SpinnerICS(Context context, AttributeSet attrs) {
-        this(context, attrs, R.attr.spinnerStyle);
-    }
-
-    /**
-     * Construct a new spinner with the given context's theme, the supplied attribute set,
-     * and default style.
-     *
-     * @param context The Context the view is running in, through which it can
-     *        access the current theme, resources, etc.
-     * @param attrs The attributes of the XML tag that is inflating the view.
-     * @param defStyle The default style to apply to this view. If 0, no style
-     *        will be applied (beyond what is included in the theme). This may
-     *        either be an attribute resource, whose value will be retrieved
-     *        from the current theme, or an explicit style resource.
-     */
-    SpinnerICS(Context context, AttributeSet attrs, int defStyle) {
-        this(context, attrs, defStyle, MODE_THEME);
-    }
-
-    /**
-     * Construct a new spinner with the given context's theme, the supplied attribute set,
-     * and default style. <code>mode</code> may be one of {@link #MODE_DIALOG} or
-     * {@link #MODE_DROPDOWN} and determines how the user will select choices from the spinner.
-     *
-     * @param context The Context the view is running in, through which it can
-     *        access the current theme, resources, etc.
-     * @param attrs The attributes of the XML tag that is inflating the view.
-     * @param defStyle The default style to apply to this view. If 0, no style
-     *        will be applied (beyond what is included in the theme). This may
-     *        either be an attribute resource, whose value will be retrieved
-     *        from the current theme, or an explicit style resource.
-     * @param mode Constant describing how the user will select choices from the spinner.
-     *
-     * @see #MODE_DIALOG
-     * @see #MODE_DROPDOWN
-     */
-    SpinnerICS(Context context, AttributeSet attrs, int defStyle, int mode) {
-        super(context, attrs, defStyle);
-
-        TypedArray a = context.obtainStyledAttributes(attrs,
-                R.styleable.Spinner, defStyle, 0);
-
-        if (mode == MODE_THEME) {
-            mode = a.getInt(R.styleable.Spinner_spinnerMode, MODE_DIALOG);
-        }
-
-        switch (mode) {
-            case MODE_DIALOG: {
-                mPopup = new DialogPopup();
-                break;
-            }
-
-            case MODE_DROPDOWN: {
-                DropdownPopup popup = new DropdownPopup(context, attrs, defStyle);
-
-                mDropDownWidth = a.getLayoutDimension(R.styleable.Spinner_android_dropDownWidth,
-                        ViewGroup.LayoutParams.WRAP_CONTENT);
-
-                popup.setBackgroundDrawable(
-                        a.getDrawable(R.styleable.Spinner_android_popupBackground));
-
-                final int verticalOffset = a.getDimensionPixelOffset(
-                        R.styleable.Spinner_android_dropDownVerticalOffset, 0);
-                if (verticalOffset != 0) {
-                    popup.setVerticalOffset(verticalOffset);
-                }
-
-                final int horizontalOffset = a.getDimensionPixelOffset(
-                        R.styleable.Spinner_android_dropDownHorizontalOffset, 0);
-                if (horizontalOffset != 0) {
-                    popup.setHorizontalOffset(horizontalOffset);
-                }
-
-                mPopup = popup;
-                break;
-            }
-        }
-
-        mGravity = a.getInt(R.styleable.Spinner_android_gravity, Gravity.CENTER);
-
-        mPopup.setPromptText(a.getString(R.styleable.Spinner_prompt));
-
-        a.recycle();
-
-        // Base constructor can call setAdapter before we initialize mPopup.
-        // Finish setting things up if this happened.
-        if (mTempAdapter != null) {
-            mPopup.setAdapter(mTempAdapter);
-            mTempAdapter = null;
-        }
-    }
-
-    /**
-     * Describes how the selected item view is positioned. Currently only the horizontal component
-     * is used. The default is determined by the current theme.
-     *
-     * @param gravity See {@link android.view.Gravity}
-     *
-     * @attr ref android.R.styleable#Spinner_gravity
-     */
-    public void setGravity(int gravity) {
-        if (mGravity != gravity) {
-            if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
-                gravity |= Gravity.LEFT;
-            }
-            mGravity = gravity;
-            requestLayout();
-        }
-    }
-
-    @Override
-    public void setAdapter(SpinnerAdapter adapter) {
-        super.setAdapter(adapter);
-
-        if (mPopup != null) {
-            mPopup.setAdapter(new DropDownAdapter(adapter));
-        } else {
-            mTempAdapter = new DropDownAdapter(adapter);
-        }
-    }
-
-    @Override
-    public int getBaseline() {
-        View child = null;
-
-        if (getChildCount() > 0) {
-            child = getChildAt(0);
-        } else if (mAdapter != null && mAdapter.getCount() > 0) {
-            child = makeAndAddView(0);
-            mRecycler.put(0, child);
-            removeAllViewsInLayout();
-        }
-
-        if (child != null) {
-            final int childBaseline = child.getBaseline();
-            return childBaseline >= 0 ? child.getTop() + childBaseline : -1;
-        } else {
-            return -1;
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-
-        if (mPopup != null && mPopup.isShowing()) {
-            mPopup.dismiss();
-        }
-    }
-
-    /**
-     * <p>A spinner does not support item click events. Calling this method
-     * will raise an exception.</p>
-     *
-     * @param l this listener will be ignored
-     */
-    @Override
-    public void setOnItemClickListener(OnItemClickListener l) {
-        throw new RuntimeException("setOnItemClickListener cannot be used with a spinner.");
-    }
-
-    void setOnItemClickListenerInt(OnItemClickListener l) {
-        super.setOnItemClickListener(l);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        if (mPopup != null && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
-            final int measuredWidth = getMeasuredWidth();
-            setMeasuredDimension(Math.min(Math.max(measuredWidth,
-                    measureContentWidth(getAdapter(), getBackground())),
-                    MeasureSpec.getSize(widthMeasureSpec)),
-                    getMeasuredHeight());
-        }
-    }
-
-    /**
-     * @see android.view.View#onLayout(boolean,int,int,int,int)
-     *
-     * Creates and positions all views
-     *
-     */
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        super.onLayout(changed, l, t, r, b);
-        mInLayout = true;
-        layout(0, false);
-        mInLayout = false;
-    }
-
-    /**
-     * Creates and positions all views for this Spinner.
-     *
-     * @param delta Change in the selected position. +1 moves selection is moving to the right,
-     * so views are scrolling to the left. -1 means selection is moving to the left.
-     */
-    @Override
-    void layout(int delta, boolean animate) {
-        int childrenLeft = mSpinnerPadding.left;
-        int childrenWidth = getRight() - getLeft() - mSpinnerPadding.left - mSpinnerPadding.right;
-
-        if (mDataChanged) {
-            handleDataChanged();
-        }
-
-        // Handle the empty set by removing all views
-        if (mItemCount == 0) {
-            resetList();
-            return;
-        }
-
-        if (mNextSelectedPosition >= 0) {
-            setSelectedPositionInt(mNextSelectedPosition);
-        }
-
-        recycleAllViews();
-
-        // Clear out old views
-        removeAllViewsInLayout();
-
-        // Make selected view and position it
-        mFirstPosition = mSelectedPosition;
-        View sel = makeAndAddView(mSelectedPosition);
-        int width = sel.getMeasuredWidth();
-        int selectedOffset = childrenLeft;
-        switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
-            case Gravity.CENTER_HORIZONTAL:
-                selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2);
-                break;
-            case Gravity.RIGHT:
-                selectedOffset = childrenLeft + childrenWidth - width;
-                break;
-        }
-        sel.offsetLeftAndRight(selectedOffset);
-
-        // Flush any cached views that did not get reused above
-        mRecycler.clear();
-
-        invalidate();
-
-        checkSelectionChanged();
-
-        mDataChanged = false;
-        mNeedSync = false;
-        setNextSelectedPositionInt(mSelectedPosition);
-    }
-
-    /**
-     * Obtain a view, either by pulling an existing view from the recycler or
-     * by getting a new one from the adapter. If we are animating, make sure
-     * there is enough information in the view's layout parameters to animate
-     * from the old to new positions.
-     *
-     * @param position Position in the spinner for the view to obtain
-     * @return A view that has been added to the spinner
-     */
-    private View makeAndAddView(int position) {
-
-        View child;
-
-        if (!mDataChanged) {
-            child = mRecycler.get(position);
-            if (child != null) {
-                // Position the view
-                setUpChild(child);
-
-                return child;
-            }
-        }
-
-        // Nothing found in the recycler -- ask the adapter for a view
-        child = mAdapter.getView(position, null, this);
-
-        // Position the view
-        setUpChild(child);
-
-        return child;
-    }
-
-    /**
-     * Helper for makeAndAddView to set the position of a view
-     * and fill out its layout paramters.
-     *
-     * @param child The view to position
-     */
-    private void setUpChild(View child) {
-
-        // Respect layout params that are already in the view. Otherwise
-        // make some up...
-        ViewGroup.LayoutParams lp = child.getLayoutParams();
-        if (lp == null) {
-            lp = generateDefaultLayoutParams();
-        }
-
-        addViewInLayout(child, 0, lp);
-
-        child.setSelected(hasFocus());
-
-        // Get measure specs
-        int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
-                mSpinnerPadding.top + mSpinnerPadding.bottom, lp.height);
-        int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
-                mSpinnerPadding.left + mSpinnerPadding.right, lp.width);
-
-        // Measure child
-        child.measure(childWidthSpec, childHeightSpec);
-
-        int childLeft;
-        int childRight;
-
-        // Position vertically based on gravity setting
-        int childTop = mSpinnerPadding.top
-                + ((getMeasuredHeight() - mSpinnerPadding.bottom -
-                mSpinnerPadding.top - child.getMeasuredHeight()) / 2);
-        int childBottom = childTop + child.getMeasuredHeight();
-
-        int width = child.getMeasuredWidth();
-        childLeft = 0;
-        childRight = childLeft + width;
-
-        child.layout(childLeft, childTop, childRight, childBottom);
-    }
-
-    @Override
-    public boolean performClick() {
-        boolean handled = super.performClick();
-
-        if (!handled) {
-            handled = true;
-
-            if (!mPopup.isShowing()) {
-                mPopup.show();
-            }
-        }
-
-        return handled;
-    }
-
-    public void onClick(DialogInterface dialog, int which) {
-        setSelection(which);
-        dialog.dismiss();
-    }
-
-    /**
-     * Sets the prompt to display when the dialog is shown.
-     * @param prompt the prompt to set
-     */
-    public void setPrompt(CharSequence prompt) {
-        mPopup.setPromptText(prompt);
-    }
-
-    /**
-     * Sets the prompt to display when the dialog is shown.
-     * @param promptId the resource ID of the prompt to display when the dialog is shown
-     */
-    public void setPromptId(int promptId) {
-        setPrompt(getContext().getText(promptId));
-    }
-
-    /**
-     * @return The prompt to display when the dialog is shown
-     */
-    public CharSequence getPrompt() {
-        return mPopup.getHintText();
-    }
-
-    int measureContentWidth(SpinnerAdapter adapter, Drawable background) {
-        if (adapter == null) {
-            return 0;
-        }
-
-        int width = 0;
-        View itemView = null;
-        int itemType = 0;
-        final int widthMeasureSpec =
-                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
-        final int heightMeasureSpec =
-                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
-
-        // Make sure the number of items we'll measure is capped. If it's a huge data set
-        // with wildly varying sizes, oh well.
-        int start = Math.max(0, getSelectedItemPosition());
-        final int end = Math.min(adapter.getCount(), start + MAX_ITEMS_MEASURED);
-        final int count = end - start;
-        start = Math.max(0, start - (MAX_ITEMS_MEASURED - count));
-        for (int i = start; i < end; i++) {
-            final int positionType = adapter.getItemViewType(i);
-            if (positionType != itemType) {
-                itemType = positionType;
-                itemView = null;
-            }
-            itemView = adapter.getView(i, itemView, this);
-            if (itemView.getLayoutParams() == null) {
-                itemView.setLayoutParams(new ViewGroup.LayoutParams(
-                        ViewGroup.LayoutParams.WRAP_CONTENT,
-                        ViewGroup.LayoutParams.WRAP_CONTENT));
-            }
-            itemView.measure(widthMeasureSpec, heightMeasureSpec);
-            width = Math.max(width, itemView.getMeasuredWidth());
-        }
-
-        // Add background padding to measured width
-        if (background != null) {
-            background.getPadding(mTempRect);
-            width += mTempRect.left + mTempRect.right;
-        }
-
-        return width;
-    }
-
-    /**
-     * <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance
-     * into a ListAdapter.</p>
-     */
-    private static class DropDownAdapter implements ListAdapter, SpinnerAdapter {
-        private SpinnerAdapter mAdapter;
-        private ListAdapter mListAdapter;
-
-        /**
-         * <p>Creates a new ListAdapter wrapper for the specified adapter.</p>
-         *
-         * @param adapter the Adapter to transform into a ListAdapter
-         */
-        public DropDownAdapter(SpinnerAdapter adapter) {
-            this.mAdapter = adapter;
-            if (adapter instanceof ListAdapter) {
-                this.mListAdapter = (ListAdapter) adapter;
-            }
-        }
-
-        public int getCount() {
-            return mAdapter == null ? 0 : mAdapter.getCount();
-        }
-
-        public Object getItem(int position) {
-            return mAdapter == null ? null : mAdapter.getItem(position);
-        }
-
-        public long getItemId(int position) {
-            return mAdapter == null ? -1 : mAdapter.getItemId(position);
-        }
-
-        public View getView(int position, View convertView, ViewGroup parent) {
-            return getDropDownView(position, convertView, parent);
-        }
-
-        public View getDropDownView(int position, View convertView, ViewGroup parent) {
-            return mAdapter == null ? null :
-                    mAdapter.getDropDownView(position, convertView, parent);
-        }
-
-        public boolean hasStableIds() {
-            return mAdapter != null && mAdapter.hasStableIds();
-        }
-
-        public void registerDataSetObserver(DataSetObserver observer) {
-            if (mAdapter != null) {
-                mAdapter.registerDataSetObserver(observer);
-            }
-        }
-
-        public void unregisterDataSetObserver(DataSetObserver observer) {
-            if (mAdapter != null) {
-                mAdapter.unregisterDataSetObserver(observer);
-            }
-        }
-
-        /**
-         * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
-         * Otherwise, return true.
-         */
-        public boolean areAllItemsEnabled() {
-            final ListAdapter adapter = mListAdapter;
-            if (adapter != null) {
-                return adapter.areAllItemsEnabled();
-            } else {
-                return true;
-            }
-        }
-
-        /**
-         * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
-         * Otherwise, return true.
-         */
-        public boolean isEnabled(int position) {
-            final ListAdapter adapter = mListAdapter;
-            if (adapter != null) {
-                return adapter.isEnabled(position);
-            } else {
-                return true;
-            }
-        }
-
-        public int getItemViewType(int position) {
-            return 0;
-        }
-
-        public int getViewTypeCount() {
-            return 1;
-        }
-
-        public boolean isEmpty() {
-            return getCount() == 0;
-        }
-    }
-
-    /**
-     * Implements some sort of popup selection interface for selecting a spinner option.
-     * Allows for different spinner modes.
-     */
-    private interface SpinnerPopup {
-        public void setAdapter(ListAdapter adapter);
-
-        /**
-         * Show the popup
-         */
-        public void show();
-
-        /**
-         * Dismiss the popup
-         */
-        public void dismiss();
-
-        /**
-         * @return true if the popup is showing, false otherwise.
-         */
-        public boolean isShowing();
-
-        /**
-         * Set hint text to be displayed to the user. This should provide
-         * a description of the choice being made.
-         * @param hintText Hint text to set.
-         */
-        public void setPromptText(CharSequence hintText);
-        public CharSequence getHintText();
-    }
-
-    private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener {
-        private AlertDialog mPopup;
-        private ListAdapter mListAdapter;
-        private CharSequence mPrompt;
-
-        public void dismiss() {
-            mPopup.dismiss();
-            mPopup = null;
-        }
-
-        public boolean isShowing() {
-            return mPopup != null ? mPopup.isShowing() : false;
-        }
-
-        public void setAdapter(ListAdapter adapter) {
-            mListAdapter = adapter;
-        }
-
-        public void setPromptText(CharSequence hintText) {
-            mPrompt = hintText;
-        }
-
-        public CharSequence getHintText() {
-            return mPrompt;
-        }
-
-        public void show() {
-            AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
-            if (mPrompt != null) {
-                builder.setTitle(mPrompt);
-            }
-            mPopup = builder.setSingleChoiceItems(mListAdapter,
-                    getSelectedItemPosition(), this).show();
-        }
-
-        public void onClick(DialogInterface dialog, int which) {
-            setSelection(which);
-            if (mOnItemClickListener != null) {
-                performItemClick(null, which, mListAdapter.getItemId(which));
-            }
-            dismiss();
-        }
-    }
-
-    private class DropdownPopup extends android.support.v7.internal.widget.ListPopupWindow
-            implements SpinnerPopup {
-        private CharSequence mHintText;
-        private ListAdapter mAdapter;
-
-        public DropdownPopup(Context context, AttributeSet attrs, int defStyleRes) {
-            super(context, attrs, defStyleRes);
-
-            setAnchorView(SpinnerICS.this);
-            setModal(true);
-            setPromptPosition(POSITION_PROMPT_ABOVE);
-
-            AdapterView.OnItemClickListener listener = new OnItemClickListenerWrapper(
-                    new OnItemClickListener() {
-                public void onItemClick(AdapterViewICS parent, View v, int position, long id) {
-                    SpinnerICS.this.setSelection(position);
-                    if (mOnItemClickListener != null) {
-                        SpinnerICS.this.performItemClick(v, position, mAdapter.getItemId(position));
-                    }
-                    dismiss();
-                }
-            });
-
-            setOnItemClickListener(listener);
-        }
-
-        @Override
-        public void setAdapter(ListAdapter adapter) {
-            super.setAdapter(adapter);
-            mAdapter = adapter;
-        }
-
-        public CharSequence getHintText() {
-            return mHintText;
-        }
-
-        public void setPromptText(CharSequence hintText) {
-            // Hint text is ignored for dropdowns, but maintain it here.
-            mHintText = hintText;
-        }
-
-        @Override
-        public void show() {
-            final int spinnerPaddingLeft = SpinnerICS.this.getPaddingLeft();
-            if (mDropDownWidth == WRAP_CONTENT) {
-                final int spinnerWidth = SpinnerICS.this.getWidth();
-                final int spinnerPaddingRight = SpinnerICS.this.getPaddingRight();
-                setContentWidth(Math.max(
-                        measureContentWidth((SpinnerAdapter) mAdapter, getBackground()),
-                        spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight));
-            } else if (mDropDownWidth == FILL_PARENT) {
-                final int spinnerWidth = SpinnerICS.this.getWidth();
-                final int spinnerPaddingRight = SpinnerICS.this.getPaddingRight();
-                setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight);
-            } else {
-                setContentWidth(mDropDownWidth);
-            }
-            final Drawable background = getBackground();
-            int bgOffset = 0;
-            if (background != null) {
-                background.getPadding(mTempRect);
-                bgOffset = -mTempRect.left;
-            }
-            setHorizontalOffset(bgOffset + spinnerPaddingLeft);
-            setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
-            super.show();
-            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
-            setSelection(SpinnerICS.this.getSelectedItemPosition());
-        }
-    }
-}
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ToolbarWidgetWrapper.java b/v7/appcompat/src/android/support/v7/internal/widget/ToolbarWidgetWrapper.java
new file mode 100644
index 0000000..fb91876
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ToolbarWidgetWrapper.java
@@ -0,0 +1,612 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v7.internal.widget;
+
+import android.app.ActionBar;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Parcelable;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
+import android.support.v7.appcompat.R;
+import android.support.v7.internal.app.WindowCallback;
+import android.support.v7.internal.view.menu.ActionMenuItem;
+import android.support.v7.widget.ActionMenuPresenter;
+import android.support.v7.internal.view.menu.MenuBuilder;
+import android.support.v7.internal.view.menu.MenuPresenter;
+import android.support.v7.widget.Toolbar;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.SpinnerAdapter;
+
+/**
+ * Internal class used to interact with the Toolbar widget without
+ * exposing interface methods to the public API.
+ *
+ * <p>ToolbarWidgetWrapper manages the differences between Toolbar and ActionBarView
+ * so that either variant acting as a
+ * {@link android.support.v7.internal.app.WindowDecorActionBar WindowDecorActionBar} can behave
+ * in the same way.</p>
+ *
+ * @hide
+ */
+public class ToolbarWidgetWrapper implements DecorToolbar {
+    private static final String TAG = "ToolbarWidgetWrapper";
+
+    private static final int AFFECTS_LOGO_MASK =
+            ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_USE_LOGO;
+
+    private Toolbar mToolbar;
+
+    private int mDisplayOpts;
+    private View mTabView;
+    private SpinnerCompat mSpinner;
+    private View mCustomView;
+
+    private Drawable mIcon;
+    private Drawable mLogo;
+    private Drawable mNavIcon;
+
+    private boolean mTitleSet;
+    private CharSequence mTitle;
+    private CharSequence mSubtitle;
+
+    private WindowCallback mWindowCallback;
+    private boolean mMenuPrepared;
+    private ActionMenuPresenter mActionMenuPresenter;
+
+    private int mNavigationMode = ActionBar.NAVIGATION_MODE_STANDARD;
+
+    public ToolbarWidgetWrapper(Toolbar toolbar, boolean style) {
+        mToolbar = toolbar;
+
+        mTitle = toolbar.getTitle();
+        mSubtitle = toolbar.getSubtitle();
+        mTitleSet = !TextUtils.isEmpty(mTitle);
+
+        if (style) {
+            final TypedArray a = toolbar.getContext().obtainStyledAttributes(null,
+                    R.styleable.ActionBar, R.attr.actionBarStyle, 0);
+
+            final CharSequence title = a.getText(R.styleable.ActionBar_title);
+            if (!TextUtils.isEmpty(title)) {
+                setTitle(title);
+            }
+
+            final CharSequence subtitle = a.getText(R.styleable.ActionBar_subtitle);
+            if (!TextUtils.isEmpty(subtitle)) {
+                setSubtitle(subtitle);
+            }
+
+            final Drawable logo = a.getDrawable(R.styleable.ActionBar_logo);
+            if (logo != null) {
+                setLogo(logo);
+            }
+
+            final Drawable icon = a.getDrawable(R.styleable.ActionBar_icon);
+            if (icon != null) {
+                setIcon(icon);
+            }
+
+            final Drawable navIcon = a.getDrawable(R.styleable.ActionBar_homeAsUpIndicator);
+            if (navIcon != null) {
+                setNavigationIcon(navIcon);
+            }
+
+            setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, 0));
+
+            final int customNavId = a.getResourceId(
+                    R.styleable.ActionBar_customNavigationLayout, 0);
+            if (customNavId != 0) {
+                setCustomView(LayoutInflater.from(mToolbar.getContext()).inflate(customNavId,
+                        mToolbar, false));
+                setDisplayOptions(mDisplayOpts | ActionBar.DISPLAY_SHOW_CUSTOM);
+            }
+
+            final int height = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
+            if (height > 0) {
+                final ViewGroup.LayoutParams lp = mToolbar.getLayoutParams();
+                lp.height = height;
+                mToolbar.setLayoutParams(lp);
+            }
+
+            final int contentInsetStart = a.getDimensionPixelOffset(
+                    R.styleable.ActionBar_contentInsetStart, -1);
+            final int contentInsetEnd = a.getDimensionPixelOffset(
+                    R.styleable.ActionBar_contentInsetEnd, -1);
+            if (contentInsetStart >= 0 || contentInsetEnd >= 0) {
+                mToolbar.setContentInsetsRelative(Math.max(contentInsetStart, 0),
+                        Math.max(contentInsetEnd, 0));
+            }
+
+            final int titleTextStyle = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0);
+            if (titleTextStyle != 0) {
+                mToolbar.setTitleTextAppearance(mToolbar.getContext(), titleTextStyle);
+            }
+
+            final int subtitleTextStyle = a.getResourceId(
+                    R.styleable.ActionBar_subtitleTextStyle, 0);
+            if (subtitleTextStyle != 0) {
+                mToolbar.setSubtitleTextAppearance(mToolbar.getContext(), subtitleTextStyle);
+            }
+
+            final int popupTheme = a.getResourceId(R.styleable.ActionBar_popupTheme, 0);
+            if (popupTheme != 0) {
+                mToolbar.setPopupTheme(popupTheme);
+            }
+
+            a.recycle();
+        }
+
+        if (TextUtils.isEmpty(mToolbar.getNavigationContentDescription())) {
+            mToolbar.setNavigationContentDescription(
+                    getContext().getResources().getText(R.string.abc_action_bar_up_description));
+        }
+
+        mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
+            final ActionMenuItem mNavItem = new ActionMenuItem(mToolbar.getContext(),
+                    0, android.R.id.home, 0, 0, mTitle);
+            @Override
+            public void onClick(View v) {
+                if (mWindowCallback != null && mMenuPrepared) {
+                    mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mNavItem);
+                }
+            }
+        });
+    }
+
+    @Override
+    public ViewGroup getViewGroup() {
+        return mToolbar;
+    }
+
+    @Override
+    public Context getContext() {
+        return mToolbar.getContext();
+    }
+
+    @Override
+    public boolean isSplit() {
+        return false;
+    }
+
+    @Override
+    public boolean hasExpandedActionView() {
+        return mToolbar.hasExpandedActionView();
+    }
+
+    @Override
+    public void collapseActionView() {
+        mToolbar.collapseActionView();
+    }
+
+    @Override
+    public void setWindowCallback(WindowCallback cb) {
+        mWindowCallback = cb;
+    }
+
+    @Override
+    public void setWindowTitle(CharSequence title) {
+        // "Real" title always trumps window title.
+        if (!mTitleSet) {
+            setTitleInt(title);
+        }
+    }
+
+    @Override
+    public CharSequence getTitle() {
+        return mToolbar.getTitle();
+    }
+
+    @Override
+    public void setTitle(CharSequence title) {
+        mTitleSet = true;
+        setTitleInt(title);
+    }
+
+    private void setTitleInt(CharSequence title) {
+        mTitle = title;
+        if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
+            mToolbar.setTitle(title);
+        }
+    }
+
+    @Override
+    public CharSequence getSubtitle() {
+        return mToolbar.getSubtitle();
+    }
+
+    @Override
+    public void setSubtitle(CharSequence subtitle) {
+        mSubtitle = subtitle;
+        if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
+            mToolbar.setSubtitle(subtitle);
+        }
+    }
+
+    @Override
+    public void initProgress() {
+        Log.i(TAG, "Progress display unsupported");
+    }
+
+    @Override
+    public void initIndeterminateProgress() {
+        Log.i(TAG, "Progress display unsupported");
+    }
+
+    @Override
+    public boolean canSplit() {
+        return false;
+    }
+
+    @Override
+    public void setSplitView(ViewGroup splitView) {
+    }
+
+    @Override
+    public void setSplitToolbar(boolean split) {
+        if (split) {
+            throw new UnsupportedOperationException("Cannot split an android.widget.Toolbar");
+        }
+    }
+
+    @Override
+    public void setSplitWhenNarrow(boolean splitWhenNarrow) {
+        // Ignore.
+    }
+
+    @Override
+    public boolean hasIcon() {
+        return mIcon != null;
+    }
+
+    @Override
+    public boolean hasLogo() {
+        return mLogo != null;
+    }
+
+    @Override
+    public void setIcon(int resId) {
+        setIcon(resId != 0 ? ContextCompat.getDrawable(getContext(), resId) : null);
+    }
+
+    @Override
+    public void setIcon(Drawable d) {
+        mIcon = d;
+        updateToolbarLogo();
+    }
+
+    @Override
+    public void setLogo(int resId) {
+        setLogo(resId != 0 ? ContextCompat.getDrawable(getContext(), resId) : null);
+    }
+
+    @Override
+    public void setLogo(Drawable d) {
+        mLogo = d;
+        updateToolbarLogo();
+    }
+
+    private void updateToolbarLogo() {
+        Drawable logo = null;
+        if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_HOME) != 0) {
+            if ((mDisplayOpts & ActionBar.DISPLAY_USE_LOGO) != 0) {
+                logo = mLogo != null ? mLogo : mIcon;
+            } else {
+                logo = mIcon;
+            }
+        }
+        mToolbar.setLogo(logo);
+    }
+
+    @Override
+    public boolean canShowOverflowMenu() {
+        return mToolbar.canShowOverflowMenu();
+    }
+
+    @Override
+    public boolean isOverflowMenuShowing() {
+        return mToolbar.isOverflowMenuShowing();
+    }
+
+    @Override
+    public boolean isOverflowMenuShowPending() {
+        return mToolbar.isOverflowMenuShowPending();
+    }
+
+    @Override
+    public boolean showOverflowMenu() {
+        return mToolbar.showOverflowMenu();
+    }
+
+    @Override
+    public boolean hideOverflowMenu() {
+        return mToolbar.hideOverflowMenu();
+    }
+
+    @Override
+    public void setMenuPrepared() {
+        mMenuPrepared = true;
+    }
+
+    @Override
+    public void setMenu(Menu menu, MenuPresenter.Callback cb) {
+        if (mActionMenuPresenter == null) {
+            mActionMenuPresenter = new ActionMenuPresenter(mToolbar.getContext());
+            mActionMenuPresenter.setId(R.id.action_menu_presenter);
+        }
+        mActionMenuPresenter.setCallback(cb);
+        mToolbar.setMenu((MenuBuilder) menu, mActionMenuPresenter);
+    }
+
+    @Override
+    public void dismissPopupMenus() {
+        mToolbar.dismissPopupMenus();
+    }
+
+    @Override
+    public int getDisplayOptions() {
+        return mDisplayOpts;
+    }
+
+    @Override
+    public void setDisplayOptions(int newOpts) {
+        final int oldOpts = mDisplayOpts;
+        final int changed = oldOpts ^ newOpts;
+        mDisplayOpts = newOpts;
+        if (changed != 0) {
+            if ((changed & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
+                if ((newOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
+                    mToolbar.setNavigationIcon(mNavIcon);
+                } else {
+                    mToolbar.setNavigationIcon(null);
+                }
+            }
+
+            if ((changed & AFFECTS_LOGO_MASK) != 0) {
+                updateToolbarLogo();
+            }
+
+            if ((changed & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
+                if ((newOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
+                    mToolbar.setTitle(mTitle);
+                    mToolbar.setSubtitle(mSubtitle);
+                } else {
+                    mToolbar.setTitle(null);
+                    mToolbar.setSubtitle(null);
+                }
+            }
+
+            if ((changed & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomView != null) {
+                if ((newOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
+                    mToolbar.addView(mCustomView);
+                } else {
+                    mToolbar.removeView(mCustomView);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setEmbeddedTabView(ScrollingTabContainerView tabView) {
+        if (mTabView != null && mTabView.getParent() == mToolbar) {
+            mToolbar.removeView(mTabView);
+        }
+        mTabView = tabView;
+        if (tabView != null && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
+            mToolbar.addView(mTabView, 0);
+            Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams();
+            lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+            lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+            lp.gravity = Gravity.START | Gravity.BOTTOM;
+            tabView.setAllowCollapse(true);
+        }
+    }
+
+    @Override
+    public boolean hasEmbeddedTabs() {
+        return mTabView != null;
+    }
+
+    @Override
+    public boolean isTitleTruncated() {
+        return mToolbar.isTitleTruncated();
+    }
+
+    @Override
+    public void setCollapsible(boolean collapsible) {
+        mToolbar.setCollapsible(collapsible);
+    }
+
+    @Override
+    public void setHomeButtonEnabled(boolean enable) {
+        // Ignore
+    }
+
+    @Override
+    public int getNavigationMode() {
+        return mNavigationMode;
+    }
+
+    @Override
+    public void setNavigationMode(int mode) {
+        final int oldMode = mNavigationMode;
+        if (mode != oldMode) {
+            switch (oldMode) {
+                case ActionBar.NAVIGATION_MODE_LIST:
+                    if (mSpinner != null && mSpinner.getParent() == mToolbar) {
+                        mToolbar.removeView(mSpinner);
+                    }
+                    break;
+                case ActionBar.NAVIGATION_MODE_TABS:
+                    if (mTabView != null && mTabView.getParent() == mToolbar) {
+                        mToolbar.removeView(mTabView);
+                    }
+                    break;
+            }
+
+            mNavigationMode = mode;
+
+            switch (mode) {
+                case ActionBar.NAVIGATION_MODE_STANDARD:
+                    break;
+                case ActionBar.NAVIGATION_MODE_LIST:
+                    ensureSpinner();
+                    mToolbar.addView(mSpinner, 0);
+                    break;
+                case ActionBar.NAVIGATION_MODE_TABS:
+                    if (mTabView != null) {
+                        mToolbar.addView(mTabView, 0);
+                        Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams();
+                        lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+                        lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+                        lp.gravity = Gravity.START | Gravity.BOTTOM;
+                    }
+                    break;
+                default:
+                    throw new IllegalArgumentException("Invalid navigation mode " + mode);
+            }
+        }
+    }
+
+    private void ensureSpinner() {
+        if (mSpinner == null) {
+            mSpinner = new SpinnerCompat(getContext(), null, R.attr.actionDropDownStyle);
+            Toolbar.LayoutParams lp = new Toolbar.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL);
+            mSpinner.setLayoutParams(lp);
+        }
+    }
+
+    @Override
+    public void setDropdownParams(SpinnerAdapter adapter,
+            AdapterViewCompat.OnItemSelectedListener listener) {
+        ensureSpinner();
+        mSpinner.setAdapter(adapter);
+        mSpinner.setOnItemSelectedListener(listener);
+    }
+
+    @Override
+    public void setDropdownSelectedPosition(int position) {
+        if (mSpinner == null) {
+            throw new IllegalStateException(
+                    "Can't set dropdown selected position without an adapter");
+        }
+        mSpinner.setSelection(position);
+    }
+
+    @Override
+    public int getDropdownSelectedPosition() {
+        return mSpinner != null ? mSpinner.getSelectedItemPosition() : 0;
+    }
+
+    @Override
+    public int getDropdownItemCount() {
+        return mSpinner != null ? mSpinner.getCount() : 0;
+    }
+
+    @Override
+    public void setCustomView(View view) {
+        if (mCustomView != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
+            mToolbar.removeView(mCustomView);
+        }
+        mCustomView = view;
+        if (view != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
+            mToolbar.addView(mCustomView);
+        }
+    }
+
+    @Override
+    public View getCustomView() {
+        return mCustomView;
+    }
+
+    @Override
+    public void animateToVisibility(int visibility) {
+        if (visibility == View.GONE) {
+            ViewCompat.animate(mToolbar).translationY(mToolbar.getHeight()).alpha(0)
+                    .setListener(new ViewPropertyAnimatorListenerAdapter() {
+                        private boolean mCanceled = false;
+                        @Override
+                        public void onAnimationEnd(View view) {
+                            if (!mCanceled) {
+                                mToolbar.setVisibility(View.GONE);
+                            }
+                        }
+
+                        @Override
+                        public void onAnimationCancel(View view) {
+                            mCanceled = true;
+                        }
+                    });
+        } else if (visibility == View.VISIBLE) {
+            ViewCompat.animate(mToolbar).translationY(0).alpha(1)
+                    .setListener(new ViewPropertyAnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationStart(View view) {
+                            mToolbar.setVisibility(View.VISIBLE);
+                        }
+                    });
+        }
+    }
+
+    @Override
+    public void setNavigationIcon(Drawable icon) {
+        mNavIcon = icon;
+        if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
+            mToolbar.setNavigationIcon(icon);
+        }
+    }
+
+    @Override
+    public void setNavigationIcon(int resId) {
+        setNavigationIcon(resId != 0
+                ? ContextCompat.getDrawable(mToolbar.getContext(), resId)
+                : null);
+    }
+
+    @Override
+    public void setNavigationContentDescription(CharSequence description) {
+        mToolbar.setNavigationContentDescription(description);
+    }
+
+    @Override
+    public void setNavigationContentDescription(int resId) {
+        mToolbar.setNavigationContentDescription(resId);
+    }
+
+    @Override
+    public void saveHierarchyState(SparseArray<Parcelable> toolbarStates) {
+        mToolbar.saveHierarchyState(toolbarStates);
+    }
+
+    @Override
+    public void restoreHierarchyState(SparseArray<Parcelable> toolbarStates) {
+        mToolbar.restoreHierarchyState(toolbarStates);
+    }
+
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/internal/widget/ViewUtils.java b/v7/appcompat/src/android/support/v7/internal/widget/ViewUtils.java
new file mode 100644
index 0000000..262f434
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/internal/widget/ViewUtils.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.internal.widget;
+
+import android.support.v4.view.ViewCompat;
+import android.view.View;
+
+/**
+ * @hide
+ */
+public class ViewUtils {
+
+    private ViewUtils() {}
+
+    public static boolean isLayoutRtl(View view) {
+        return ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_RTL;
+    }
+
+    /**
+     * Merge two states as returned by {@link ViewCompat#getMeasuredState(android.view.View)} ()}.
+     * @param curState The current state as returned from a view or the result
+     * of combining multiple views.
+     * @param newState The new view state to combine.
+     * @return Returns a new integer reflecting the combination of the two
+     * states.
+     */
+    public static int combineMeasuredStates(int curState, int newState) {
+        return curState | newState;
+    }
+}
diff --git a/v7/appcompat/src/android/support/v7/view/ActionMode.java b/v7/appcompat/src/android/support/v7/view/ActionMode.java
index 65a0cf9..579305f 100644
--- a/v7/appcompat/src/android/support/v7/view/ActionMode.java
+++ b/v7/appcompat/src/android/support/v7/view/ActionMode.java
@@ -23,20 +23,13 @@
 
 /**
  * Represents a contextual mode of the user interface. Action modes can be used to provide
- * alternative interaction modes and replace parts of the normal UI until finished. Examples of good
- * action modes include text selection and contextual actions.
- *
- * <p class="note"><strong>Note:</strong> This class is included in the <a
- * href="{@docRoot}tools/extras/support-library.html">support library</a> for compatibility
- * with API level 7 and higher. If you're developing your app for API level 11 and higher
- * <em>only</em>, you should instead use the framework {@link android.view.ActionMode} class.</p>
- *
+ * alternative interaction modes and replace parts of the normal UI until finished.
+ * Examples of good action modes include text selection and contextual actions.
  * <div class="special reference">
  *
  * <h3>Developer Guides</h3>
- *
- * <p>For information about how to provide contextual actions with {@code
- * ActionMode}, read the <a href="{@docRoot}guide/topics/ui/menus.html#context-menu">Menus</a>
+ * <p>For information about how to provide contextual actions with {@code ActionMode},
+ * read the <a href="{@docRoot}guide/topics/ui/menus.html#context-menu">Menus</a>
  * developer guide.</p>
  *
  * </div>
@@ -49,10 +42,11 @@
     /**
      * Set a tag object associated with this ActionMode.
      *
-     * <p>Like the tag available to views, this allows applications to associate arbitrary data with
-     * an ActionMode for later reference.
+     * <p>Like the tag available to views, this allows applications to associate arbitrary
+     * data with an ActionMode for later reference.
      *
      * @param tag Tag to associate with this ActionMode
+     *
      * @see #getTag()
      */
     public void setTag(Object tag) {
@@ -62,10 +56,11 @@
     /**
      * Retrieve the tag object associated with this ActionMode.
      *
-     * <p>Like the tag available to views, this allows applications to associate arbitrary data with
-     * an ActionMode for later reference.
+     * <p>Like the tag available to views, this allows applications to associate arbitrary
+     * data with an ActionMode for later reference.
      *
      * @return Tag associated with this ActionMode
+     *
      * @see #setTag(Object)
      */
     public Object getTag() {
@@ -73,55 +68,61 @@
     }
 
     /**
-     * Set the title of the action mode. This method will have no visible effect if a custom view
-     * has been set.
+     * Set the title of the action mode. This method will have no visible effect if
+     * a custom view has been set.
      *
      * @param title Title string to set
+     *
      * @see #setTitle(int)
      * @see #setCustomView(View)
      */
     public abstract void setTitle(CharSequence title);
 
     /**
-     * Set the title of the action mode. This method will have no visible effect if a custom view
-     * has been set.
+     * Set the title of the action mode. This method will have no visible effect if
+     * a custom view has been set.
      *
      * @param resId Resource ID of a string to set as the title
+     *
      * @see #setTitle(CharSequence)
      * @see #setCustomView(View)
      */
     public abstract void setTitle(int resId);
 
     /**
-     * Set the subtitle of the action mode. This method will have no visible effect if a custom view
-     * has been set.
+     * Set the subtitle of the action mode. This method will have no visible effect if
+     * a custom view has been set.
      *
      * @param subtitle Subtitle string to set
+     *
      * @see #setSubtitle(int)
      * @see #setCustomView(View)
      */
     public abstract void setSubtitle(CharSequence subtitle);
 
     /**
-     * Set the subtitle of the action mode. This method will have no visible effect if a custom view
-     * has been set.
+     * Set the subtitle of the action mode. This method will have no visible effect if
+     * a custom view has been set.
      *
      * @param resId Resource ID of a string to set as the subtitle
+     *
      * @see #setSubtitle(CharSequence)
      * @see #setCustomView(View)
      */
     public abstract void setSubtitle(int resId);
 
     /**
-     * Set whether or not the title/subtitle display for this action mode is optional.
+     * Set whether or not the title/subtitle display for this action mode
+     * is optional.
      *
-     * <p>In many cases the supplied title for an action mode is merely meant to add context and is
-     * not strictly required for the action mode to be useful. If the title is optional, the system
-     * may choose to hide the title entirely rather than truncate it due to a lack of available
-     * space.</p>
+     * <p>In many cases the supplied title for an action mode is merely
+     * meant to add context and is not strictly required for the action
+     * mode to be useful. If the title is optional, the system may choose
+     * to hide the title entirely rather than truncate it due to a lack
+     * of available space.</p>
      *
-     * <p>Note that this is merely a hint; the underlying implementation may choose to ignore this
-     * setting under some circumstances.</p>
+     * <p>Note that this is merely a hint; the underlying implementation
+     * may choose to ignore this setting under some circumstances.</p>
      *
      * @param titleOptional true if the title only presents optional information.
      */
@@ -130,8 +131,9 @@
     }
 
     /**
-     * @return true if this action mode has been given a hint to consider the title/subtitle display
-     *         to be optional.
+     * @return true if this action mode has been given a hint to consider the
+     *         title/subtitle display to be optional.
+     *
      * @see #setTitleOptionalHint(boolean)
      * @see #isTitleOptional()
      */
@@ -140,34 +142,36 @@
     }
 
     /**
-     * @return true if this action mode considers the title and subtitle fields as optional.
-     *         Optional titles may not be displayed to the user.
+     * @return true if this action mode considers the title and subtitle fields
+     *         as optional. Optional titles may not be displayed to the user.
      */
     public boolean isTitleOptional() {
         return false;
     }
 
     /**
-     * Set a custom view for this action mode. The custom view will take the place of the title and
-     * subtitle. Useful for things like search boxes.
+     * Set a custom view for this action mode. The custom view will take the place of
+     * the title and subtitle. Useful for things like search boxes.
      *
      * @param view Custom view to use in place of the title/subtitle.
+     *
      * @see #setTitle(CharSequence)
      * @see #setSubtitle(CharSequence)
      */
     public abstract void setCustomView(View view);
 
     /**
-     * Invalidate the action mode and refresh menu content. The mode's {@link ActionMode.Callback}
-     * will have its {@link Callback#onPrepareActionMode(ActionMode, Menu)} method called. If it
-     * returns true the menu will be scanned for updated content and any relevant changes will be
-     * reflected to the user.
+     * Invalidate the action mode and refresh menu content. The mode's
+     * {@link ActionMode.Callback} will have its
+     * {@link Callback#onPrepareActionMode(ActionMode, Menu)} method called.
+     * If it returns true the menu will be scanned for updated content and any relevant changes
+     * will be reflected to the user.
      */
     public abstract void invalidate();
 
     /**
-     * Finish and close this action mode. The action mode's {@link ActionMode.Callback} will have
-     * its {@link Callback#onDestroyActionMode(ActionMode)} method called.
+     * Finish and close this action mode. The action mode's {@link ActionMode.Callback} will
+     * have its {@link Callback#onDestroyActionMode(ActionMode)} method called.
      */
     public abstract void finish();
 
@@ -205,9 +209,9 @@
     public abstract MenuInflater getMenuInflater();
 
     /**
-     * Returns whether the UI presenting this action mode can take focus or not. This is used by
-     * internal components within the framework that would otherwise present an action mode UI that
-     * requires focus, such as an EditText as a custom view.
+     * Returns whether the UI presenting this action mode can take focus or not.
+     * This is used by internal components within the framework that would otherwise
+     * present an action mode UI that requires focus, such as an EditText as a custom view.
      *
      * @return true if the UI used to show this action mode can take focus
      * @hide Internal use only
@@ -218,34 +222,31 @@
 
     /**
      * Callback interface for action modes. Supplied to
-     * {@link android.support.v7.app.ActionBarActivity#startSupportActionMode(Callback)}
+     * {@link android.support.v7.app.ActionBarActivity#startSupportActionMode(Callback)} (Callback)},
      * a Callback configures and handles events raised by a user's interaction with an action mode.
      *
      * <p>An action mode's lifecycle is as follows:
-     *
-     * <ul><li>{@link Callback#onCreateActionMode(ActionMode,
-     * Menu)} once on initial creation</li>
-     *
-     * <li>{@link Callback#onPrepareActionMode(ActionMode, Menu)}
-     * after creation and any time the {@link ActionMode} is invalidated</li>
-     *
-     * <li>{@link
-     * Callback#onActionItemClicked(ActionMode, MenuItem)} any time a contextual action button is
-     * clicked</li>
-     *
-     * <li>{@link Callback#onDestroyActionMode(ActionMode)} when the action mode is
-     * closed</li></ul>
+     * <ul>
+     * <li>{@link Callback#onCreateActionMode(ActionMode, Menu)} once on initial
+     * creation</li>
+     * <li>{@link Callback#onPrepareActionMode(ActionMode, Menu)} after creation
+     * and any time the {@link ActionMode} is invalidated</li>
+     * <li>{@link Callback#onActionItemClicked(ActionMode, MenuItem)} any time a
+     * contextual action button is clicked</li>
+     * <li>{@link Callback#onDestroyActionMode(ActionMode)} when the action mode
+     * is closed</li>
+     * </ul>
      */
     public interface Callback {
 
         /**
-         * Called when action mode is first created. The menu supplied will be used to generate
-         * action buttons for the action mode.
+         * Called when action mode is first created. The menu supplied will be used to
+         * generate action buttons for the action mode.
          *
          * @param mode ActionMode being created
          * @param menu Menu used to populate action buttons
-         * @return true if the action mode should be created, false if entering this mode should be
-         *         aborted.
+         * @return true if the action mode should be created, false if entering this
+         *              mode should be aborted.
          */
         public boolean onCreateActionMode(ActionMode mode, Menu menu);
 
diff --git a/v7/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java b/v7/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java
new file mode 100644
index 0000000..4cb0d0f
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java
@@ -0,0 +1,742 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v4.view.ActionProvider;
+import android.support.v4.view.GravityCompat;
+import android.support.v7.appcompat.R;
+import android.support.v7.internal.transition.ActionBarTransition;
+import android.support.v7.internal.view.ActionBarPolicy;
+import android.support.v7.internal.view.menu.ActionMenuItemView;
+import android.support.v7.internal.view.menu.BaseMenuPresenter;
+import android.support.v7.internal.view.menu.MenuBuilder;
+import android.support.v7.internal.view.menu.MenuItemImpl;
+import android.support.v7.internal.view.menu.MenuPopupHelper;
+import android.support.v7.internal.view.menu.MenuView;
+import android.support.v7.internal.view.menu.SubMenuBuilder;
+import android.util.SparseBooleanArray;
+import android.view.MenuItem;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+
+import java.util.ArrayList;
+
+/**
+ * MenuPresenter for building action menus as seen in the action bar and action modes.
+ *
+ * @hide
+ */
+public class ActionMenuPresenter extends BaseMenuPresenter
+        implements ActionProvider.SubUiVisibilityListener {
+
+    private static final String TAG = "ActionMenuPresenter";
+
+    private View mOverflowButton;
+    private boolean mReserveOverflow;
+    private boolean mReserveOverflowSet;
+    private int mWidthLimit;
+    private int mActionItemWidthLimit;
+    private int mMaxItems;
+    private boolean mMaxItemsSet;
+    private boolean mStrictWidthLimit;
+    private boolean mWidthLimitSet;
+    private boolean mExpandedActionViewsExclusive;
+
+    private int mMinCellSize;
+
+    // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
+    private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
+
+    private View mScrapActionButtonView;
+
+    private OverflowPopup mOverflowPopup;
+    private ActionButtonSubmenu mActionButtonPopup;
+
+    private OpenOverflowRunnable mPostedOpenRunnable;
+    private ActionMenuPopupCallback mPopupCallback;
+
+    final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback();
+    int mOpenSubMenuId;
+
+    public ActionMenuPresenter(Context context) {
+        super(context, R.layout.abc_action_menu_layout, R.layout.abc_action_menu_item_layout);
+    }
+
+    @Override
+    public void initForMenu(Context context, MenuBuilder menu) {
+        super.initForMenu(context, menu);
+
+        final Resources res = context.getResources();
+
+        final ActionBarPolicy abp = ActionBarPolicy.get(context);
+        if (!mReserveOverflowSet) {
+            mReserveOverflow = abp.showsOverflowMenuButton();
+        }
+
+        if (!mWidthLimitSet) {
+            mWidthLimit = abp.getEmbeddedMenuWidthLimit();
+        }
+
+        // Measure for initial configuration
+        if (!mMaxItemsSet) {
+            mMaxItems = abp.getMaxActionButtons();
+        }
+
+        int width = mWidthLimit;
+        if (mReserveOverflow) {
+            if (mOverflowButton == null) {
+                mOverflowButton = new OverflowMenuButton(mSystemContext);
+                final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+                mOverflowButton.measure(spec, spec);
+            }
+            width -= mOverflowButton.getMeasuredWidth();
+        } else {
+            mOverflowButton = null;
+        }
+
+        mActionItemWidthLimit = width;
+
+        mMinCellSize = (int) (ActionMenuView.MIN_CELL_SIZE * res.getDisplayMetrics().density);
+
+        // Drop a scrap view as it may no longer reflect the proper context/config.
+        mScrapActionButtonView = null;
+    }
+
+    public void onConfigurationChanged(Configuration newConfig) {
+        if (!mMaxItemsSet) {
+            mMaxItems = mContext.getResources().getInteger(
+                    R.integer.abc_max_action_buttons);
+        }
+        if (mMenu != null) {
+            mMenu.onItemsChanged(true);
+        }
+    }
+
+    public void setWidthLimit(int width, boolean strict) {
+        mWidthLimit = width;
+        mStrictWidthLimit = strict;
+        mWidthLimitSet = true;
+    }
+
+    public void setReserveOverflow(boolean reserveOverflow) {
+        mReserveOverflow = reserveOverflow;
+        mReserveOverflowSet = true;
+    }
+
+    public void setItemLimit(int itemCount) {
+        mMaxItems = itemCount;
+        mMaxItemsSet = true;
+    }
+
+    public void setExpandedActionViewsExclusive(boolean isExclusive) {
+        mExpandedActionViewsExclusive = isExclusive;
+    }
+
+    @Override
+    public MenuView getMenuView(ViewGroup root) {
+        MenuView result = super.getMenuView(root);
+        ((ActionMenuView) result).setPresenter(this);
+        return result;
+    }
+
+    @Override
+    public View getItemView(final MenuItemImpl item, View convertView, ViewGroup parent) {
+        View actionView = item.getActionView();
+        if (actionView == null || item.hasCollapsibleActionView()) {
+            actionView = super.getItemView(item, convertView, parent);
+        }
+        actionView.setVisibility(item.isActionViewExpanded() ? View.GONE : View.VISIBLE);
+
+        final ActionMenuView menuParent = (ActionMenuView) parent;
+        final ViewGroup.LayoutParams lp = actionView.getLayoutParams();
+        if (!menuParent.checkLayoutParams(lp)) {
+            actionView.setLayoutParams(menuParent.generateLayoutParams(lp));
+        }
+        return actionView;
+    }
+
+    @Override
+    public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) {
+        itemView.initialize(item, 0);
+
+        final ActionMenuView menuView = (ActionMenuView) mMenuView;
+        final ActionMenuItemView actionItemView = (ActionMenuItemView) itemView;
+        actionItemView.setItemInvoker(menuView);
+
+        if (mPopupCallback == null) {
+            mPopupCallback = new ActionMenuPopupCallback();
+        }
+        actionItemView.setPopupCallback(mPopupCallback);
+    }
+
+    @Override
+    public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+        return item.isActionButton();
+    }
+
+    @Override
+    public void updateMenuView(boolean cleared) {
+        final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent();
+        if (menuViewParent != null) {
+            ActionBarTransition.beginDelayedTransition(menuViewParent);
+        }
+        super.updateMenuView(cleared);
+
+        ((View) mMenuView).requestLayout();
+
+        if (mMenu != null) {
+            final ArrayList<MenuItemImpl> actionItems = mMenu.getActionItems();
+            final int count = actionItems.size();
+            for (int i = 0; i < count; i++) {
+                final ActionProvider provider = actionItems.get(i).getSupportActionProvider();
+                if (provider != null) {
+                    provider.setSubUiVisibilityListener(this);
+                }
+            }
+        }
+
+        final ArrayList<MenuItemImpl> nonActionItems = mMenu != null ?
+                mMenu.getNonActionItems() : null;
+
+        boolean hasOverflow = false;
+        if (mReserveOverflow && nonActionItems != null) {
+            final int count = nonActionItems.size();
+            if (count == 1) {
+                hasOverflow = !nonActionItems.get(0).isActionViewExpanded();
+            } else {
+                hasOverflow = count > 0;
+            }
+        }
+
+        if (hasOverflow) {
+            if (mOverflowButton == null) {
+                mOverflowButton = new OverflowMenuButton(mSystemContext);
+            }
+            ViewGroup parent = (ViewGroup) mOverflowButton.getParent();
+            if (parent != mMenuView) {
+                if (parent != null) {
+                    parent.removeView(mOverflowButton);
+                }
+                ActionMenuView menuView = (ActionMenuView) mMenuView;
+                menuView.addView(mOverflowButton, menuView.generateOverflowButtonLayoutParams());
+            }
+        } else if (mOverflowButton != null && mOverflowButton.getParent() == mMenuView) {
+            ((ViewGroup) mMenuView).removeView(mOverflowButton);
+        }
+
+        ((ActionMenuView) mMenuView).setOverflowReserved(mReserveOverflow);
+    }
+
+    @Override
+    public boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+        if (parent.getChildAt(childIndex) == mOverflowButton) return false;
+        return super.filterLeftoverView(parent, childIndex);
+    }
+
+    public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+        if (!subMenu.hasVisibleItems()) return false;
+
+        SubMenuBuilder topSubMenu = subMenu;
+        while (topSubMenu.getParentMenu() != mMenu) {
+            topSubMenu = (SubMenuBuilder) topSubMenu.getParentMenu();
+        }
+        View anchor = findViewForItem(topSubMenu.getItem());
+        if (anchor == null) {
+            if (mOverflowButton == null) return false;
+            anchor = mOverflowButton;
+        }
+
+        mOpenSubMenuId = subMenu.getItem().getItemId();
+        mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu);
+        mActionButtonPopup.setAnchorView(anchor);
+        mActionButtonPopup.show();
+        super.onSubMenuSelected(subMenu);
+        return true;
+    }
+
+    private View findViewForItem(MenuItem item) {
+        final ViewGroup parent = (ViewGroup) mMenuView;
+        if (parent == null) return null;
+
+        final int count = parent.getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = parent.getChildAt(i);
+            if (child instanceof MenuView.ItemView &&
+                    ((MenuView.ItemView) child).getItemData() == item) {
+                return child;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Display the overflow menu if one is present.
+     * @return true if the overflow menu was shown, false otherwise.
+     */
+    public boolean showOverflowMenu() {
+        if (mReserveOverflow && !isOverflowMenuShowing() && mMenu != null && mMenuView != null &&
+                mPostedOpenRunnable == null && !mMenu.getNonActionItems().isEmpty()) {
+            OverflowPopup popup = new OverflowPopup(mContext, mMenu, mOverflowButton, true);
+            mPostedOpenRunnable = new OpenOverflowRunnable(popup);
+            // Post this for later; we might still need a layout for the anchor to be right.
+            ((View) mMenuView).post(mPostedOpenRunnable);
+
+            // ActionMenuPresenter uses null as a callback argument here
+            // to indicate overflow is opening.
+            super.onSubMenuSelected(null);
+
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Hide the overflow menu if it is currently showing.
+     *
+     * @return true if the overflow menu was hidden, false otherwise.
+     */
+    public boolean hideOverflowMenu() {
+        if (mPostedOpenRunnable != null && mMenuView != null) {
+            ((View) mMenuView).removeCallbacks(mPostedOpenRunnable);
+            mPostedOpenRunnable = null;
+            return true;
+        }
+
+        MenuPopupHelper popup = mOverflowPopup;
+        if (popup != null) {
+            popup.dismiss();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Dismiss all popup menus - overflow and submenus.
+     * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
+     */
+    public boolean dismissPopupMenus() {
+        boolean result = hideOverflowMenu();
+        result |= hideSubMenus();
+        return result;
+    }
+
+    /**
+     * Dismiss all submenu popups.
+     *
+     * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
+     */
+    public boolean hideSubMenus() {
+        if (mActionButtonPopup != null) {
+            mActionButtonPopup.dismiss();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @return true if the overflow menu is currently showing
+     */
+    public boolean isOverflowMenuShowing() {
+        return mOverflowPopup != null && mOverflowPopup.isShowing();
+    }
+
+    public boolean isOverflowMenuShowPending() {
+        return mPostedOpenRunnable != null || isOverflowMenuShowing();
+    }
+
+    /**
+     * @return true if space has been reserved in the action menu for an overflow item.
+     */
+    public boolean isOverflowReserved() {
+        return mReserveOverflow;
+    }
+
+    public boolean flagActionItems() {
+        final ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
+        final int itemsSize = visibleItems.size();
+        int maxActions = mMaxItems;
+        int widthLimit = mActionItemWidthLimit;
+        final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        final ViewGroup parent = (ViewGroup) mMenuView;
+
+        int requiredItems = 0;
+        int requestedItems = 0;
+        int firstActionWidth = 0;
+        boolean hasOverflow = false;
+        for (int i = 0; i < itemsSize; i++) {
+            MenuItemImpl item = visibleItems.get(i);
+            if (item.requiresActionButton()) {
+                requiredItems++;
+            } else if (item.requestsActionButton()) {
+                requestedItems++;
+            } else {
+                hasOverflow = true;
+            }
+            if (mExpandedActionViewsExclusive && item.isActionViewExpanded()) {
+                // Overflow everything if we have an expanded action view and we're
+                // space constrained.
+                maxActions = 0;
+            }
+        }
+
+        // Reserve a spot for the overflow item if needed.
+        if (mReserveOverflow &&
+                (hasOverflow || requiredItems + requestedItems > maxActions)) {
+            maxActions--;
+        }
+        maxActions -= requiredItems;
+
+        final SparseBooleanArray seenGroups = mActionButtonGroups;
+        seenGroups.clear();
+
+        int cellSize = 0;
+        int cellsRemaining = 0;
+        if (mStrictWidthLimit) {
+            cellsRemaining = widthLimit / mMinCellSize;
+            final int cellSizeRemaining = widthLimit % mMinCellSize;
+            cellSize = mMinCellSize + cellSizeRemaining / cellsRemaining;
+        }
+
+        // Flag as many more requested items as will fit.
+        for (int i = 0; i < itemsSize; i++) {
+            MenuItemImpl item = visibleItems.get(i);
+
+            if (item.requiresActionButton()) {
+                View v = getItemView(item, mScrapActionButtonView, parent);
+                if (mScrapActionButtonView == null) {
+                    mScrapActionButtonView = v;
+                }
+                if (mStrictWidthLimit) {
+                    cellsRemaining -= ActionMenuView.measureChildForCells(v,
+                            cellSize, cellsRemaining, querySpec, 0);
+                } else {
+                    v.measure(querySpec, querySpec);
+                }
+                final int measuredWidth = v.getMeasuredWidth();
+                widthLimit -= measuredWidth;
+                if (firstActionWidth == 0) {
+                    firstActionWidth = measuredWidth;
+                }
+                final int groupId = item.getGroupId();
+                if (groupId != 0) {
+                    seenGroups.put(groupId, true);
+                }
+                item.setIsActionButton(true);
+            } else if (item.requestsActionButton()) {
+                // Items in a group with other items that already have an action slot
+                // can break the max actions rule, but not the width limit.
+                final int groupId = item.getGroupId();
+                final boolean inGroup = seenGroups.get(groupId);
+                boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0 &&
+                        (!mStrictWidthLimit || cellsRemaining > 0);
+
+                if (isAction) {
+                    View v = getItemView(item, mScrapActionButtonView, parent);
+                    if (mScrapActionButtonView == null) {
+                        mScrapActionButtonView = v;
+                    }
+                    if (mStrictWidthLimit) {
+                        final int cells = ActionMenuView.measureChildForCells(v,
+                                cellSize, cellsRemaining, querySpec, 0);
+                        cellsRemaining -= cells;
+                        if (cells == 0) {
+                            isAction = false;
+                        }
+                    } else {
+                        v.measure(querySpec, querySpec);
+                    }
+                    final int measuredWidth = v.getMeasuredWidth();
+                    widthLimit -= measuredWidth;
+                    if (firstActionWidth == 0) {
+                        firstActionWidth = measuredWidth;
+                    }
+
+                    if (mStrictWidthLimit) {
+                        isAction &= widthLimit >= 0;
+                    } else {
+                        // Did this push the entire first item past the limit?
+                        isAction &= widthLimit + firstActionWidth > 0;
+                    }
+                }
+
+                if (isAction && groupId != 0) {
+                    seenGroups.put(groupId, true);
+                } else if (inGroup) {
+                    // We broke the width limit. Demote the whole group, they all overflow now.
+                    seenGroups.put(groupId, false);
+                    for (int j = 0; j < i; j++) {
+                        MenuItemImpl areYouMyGroupie = visibleItems.get(j);
+                        if (areYouMyGroupie.getGroupId() == groupId) {
+                            // Give back the action slot
+                            if (areYouMyGroupie.isActionButton()) maxActions++;
+                            areYouMyGroupie.setIsActionButton(false);
+                        }
+                    }
+                }
+
+                if (isAction) maxActions--;
+
+                item.setIsActionButton(isAction);
+            } else {
+                // Neither requires nor requests an action button.
+                item.setIsActionButton(false);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+        dismissPopupMenus();
+        super.onCloseMenu(menu, allMenusAreClosing);
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        SavedState state = new SavedState();
+        state.openSubMenuId = mOpenSubMenuId;
+        return state;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        SavedState saved = (SavedState) state;
+        if (saved.openSubMenuId > 0) {
+            MenuItem item = mMenu.findItem(saved.openSubMenuId);
+            if (item != null) {
+                SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
+                onSubMenuSelected(subMenu);
+            }
+        }
+    }
+
+    @Override
+    public void onSubUiVisibilityChanged(boolean isVisible) {
+        if (isVisible) {
+            // Not a submenu, but treat it like one.
+            super.onSubMenuSelected(null);
+        } else {
+            mMenu.close(false);
+        }
+    }
+
+    public void setMenuView(ActionMenuView menuView) {
+        mMenuView = menuView;
+        menuView.initialize(mMenu);
+    }
+
+    private static class SavedState implements Parcelable {
+        public int openSubMenuId;
+
+        SavedState() {
+        }
+
+        SavedState(Parcel in) {
+            openSubMenuId = in.readInt();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(openSubMenuId);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    private class OverflowMenuButton extends ImageButton implements ActionMenuView.ActionMenuChildView {
+        public OverflowMenuButton(Context context) {
+            super(context, null, R.attr.actionOverflowButtonStyle);
+
+            setClickable(true);
+            setFocusable(true);
+            setVisibility(VISIBLE);
+            setEnabled(true);
+            setOnTouchListener(new ListPopupWindow.ForwardingListener(this) {
+                @Override
+                public ListPopupWindow getPopup() {
+                    if (mOverflowPopup == null) {
+                        return null;
+                    }
+
+                    return mOverflowPopup.getPopup();
+                }
+
+                @Override
+                public boolean onForwardingStarted() {
+                    showOverflowMenu();
+                    return true;
+                }
+
+                @Override
+                public boolean onForwardingStopped() {
+                    // Displaying the popup occurs asynchronously, so wait for
+                    // the runnable to finish before deciding whether to stop
+                    // forwarding.
+                    if (mPostedOpenRunnable != null) {
+                        return false;
+                    }
+
+                    hideOverflowMenu();
+                    return true;
+                }
+            });
+        }
+
+        @Override
+        public boolean performClick() {
+            if (super.performClick()) {
+                return true;
+            }
+
+            playSoundEffect(SoundEffectConstants.CLICK);
+            showOverflowMenu();
+            return true;
+        }
+
+        @Override
+        public boolean needsDividerBefore() {
+            return false;
+        }
+
+        @Override
+        public boolean needsDividerAfter() {
+            return false;
+        }
+    }
+
+    private class OverflowPopup extends MenuPopupHelper {
+
+        public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
+                boolean overflowOnly) {
+            super(context, menu, anchorView, overflowOnly, R.attr.actionOverflowMenuStyle);
+            setGravity(GravityCompat.END);
+            setCallback(mPopupPresenterCallback);
+        }
+
+        @Override
+        public void onDismiss() {
+            super.onDismiss();
+            mMenu.close();
+            mOverflowPopup = null;
+        }
+    }
+
+    private class ActionButtonSubmenu extends MenuPopupHelper {
+        private SubMenuBuilder mSubMenu;
+
+        public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) {
+            super(context, subMenu, null, false,
+                    R.attr.actionOverflowMenuStyle);
+            mSubMenu = subMenu;
+
+            MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
+            if (!item.isActionButton()) {
+                // Give a reasonable anchor to nested submenus.
+                setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton);
+            }
+
+            setCallback(mPopupPresenterCallback);
+
+            boolean preserveIconSpacing = false;
+            final int count = subMenu.size();
+            for (int i = 0; i < count; i++) {
+                MenuItem childItem = subMenu.getItem(i);
+                if (childItem.isVisible() && childItem.getIcon() != null) {
+                    preserveIconSpacing = true;
+                    break;
+                }
+            }
+            setForceShowIcon(preserveIconSpacing);
+        }
+
+        @Override
+        public void onDismiss() {
+            super.onDismiss();
+            mActionButtonPopup = null;
+            mOpenSubMenuId = 0;
+        }
+    }
+
+    private class PopupPresenterCallback implements Callback {
+
+        @Override
+        public boolean onOpenSubMenu(MenuBuilder subMenu) {
+            if (subMenu == null) return false;
+
+            mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId();
+            final Callback cb = getCallback();
+            return cb != null ? cb.onOpenSubMenu(subMenu) : false;
+        }
+
+        @Override
+        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+            if (menu instanceof SubMenuBuilder) {
+                ((SubMenuBuilder) menu).getRootMenu().close(false);
+            }
+            final Callback cb = getCallback();
+            if (cb != null) {
+                cb.onCloseMenu(menu, allMenusAreClosing);
+            }
+        }
+    }
+
+    private class OpenOverflowRunnable implements Runnable {
+        private OverflowPopup mPopup;
+
+        public OpenOverflowRunnable(OverflowPopup popup) {
+            mPopup = popup;
+        }
+
+        public void run() {
+            mMenu.changeMenuMode();
+            final View menuView = (View) mMenuView;
+            if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) {
+                mOverflowPopup = mPopup;
+            }
+            mPostedOpenRunnable = null;
+        }
+    }
+
+    private class ActionMenuPopupCallback extends ActionMenuItemView.PopupCallback {
+        @Override
+        public ListPopupWindow getPopup() {
+            return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
+        }
+    }
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/ActionMenuView.java b/v7/appcompat/src/android/support/v7/widget/ActionMenuView.java
new file mode 100644
index 0000000..c9e05b1
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/ActionMenuView.java
@@ -0,0 +1,792 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.support.v7.internal.view.menu.ActionMenuItemView;
+import android.support.v7.internal.view.menu.MenuBuilder;
+import android.support.v7.internal.view.menu.MenuItemImpl;
+import android.support.v7.internal.view.menu.MenuView;
+import android.support.v7.internal.widget.ViewUtils;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * ActionMenuView is a presentation of a series of menu options as a View. It provides
+ * several top level options as action buttons while spilling remaining options over as
+ * items in an overflow menu. This allows applications to present packs of actions inline with
+ * specific or repeating content.
+ */
+public class ActionMenuView extends LinearLayoutCompat implements MenuBuilder.ItemInvoker,
+        MenuView {
+
+    private static final String TAG = "ActionMenuView";
+
+    static final int MIN_CELL_SIZE = 56; // dips
+    static final int GENERATED_ITEM_PADDING = 4; // dips
+
+    private MenuBuilder mMenu;
+
+    private Context mContext;
+
+    /** Context against which to inflate popup menus. */
+    private Context mPopupContext;
+
+    /** Theme resource against which to inflate popup menus. */
+    private int mPopupTheme;
+
+    private boolean mReserveOverflow;
+    private ActionMenuPresenter mPresenter;
+    private boolean mFormatItems;
+    private int mFormatItemsWidth;
+    private int mMinCellSize;
+    private int mGeneratedItemPadding;
+
+    private OnMenuItemClickListener mOnMenuItemClickListener;
+
+    public ActionMenuView(Context context) {
+        this(context, null);
+    }
+
+    public ActionMenuView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mContext = context;
+        setBaselineAligned(false);
+        final float density = context.getResources().getDisplayMetrics().density;
+        mMinCellSize = (int) (MIN_CELL_SIZE * density);
+        mGeneratedItemPadding = (int) (GENERATED_ITEM_PADDING * density);
+        mPopupContext = context;
+        mPopupTheme = 0;
+    }
+
+    /**
+     * Specifies the theme to use when inflating popup menus. By default, uses
+     * the same theme as the action menu view itself.
+     *
+     * @param resId theme used to inflate popup menus
+     * @see #getPopupTheme()
+     */
+    public void setPopupTheme(int resId) {
+        if (mPopupTheme != resId) {
+            mPopupTheme = resId;
+            if (resId == 0) {
+                mPopupContext = mContext;
+            } else {
+                mPopupContext = new ContextThemeWrapper(mContext, resId);
+            }
+        }
+    }
+
+    /**
+     * @return resource identifier of the theme used to inflate popup menus, or
+     *         0 if menus are inflated against the action menu view theme
+     * @see #setPopupTheme(int)
+     */
+    public int getPopupTheme() {
+        return mPopupTheme;
+    }
+
+    /**
+     * @param presenter Menu presenter used to display popup menu
+     * @hide
+     */
+    public void setPresenter(ActionMenuPresenter presenter) {
+        mPresenter = presenter;
+        mPresenter.setMenuView(this);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        if (Build.VERSION.SDK_INT >= 8) {
+            super.onConfigurationChanged(newConfig);
+        }
+
+        mPresenter.updateMenuView(false);
+
+        if (mPresenter != null && mPresenter.isOverflowMenuShowing()) {
+            mPresenter.hideOverflowMenu();
+            mPresenter.showOverflowMenu();
+        }
+    }
+
+    public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
+        mOnMenuItemClickListener = listener;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // If we've been given an exact size to match, apply special formatting during layout.
+        final boolean wasFormatted = mFormatItems;
+        mFormatItems = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY;
+
+        if (wasFormatted != mFormatItems) {
+            mFormatItemsWidth = 0; // Reset this when switching modes
+        }
+
+        // Special formatting can change whether items can fit as action buttons.
+        // Kick the menu and update presenters when this changes.
+        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+        if (mFormatItems && mMenu != null && widthSize != mFormatItemsWidth) {
+            mFormatItemsWidth = widthSize;
+            mMenu.onItemsChanged(true);
+        }
+
+        final int childCount = getChildCount();
+        if (mFormatItems && childCount > 0) {
+            onMeasureExactFormat(widthMeasureSpec, heightMeasureSpec);
+        } else {
+            // Previous measurement at exact format may have set margins - reset them.
+            for (int i = 0; i < childCount; i++) {
+                final View child = getChildAt(i);
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                lp.leftMargin = lp.rightMargin = 0;
+            }
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+    private void onMeasureExactFormat(int widthMeasureSpec, int heightMeasureSpec) {
+        // We already know the width mode is EXACTLY if we're here.
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+        final int widthPadding = getPaddingLeft() + getPaddingRight();
+        final int heightPadding = getPaddingTop() + getPaddingBottom();
+
+        final int itemHeightSpec = getChildMeasureSpec(heightMeasureSpec, heightPadding,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+
+        widthSize -= widthPadding;
+
+        // Divide the view into cells.
+        final int cellCount = widthSize / mMinCellSize;
+        final int cellSizeRemaining = widthSize % mMinCellSize;
+
+        if (cellCount == 0) {
+            // Give up, nothing fits.
+            setMeasuredDimension(widthSize, 0);
+            return;
+        }
+
+        final int cellSize = mMinCellSize + cellSizeRemaining / cellCount;
+
+        int cellsRemaining = cellCount;
+        int maxChildHeight = 0;
+        int maxCellsUsed = 0;
+        int expandableItemCount = 0;
+        int visibleItemCount = 0;
+        boolean hasOverflow = false;
+
+        // This is used as a bitfield to locate the smallest items present. Assumes childCount < 64.
+        long smallestItemsAt = 0;
+
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() == GONE) continue;
+
+            final boolean isGeneratedItem = child instanceof ActionMenuItemView;
+            visibleItemCount++;
+
+            if (isGeneratedItem) {
+                // Reset padding for generated menu item views; it may change below
+                // and views are recycled.
+                child.setPadding(mGeneratedItemPadding, 0, mGeneratedItemPadding, 0);
+            }
+
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            lp.expanded = false;
+            lp.extraPixels = 0;
+            lp.cellsUsed = 0;
+            lp.expandable = false;
+            lp.leftMargin = 0;
+            lp.rightMargin = 0;
+            lp.preventEdgeOffset = isGeneratedItem && ((ActionMenuItemView) child).hasText();
+
+            // Overflow always gets 1 cell. No more, no less.
+            final int cellsAvailable = lp.isOverflowButton ? 1 : cellsRemaining;
+
+            final int cellsUsed = measureChildForCells(child, cellSize, cellsAvailable,
+                    itemHeightSpec, heightPadding);
+
+            maxCellsUsed = Math.max(maxCellsUsed, cellsUsed);
+            if (lp.expandable) expandableItemCount++;
+            if (lp.isOverflowButton) hasOverflow = true;
+
+            cellsRemaining -= cellsUsed;
+            maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
+            if (cellsUsed == 1) smallestItemsAt |= (1 << i);
+        }
+
+        // When we have overflow and a single expanded (text) item, we want to try centering it
+        // visually in the available space even though overflow consumes some of it.
+        final boolean centerSingleExpandedItem = hasOverflow && visibleItemCount == 2;
+
+        // Divide space for remaining cells if we have items that can expand.
+        // Try distributing whole leftover cells to smaller items first.
+
+        boolean needsExpansion = false;
+        while (expandableItemCount > 0 && cellsRemaining > 0) {
+            int minCells = Integer.MAX_VALUE;
+            long minCellsAt = 0; // Bit locations are indices of relevant child views
+            int minCellsItemCount = 0;
+            for (int i = 0; i < childCount; i++) {
+                final View child = getChildAt(i);
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+                // Don't try to expand items that shouldn't.
+                if (!lp.expandable) continue;
+
+                // Mark indices of children that can receive an extra cell.
+                if (lp.cellsUsed < minCells) {
+                    minCells = lp.cellsUsed;
+                    minCellsAt = 1 << i;
+                    minCellsItemCount = 1;
+                } else if (lp.cellsUsed == minCells) {
+                    minCellsAt |= 1 << i;
+                    minCellsItemCount++;
+                }
+            }
+
+            // Items that get expanded will always be in the set of smallest items when we're done.
+            smallestItemsAt |= minCellsAt;
+
+            if (minCellsItemCount > cellsRemaining) break; // Couldn't expand anything evenly. Stop.
+
+            // We have enough cells, all minimum size items will be incremented.
+            minCells++;
+
+            for (int i = 0; i < childCount; i++) {
+                final View child = getChildAt(i);
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if ((minCellsAt & (1 << i)) == 0) {
+                    // If this item is already at our small item count, mark it for later.
+                    if (lp.cellsUsed == minCells) smallestItemsAt |= 1 << i;
+                    continue;
+                }
+
+                if (centerSingleExpandedItem && lp.preventEdgeOffset && cellsRemaining == 1) {
+                    // Add padding to this item such that it centers.
+                    child.setPadding(mGeneratedItemPadding + cellSize, 0, mGeneratedItemPadding, 0);
+                }
+                lp.cellsUsed++;
+                lp.expanded = true;
+                cellsRemaining--;
+            }
+
+            needsExpansion = true;
+        }
+
+        // Divide any space left that wouldn't divide along cell boundaries
+        // evenly among the smallest items
+
+        final boolean singleItem = !hasOverflow && visibleItemCount == 1;
+        if (cellsRemaining > 0 && smallestItemsAt != 0 &&
+                (cellsRemaining < visibleItemCount - 1 || singleItem || maxCellsUsed > 1)) {
+            float expandCount = Long.bitCount(smallestItemsAt);
+
+            if (!singleItem) {
+                // The items at the far edges may only expand by half in order to pin to either side.
+                if ((smallestItemsAt & 1) != 0) {
+                    LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams();
+                    if (!lp.preventEdgeOffset) expandCount -= 0.5f;
+                }
+                if ((smallestItemsAt & (1 << (childCount - 1))) != 0) {
+                    LayoutParams lp = ((LayoutParams) getChildAt(childCount - 1).getLayoutParams());
+                    if (!lp.preventEdgeOffset) expandCount -= 0.5f;
+                }
+            }
+
+            final int extraPixels = expandCount > 0 ?
+                    (int) (cellsRemaining * cellSize / expandCount) : 0;
+
+            for (int i = 0; i < childCount; i++) {
+                if ((smallestItemsAt & (1 << i)) == 0) continue;
+
+                final View child = getChildAt(i);
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if (child instanceof ActionMenuItemView) {
+                    // If this is one of our views, expand and measure at the larger size.
+                    lp.extraPixels = extraPixels;
+                    lp.expanded = true;
+                    if (i == 0 && !lp.preventEdgeOffset) {
+                        // First item gets part of its new padding pushed out of sight.
+                        // The last item will get this implicitly from layout.
+                        lp.leftMargin = -extraPixels / 2;
+                    }
+                    needsExpansion = true;
+                } else if (lp.isOverflowButton) {
+                    lp.extraPixels = extraPixels;
+                    lp.expanded = true;
+                    lp.rightMargin = -extraPixels / 2;
+                    needsExpansion = true;
+                } else {
+                    // If we don't know what it is, give it some margins instead
+                    // and let it center within its space. We still want to pin
+                    // against the edges.
+                    if (i != 0) {
+                        lp.leftMargin = extraPixels / 2;
+                    }
+                    if (i != childCount - 1) {
+                        lp.rightMargin = extraPixels / 2;
+                    }
+                }
+            }
+
+            cellsRemaining = 0;
+        }
+
+        // Remeasure any items that have had extra space allocated to them.
+        if (needsExpansion) {
+            for (int i = 0; i < childCount; i++) {
+                final View child = getChildAt(i);
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+                if (!lp.expanded) continue;
+
+                final int width = lp.cellsUsed * cellSize + lp.extraPixels;
+                child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                        itemHeightSpec);
+            }
+        }
+
+        if (heightMode != MeasureSpec.EXACTLY) {
+            heightSize = maxChildHeight;
+        }
+
+        setMeasuredDimension(widthSize, heightSize);
+    }
+
+    /**
+     * Measure a child view to fit within cell-based formatting. The child's width
+     * will be measured to a whole multiple of cellSize.
+     *
+     * <p>Sets the expandable and cellsUsed fields of LayoutParams.
+     *
+     * @param child Child to measure
+     * @param cellSize Size of one cell
+     * @param cellsRemaining Number of cells remaining that this view can expand to fill
+     * @param parentHeightMeasureSpec MeasureSpec used by the parent view
+     * @param parentHeightPadding Padding present in the parent view
+     * @return Number of cells this child was measured to occupy
+     */
+    static int measureChildForCells(View child, int cellSize, int cellsRemaining,
+            int parentHeightMeasureSpec, int parentHeightPadding) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+        final int childHeightSize = MeasureSpec.getSize(parentHeightMeasureSpec) -
+                parentHeightPadding;
+        final int childHeightMode = MeasureSpec.getMode(parentHeightMeasureSpec);
+        final int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode);
+
+        final ActionMenuItemView itemView = child instanceof ActionMenuItemView ?
+                (ActionMenuItemView) child : null;
+        final boolean hasText = itemView != null && itemView.hasText();
+
+        int cellsUsed = 0;
+        if (cellsRemaining > 0 && (!hasText || cellsRemaining >= 2)) {
+            final int childWidthSpec = MeasureSpec.makeMeasureSpec(
+                    cellSize * cellsRemaining, MeasureSpec.AT_MOST);
+            child.measure(childWidthSpec, childHeightSpec);
+
+            final int measuredWidth = child.getMeasuredWidth();
+            cellsUsed = measuredWidth / cellSize;
+            if (measuredWidth % cellSize != 0) cellsUsed++;
+            if (hasText && cellsUsed < 2) cellsUsed = 2;
+        }
+
+        final boolean expandable = !lp.isOverflowButton && hasText;
+        lp.expandable = expandable;
+
+        lp.cellsUsed = cellsUsed;
+        final int targetWidth = cellsUsed * cellSize;
+        child.measure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
+                childHeightSpec);
+        return cellsUsed;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        if (!mFormatItems) {
+            super.onLayout(changed, left, top, right, bottom);
+            return;
+        }
+
+        final int childCount = getChildCount();
+        final int midVertical = (top + bottom) / 2;
+        final int dividerWidth = getDividerWidth();
+        int overflowWidth = 0;
+        int nonOverflowWidth = 0;
+        int nonOverflowCount = 0;
+        int widthRemaining = right - left - getPaddingRight() - getPaddingLeft();
+        boolean hasOverflow = false;
+        final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this);
+        for (int i = 0; i < childCount; i++) {
+            final View v = getChildAt(i);
+            if (v.getVisibility() == GONE) {
+                continue;
+            }
+
+            LayoutParams p = (LayoutParams) v.getLayoutParams();
+            if (p.isOverflowButton) {
+                overflowWidth = v.getMeasuredWidth();
+                if (hasSupportDividerBeforeChildAt(i)) {
+                    overflowWidth += dividerWidth;
+                }
+                int height = v.getMeasuredHeight();
+                int r;
+                int l;
+                if (isLayoutRtl) {
+                    l = getPaddingLeft() + p.leftMargin;
+                    r = l + overflowWidth;
+                } else {
+                    r = getWidth() - getPaddingRight() - p.rightMargin;
+                    l = r - overflowWidth;
+                }
+                int t = midVertical - (height / 2);
+                int b = t + height;
+                v.layout(l, t, r, b);
+
+                widthRemaining -= overflowWidth;
+                hasOverflow = true;
+            } else {
+                final int size = v.getMeasuredWidth() + p.leftMargin + p.rightMargin;
+                nonOverflowWidth += size;
+                widthRemaining -= size;
+                if (hasSupportDividerBeforeChildAt(i)) {
+                    nonOverflowWidth += dividerWidth;
+                }
+                nonOverflowCount++;
+            }
+        }
+
+        if (childCount == 1 && !hasOverflow) {
+            // Center a single child
+            final View v = getChildAt(0);
+            final int width = v.getMeasuredWidth();
+            final int height = v.getMeasuredHeight();
+            final int midHorizontal = (right - left) / 2;
+            final int l = midHorizontal - width / 2;
+            final int t = midVertical - height / 2;
+            v.layout(l, t, l + width, t + height);
+            return;
+        }
+
+        final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1);
+        final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0);
+
+        if (isLayoutRtl) {
+            int startRight = getWidth() - getPaddingRight();
+            for (int i = 0; i < childCount; i++) {
+                final View v = getChildAt(i);
+                final LayoutParams lp = (LayoutParams) v.getLayoutParams();
+                if (v.getVisibility() == GONE || lp.isOverflowButton) {
+                    continue;
+                }
+
+                startRight -= lp.rightMargin;
+                int width = v.getMeasuredWidth();
+                int height = v.getMeasuredHeight();
+                int t = midVertical - height / 2;
+                v.layout(startRight - width, t, startRight, t + height);
+                startRight -= width + lp.leftMargin + spacerSize;
+            }
+        } else {
+            int startLeft = getPaddingLeft();
+            for (int i = 0; i < childCount; i++) {
+                final View v = getChildAt(i);
+                final LayoutParams lp = (LayoutParams) v.getLayoutParams();
+                if (v.getVisibility() == GONE || lp.isOverflowButton) {
+                    continue;
+                }
+
+                startLeft += lp.leftMargin;
+                int width = v.getMeasuredWidth();
+                int height = v.getMeasuredHeight();
+                int t = midVertical - height / 2;
+                v.layout(startLeft, t, startLeft + width, t + height);
+                startLeft += width + lp.rightMargin + spacerSize;
+            }
+        }
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        dismissPopupMenus();
+    }
+
+    /** @hide */
+    public boolean isOverflowReserved() {
+        return mReserveOverflow;
+    }
+
+    /** @hide */
+    public void setOverflowReserved(boolean reserveOverflow) {
+        mReserveOverflow = reserveOverflow;
+    }
+
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
+                LayoutParams.WRAP_CONTENT);
+        params.gravity = Gravity.CENTER_VERTICAL;
+        return params;
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new LayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        if (p != null) {
+            final LayoutParams result = p instanceof LayoutParams
+                    ? new LayoutParams((LayoutParams) p)
+                    : new LayoutParams(p);
+            if (result.gravity <= Gravity.NO_GRAVITY) {
+                result.gravity = Gravity.CENTER_VERTICAL;
+            }
+            return result;
+        }
+        return generateDefaultLayoutParams();
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p != null && p instanceof LayoutParams;
+    }
+
+    /** @hide */
+    public LayoutParams generateOverflowButtonLayoutParams() {
+        LayoutParams result = generateDefaultLayoutParams();
+        result.isOverflowButton = true;
+        return result;
+    }
+
+    /** @hide */
+    public boolean invokeItem(MenuItemImpl item) {
+        return mMenu.performItemAction(item, 0);
+    }
+
+    /** @hide */
+    public int getWindowAnimations() {
+        return 0;
+    }
+
+    /** @hide */
+    public void initialize(MenuBuilder menu) {
+        mMenu = menu;
+    }
+
+    /**
+     * Returns the Menu object that this ActionMenuView is currently presenting.
+     *
+     * <p>Applications should use this method to obtain the ActionMenuView's Menu object
+     * and inflate or add content to it as necessary.</p>
+     *
+     * @return the Menu presented by this view
+     */
+    public Menu getMenu() {
+        if (mMenu == null) {
+            final Context context = getContext();
+            mMenu = new MenuBuilder(context);
+            mMenu.setCallback(new MenuBuilderCallback());
+            mPresenter = new ActionMenuPresenter(context);
+            mPresenter.setCallback(new ActionMenuPresenterCallback());
+            mMenu.addMenuPresenter(mPresenter, mPopupContext);
+            mPresenter.setMenuView(this);
+        }
+
+        return mMenu;
+    }
+
+    /**
+     * Returns the current menu or null if one has not yet been configured.
+     * @hide Internal use only for action bar integration
+     */
+    public MenuBuilder peekMenu() {
+        return mMenu;
+    }
+
+    /**
+     * Show the overflow items from the associated menu.
+     *
+     * @return true if the menu was able to be shown, false otherwise
+     */
+    public boolean showOverflowMenu() {
+        return mPresenter != null && mPresenter.showOverflowMenu();
+    }
+
+    /**
+     * Hide the overflow items from the associated menu.
+     *
+     * @return true if the menu was able to be hidden, false otherwise
+     */
+    public boolean hideOverflowMenu() {
+        return mPresenter != null && mPresenter.hideOverflowMenu();
+    }
+
+    /**
+     * Check whether the overflow menu is currently showing. This may not reflect
+     * a pending show operation in progress.
+     *
+     * @return true if the overflow menu is currently showing
+     */
+    public boolean isOverflowMenuShowing() {
+        return mPresenter != null && mPresenter.isOverflowMenuShowing();
+    }
+
+    /** @hide */
+    public boolean isOverflowMenuShowPending() {
+        return mPresenter != null && mPresenter.isOverflowMenuShowPending();
+    }
+
+    /**
+     * Dismiss any popups associated with this menu view.
+     */
+    public void dismissPopupMenus() {
+        if (mPresenter != null) {
+            mPresenter.dismissPopupMenus();
+        }
+    }
+
+    /**
+     * @hide Private LinearLayout (superclass) API. Un-hide if LinearLayout API is made public.
+     */
+    protected boolean hasSupportDividerBeforeChildAt(int childIndex) {
+        if (childIndex == 0) {
+            return false;
+        }
+        final View childBefore = getChildAt(childIndex - 1);
+        final View child = getChildAt(childIndex);
+        boolean result = false;
+        if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
+            result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
+        }
+        if (childIndex > 0 && child instanceof ActionMenuChildView) {
+            result |= ((ActionMenuChildView) child).needsDividerBefore();
+        }
+        return result;
+    }
+
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        return false;
+    }
+
+    /** @hide */
+    public void setExpandedActionViewsExclusive(boolean exclusive) {
+        mPresenter.setExpandedActionViewsExclusive(exclusive);
+    }
+
+    /**
+     * Interface responsible for receiving menu item click events if the items themselves
+     * do not have individual item click listeners.
+     */
+    public interface OnMenuItemClickListener {
+        /**
+         * This method will be invoked when a menu item is clicked if the item itself did
+         * not already handle the event.
+         *
+         * @param item {@link MenuItem} that was clicked
+         * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
+         */
+        public boolean onMenuItemClick(MenuItem item);
+    }
+
+    private class MenuBuilderCallback implements MenuBuilder.Callback {
+        @Override
+        public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+            return mOnMenuItemClickListener != null &&
+                    mOnMenuItemClickListener.onMenuItemClick(item);
+        }
+
+        @Override
+        public void onMenuModeChange(MenuBuilder menu) {
+        }
+    }
+
+    private class ActionMenuPresenterCallback implements ActionMenuPresenter.Callback {
+        @Override
+        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+        }
+
+        @Override
+        public boolean onOpenSubMenu(MenuBuilder subMenu) {
+            return false;
+        }
+    }
+
+    /** @hide */
+    public interface ActionMenuChildView {
+        public boolean needsDividerBefore();
+        public boolean needsDividerAfter();
+    }
+
+    public static class LayoutParams extends LinearLayoutCompat.LayoutParams {
+
+        @ViewDebug.ExportedProperty()
+        public boolean isOverflowButton;
+
+        @ViewDebug.ExportedProperty()
+        public int cellsUsed;
+
+        @ViewDebug.ExportedProperty()
+        public int extraPixels;
+
+        @ViewDebug.ExportedProperty()
+        public boolean expandable;
+
+        @ViewDebug.ExportedProperty()
+        public boolean preventEdgeOffset;
+
+        boolean expanded;
+
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+        }
+
+        public LayoutParams(ViewGroup.LayoutParams other) {
+            super(other);
+        }
+
+        public LayoutParams(LayoutParams other) {
+            super((ViewGroup.LayoutParams) other);
+            isOverflowButton = other.isOverflowButton;
+        }
+
+        public LayoutParams(int width, int height) {
+            super(width, height);
+            isOverflowButton = false;
+        }
+
+        LayoutParams(int width, int height, boolean isOverflowButton) {
+            super(width, height);
+            this.isOverflowButton = isOverflowButton;
+        }
+    }
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/LinearLayoutCompat.java b/v7/appcompat/src/android/support/v7/widget/LinearLayoutCompat.java
new file mode 100644
index 0000000..340014b
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/LinearLayoutCompat.java
@@ -0,0 +1,1837 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.IntDef;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.appcompat.R;
+import android.support.v7.internal.widget.ViewUtils;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
+/**
+ * A Layout that arranges its children in a single column or a single row. The direction of
+ * the row can be set by calling {@link #setOrientation(int) setOrientation()}.
+ * You can also specify gravity, which specifies the alignment of all the child elements by
+ * calling {@link #setGravity(int) setGravity()} or specify that specific children
+ * grow to fill up any remaining space in the layout by setting the <em>weight</em> member of
+ * {@link LinearLayoutCompat.LayoutParams LinearLayoutCompat.LayoutParams}.
+ * The default orientation is horizontal.
+ *
+ * <p>See the <a href="{@docRoot}guide/topics/ui/layout/linear.html">Linear Layout</a>
+ * guide.</p>
+ *
+ * <p>
+ * Also see {@link LinearLayoutCompat.LayoutParams android.widget.LinearLayoutCompat.LayoutParams}
+ * for layout attributes </p>
+ */
+public class LinearLayoutCompat extends ViewGroup {
+    /** @hide */
+    @IntDef({HORIZONTAL, VERTICAL})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface OrientationMode {}
+
+    public static final int HORIZONTAL = 0;
+    public static final int VERTICAL = 1;
+
+    /** @hide */
+    @IntDef(flag = true,
+            value = {
+                    SHOW_DIVIDER_NONE,
+                    SHOW_DIVIDER_BEGINNING,
+                    SHOW_DIVIDER_MIDDLE,
+                    SHOW_DIVIDER_END
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DividerMode {}
+
+    /**
+     * Don't show any dividers.
+     */
+    public static final int SHOW_DIVIDER_NONE = 0;
+    /**
+     * Show a divider at the beginning of the group.
+     */
+    public static final int SHOW_DIVIDER_BEGINNING = 1;
+    /**
+     * Show dividers between each item in the group.
+     */
+    public static final int SHOW_DIVIDER_MIDDLE = 2;
+    /**
+     * Show a divider at the end of the group.
+     */
+    public static final int SHOW_DIVIDER_END = 4;
+
+    /**
+     * Whether the children of this layout are baseline aligned.  Only applicable
+     * if {@link #mOrientation} is horizontal.
+     */
+    private boolean mBaselineAligned = true;
+
+    /**
+     * If this layout is part of another layout that is baseline aligned,
+     * use the child at this index as the baseline.
+     *
+     * Note: this is orthogonal to {@link #mBaselineAligned}, which is concerned
+     * with whether the children of this layout are baseline aligned.
+     */
+    private int mBaselineAlignedChildIndex = -1;
+
+    /**
+     * The additional offset to the child's baseline.
+     * We'll calculate the baseline of this layout as we measure vertically; for
+     * horizontal linear layouts, the offset of 0 is appropriate.
+     */
+    private int mBaselineChildTop = 0;
+
+    private int mOrientation;
+
+    private int mGravity = GravityCompat.START | Gravity.TOP;
+
+    private int mTotalLength;
+
+    private float mWeightSum;
+
+    private boolean mUseLargestChild;
+
+    private int[] mMaxAscent;
+    private int[] mMaxDescent;
+
+    private static final int VERTICAL_GRAVITY_COUNT = 4;
+
+    private static final int INDEX_CENTER_VERTICAL = 0;
+    private static final int INDEX_TOP = 1;
+    private static final int INDEX_BOTTOM = 2;
+    private static final int INDEX_FILL = 3;
+
+    private Drawable mDivider;
+    private int mDividerWidth;
+    private int mDividerHeight;
+    private int mShowDividers;
+    private int mDividerPadding;
+
+    public LinearLayoutCompat(Context context) {
+        this(context, null);
+    }
+
+    public LinearLayoutCompat(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public LinearLayoutCompat(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        final TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.LinearLayoutCompat, defStyleAttr, 0);
+
+        int index = a.getInt(R.styleable.LinearLayoutCompat_android_orientation, -1);
+        if (index >= 0) {
+            setOrientation(index);
+        }
+
+        index = a.getInt(R.styleable.LinearLayoutCompat_android_gravity, -1);
+        if (index >= 0) {
+            setGravity(index);
+        }
+
+        boolean baselineAligned = a.getBoolean(R.styleable.LinearLayoutCompat_android_baselineAligned, true);
+        if (!baselineAligned) {
+            setBaselineAligned(baselineAligned);
+        }
+
+        mWeightSum = a.getFloat(R.styleable.LinearLayoutCompat_android_weightSum, -1.0f);
+
+        mBaselineAlignedChildIndex =
+                a.getInt(R.styleable.LinearLayoutCompat_android_baselineAlignedChildIndex, -1);
+
+        mUseLargestChild = a.getBoolean(R.styleable.LinearLayoutCompat_measureWithLargestChild, false);
+
+        setDividerDrawable(a.getDrawable(R.styleable.LinearLayoutCompat_divider));
+        mShowDividers = a.getInt(R.styleable.LinearLayoutCompat_showDividers, SHOW_DIVIDER_NONE);
+        mDividerPadding = a.getDimensionPixelSize(R.styleable.LinearLayoutCompat_dividerPadding, 0);
+
+        a.recycle();
+    }
+
+    /**
+     * Set how dividers should be shown between items in this layout
+     *
+     * @param showDividers One or more of {@link #SHOW_DIVIDER_BEGINNING},
+     *                     {@link #SHOW_DIVIDER_MIDDLE}, or {@link #SHOW_DIVIDER_END},
+     *                     or {@link #SHOW_DIVIDER_NONE} to show no dividers.
+     */
+    public void setShowDividers(@DividerMode int showDividers) {
+        if (showDividers != mShowDividers) {
+            requestLayout();
+        }
+        mShowDividers = showDividers;
+    }
+
+    @Override
+    public boolean shouldDelayChildPressedState() {
+        return false;
+    }
+
+    /**
+     * @return A flag set indicating how dividers should be shown around items.
+     * @see #setShowDividers(int)
+     */
+    @DividerMode
+    public int getShowDividers() {
+        return mShowDividers;
+    }
+
+    /**
+     * @return the divider Drawable that will divide each item.
+     *
+     * @see #setDividerDrawable(Drawable)
+     */
+    public Drawable getDividerDrawable() {
+        return mDivider;
+    }
+
+    /**
+     * Set a drawable to be used as a divider between items.
+     *
+     * @param divider Drawable that will divide each item.
+     *
+     * @see #setShowDividers(int)
+     */
+    public void setDividerDrawable(Drawable divider) {
+        if (divider == mDivider) {
+            return;
+        }
+        mDivider = divider;
+        if (divider != null) {
+            mDividerWidth = divider.getIntrinsicWidth();
+            mDividerHeight = divider.getIntrinsicHeight();
+        } else {
+            mDividerWidth = 0;
+            mDividerHeight = 0;
+        }
+        setWillNotDraw(divider == null);
+        requestLayout();
+    }
+
+    /**
+     * Set padding displayed on both ends of dividers.
+     *
+     * @param padding Padding value in pixels that will be applied to each end
+     *
+     * @see #setShowDividers(int)
+     * @see #setDividerDrawable(Drawable)
+     * @see #getDividerPadding()
+     */
+    public void setDividerPadding(int padding) {
+        mDividerPadding = padding;
+    }
+
+    /**
+     * Get the padding size used to inset dividers in pixels
+     *
+     * @see #setShowDividers(int)
+     * @see #setDividerDrawable(Drawable)
+     * @see #setDividerPadding(int)
+     */
+    public int getDividerPadding() {
+        return mDividerPadding;
+    }
+
+    /**
+     * Get the width of the current divider drawable.
+     *
+     * @hide Used internally by framework.
+     */
+    public int getDividerWidth() {
+        return mDividerWidth;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mDivider == null) {
+            return;
+        }
+
+        if (mOrientation == VERTICAL) {
+            drawDividersVertical(canvas);
+        } else {
+            drawDividersHorizontal(canvas);
+        }
+    }
+
+    void drawDividersVertical(Canvas canvas) {
+        final int count = getVirtualChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getVirtualChildAt(i);
+
+            if (child != null && child.getVisibility() != GONE) {
+                if (hasDividerBeforeChildAt(i)) {
+                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                    final int top = child.getTop() - lp.topMargin - mDividerHeight;
+                    drawHorizontalDivider(canvas, top);
+                }
+            }
+        }
+
+        if (hasDividerBeforeChildAt(count)) {
+            final View child = getVirtualChildAt(count - 1);
+            int bottom = 0;
+            if (child == null) {
+                bottom = getHeight() - getPaddingBottom() - mDividerHeight;
+            } else {
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                bottom = child.getBottom() + lp.bottomMargin;
+            }
+            drawHorizontalDivider(canvas, bottom);
+        }
+    }
+
+    void drawDividersHorizontal(Canvas canvas) {
+        final int count = getVirtualChildCount();
+        final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this);
+        for (int i = 0; i < count; i++) {
+            final View child = getVirtualChildAt(i);
+
+            if (child != null && child.getVisibility() != GONE) {
+                if (hasDividerBeforeChildAt(i)) {
+                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                    final int position;
+                    if (isLayoutRtl) {
+                        position = child.getRight() + lp.rightMargin;
+                    } else {
+                        position = child.getLeft() - lp.leftMargin - mDividerWidth;
+                    }
+                    drawVerticalDivider(canvas, position);
+                }
+            }
+        }
+
+        if (hasDividerBeforeChildAt(count)) {
+            final View child = getVirtualChildAt(count - 1);
+            int position;
+            if (child == null) {
+                if (isLayoutRtl) {
+                    position = getPaddingLeft();
+                } else {
+                    position = getWidth() - getPaddingRight() - mDividerWidth;
+                }
+            } else {
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if (isLayoutRtl) {
+                    position = child.getLeft() - lp.leftMargin - mDividerWidth;
+                } else {
+                    position = child.getRight() + lp.rightMargin;
+                }
+            }
+            drawVerticalDivider(canvas, position);
+        }
+    }
+
+    void drawHorizontalDivider(Canvas canvas, int top) {
+        mDivider.setBounds(getPaddingLeft() + mDividerPadding, top,
+                getWidth() - getPaddingRight() - mDividerPadding, top + mDividerHeight);
+        mDivider.draw(canvas);
+    }
+
+    void drawVerticalDivider(Canvas canvas, int left) {
+        mDivider.setBounds(left, getPaddingTop() + mDividerPadding,
+                left + mDividerWidth, getHeight() - getPaddingBottom() - mDividerPadding);
+        mDivider.draw(canvas);
+    }
+
+    /**
+     * <p>Indicates whether widgets contained within this layout are aligned
+     * on their baseline or not.</p>
+     *
+     * @return true when widgets are baseline-aligned, false otherwise
+     */
+    public boolean isBaselineAligned() {
+        return mBaselineAligned;
+    }
+
+    /**
+     * <p>Defines whether widgets contained in this layout are
+     * baseline-aligned or not.</p>
+     *
+     * @param baselineAligned true to align widgets on their baseline,
+     *         false otherwise
+     */
+    public void setBaselineAligned(boolean baselineAligned) {
+        mBaselineAligned = baselineAligned;
+    }
+
+    /**
+     * When true, all children with a weight will be considered having
+     * the minimum size of the largest child. If false, all children are
+     * measured normally.
+     *
+     * @return True to measure children with a weight using the minimum
+     *         size of the largest child, false otherwise.
+     */
+    public boolean isMeasureWithLargestChildEnabled() {
+        return mUseLargestChild;
+    }
+
+    /**
+     * When set to true, all children with a weight will be considered having
+     * the minimum size of the largest child. If false, all children are
+     * measured normally.
+     *
+     * Disabled by default.
+     *
+     * @param enabled True to measure children with a weight using the
+     *        minimum size of the largest child, false otherwise.
+     */
+    public void setMeasureWithLargestChildEnabled(boolean enabled) {
+        mUseLargestChild = enabled;
+    }
+
+    @Override
+    public int getBaseline() {
+        if (mBaselineAlignedChildIndex < 0) {
+            return super.getBaseline();
+        }
+
+        if (getChildCount() <= mBaselineAlignedChildIndex) {
+            throw new RuntimeException("mBaselineAlignedChildIndex of LinearLayout "
+                    + "set to an index that is out of bounds.");
+        }
+
+        final View child = getChildAt(mBaselineAlignedChildIndex);
+        final int childBaseline = child.getBaseline();
+
+        if (childBaseline == -1) {
+            if (mBaselineAlignedChildIndex == 0) {
+                // this is just the default case, safe to return -1
+                return -1;
+            }
+            // the user picked an index that points to something that doesn't
+            // know how to calculate its baseline.
+            throw new RuntimeException("mBaselineAlignedChildIndex of LinearLayout "
+                    + "points to a View that doesn't know how to get its baseline.");
+        }
+
+        // TODO: This should try to take into account the virtual offsets
+        // (See getNextLocationOffset and getLocationOffset)
+        // We should add to childTop:
+        // sum([getNextLocationOffset(getChildAt(i)) / i < mBaselineAlignedChildIndex])
+        // and also add:
+        // getLocationOffset(child)
+        int childTop = mBaselineChildTop;
+
+        if (mOrientation == VERTICAL) {
+            final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+            if (majorGravity != Gravity.TOP) {
+                switch (majorGravity) {
+                    case Gravity.BOTTOM:
+                        childTop = getBottom() - getTop() - getPaddingBottom() - mTotalLength;
+                        break;
+
+                    case Gravity.CENTER_VERTICAL:
+                        childTop += ((getBottom() - getTop() - getPaddingTop() - getPaddingBottom()) -
+                                mTotalLength) / 2;
+                        break;
+                }
+            }
+        }
+
+        LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams) child.getLayoutParams();
+        return childTop + lp.topMargin + childBaseline;
+    }
+
+    /**
+     * @return The index of the child that will be used if this layout is
+     *   part of a larger layout that is baseline aligned, or -1 if none has
+     *   been set.
+     */
+    public int getBaselineAlignedChildIndex() {
+        return mBaselineAlignedChildIndex;
+    }
+
+    /**
+     * @param i The index of the child that will be used if this layout is
+     *          part of a larger layout that is baseline aligned.
+     */
+    public void setBaselineAlignedChildIndex(int i) {
+        if ((i < 0) || (i >= getChildCount())) {
+            throw new IllegalArgumentException("base aligned child index out "
+                    + "of range (0, " + getChildCount() + ")");
+        }
+        mBaselineAlignedChildIndex = i;
+    }
+
+    /**
+     * <p>Returns the view at the specified index. This method can be overriden
+     * to take into account virtual children. Refer to
+     * {@link android.widget.TableLayout} and {@link android.widget.TableRow}
+     * for an example.</p>
+     *
+     * @param index the child's index
+     * @return the child at the specified index
+     */
+    View getVirtualChildAt(int index) {
+        return getChildAt(index);
+    }
+
+    /**
+     * <p>Returns the virtual number of children. This number might be different
+     * than the actual number of children if the layout can hold virtual
+     * children. Refer to
+     * {@link android.widget.TableLayout} and {@link android.widget.TableRow}
+     * for an example.</p>
+     *
+     * @return the virtual number of children
+     */
+    int getVirtualChildCount() {
+        return getChildCount();
+    }
+
+    /**
+     * Returns the desired weights sum.
+     *
+     * @return A number greater than 0.0f if the weight sum is defined, or
+     *         a number lower than or equals to 0.0f if not weight sum is
+     *         to be used.
+     */
+    public float getWeightSum() {
+        return mWeightSum;
+    }
+
+    /**
+     * Defines the desired weights sum. If unspecified the weights sum is computed
+     * at layout time by adding the layout_weight of each child.
+     *
+     * This can be used for instance to give a single child 50% of the total
+     * available space by giving it a layout_weight of 0.5 and setting the
+     * weightSum to 1.0.
+     *
+     * @param weightSum a number greater than 0.0f, or a number lower than or equals
+     *        to 0.0f if the weight sum should be computed from the children's
+     *        layout_weight
+     */
+    public void setWeightSum(float weightSum) {
+        mWeightSum = Math.max(0.0f, weightSum);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (mOrientation == VERTICAL) {
+            measureVertical(widthMeasureSpec, heightMeasureSpec);
+        } else {
+            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+    /**
+     * Determines where to position dividers between children.
+     *
+     * @param childIndex Index of child to check for preceding divider
+     * @return true if there should be a divider before the child at childIndex
+     * @hide Pending API consideration. Currently only used internally by the system.
+     */
+    protected boolean hasDividerBeforeChildAt(int childIndex) {
+        if (childIndex == 0) {
+            return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0;
+        } else if (childIndex == getChildCount()) {
+            return (mShowDividers & SHOW_DIVIDER_END) != 0;
+        } else if ((mShowDividers & SHOW_DIVIDER_MIDDLE) != 0) {
+            boolean hasVisibleViewBefore = false;
+            for (int i = childIndex - 1; i >= 0; i--) {
+                if (getChildAt(i).getVisibility() != GONE) {
+                    hasVisibleViewBefore = true;
+                    break;
+                }
+            }
+            return hasVisibleViewBefore;
+        }
+        return false;
+    }
+
+    /**
+     * Measures the children when the orientation of this LinearLayout is set
+     * to {@link #VERTICAL}.
+     *
+     * @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
+     * @param heightMeasureSpec Vertical space requirements as imposed by the parent.
+     *
+     * @see #getOrientation()
+     * @see #setOrientation(int)
+     * @see #onMeasure(int, int)
+     */
+    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
+        mTotalLength = 0;
+        int maxWidth = 0;
+        int childState = 0;
+        int alternativeMaxWidth = 0;
+        int weightedMaxWidth = 0;
+        boolean allFillParent = true;
+        float totalWeight = 0;
+
+        final int count = getVirtualChildCount();
+
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+        boolean matchWidth = false;
+        boolean skippedMeasure = false;
+
+        final int baselineChildIndex = mBaselineAlignedChildIndex;
+        final boolean useLargestChild = mUseLargestChild;
+
+        int largestChildHeight = Integer.MIN_VALUE;
+
+        // See how tall everyone is. Also remember max width.
+        for (int i = 0; i < count; ++i) {
+            final View child = getVirtualChildAt(i);
+
+            if (child == null) {
+                mTotalLength += measureNullChild(i);
+                continue;
+            }
+
+            if (child.getVisibility() == View.GONE) {
+                i += getChildrenSkipCount(child, i);
+                continue;
+            }
+
+            if (hasDividerBeforeChildAt(i)) {
+                mTotalLength += mDividerHeight;
+            }
+
+            LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams) child.getLayoutParams();
+
+            totalWeight += lp.weight;
+
+            if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
+                // Optimization: don't bother measuring children who are going to use
+                // leftover space. These views will get measured again down below if
+                // there is any leftover space.
+                final int totalLength = mTotalLength;
+                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
+                skippedMeasure = true;
+            } else {
+                int oldHeight = Integer.MIN_VALUE;
+
+                if (lp.height == 0 && lp.weight > 0) {
+                    // heightMode is either UNSPECIFIED or AT_MOST, and this
+                    // child wanted to stretch to fill available space.
+                    // Translate that to WRAP_CONTENT so that it does not end up
+                    // with a height of 0
+                    oldHeight = 0;
+                    lp.height = LayoutParams.WRAP_CONTENT;
+                }
+
+                // Determine how big this child would like to be. If this or
+                // previous children have given a weight, then we allow it to
+                // use all available space (and we will shrink things later
+                // if needed).
+                measureChildBeforeLayout(
+                        child, i, widthMeasureSpec, 0, heightMeasureSpec,
+                        totalWeight == 0 ? mTotalLength : 0);
+
+                if (oldHeight != Integer.MIN_VALUE) {
+                    lp.height = oldHeight;
+                }
+
+                final int childHeight = child.getMeasuredHeight();
+                final int totalLength = mTotalLength;
+                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
+                        lp.bottomMargin + getNextLocationOffset(child));
+
+                if (useLargestChild) {
+                    largestChildHeight = Math.max(childHeight, largestChildHeight);
+                }
+            }
+
+            /**
+             * If applicable, compute the additional offset to the child's baseline
+             * we'll need later when asked {@link #getBaseline}.
+             */
+            if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
+                mBaselineChildTop = mTotalLength;
+            }
+
+            // if we are trying to use a child index for our baseline, the above
+            // book keeping only works if there are no children above it with
+            // weight.  fail fast to aid the developer.
+            if (i < baselineChildIndex && lp.weight > 0) {
+                throw new RuntimeException("A child of LinearLayout with index "
+                        + "less than mBaselineAlignedChildIndex has weight > 0, which "
+                        + "won't work.  Either remove the weight, or don't set "
+                        + "mBaselineAlignedChildIndex.");
+            }
+
+            boolean matchWidthLocally = false;
+            if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
+                // The width of the linear layout will scale, and at least one
+                // child said it wanted to match our width. Set a flag
+                // indicating that we need to remeasure at least that view when
+                // we know our width.
+                matchWidth = true;
+                matchWidthLocally = true;
+            }
+
+            final int margin = lp.leftMargin + lp.rightMargin;
+            final int measuredWidth = child.getMeasuredWidth() + margin;
+            maxWidth = Math.max(maxWidth, measuredWidth);
+            childState = ViewUtils.combineMeasuredStates(childState,
+                    ViewCompat.getMeasuredState(child));
+
+            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
+            if (lp.weight > 0) {
+                /*
+                 * Widths of weighted Views are bogus if we end up
+                 * remeasuring, so keep them separate.
+                 */
+                weightedMaxWidth = Math.max(weightedMaxWidth,
+                        matchWidthLocally ? margin : measuredWidth);
+            } else {
+                alternativeMaxWidth = Math.max(alternativeMaxWidth,
+                        matchWidthLocally ? margin : measuredWidth);
+            }
+
+            i += getChildrenSkipCount(child, i);
+        }
+
+        if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
+            mTotalLength += mDividerHeight;
+        }
+
+        if (useLargestChild &&
+                (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
+            mTotalLength = 0;
+
+            for (int i = 0; i < count; ++i) {
+                final View child = getVirtualChildAt(i);
+
+                if (child == null) {
+                    mTotalLength += measureNullChild(i);
+                    continue;
+                }
+
+                if (child.getVisibility() == GONE) {
+                    i += getChildrenSkipCount(child, i);
+                    continue;
+                }
+
+                final LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams)
+                        child.getLayoutParams();
+                // Account for negative margins
+                final int totalLength = mTotalLength;
+                mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
+                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
+            }
+        }
+
+        // Add in our padding
+        mTotalLength += getPaddingTop() + getPaddingBottom();
+
+        int heightSize = mTotalLength;
+
+        // Check against our minimum height
+        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
+
+        // Reconcile our calculated size with the heightMeasureSpec
+        int heightSizeAndState = ViewCompat.resolveSizeAndState(heightSize, heightMeasureSpec, 0);
+        heightSize = heightSizeAndState & ViewCompat.MEASURED_SIZE_MASK;
+
+        // Either expand children with weight to take up available space or
+        // shrink them if they extend beyond our current bounds. If we skipped
+        // measurement on any children, we need to measure them now.
+        int delta = heightSize - mTotalLength;
+        if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
+            float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
+
+            mTotalLength = 0;
+
+            for (int i = 0; i < count; ++i) {
+                final View child = getVirtualChildAt(i);
+
+                if (child.getVisibility() == View.GONE) {
+                    continue;
+                }
+
+                LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams) child.getLayoutParams();
+
+                float childExtra = lp.weight;
+                if (childExtra > 0) {
+                    // Child said it could absorb extra space -- give him his share
+                    int share = (int) (childExtra * delta / weightSum);
+                    weightSum -= childExtra;
+                    delta -= share;
+
+                    final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
+                            getPaddingLeft() + getPaddingRight() +
+                                    lp.leftMargin + lp.rightMargin, lp.width);
+
+                    // TODO: Use a field like lp.isMeasured to figure out if this
+                    // child has been previously measured
+                    if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
+                        // child was measured once already above...
+                        // base new measurement on stored values
+                        int childHeight = child.getMeasuredHeight() + share;
+                        if (childHeight < 0) {
+                            childHeight = 0;
+                        }
+
+                        child.measure(childWidthMeasureSpec,
+                                MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
+                    } else {
+                        // child was skipped in the loop above.
+                        // Measure for this first time here
+                        child.measure(childWidthMeasureSpec,
+                                MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
+                                        MeasureSpec.EXACTLY));
+                    }
+
+                    // Child may now not fit in vertical dimension.
+                    childState = ViewUtils.combineMeasuredStates(childState,
+                            ViewCompat.getMeasuredState(child) & (ViewCompat.MEASURED_STATE_MASK
+                                    >> ViewCompat.MEASURED_HEIGHT_STATE_SHIFT));
+                }
+
+                final int margin =  lp.leftMargin + lp.rightMargin;
+                final int measuredWidth = child.getMeasuredWidth() + margin;
+                maxWidth = Math.max(maxWidth, measuredWidth);
+
+                boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
+                        lp.width == LayoutParams.MATCH_PARENT;
+
+                alternativeMaxWidth = Math.max(alternativeMaxWidth,
+                        matchWidthLocally ? margin : measuredWidth);
+
+                allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
+
+                final int totalLength = mTotalLength;
+                mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
+                        lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
+            }
+
+            // Add in our padding
+            mTotalLength += getPaddingTop() + getPaddingBottom();
+            // TODO: Should we recompute the heightSpec based on the new total length?
+        } else {
+            alternativeMaxWidth = Math.max(alternativeMaxWidth,
+                    weightedMaxWidth);
+
+
+            // We have no limit, so make all weighted views as tall as the largest child.
+            // Children will have already been measured once.
+            if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
+                for (int i = 0; i < count; i++) {
+                    final View child = getVirtualChildAt(i);
+
+                    if (child == null || child.getVisibility() == View.GONE) {
+                        continue;
+                    }
+
+                    final LinearLayoutCompat.LayoutParams lp =
+                            (LinearLayoutCompat.LayoutParams) child.getLayoutParams();
+
+                    float childExtra = lp.weight;
+                    if (childExtra > 0) {
+                        child.measure(
+                                MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
+                                        MeasureSpec.EXACTLY),
+                                MeasureSpec.makeMeasureSpec(largestChildHeight,
+                                        MeasureSpec.EXACTLY));
+                    }
+                }
+            }
+        }
+
+        if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
+            maxWidth = alternativeMaxWidth;
+        }
+
+        maxWidth += getPaddingLeft() + getPaddingRight();
+
+        // Check against our minimum width
+        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
+
+        setMeasuredDimension(ViewCompat.resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
+                heightSizeAndState);
+
+        if (matchWidth) {
+            forceUniformWidth(count, heightMeasureSpec);
+        }
+    }
+
+    private void forceUniformWidth(int count, int heightMeasureSpec) {
+        // Pretend that the linear layout has an exact size.
+        int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(),
+                MeasureSpec.EXACTLY);
+        for (int i = 0; i< count; ++i) {
+            final View child = getVirtualChildAt(i);
+            if (child.getVisibility() != GONE) {
+                LinearLayoutCompat.LayoutParams lp = ((LinearLayoutCompat.LayoutParams)child.getLayoutParams());
+
+                if (lp.width == LayoutParams.MATCH_PARENT) {
+                    // Temporarily force children to reuse their old measured height
+                    // FIXME: this may not be right for something like wrapping text?
+                    int oldHeight = lp.height;
+                    lp.height = child.getMeasuredHeight();
+
+                    // Remeasue with new dimensions
+                    measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0);
+                    lp.height = oldHeight;
+                }
+            }
+        }
+    }
+
+    /**
+     * Measures the children when the orientation of this LinearLayout is set
+     * to {@link #HORIZONTAL}.
+     *
+     * @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
+     * @param heightMeasureSpec Vertical space requirements as imposed by the parent.
+     *
+     * @see #getOrientation()
+     * @see #setOrientation(int)
+     * @see #onMeasure(int, int)
+     */
+    void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
+        mTotalLength = 0;
+        int maxHeight = 0;
+        int childState = 0;
+        int alternativeMaxHeight = 0;
+        int weightedMaxHeight = 0;
+        boolean allFillParent = true;
+        float totalWeight = 0;
+
+        final int count = getVirtualChildCount();
+
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+
+        boolean matchHeight = false;
+        boolean skippedMeasure = false;
+
+        if (mMaxAscent == null || mMaxDescent == null) {
+            mMaxAscent = new int[VERTICAL_GRAVITY_COUNT];
+            mMaxDescent = new int[VERTICAL_GRAVITY_COUNT];
+        }
+
+        final int[] maxAscent = mMaxAscent;
+        final int[] maxDescent = mMaxDescent;
+
+        maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1;
+        maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1;
+
+        final boolean baselineAligned = mBaselineAligned;
+        final boolean useLargestChild = mUseLargestChild;
+
+        final boolean isExactly = widthMode == MeasureSpec.EXACTLY;
+
+        int largestChildWidth = Integer.MIN_VALUE;
+
+        // See how wide everyone is. Also remember max height.
+        for (int i = 0; i < count; ++i) {
+            final View child = getVirtualChildAt(i);
+
+            if (child == null) {
+                mTotalLength += measureNullChild(i);
+                continue;
+            }
+
+            if (child.getVisibility() == GONE) {
+                i += getChildrenSkipCount(child, i);
+                continue;
+            }
+
+            if (hasDividerBeforeChildAt(i)) {
+                mTotalLength += mDividerWidth;
+            }
+
+            final LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams)
+                    child.getLayoutParams();
+
+            totalWeight += lp.weight;
+
+            if (widthMode == MeasureSpec.EXACTLY && lp.width == 0 && lp.weight > 0) {
+                // Optimization: don't bother measuring children who are going to use
+                // leftover space. These views will get measured again down below if
+                // there is any leftover space.
+                if (isExactly) {
+                    mTotalLength += lp.leftMargin + lp.rightMargin;
+                } else {
+                    final int totalLength = mTotalLength;
+                    mTotalLength = Math.max(totalLength, totalLength +
+                            lp.leftMargin + lp.rightMargin);
+                }
+
+                // Baseline alignment requires to measure widgets to obtain the
+                // baseline offset (in particular for TextViews). The following
+                // defeats the optimization mentioned above. Allow the child to
+                // use as much space as it wants because we can shrink things
+                // later (and re-measure).
+                if (baselineAligned) {
+                    final int freeSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+                    child.measure(freeSpec, freeSpec);
+                } else {
+                    skippedMeasure = true;
+                }
+            } else {
+                int oldWidth = Integer.MIN_VALUE;
+
+                if (lp.width == 0 && lp.weight > 0) {
+                    // widthMode is either UNSPECIFIED or AT_MOST, and this
+                    // child
+                    // wanted to stretch to fill available space. Translate that to
+                    // WRAP_CONTENT so that it does not end up with a width of 0
+                    oldWidth = 0;
+                    lp.width = LayoutParams.WRAP_CONTENT;
+                }
+
+                // Determine how big this child would like to be. If this or
+                // previous children have given a weight, then we allow it to
+                // use all available space (and we will shrink things later
+                // if needed).
+                measureChildBeforeLayout(child, i, widthMeasureSpec,
+                        totalWeight == 0 ? mTotalLength : 0,
+                        heightMeasureSpec, 0);
+
+                if (oldWidth != Integer.MIN_VALUE) {
+                    lp.width = oldWidth;
+                }
+
+                final int childWidth = child.getMeasuredWidth();
+                if (isExactly) {
+                    mTotalLength += childWidth + lp.leftMargin + lp.rightMargin +
+                            getNextLocationOffset(child);
+                } else {
+                    final int totalLength = mTotalLength;
+                    mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin +
+                            lp.rightMargin + getNextLocationOffset(child));
+                }
+
+                if (useLargestChild) {
+                    largestChildWidth = Math.max(childWidth, largestChildWidth);
+                }
+            }
+
+            boolean matchHeightLocally = false;
+            if (heightMode != MeasureSpec.EXACTLY && lp.height == LayoutParams.MATCH_PARENT) {
+                // The height of the linear layout will scale, and at least one
+                // child said it wanted to match our height. Set a flag indicating that
+                // we need to remeasure at least that view when we know our height.
+                matchHeight = true;
+                matchHeightLocally = true;
+            }
+
+            final int margin = lp.topMargin + lp.bottomMargin;
+            final int childHeight = child.getMeasuredHeight() + margin;
+            childState = ViewUtils.combineMeasuredStates(childState,
+                    ViewCompat.getMeasuredState(child));
+
+            if (baselineAligned) {
+                final int childBaseline = child.getBaseline();
+                if (childBaseline != -1) {
+                    // Translates the child's vertical gravity into an index
+                    // in the range 0..VERTICAL_GRAVITY_COUNT
+                    final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity)
+                            & Gravity.VERTICAL_GRAVITY_MASK;
+                    final int index = ((gravity >> Gravity.AXIS_Y_SHIFT)
+                            & ~Gravity.AXIS_SPECIFIED) >> 1;
+
+                    maxAscent[index] = Math.max(maxAscent[index], childBaseline);
+                    maxDescent[index] = Math.max(maxDescent[index], childHeight - childBaseline);
+                }
+            }
+
+            maxHeight = Math.max(maxHeight, childHeight);
+
+            allFillParent = allFillParent && lp.height == LayoutParams.MATCH_PARENT;
+            if (lp.weight > 0) {
+                /*
+                 * Heights of weighted Views are bogus if we end up
+                 * remeasuring, so keep them separate.
+                 */
+                weightedMaxHeight = Math.max(weightedMaxHeight,
+                        matchHeightLocally ? margin : childHeight);
+            } else {
+                alternativeMaxHeight = Math.max(alternativeMaxHeight,
+                        matchHeightLocally ? margin : childHeight);
+            }
+
+            i += getChildrenSkipCount(child, i);
+        }
+
+        if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
+            mTotalLength += mDividerWidth;
+        }
+
+        // Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP,
+        // the most common case
+        if (maxAscent[INDEX_TOP] != -1 ||
+                maxAscent[INDEX_CENTER_VERTICAL] != -1 ||
+                maxAscent[INDEX_BOTTOM] != -1 ||
+                maxAscent[INDEX_FILL] != -1) {
+            final int ascent = Math.max(maxAscent[INDEX_FILL],
+                    Math.max(maxAscent[INDEX_CENTER_VERTICAL],
+                            Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM])));
+            final int descent = Math.max(maxDescent[INDEX_FILL],
+                    Math.max(maxDescent[INDEX_CENTER_VERTICAL],
+                            Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM])));
+            maxHeight = Math.max(maxHeight, ascent + descent);
+        }
+
+        if (useLargestChild &&
+                (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED)) {
+            mTotalLength = 0;
+
+            for (int i = 0; i < count; ++i) {
+                final View child = getVirtualChildAt(i);
+
+                if (child == null) {
+                    mTotalLength += measureNullChild(i);
+                    continue;
+                }
+
+                if (child.getVisibility() == GONE) {
+                    i += getChildrenSkipCount(child, i);
+                    continue;
+                }
+
+                final LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams)
+                        child.getLayoutParams();
+                if (isExactly) {
+                    mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin +
+                            getNextLocationOffset(child);
+                } else {
+                    final int totalLength = mTotalLength;
+                    mTotalLength = Math.max(totalLength, totalLength + largestChildWidth +
+                            lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
+                }
+            }
+        }
+
+        // Add in our padding
+        mTotalLength += getPaddingLeft() + getPaddingRight();
+
+        int widthSize = mTotalLength;
+
+        // Check against our minimum width
+        widthSize = Math.max(widthSize, getSuggestedMinimumWidth());
+
+        // Reconcile our calculated size with the widthMeasureSpec
+        int widthSizeAndState = ViewCompat.resolveSizeAndState(widthSize, widthMeasureSpec, 0);
+        widthSize = widthSizeAndState & ViewCompat.MEASURED_SIZE_MASK;
+
+        // Either expand children with weight to take up available space or
+        // shrink them if they extend beyond our current bounds. If we skipped
+        // measurement on any children, we need to measure them now.
+        int delta = widthSize - mTotalLength;
+        if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
+            float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
+
+            maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1;
+            maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1;
+            maxHeight = -1;
+
+            mTotalLength = 0;
+
+            for (int i = 0; i < count; ++i) {
+                final View child = getVirtualChildAt(i);
+
+                if (child == null || child.getVisibility() == View.GONE) {
+                    continue;
+                }
+
+                final LinearLayoutCompat.LayoutParams lp =
+                        (LinearLayoutCompat.LayoutParams) child.getLayoutParams();
+
+                float childExtra = lp.weight;
+                if (childExtra > 0) {
+                    // Child said it could absorb extra space -- give him his share
+                    int share = (int) (childExtra * delta / weightSum);
+                    weightSum -= childExtra;
+                    delta -= share;
+
+                    final int childHeightMeasureSpec = getChildMeasureSpec(
+                            heightMeasureSpec,
+                            getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin,
+                            lp.height);
+
+                    // TODO: Use a field like lp.isMeasured to figure out if this
+                    // child has been previously measured
+                    if ((lp.width != 0) || (widthMode != MeasureSpec.EXACTLY)) {
+                        // child was measured once already above ... base new measurement
+                        // on stored values
+                        int childWidth = child.getMeasuredWidth() + share;
+                        if (childWidth < 0) {
+                            childWidth = 0;
+                        }
+
+                        child.measure(
+                                MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
+                                childHeightMeasureSpec);
+                    } else {
+                        // child was skipped in the loop above. Measure for this first time here
+                        child.measure(MeasureSpec.makeMeasureSpec(
+                                        share > 0 ? share : 0, MeasureSpec.EXACTLY),
+                                childHeightMeasureSpec);
+                    }
+
+                    // Child may now not fit in horizontal dimension.
+                    childState = ViewUtils.combineMeasuredStates(childState,
+                            ViewCompat.getMeasuredState(child) & ViewCompat.MEASURED_STATE_MASK);
+                }
+
+                if (isExactly) {
+                    mTotalLength += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin +
+                            getNextLocationOffset(child);
+                } else {
+                    final int totalLength = mTotalLength;
+                    mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredWidth() +
+                            lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
+                }
+
+                boolean matchHeightLocally = heightMode != MeasureSpec.EXACTLY &&
+                        lp.height == LayoutParams.MATCH_PARENT;
+
+                final int margin = lp.topMargin + lp .bottomMargin;
+                int childHeight = child.getMeasuredHeight() + margin;
+                maxHeight = Math.max(maxHeight, childHeight);
+                alternativeMaxHeight = Math.max(alternativeMaxHeight,
+                        matchHeightLocally ? margin : childHeight);
+
+                allFillParent = allFillParent && lp.height == LayoutParams.MATCH_PARENT;
+
+                if (baselineAligned) {
+                    final int childBaseline = child.getBaseline();
+                    if (childBaseline != -1) {
+                        // Translates the child's vertical gravity into an index in the range 0..2
+                        final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity)
+                                & Gravity.VERTICAL_GRAVITY_MASK;
+                        final int index = ((gravity >> Gravity.AXIS_Y_SHIFT)
+                                & ~Gravity.AXIS_SPECIFIED) >> 1;
+
+                        maxAscent[index] = Math.max(maxAscent[index], childBaseline);
+                        maxDescent[index] = Math.max(maxDescent[index],
+                                childHeight - childBaseline);
+                    }
+                }
+            }
+
+            // Add in our padding
+            mTotalLength += getPaddingLeft() + getPaddingRight();
+            // TODO: Should we update widthSize with the new total length?
+
+            // Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP,
+            // the most common case
+            if (maxAscent[INDEX_TOP] != -1 ||
+                    maxAscent[INDEX_CENTER_VERTICAL] != -1 ||
+                    maxAscent[INDEX_BOTTOM] != -1 ||
+                    maxAscent[INDEX_FILL] != -1) {
+                final int ascent = Math.max(maxAscent[INDEX_FILL],
+                        Math.max(maxAscent[INDEX_CENTER_VERTICAL],
+                                Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM])));
+                final int descent = Math.max(maxDescent[INDEX_FILL],
+                        Math.max(maxDescent[INDEX_CENTER_VERTICAL],
+                                Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM])));
+                maxHeight = Math.max(maxHeight, ascent + descent);
+            }
+        } else {
+            alternativeMaxHeight = Math.max(alternativeMaxHeight, weightedMaxHeight);
+
+            // We have no limit, so make all weighted views as wide as the largest child.
+            // Children will have already been measured once.
+            if (useLargestChild && widthMode != MeasureSpec.EXACTLY) {
+                for (int i = 0; i < count; i++) {
+                    final View child = getVirtualChildAt(i);
+
+                    if (child == null || child.getVisibility() == View.GONE) {
+                        continue;
+                    }
+
+                    final LinearLayoutCompat.LayoutParams lp =
+                            (LinearLayoutCompat.LayoutParams) child.getLayoutParams();
+
+                    float childExtra = lp.weight;
+                    if (childExtra > 0) {
+                        child.measure(
+                                MeasureSpec.makeMeasureSpec(largestChildWidth, MeasureSpec.EXACTLY),
+                                MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(),
+                                        MeasureSpec.EXACTLY));
+                    }
+                }
+            }
+        }
+
+        if (!allFillParent && heightMode != MeasureSpec.EXACTLY) {
+            maxHeight = alternativeMaxHeight;
+        }
+
+        maxHeight += getPaddingTop() + getPaddingBottom();
+
+        // Check against our minimum height
+        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
+
+        setMeasuredDimension(widthSizeAndState | (childState&ViewCompat.MEASURED_STATE_MASK),
+                ViewCompat.resolveSizeAndState(maxHeight, heightMeasureSpec,
+                        (childState<<ViewCompat.MEASURED_HEIGHT_STATE_SHIFT)));
+
+        if (matchHeight) {
+            forceUniformHeight(count, widthMeasureSpec);
+        }
+    }
+
+    private void forceUniformHeight(int count, int widthMeasureSpec) {
+        // Pretend that the linear layout has an exact size. This is the measured height of
+        // ourselves. The measured height should be the max height of the children, changed
+        // to accommodate the heightMeasureSpec from the parent
+        int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(),
+                MeasureSpec.EXACTLY);
+        for (int i = 0; i < count; ++i) {
+            final View child = getVirtualChildAt(i);
+            if (child.getVisibility() != GONE) {
+                LinearLayoutCompat.LayoutParams lp = (LinearLayoutCompat.LayoutParams) child.getLayoutParams();
+
+                if (lp.height == LayoutParams.MATCH_PARENT) {
+                    // Temporarily force children to reuse their old measured width
+                    // FIXME: this may not be right for something like wrapping text?
+                    int oldWidth = lp.width;
+                    lp.width = child.getMeasuredWidth();
+
+                    // Remeasure with new dimensions
+                    measureChildWithMargins(child, widthMeasureSpec, 0, uniformMeasureSpec, 0);
+                    lp.width = oldWidth;
+                }
+            }
+        }
+    }
+
+    /**
+     * <p>Returns the number of children to skip after measuring/laying out
+     * the specified child.</p>
+     *
+     * @param child the child after which we want to skip children
+     * @param index the index of the child after which we want to skip children
+     * @return the number of children to skip, 0 by default
+     */
+    int getChildrenSkipCount(View child, int index) {
+        return 0;
+    }
+
+    /**
+     * <p>Returns the size (width or height) that should be occupied by a null
+     * child.</p>
+     *
+     * @param childIndex the index of the null child
+     * @return the width or height of the child depending on the orientation
+     */
+    int measureNullChild(int childIndex) {
+        return 0;
+    }
+
+    /**
+     * <p>Measure the child according to the parent's measure specs. This
+     * method should be overriden by subclasses to force the sizing of
+     * children. This method is called by {@link #measureVertical(int, int)} and
+     * {@link #measureHorizontal(int, int)}.</p>
+     *
+     * @param child the child to measure
+     * @param childIndex the index of the child in this view
+     * @param widthMeasureSpec horizontal space requirements as imposed by the parent
+     * @param totalWidth extra space that has been used up by the parent horizontally
+     * @param heightMeasureSpec vertical space requirements as imposed by the parent
+     * @param totalHeight extra space that has been used up by the parent vertically
+     */
+    void measureChildBeforeLayout(View child, int childIndex,
+            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
+            int totalHeight) {
+        measureChildWithMargins(child, widthMeasureSpec, totalWidth,
+                heightMeasureSpec, totalHeight);
+    }
+
+    /**
+     * <p>Return the location offset of the specified child. This can be used
+     * by subclasses to change the location of a given widget.</p>
+     *
+     * @param child the child for which to obtain the location offset
+     * @return the location offset in pixels
+     */
+    int getLocationOffset(View child) {
+        return 0;
+    }
+
+    /**
+     * <p>Return the size offset of the next sibling of the specified child.
+     * This can be used by subclasses to change the location of the widget
+     * following <code>child</code>.</p>
+     *
+     * @param child the child whose next sibling will be moved
+     * @return the location offset of the next child in pixels
+     */
+    int getNextLocationOffset(View child) {
+        return 0;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        if (mOrientation == VERTICAL) {
+            layoutVertical(l, t, r, b);
+        } else {
+            layoutHorizontal(l, t, r, b);
+        }
+    }
+
+    /**
+     * Position the children during a layout pass if the orientation of this
+     * LinearLayout is set to {@link #VERTICAL}.
+     *
+     * @see #getOrientation()
+     * @see #setOrientation(int)
+     * @see #onLayout(boolean, int, int, int, int)
+     * @param left
+     * @param top
+     * @param right
+     * @param bottom
+     */
+    void layoutVertical(int left, int top, int right, int bottom) {
+        final int paddingLeft = getPaddingLeft();
+
+        int childTop;
+        int childLeft;
+
+        // Where right end of child should go
+        final int width = right - left;
+        int childRight = width - getPaddingRight();
+
+        // Space available for child
+        int childSpace = width - paddingLeft - getPaddingRight();
+
+        final int count = getVirtualChildCount();
+
+        final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+        final int minorGravity = mGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK;
+
+        switch (majorGravity) {
+            case Gravity.BOTTOM:
+                // mTotalLength contains the padding already
+                childTop = getPaddingTop() + bottom - top - mTotalLength;
+                break;
+
+            // mTotalLength contains the padding already
+            case Gravity.CENTER_VERTICAL:
+                childTop = getPaddingTop() + (bottom - top - mTotalLength) / 2;
+                break;
+
+            case Gravity.TOP:
+            default:
+                childTop = getPaddingTop();
+                break;
+        }
+
+        for (int i = 0; i < count; i++) {
+            final View child = getVirtualChildAt(i);
+            if (child == null) {
+                childTop += measureNullChild(i);
+            } else if (child.getVisibility() != GONE) {
+                final int childWidth = child.getMeasuredWidth();
+                final int childHeight = child.getMeasuredHeight();
+
+                final LinearLayoutCompat.LayoutParams lp =
+                        (LinearLayoutCompat.LayoutParams) child.getLayoutParams();
+
+                int gravity = lp.gravity;
+                if (gravity < 0) {
+                    gravity = minorGravity;
+                }
+                final int layoutDirection = ViewCompat.getLayoutDirection(this);
+                final int absoluteGravity = GravityCompat.getAbsoluteGravity(gravity,
+                        layoutDirection);
+                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+                    case Gravity.CENTER_HORIZONTAL:
+                        childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+                                + lp.leftMargin - lp.rightMargin;
+                        break;
+
+                    case Gravity.RIGHT:
+                        childLeft = childRight - childWidth - lp.rightMargin;
+                        break;
+
+                    case Gravity.LEFT:
+                    default:
+                        childLeft = paddingLeft + lp.leftMargin;
+                        break;
+                }
+
+                if (hasDividerBeforeChildAt(i)) {
+                    childTop += mDividerHeight;
+                }
+
+                childTop += lp.topMargin;
+                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
+                        childWidth, childHeight);
+                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
+
+                i += getChildrenSkipCount(child, i);
+            }
+        }
+    }
+
+    /**
+     * Position the children during a layout pass if the orientation of this
+     * LinearLayout is set to {@link #HORIZONTAL}.
+     *
+     * @see #getOrientation()
+     * @see #setOrientation(int)
+     * @see #onLayout(boolean, int, int, int, int)
+     * @param left
+     * @param top
+     * @param right
+     * @param bottom
+     */
+    void layoutHorizontal(int left, int top, int right, int bottom) {
+        final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this);
+        final int paddingTop = getPaddingTop();
+
+        int childTop;
+        int childLeft;
+
+        // Where bottom of child should go
+        final int height = bottom - top;
+        int childBottom = height - getPaddingBottom();
+
+        // Space available for child
+        int childSpace = height - paddingTop - getPaddingBottom();
+
+        final int count = getVirtualChildCount();
+
+        final int majorGravity = mGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK;
+        final int minorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+        final boolean baselineAligned = mBaselineAligned;
+
+        final int[] maxAscent = mMaxAscent;
+        final int[] maxDescent = mMaxDescent;
+
+        final int layoutDirection = ViewCompat.getLayoutDirection(this);
+        switch (GravityCompat.getAbsoluteGravity(majorGravity, layoutDirection)) {
+            case Gravity.RIGHT:
+                // mTotalLength contains the padding already
+                childLeft = getPaddingLeft() + right - left - mTotalLength;
+                break;
+
+            case Gravity.CENTER_HORIZONTAL:
+                // mTotalLength contains the padding already
+                childLeft = getPaddingLeft() + (right - left - mTotalLength) / 2;
+                break;
+
+            case Gravity.LEFT:
+            default:
+                childLeft = getPaddingLeft();
+                break;
+        }
+
+        int start = 0;
+        int dir = 1;
+        //In case of RTL, start drawing from the last child.
+        if (isLayoutRtl) {
+            start = count - 1;
+            dir = -1;
+        }
+
+        for (int i = 0; i < count; i++) {
+            int childIndex = start + dir * i;
+            final View child = getVirtualChildAt(childIndex);
+
+            if (child == null) {
+                childLeft += measureNullChild(childIndex);
+            } else if (child.getVisibility() != GONE) {
+                final int childWidth = child.getMeasuredWidth();
+                final int childHeight = child.getMeasuredHeight();
+                int childBaseline = -1;
+
+                final LinearLayoutCompat.LayoutParams lp =
+                        (LinearLayoutCompat.LayoutParams) child.getLayoutParams();
+
+                if (baselineAligned && lp.height != LayoutParams.MATCH_PARENT) {
+                    childBaseline = child.getBaseline();
+                }
+
+                int gravity = lp.gravity;
+                if (gravity < 0) {
+                    gravity = minorGravity;
+                }
+
+                switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
+                    case Gravity.TOP:
+                        childTop = paddingTop + lp.topMargin;
+                        if (childBaseline != -1) {
+                            childTop += maxAscent[INDEX_TOP] - childBaseline;
+                        }
+                        break;
+
+                    case Gravity.CENTER_VERTICAL:
+                        // Removed support for baseline alignment when layout_gravity or
+                        // gravity == center_vertical. See bug #1038483.
+                        // Keep the code around if we need to re-enable this feature
+                        // if (childBaseline != -1) {
+                        //     // Align baselines vertically only if the child is smaller than us
+                        //     if (childSpace - childHeight > 0) {
+                        //         childTop = paddingTop + (childSpace / 2) - childBaseline;
+                        //     } else {
+                        //         childTop = paddingTop + (childSpace - childHeight) / 2;
+                        //     }
+                        // } else {
+                        childTop = paddingTop + ((childSpace - childHeight) / 2)
+                                + lp.topMargin - lp.bottomMargin;
+                        break;
+
+                    case Gravity.BOTTOM:
+                        childTop = childBottom - childHeight - lp.bottomMargin;
+                        if (childBaseline != -1) {
+                            int descent = child.getMeasuredHeight() - childBaseline;
+                            childTop -= (maxDescent[INDEX_BOTTOM] - descent);
+                        }
+                        break;
+                    default:
+                        childTop = paddingTop;
+                        break;
+                }
+
+                if (hasDividerBeforeChildAt(childIndex)) {
+                    childLeft += mDividerWidth;
+                }
+
+                childLeft += lp.leftMargin;
+                setChildFrame(child, childLeft + getLocationOffset(child), childTop,
+                        childWidth, childHeight);
+                childLeft += childWidth + lp.rightMargin +
+                        getNextLocationOffset(child);
+
+                i += getChildrenSkipCount(child, childIndex);
+            }
+        }
+    }
+
+    private void setChildFrame(View child, int left, int top, int width, int height) {
+        child.layout(left, top, left + width, top + height);
+    }
+
+    /**
+     * Should the layout be a column or a row.
+     * @param orientation Pass {@link #HORIZONTAL} or {@link #VERTICAL}. Default
+     * value is {@link #HORIZONTAL}.
+     */
+    public void setOrientation(@OrientationMode int orientation) {
+        if (mOrientation != orientation) {
+            mOrientation = orientation;
+            requestLayout();
+        }
+    }
+
+    /**
+     * Returns the current orientation.
+     *
+     * @return either {@link #HORIZONTAL} or {@link #VERTICAL}
+     */
+    @OrientationMode
+    public int getOrientation() {
+        return mOrientation;
+    }
+
+    /**
+     * Describes how the child views are positioned. Defaults to GRAVITY_TOP. If
+     * this layout has a VERTICAL orientation, this controls where all the child
+     * views are placed if there is extra vertical space. If this layout has a
+     * HORIZONTAL orientation, this controls the alignment of the children.
+     *
+     * @param gravity See {@link android.view.Gravity}
+     */
+    public void setGravity(int gravity) {
+        if (mGravity != gravity) {
+            if ((gravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
+                gravity |= GravityCompat.START;
+            }
+
+            if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
+                gravity |= Gravity.TOP;
+            }
+
+            mGravity = gravity;
+            requestLayout();
+        }
+    }
+
+    public void setHorizontalGravity(int horizontalGravity) {
+        final int gravity = horizontalGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK;
+        if ((mGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) != gravity) {
+            mGravity = (mGravity & ~GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) | gravity;
+            requestLayout();
+        }
+    }
+
+    public void setVerticalGravity(int verticalGravity) {
+        final int gravity = verticalGravity & Gravity.VERTICAL_GRAVITY_MASK;
+        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != gravity) {
+            mGravity = (mGravity & ~Gravity.VERTICAL_GRAVITY_MASK) | gravity;
+            requestLayout();
+        }
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new LinearLayoutCompat.LayoutParams(getContext(), attrs);
+    }
+
+    /**
+     * Returns a set of layout parameters with a width of
+     * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
+     * and a height of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
+     * when the layout's orientation is {@link #VERTICAL}. When the orientation is
+     * {@link #HORIZONTAL}, the width is set to {@link LayoutParams#WRAP_CONTENT}
+     * and the height to {@link LayoutParams#WRAP_CONTENT}.
+     */
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        if (mOrientation == HORIZONTAL) {
+            return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+        } else if (mOrientation == VERTICAL) {
+            return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+        }
+        return null;
+    }
+
+    @Override
+    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        return new LayoutParams(p);
+    }
+
+
+    // Override to allow type-checking of LayoutParams.
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof LinearLayoutCompat.LayoutParams;
+    }
+
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        if (Build.VERSION.SDK_INT >= 14) {
+            super.onInitializeAccessibilityEvent(event);
+            event.setClassName(LinearLayoutCompat.class.getName());
+        }
+    }
+
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        if (Build.VERSION.SDK_INT >= 14) {
+            super.onInitializeAccessibilityNodeInfo(info);
+            info.setClassName(LinearLayoutCompat.class.getName());
+        }
+    }
+
+    /**
+     * Per-child layout information associated with ViewLinearLayout.
+     */
+    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
+        /**
+         * Indicates how much of the extra space in the LinearLayout will be
+         * allocated to the view associated with these LayoutParams. Specify
+         * 0 if the view should not be stretched. Otherwise the extra pixels
+         * will be pro-rated among all views whose weight is greater than 0.
+         */
+        public float weight;
+
+        /**
+         * Gravity for the view associated with these LayoutParams.
+         *
+         * @see android.view.Gravity
+         */
+        public int gravity = -1;
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+            TypedArray a =
+                    c.obtainStyledAttributes(attrs, R.styleable.LinearLayoutCompat_Layout);
+
+            weight = a.getFloat(R.styleable.LinearLayoutCompat_Layout_android_layout_weight, 0);
+            gravity = a.getInt(R.styleable.LinearLayoutCompat_Layout_android_layout_gravity, -1);
+
+            a.recycle();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(int width, int height) {
+            super(width, height);
+            weight = 0;
+        }
+
+        /**
+         * Creates a new set of layout parameters with the specified width, height
+         * and weight.
+         *
+         * @param width the width, either {@link #MATCH_PARENT},
+         *        {@link #WRAP_CONTENT} or a fixed size in pixels
+         * @param height the height, either {@link #MATCH_PARENT},
+         *        {@link #WRAP_CONTENT} or a fixed size in pixels
+         * @param weight the weight
+         */
+        public LayoutParams(int width, int height, float weight) {
+            super(width, height);
+            this.weight = weight;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(ViewGroup.LayoutParams p) {
+            super(p);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public LayoutParams(ViewGroup.MarginLayoutParams source) {
+            super(source);
+        }
+
+        /**
+         * Copy constructor. Clones the width, height, margin values, weight,
+         * and gravity of the source.
+         *
+         * @param source The layout params to copy from.
+         */
+        public LayoutParams(LayoutParams source) {
+            super(source);
+
+            this.weight = source.weight;
+            this.gravity = source.gravity;
+        }
+    }
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java b/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
new file mode 100644
index 0000000..efb4086
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
@@ -0,0 +1,1726 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.support.v4.text.TextUtilsCompat;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.ViewPropertyAnimatorCompat;
+import android.support.v4.widget.ListViewAutoScrollHelper;
+import android.support.v4.widget.PopupWindowCompat;
+import android.support.v7.appcompat.R;
+import android.support.v7.internal.widget.ListViewCompat;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.View.OnTouchListener;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.PopupWindow;
+
+import java.util.Locale;
+
+/**
+ * Static library support version of the framework's {@link android.widget.ListPopupWindow}.
+ * Used to write apps that run on platforms prior to Android L. When running
+ * on Android L or above, this implementation is still used; it does not try
+ * to switch to the framework's implementation. See the framework SDK
+ * documentation for a class overview.
+ *
+ * @see android.widget.ListPopupWindow
+ */
+public class ListPopupWindow {
+    private static final String TAG = "ListPopupWindow";
+    private static final boolean DEBUG = false;
+
+    /**
+     * This value controls the length of time that the user
+     * must leave a pointer down without scrolling to expand
+     * the autocomplete dropdown list to cover the IME.
+     */
+    private static final int EXPAND_LIST_TIMEOUT = 250;
+
+    private Context mContext;
+    private PopupWindow mPopup;
+    private ListAdapter mAdapter;
+    private DropDownListView mDropDownList;
+
+    private int mDropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
+    private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
+    private int mDropDownHorizontalOffset;
+    private int mDropDownVerticalOffset;
+    private boolean mDropDownVerticalOffsetSet;
+
+    private int mDropDownGravity = Gravity.NO_GRAVITY;
+
+    private boolean mDropDownAlwaysVisible = false;
+    private boolean mForceIgnoreOutsideTouch = false;
+    int mListItemExpandMaximum = Integer.MAX_VALUE;
+
+    private View mPromptView;
+    private int mPromptPosition = POSITION_PROMPT_ABOVE;
+
+    private DataSetObserver mObserver;
+
+    private View mDropDownAnchorView;
+
+    private Drawable mDropDownListHighlight;
+
+    private AdapterView.OnItemClickListener mItemClickListener;
+    private AdapterView.OnItemSelectedListener mItemSelectedListener;
+
+    private final ResizePopupRunnable mResizePopupRunnable = new ResizePopupRunnable();
+    private final PopupTouchInterceptor mTouchInterceptor = new PopupTouchInterceptor();
+    private final PopupScrollListener mScrollListener = new PopupScrollListener();
+    private final ListSelectorHider mHideSelector = new ListSelectorHider();
+    private Runnable mShowDropDownRunnable;
+
+    private Handler mHandler = new Handler();
+
+    private Rect mTempRect = new Rect();
+
+    private boolean mModal;
+
+    private int mLayoutDirection;
+
+    /**
+     * The provided prompt view should appear above list content.
+     *
+     * @see #setPromptPosition(int)
+     * @see #getPromptPosition()
+     * @see #setPromptView(View)
+     */
+    public static final int POSITION_PROMPT_ABOVE = 0;
+
+    /**
+     * The provided prompt view should appear below list content.
+     *
+     * @see #setPromptPosition(int)
+     * @see #getPromptPosition()
+     * @see #setPromptView(View)
+     */
+    public static final int POSITION_PROMPT_BELOW = 1;
+
+    /**
+     * Alias for {@link ViewGroup.LayoutParams#MATCH_PARENT}.
+     * If used to specify a popup width, the popup will match the width of the anchor view.
+     * If used to specify a popup height, the popup will fill available space.
+     */
+    public static final int MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT;
+
+    /**
+     * Alias for {@link ViewGroup.LayoutParams#WRAP_CONTENT}.
+     * If used to specify a popup width, the popup will use the width of its content.
+     */
+    public static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT;
+
+    /**
+     * Mode for {@link #setInputMethodMode(int)}: the requirements for the
+     * input method should be based on the focusability of the popup.  That is
+     * if it is focusable than it needs to work with the input method, else
+     * it doesn't.
+     */
+    public static final int INPUT_METHOD_FROM_FOCUSABLE = PopupWindow.INPUT_METHOD_FROM_FOCUSABLE;
+
+    /**
+     * Mode for {@link #setInputMethodMode(int)}: this popup always needs to
+     * work with an input method, regardless of whether it is focusable.  This
+     * means that it will always be displayed so that the user can also operate
+     * the input method while it is shown.
+     */
+    public static final int INPUT_METHOD_NEEDED = PopupWindow.INPUT_METHOD_NEEDED;
+
+    /**
+     * Mode for {@link #setInputMethodMode(int)}: this popup never needs to
+     * work with an input method, regardless of whether it is focusable.  This
+     * means that it will always be displayed to use as much space on the
+     * screen as needed, regardless of whether this covers the input method.
+     */
+    public static final int INPUT_METHOD_NOT_NEEDED = PopupWindow.INPUT_METHOD_NOT_NEEDED;
+
+    /**
+     * Create a new, empty popup window capable of displaying items from a ListAdapter.
+     * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
+     *
+     * @param context Context used for contained views.
+     */
+    public ListPopupWindow(Context context) {
+        this(context, null, R.attr.listPopupWindowStyle);
+    }
+
+    /**
+     * Create a new, empty popup window capable of displaying items from a ListAdapter.
+     * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
+     *
+     * @param context Context used for contained views.
+     * @param attrs   Attributes from inflating parent views used to style the popup.
+     */
+    public ListPopupWindow(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.listPopupWindowStyle);
+    }
+
+    /**
+     * Create a new, empty popup window capable of displaying items from a ListAdapter.
+     * Backgrounds should be set using {@link #setBackgroundDrawable(Drawable)}.
+     *
+     * @param context Context used for contained views.
+     * @param attrs Attributes from inflating parent views used to style the popup.
+     * @param defStyleAttr Default style attribute to use for popup content.
+     */
+    public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
+        mContext = context;
+        mPopup = new PopupWindow(context, attrs, defStyleAttr);
+        mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
+        // Set the default layout direction to match the default locale one
+        final Locale locale = mContext.getResources().getConfiguration().locale;
+        mLayoutDirection = TextUtilsCompat.getLayoutDirectionFromLocale(locale);
+    }
+
+    /**
+     * Sets the adapter that provides the data and the views to represent the data
+     * in this popup window.
+     *
+     * @param adapter The adapter to use to create this window's content.
+     */
+    public void setAdapter(ListAdapter adapter) {
+        if (mObserver == null) {
+            mObserver = new PopupDataSetObserver();
+        } else if (mAdapter != null) {
+            mAdapter.unregisterDataSetObserver(mObserver);
+        }
+        mAdapter = adapter;
+        if (mAdapter != null) {
+            adapter.registerDataSetObserver(mObserver);
+        }
+
+        if (mDropDownList != null) {
+            mDropDownList.setAdapter(mAdapter);
+        }
+    }
+
+    /**
+     * Set where the optional prompt view should appear. The default is
+     * {@link #POSITION_PROMPT_ABOVE}.
+     *
+     * @param position A position constant declaring where the prompt should be displayed.
+     *
+     * @see #POSITION_PROMPT_ABOVE
+     * @see #POSITION_PROMPT_BELOW
+     */
+    public void setPromptPosition(int position) {
+        mPromptPosition = position;
+    }
+
+    /**
+     * @return Where the optional prompt view should appear.
+     *
+     * @see #POSITION_PROMPT_ABOVE
+     * @see #POSITION_PROMPT_BELOW
+     */
+    public int getPromptPosition() {
+        return mPromptPosition;
+    }
+
+    /**
+     * Set whether this window should be modal when shown.
+     *
+     * <p>If a popup window is modal, it will receive all touch and key input.
+     * If the user touches outside the popup window's content area the popup window
+     * will be dismissed.
+     *
+     * @param modal {@code true} if the popup window should be modal, {@code false} otherwise.
+     */
+    public void setModal(boolean modal) {
+        mModal = modal;
+        mPopup.setFocusable(modal);
+    }
+
+    /**
+     * Returns whether the popup window will be modal when shown.
+     *
+     * @return {@code true} if the popup window will be modal, {@code false} otherwise.
+     */
+    public boolean isModal() {
+        return mModal;
+    }
+
+    /**
+     * Forces outside touches to be ignored. Normally if {@link #isDropDownAlwaysVisible()} is
+     * false, we allow outside touch to dismiss the dropdown. If this is set to true, then we
+     * ignore outside touch even when the drop down is not set to always visible.
+     *
+     * @hide Used only by AutoCompleteTextView to handle some internal special cases.
+     */
+    public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) {
+        mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch;
+    }
+
+    /**
+     * Sets whether the drop-down should remain visible under certain conditions.
+     *
+     * The drop-down will occupy the entire screen below {@link #getAnchorView} regardless
+     * of the size or content of the list.  {@link #getBackground()} will fill any space
+     * that is not used by the list.
+     *
+     * @param dropDownAlwaysVisible Whether to keep the drop-down visible.
+     *
+     * @hide Only used by AutoCompleteTextView under special conditions.
+     */
+    public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
+        mDropDownAlwaysVisible = dropDownAlwaysVisible;
+    }
+
+    /**
+     * @return Whether the drop-down is visible under special conditions.
+     *
+     * @hide Only used by AutoCompleteTextView under special conditions.
+     */
+    public boolean isDropDownAlwaysVisible() {
+        return mDropDownAlwaysVisible;
+    }
+
+    /**
+     * Sets the operating mode for the soft input area.
+     *
+     * @param mode The desired mode, see
+     *        {@link android.view.WindowManager.LayoutParams#softInputMode}
+     *        for the full list
+     *
+     * @see android.view.WindowManager.LayoutParams#softInputMode
+     * @see #getSoftInputMode()
+     */
+    public void setSoftInputMode(int mode) {
+        mPopup.setSoftInputMode(mode);
+    }
+
+    /**
+     * Returns the current value in {@link #setSoftInputMode(int)}.
+     *
+     * @see #setSoftInputMode(int)
+     * @see android.view.WindowManager.LayoutParams#softInputMode
+     */
+    public int getSoftInputMode() {
+        return mPopup.getSoftInputMode();
+    }
+
+    /**
+     * Sets a drawable to use as the list item selector.
+     *
+     * @param selector List selector drawable to use in the popup.
+     */
+    public void setListSelector(Drawable selector) {
+        mDropDownListHighlight = selector;
+    }
+
+    /**
+     * @return The background drawable for the popup window.
+     */
+    public Drawable getBackground() {
+        return mPopup.getBackground();
+    }
+
+    /**
+     * Sets a drawable to be the background for the popup window.
+     *
+     * @param d A drawable to set as the background.
+     */
+    public void setBackgroundDrawable(Drawable d) {
+        mPopup.setBackgroundDrawable(d);
+    }
+
+    /**
+     * Set an animation style to use when the popup window is shown or dismissed.
+     *
+     * @param animationStyle Animation style to use.
+     */
+    public void setAnimationStyle(int animationStyle) {
+        mPopup.setAnimationStyle(animationStyle);
+    }
+
+    /**
+     * Returns the animation style that will be used when the popup window is shown or dismissed.
+     *
+     * @return Animation style that will be used.
+     */
+    public int getAnimationStyle() {
+        return mPopup.getAnimationStyle();
+    }
+
+    /**
+     * Returns the view that will be used to anchor this popup.
+     *
+     * @return The popup's anchor view
+     */
+    public View getAnchorView() {
+        return mDropDownAnchorView;
+    }
+
+    /**
+     * Sets the popup's anchor view. This popup will always be positioned relative to the anchor
+     * view when shown.
+     *
+     * @param anchor The view to use as an anchor.
+     */
+    public void setAnchorView(View anchor) {
+        mDropDownAnchorView = anchor;
+    }
+
+    /**
+     * @return The horizontal offset of the popup from its anchor in pixels.
+     */
+    public int getHorizontalOffset() {
+        return mDropDownHorizontalOffset;
+    }
+
+    /**
+     * Set the horizontal offset of this popup from its anchor view in pixels.
+     *
+     * @param offset The horizontal offset of the popup from its anchor.
+     */
+    public void setHorizontalOffset(int offset) {
+        mDropDownHorizontalOffset = offset;
+    }
+
+    /**
+     * @return The vertical offset of the popup from its anchor in pixels.
+     */
+    public int getVerticalOffset() {
+        if (!mDropDownVerticalOffsetSet) {
+            return 0;
+        }
+        return mDropDownVerticalOffset;
+    }
+
+    /**
+     * Set the vertical offset of this popup from its anchor view in pixels.
+     *
+     * @param offset The vertical offset of the popup from its anchor.
+     */
+    public void setVerticalOffset(int offset) {
+        mDropDownVerticalOffset = offset;
+        mDropDownVerticalOffsetSet = true;
+    }
+
+    /**
+     * Set the gravity of the dropdown list. This is commonly used to
+     * set gravity to START or END for alignment with the anchor.
+     *
+     * @param gravity Gravity value to use
+     */
+    public void setDropDownGravity(int gravity) {
+        mDropDownGravity = gravity;
+    }
+
+    /**
+     * @return The width of the popup window in pixels.
+     */
+    public int getWidth() {
+        return mDropDownWidth;
+    }
+
+    /**
+     * Sets the width of the popup window in pixels. Can also be {@link #MATCH_PARENT}
+     * or {@link #WRAP_CONTENT}.
+     *
+     * @param width Width of the popup window.
+     */
+    public void setWidth(int width) {
+        mDropDownWidth = width;
+    }
+
+    /**
+     * Sets the width of the popup window by the size of its content. The final width may be
+     * larger to accommodate styled window dressing.
+     *
+     * @param width Desired width of content in pixels.
+     */
+    public void setContentWidth(int width) {
+        Drawable popupBackground = mPopup.getBackground();
+        if (popupBackground != null) {
+            popupBackground.getPadding(mTempRect);
+            mDropDownWidth = mTempRect.left + mTempRect.right + width;
+        } else {
+            setWidth(width);
+        }
+    }
+
+    /**
+     * @return The height of the popup window in pixels.
+     */
+    public int getHeight() {
+        return mDropDownHeight;
+    }
+
+    /**
+     * Sets the height of the popup window in pixels. Can also be {@link #MATCH_PARENT}.
+     *
+     * @param height Height of the popup window.
+     */
+    public void setHeight(int height) {
+        mDropDownHeight = height;
+    }
+
+    /**
+     * Sets a listener to receive events when a list item is clicked.
+     *
+     * @param clickListener Listener to register
+     *
+     * @see ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener)
+     */
+    public void setOnItemClickListener(AdapterView.OnItemClickListener clickListener) {
+        mItemClickListener = clickListener;
+    }
+
+    /**
+     * Sets a listener to receive events when a list item is selected.
+     *
+     * @param selectedListener Listener to register.
+     *
+     * @see ListView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener)
+     */
+    public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener selectedListener) {
+        mItemSelectedListener = selectedListener;
+    }
+
+    /**
+     * Set a view to act as a user prompt for this popup window. Where the prompt view will appear
+     * is controlled by {@link #setPromptPosition(int)}.
+     *
+     * @param prompt View to use as an informational prompt.
+     */
+    public void setPromptView(View prompt) {
+        boolean showing = isShowing();
+        if (showing) {
+            removePromptView();
+        }
+        mPromptView = prompt;
+        if (showing) {
+            show();
+        }
+    }
+
+    /**
+     * Post a {@link #show()} call to the UI thread.
+     */
+    public void postShow() {
+        mHandler.post(mShowDropDownRunnable);
+    }
+
+    /**
+     * Show the popup list. If the list is already showing, this method
+     * will recalculate the popup's size and position.
+     */
+    public void show() {
+        int height = buildDropDown();
+
+        int widthSpec = 0;
+        int heightSpec = 0;
+
+        boolean noInputMethod = isInputMethodNotNeeded();
+
+        if (mPopup.isShowing()) {
+            if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
+                // The call to PopupWindow's update method below can accept -1 for any
+                // value you do not want to update.
+                widthSpec = -1;
+            } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
+                widthSpec = getAnchorView().getWidth();
+            } else {
+                widthSpec = mDropDownWidth;
+            }
+
+            if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
+                // The call to PopupWindow's update method below can accept -1 for any
+                // value you do not want to update.
+                heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT;
+                if (noInputMethod) {
+                    mPopup.setWindowLayoutMode(
+                            mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
+                                    ViewGroup.LayoutParams.MATCH_PARENT : 0, 0);
+                } else {
+                    mPopup.setWindowLayoutMode(
+                            mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ?
+                                    ViewGroup.LayoutParams.MATCH_PARENT : 0,
+                            ViewGroup.LayoutParams.MATCH_PARENT);
+                }
+            } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
+                heightSpec = height;
+            } else {
+                heightSpec = mDropDownHeight;
+            }
+
+            mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
+
+            mPopup.update(getAnchorView(), mDropDownHorizontalOffset,
+                    mDropDownVerticalOffset, widthSpec, heightSpec);
+        } else {
+            if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
+                widthSpec = ViewGroup.LayoutParams.MATCH_PARENT;
+            } else {
+                if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
+                    mPopup.setWidth(getAnchorView().getWidth());
+                } else {
+                    mPopup.setWidth(mDropDownWidth);
+                }
+            }
+
+            if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
+                heightSpec = ViewGroup.LayoutParams.MATCH_PARENT;
+            } else {
+                if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
+                    mPopup.setHeight(height);
+                } else {
+                    mPopup.setHeight(mDropDownHeight);
+                }
+            }
+
+            mPopup.setWindowLayoutMode(widthSpec, heightSpec);
+            // mPopup.setClipToScreenEnabled(true);
+
+            // use outside touchable to dismiss drop down when touching outside of it, so
+            // only set this if the dropdown is not always visible
+            mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
+            mPopup.setTouchInterceptor(mTouchInterceptor);
+            PopupWindowCompat.showAsDropDown(mPopup, getAnchorView(), mDropDownHorizontalOffset,
+                    mDropDownVerticalOffset, mDropDownGravity);
+            mDropDownList.setSelection(ListView.INVALID_POSITION);
+
+            if (!mModal || mDropDownList.isInTouchMode()) {
+                clearListSelection();
+            }
+            if (!mModal) {
+                mHandler.post(mHideSelector);
+            }
+        }
+    }
+
+    /**
+     * Dismiss the popup window.
+     */
+    public void dismiss() {
+        mPopup.dismiss();
+        removePromptView();
+        mPopup.setContentView(null);
+        mDropDownList = null;
+        mHandler.removeCallbacks(mResizePopupRunnable);
+    }
+
+    /**
+     * Set a listener to receive a callback when the popup is dismissed.
+     *
+     * @param listener Listener that will be notified when the popup is dismissed.
+     */
+    public void setOnDismissListener(PopupWindow.OnDismissListener listener) {
+        mPopup.setOnDismissListener(listener);
+    }
+
+    private void removePromptView() {
+        if (mPromptView != null) {
+            final ViewParent parent = mPromptView.getParent();
+            if (parent instanceof ViewGroup) {
+                final ViewGroup group = (ViewGroup) parent;
+                group.removeView(mPromptView);
+            }
+        }
+    }
+
+    /**
+     * Control how the popup operates with an input method: one of
+     * {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
+     * or {@link #INPUT_METHOD_NOT_NEEDED}.
+     *
+     * <p>If the popup is showing, calling this method will take effect only
+     * the next time the popup is shown or through a manual call to the {@link #show()}
+     * method.</p>
+     *
+     * @see #getInputMethodMode()
+     * @see #show()
+     */
+    public void setInputMethodMode(int mode) {
+        mPopup.setInputMethodMode(mode);
+    }
+
+    /**
+     * Return the current value in {@link #setInputMethodMode(int)}.
+     *
+     * @see #setInputMethodMode(int)
+     */
+    public int getInputMethodMode() {
+        return mPopup.getInputMethodMode();
+    }
+
+    /**
+     * Set the selected position of the list.
+     * Only valid when {@link #isShowing()} == {@code true}.
+     *
+     * @param position List position to set as selected.
+     */
+    public void setSelection(int position) {
+        DropDownListView list = mDropDownList;
+        if (isShowing() && list != null) {
+            list.mListSelectionHidden = false;
+            list.setSelection(position);
+
+            if (Build.VERSION.SDK_INT >= 11) {
+                if (list.getChoiceMode() != ListView.CHOICE_MODE_NONE) {
+                    list.setItemChecked(position, true);
+                }
+            }
+        }
+    }
+
+    /**
+     * Clear any current list selection.
+     * Only valid when {@link #isShowing()} == {@code true}.
+     */
+    public void clearListSelection() {
+        final DropDownListView list = mDropDownList;
+        if (list != null) {
+            // WARNING: Please read the comment where mListSelectionHidden is declared
+            list.mListSelectionHidden = true;
+            //list.hideSelector();
+            list.requestLayout();
+        }
+    }
+
+    /**
+     * @return {@code true} if the popup is currently showing, {@code false} otherwise.
+     */
+    public boolean isShowing() {
+        return mPopup.isShowing();
+    }
+
+    /**
+     * @return {@code true} if this popup is configured to assume the user does not need
+     * to interact with the IME while it is showing, {@code false} otherwise.
+     */
+    public boolean isInputMethodNotNeeded() {
+        return mPopup.getInputMethodMode() == INPUT_METHOD_NOT_NEEDED;
+    }
+
+    /**
+     * Perform an item click operation on the specified list adapter position.
+     *
+     * @param position Adapter position for performing the click
+     * @return true if the click action could be performed, false if not.
+     *         (e.g. if the popup was not showing, this method would return false.)
+     */
+    public boolean performItemClick(int position) {
+        if (isShowing()) {
+            if (mItemClickListener != null) {
+                final DropDownListView list = mDropDownList;
+                final View child = list.getChildAt(position - list.getFirstVisiblePosition());
+                final ListAdapter adapter = list.getAdapter();
+                mItemClickListener.onItemClick(list, child, position, adapter.getItemId(position));
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @return The currently selected item or null if the popup is not showing.
+     */
+    public Object getSelectedItem() {
+        if (!isShowing()) {
+            return null;
+        }
+        return mDropDownList.getSelectedItem();
+    }
+
+    /**
+     * @return The position of the currently selected item or {@link ListView#INVALID_POSITION}
+     * if {@link #isShowing()} == {@code false}.
+     *
+     * @see ListView#getSelectedItemPosition()
+     */
+    public int getSelectedItemPosition() {
+        if (!isShowing()) {
+            return ListView.INVALID_POSITION;
+        }
+        return mDropDownList.getSelectedItemPosition();
+    }
+
+    /**
+     * @return The ID of the currently selected item or {@link ListView#INVALID_ROW_ID}
+     * if {@link #isShowing()} == {@code false}.
+     *
+     * @see ListView#getSelectedItemId()
+     */
+    public long getSelectedItemId() {
+        if (!isShowing()) {
+            return ListView.INVALID_ROW_ID;
+        }
+        return mDropDownList.getSelectedItemId();
+    }
+
+    /**
+     * @return The View for the currently selected item or null if
+     * {@link #isShowing()} == {@code false}.
+     *
+     * @see ListView#getSelectedView()
+     */
+    public View getSelectedView() {
+        if (!isShowing()) {
+            return null;
+        }
+        return mDropDownList.getSelectedView();
+    }
+
+    /**
+     * @return The {@link ListView} displayed within the popup window.
+     * Only valid when {@link #isShowing()} == {@code true}.
+     */
+    public ListView getListView() {
+        return mDropDownList;
+    }
+
+    /**
+     * The maximum number of list items that can be visible and still have
+     * the list expand when touched.
+     *
+     * @param max Max number of items that can be visible and still allow the list to expand.
+     */
+    void setListItemExpandMax(int max) {
+        mListItemExpandMaximum = max;
+    }
+
+    /**
+     * Filter key down events. By forwarding key down events to this function,
+     * views using non-modal ListPopupWindow can have it handle key selection of items.
+     *
+     * @param keyCode keyCode param passed to the host view's onKeyDown
+     * @param event event param passed to the host view's onKeyDown
+     * @return true if the event was handled, false if it was ignored.
+     *
+     * @see #setModal(boolean)
+     */
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        // when the drop down is shown, we drive it directly
+        if (isShowing()) {
+            // the key events are forwarded to the list in the drop down view
+            // note that ListView handles space but we don't want that to happen
+            // also if selection is not currently in the drop down, then don't
+            // let center or enter presses go there since that would cause it
+            // to select one of its items
+            if (keyCode != KeyEvent.KEYCODE_SPACE
+                    && (mDropDownList.getSelectedItemPosition() >= 0
+                    || !isConfirmKey(keyCode))) {
+                int curIndex = mDropDownList.getSelectedItemPosition();
+                boolean consumed;
+
+                final boolean below = !mPopup.isAboveAnchor();
+
+                final ListAdapter adapter = mAdapter;
+
+                boolean allEnabled;
+                int firstItem = Integer.MAX_VALUE;
+                int lastItem = Integer.MIN_VALUE;
+
+                if (adapter != null) {
+                    allEnabled = adapter.areAllItemsEnabled();
+                    firstItem = allEnabled ? 0 :
+                            mDropDownList.lookForSelectablePosition(0, true);
+                    lastItem = allEnabled ? adapter.getCount() - 1 :
+                            mDropDownList.lookForSelectablePosition(adapter.getCount() - 1, false);
+                }
+
+                if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= firstItem) ||
+                        (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >= lastItem)) {
+                    // When the selection is at the top, we block the key
+                    // event to prevent focus from moving.
+                    clearListSelection();
+                    mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
+                    show();
+                    return true;
+                } else {
+                    // WARNING: Please read the comment where mListSelectionHidden
+                    //          is declared
+                    mDropDownList.mListSelectionHidden = false;
+                }
+
+                consumed = mDropDownList.onKeyDown(keyCode, event);
+                if (DEBUG) Log.v(TAG, "Key down: code=" + keyCode + " list consumed=" + consumed);
+
+                if (consumed) {
+                    // If it handled the key event, then the user is
+                    // navigating in the list, so we should put it in front.
+                    mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+                    // Here's a little trick we need to do to make sure that
+                    // the list view is actually showing its focus indicator,
+                    // by ensuring it has focus and getting its window out
+                    // of touch mode.
+                    mDropDownList.requestFocusFromTouch();
+                    show();
+
+                    switch (keyCode) {
+                        // avoid passing the focus from the text view to the
+                        // next component
+                        case KeyEvent.KEYCODE_ENTER:
+                        case KeyEvent.KEYCODE_DPAD_CENTER:
+                        case KeyEvent.KEYCODE_DPAD_DOWN:
+                        case KeyEvent.KEYCODE_DPAD_UP:
+                            return true;
+                    }
+                } else {
+                    if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
+                        // when the selection is at the bottom, we block the
+                        // event to avoid going to the next focusable widget
+                        if (curIndex == lastItem) {
+                            return true;
+                        }
+                    } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP &&
+                            curIndex == firstItem) {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Filter key down events. By forwarding key up events to this function,
+     * views using non-modal ListPopupWindow can have it handle key selection of items.
+     *
+     * @param keyCode keyCode param passed to the host view's onKeyUp
+     * @param event event param passed to the host view's onKeyUp
+     * @return true if the event was handled, false if it was ignored.
+     *
+     * @see #setModal(boolean)
+     */
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
+            boolean consumed = mDropDownList.onKeyUp(keyCode, event);
+            if (consumed && isConfirmKey(keyCode)) {
+                // if the list accepts the key events and the key event was a click, the text view
+                // gets the selected item from the drop down as its content
+                dismiss();
+            }
+            return consumed;
+        }
+        return false;
+    }
+
+    /**
+     * Filter pre-IME key events. By forwarding {@link View#onKeyPreIme(int, KeyEvent)}
+     * events to this function, views using ListPopupWindow can have it dismiss the popup
+     * when the back key is pressed.
+     *
+     * @param keyCode keyCode param passed to the host view's onKeyPreIme
+     * @param event event param passed to the host view's onKeyPreIme
+     * @return true if the event was handled, false if it was ignored.
+     *
+     * @see #setModal(boolean)
+     */
+    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK && isShowing()) {
+            // special case for the back key, we do not even try to send it
+            // to the drop down list but instead, consume it immediately
+            final View anchorView = mDropDownAnchorView;
+            if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+                KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState();
+                if (state != null) {
+                    state.startTracking(event, this);
+                }
+                return true;
+            } else if (event.getAction() == KeyEvent.ACTION_UP) {
+                KeyEvent.DispatcherState state = anchorView.getKeyDispatcherState();
+                if (state != null) {
+                    state.handleUpEvent(event);
+                }
+                if (event.isTracking() && !event.isCanceled()) {
+                    dismiss();
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns an {@link OnTouchListener} that can be added to the source view
+     * to implement drag-to-open behavior. Generally, the source view should be
+     * the same view that was passed to {@link #setAnchorView}.
+     * <p>
+     * When the listener is set on a view, touching that view and dragging
+     * outside of its bounds will open the popup window. Lifting will select the
+     * currently touched list item.
+     * <p>
+     * Example usage:
+     * <pre>
+     * ListPopupWindow myPopup = new ListPopupWindow(context);
+     * myPopup.setAnchor(myAnchor);
+     * OnTouchListener dragListener = myPopup.createDragToOpenListener(myAnchor);
+     * myAnchor.setOnTouchListener(dragListener);
+     * </pre>
+     *
+     * @param src the view on which the resulting listener will be set
+     * @return a touch listener that controls drag-to-open behavior
+     */
+    public OnTouchListener createDragToOpenListener(View src) {
+        return new ForwardingListener(src) {
+            @Override
+            public ListPopupWindow getPopup() {
+                return ListPopupWindow.this;
+            }
+        };
+    }
+
+    /**
+     * <p>Builds the popup window's content and returns the height the popup
+     * should have. Returns -1 when the content already exists.</p>
+     *
+     * @return the content's height or -1 if content already exists
+     */
+    private int buildDropDown() {
+        ViewGroup dropDownView;
+        int otherHeights = 0;
+
+        if (mDropDownList == null) {
+            Context context = mContext;
+
+            /**
+             * This Runnable exists for the sole purpose of checking if the view layout has got
+             * completed and if so call showDropDown to display the drop down. This is used to show
+             * the drop down as soon as possible after user opens up the search dialog, without
+             * waiting for the normal UI pipeline to do it's job which is slower than this method.
+             */
+            mShowDropDownRunnable = new Runnable() {
+                public void run() {
+                    // View layout should be all done before displaying the drop down.
+                    View view = getAnchorView();
+                    if (view != null && view.getWindowToken() != null) {
+                        show();
+                    }
+                }
+            };
+
+            mDropDownList = new DropDownListView(context, !mModal);
+            if (mDropDownListHighlight != null) {
+                mDropDownList.setSelector(mDropDownListHighlight);
+            }
+            mDropDownList.setAdapter(mAdapter);
+            mDropDownList.setOnItemClickListener(mItemClickListener);
+            mDropDownList.setFocusable(true);
+            mDropDownList.setFocusableInTouchMode(true);
+            mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+                public void onItemSelected(AdapterView<?> parent, View view,
+                        int position, long id) {
+
+                    if (position != -1) {
+                        DropDownListView dropDownList = mDropDownList;
+
+                        if (dropDownList != null) {
+                            dropDownList.mListSelectionHidden = false;
+                        }
+                    }
+                }
+
+                public void onNothingSelected(AdapterView<?> parent) {
+                }
+            });
+            mDropDownList.setOnScrollListener(mScrollListener);
+
+            if (mItemSelectedListener != null) {
+                mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
+            }
+
+            dropDownView = mDropDownList;
+
+            View hintView = mPromptView;
+            if (hintView != null) {
+                // if a hint has been specified, we accomodate more space for it and
+                // add a text view in the drop down menu, at the bottom of the list
+                LinearLayout hintContainer = new LinearLayout(context);
+                hintContainer.setOrientation(LinearLayout.VERTICAL);
+
+                LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
+                        ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f
+                );
+
+                switch (mPromptPosition) {
+                    case POSITION_PROMPT_BELOW:
+                        hintContainer.addView(dropDownView, hintParams);
+                        hintContainer.addView(hintView);
+                        break;
+
+                    case POSITION_PROMPT_ABOVE:
+                        hintContainer.addView(hintView);
+                        hintContainer.addView(dropDownView, hintParams);
+                        break;
+
+                    default:
+                        Log.e(TAG, "Invalid hint position " + mPromptPosition);
+                        break;
+                }
+
+                // measure the hint's height to find how much more vertical space
+                // we need to add to the drop down's height
+                int widthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.AT_MOST);
+                int heightSpec = MeasureSpec.UNSPECIFIED;
+                hintView.measure(widthSpec, heightSpec);
+
+                hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams();
+                otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin
+                        + hintParams.bottomMargin;
+
+                dropDownView = hintContainer;
+            }
+
+            mPopup.setContentView(dropDownView);
+        } else {
+            dropDownView = (ViewGroup) mPopup.getContentView();
+            final View view = mPromptView;
+            if (view != null) {
+                LinearLayout.LayoutParams hintParams =
+                        (LinearLayout.LayoutParams) view.getLayoutParams();
+                otherHeights = view.getMeasuredHeight() + hintParams.topMargin
+                        + hintParams.bottomMargin;
+            }
+        }
+
+        // getMaxAvailableHeight() subtracts the padding, so we put it back
+        // to get the available height for the whole window
+        int padding = 0;
+        Drawable background = mPopup.getBackground();
+        if (background != null) {
+            background.getPadding(mTempRect);
+            padding = mTempRect.top + mTempRect.bottom;
+
+            // If we don't have an explicit vertical offset, determine one from the window
+            // background so that content will line up.
+            if (!mDropDownVerticalOffsetSet) {
+                mDropDownVerticalOffset = -mTempRect.top;
+            }
+        } else {
+            mTempRect.setEmpty();
+        }
+
+        // Max height available on the screen for a popup.
+        boolean ignoreBottomDecorations =
+                mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
+        final int maxHeight = mPopup.getMaxAvailableHeight(
+                getAnchorView(), mDropDownVerticalOffset /*, ignoreBottomDecorations*/);
+
+        if (mDropDownAlwaysVisible || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
+            return maxHeight + padding;
+        }
+
+        final int childWidthSpec;
+        switch (mDropDownWidth) {
+            case ViewGroup.LayoutParams.WRAP_CONTENT:
+                childWidthSpec = MeasureSpec.makeMeasureSpec(
+                        mContext.getResources().getDisplayMetrics().widthPixels -
+                                (mTempRect.left + mTempRect.right),
+                        MeasureSpec.AT_MOST);
+                break;
+            case ViewGroup.LayoutParams.MATCH_PARENT:
+                childWidthSpec = MeasureSpec.makeMeasureSpec(
+                        mContext.getResources().getDisplayMetrics().widthPixels -
+                                (mTempRect.left + mTempRect.right),
+                        MeasureSpec.EXACTLY);
+                break;
+            default:
+                childWidthSpec = MeasureSpec.makeMeasureSpec(mDropDownWidth, MeasureSpec.EXACTLY);
+                break;
+        }
+
+        final int listContent = mDropDownList.measureHeightOfChildrenCompat(childWidthSpec,
+                0, DropDownListView.NO_POSITION, maxHeight - otherHeights, -1);
+        // add padding only if the list has items in it, that way we don't show
+        // the popup if it is not needed
+        if (listContent > 0) {
+            otherHeights += padding;
+        }
+
+        return listContent + otherHeights;
+    }
+
+    /**
+     * Abstract class that forwards touch events to a {@link ListPopupWindow}.
+     *
+     * @hide
+     */
+    public static abstract class ForwardingListener implements View.OnTouchListener {
+        /** Scaled touch slop, used for detecting movement outside bounds. */
+        private final float mScaledTouchSlop;
+
+        /** Timeout before disallowing intercept on the source's parent. */
+        private final int mTapTimeout;
+        /** Timeout before accepting a long-press to start forwarding. */
+        private final int mLongPressTimeout;
+
+        /** Source view from which events are forwarded. */
+        private final View mSrc;
+
+        /** Runnable used to prevent conflicts with scrolling parents. */
+        private Runnable mDisallowIntercept;
+        /** Runnable used to trigger forwarding on long-press. */
+        private Runnable mTriggerLongPress;
+
+        /** Whether this listener is currently forwarding touch events. */
+        private boolean mForwarding;
+        /**
+         * Whether forwarding was initiated by a long-press. If so, we won't
+         * force the window to dismiss when the touch stream ends.
+         */
+        private boolean mWasLongPress;
+
+        /** The id of the first pointer down in the current event stream. */
+        private int mActivePointerId;
+
+        /**
+         * Temporary Matrix instance
+         */
+        private final int[] mTmpLocation = new int[2];
+
+        public ForwardingListener(View src) {
+            mSrc = src;
+            mScaledTouchSlop = ViewConfiguration.get(src.getContext()).getScaledTouchSlop();
+            mTapTimeout = ViewConfiguration.getTapTimeout();
+            // Use a medium-press timeout. Halfway between tap and long-press.
+            mLongPressTimeout = (mTapTimeout + ViewConfiguration.getLongPressTimeout()) / 2;
+        }
+
+        /**
+         * Returns the popup to which this listener is forwarding events.
+         * <p>
+         * Override this to return the correct popup. If the popup is displayed
+         * asynchronously, you may also need to override
+         * {@link #onForwardingStopped} to prevent premature cancelation of
+         * forwarding.
+         *
+         * @return the popup to which this listener is forwarding events
+         */
+        public abstract ListPopupWindow getPopup();
+
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            final boolean wasForwarding = mForwarding;
+            final boolean forwarding;
+            if (wasForwarding) {
+                if (mWasLongPress) {
+                    // If we started forwarding as a result of a long-press,
+                    // just silently stop forwarding events so that the window
+                    // stays open.
+                    forwarding = onTouchForwarded(event);
+                } else {
+                    forwarding = onTouchForwarded(event) || !onForwardingStopped();
+                }
+            } else {
+                forwarding = onTouchObserved(event) && onForwardingStarted();
+
+                if (forwarding) {
+                    // Make sure we cancel any ongoing source event stream.
+                    final long now = SystemClock.uptimeMillis();
+                    final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL,
+                            0.0f, 0.0f, 0);
+                    mSrc.onTouchEvent(e);
+                    e.recycle();
+                }
+            }
+
+            mForwarding = forwarding;
+            return forwarding || wasForwarding;
+        }
+
+        /**
+         * Called when forwarding would like to start. <p> By default, this will show the popup
+         * returned by {@link #getPopup()}. It may be overridden to perform another action, like
+         * clicking the source view or preparing the popup before showing it.
+         *
+         * @return true to start forwarding, false otherwise
+         */
+        protected boolean onForwardingStarted() {
+            final ListPopupWindow popup = getPopup();
+            if (popup != null && !popup.isShowing()) {
+                popup.show();
+            }
+            return true;
+        }
+
+        /**
+         * Called when forwarding would like to stop. <p> By default, this will dismiss the popup
+         * returned by {@link #getPopup()}. It may be overridden to perform some other action.
+         *
+         * @return true to stop forwarding, false otherwise
+         */
+        protected boolean onForwardingStopped() {
+            final ListPopupWindow popup = getPopup();
+            if (popup != null && popup.isShowing()) {
+                popup.dismiss();
+            }
+            return true;
+        }
+
+        /**
+         * Observes motion events and determines when to start forwarding.
+         *
+         * @param srcEvent motion event in source view coordinates
+         * @return true to start forwarding motion events, false otherwise
+         */
+        private boolean onTouchObserved(MotionEvent srcEvent) {
+            final View src = mSrc;
+            if (!src.isEnabled()) {
+                return false;
+            }
+
+            final int actionMasked = MotionEventCompat.getActionMasked(srcEvent);
+            switch (actionMasked) {
+                case MotionEvent.ACTION_DOWN:
+                    mActivePointerId = srcEvent.getPointerId(0);
+                    mWasLongPress = false;
+
+                    if (mDisallowIntercept == null) {
+                        mDisallowIntercept = new DisallowIntercept();
+                    }
+                    src.postDelayed(mDisallowIntercept, mTapTimeout);
+                    if (mTriggerLongPress == null) {
+                        mTriggerLongPress = new TriggerLongPress();
+                    }
+                    src.postDelayed(mTriggerLongPress, mLongPressTimeout);
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId);
+                    if (activePointerIndex >= 0) {
+                        final float x = srcEvent.getX(activePointerIndex);
+                        final float y = srcEvent.getY(activePointerIndex);
+                        if (!pointInView(src, x, y, mScaledTouchSlop)) {
+                            clearCallbacks();
+
+                            // Don't let the parent intercept our events.
+                            src.getParent().requestDisallowInterceptTouchEvent(true);
+                            return true;
+                        }
+                    }
+                    break;
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    clearCallbacks();
+                    break;
+            }
+
+            return false;
+        }
+
+        private void clearCallbacks() {
+            if (mTriggerLongPress != null) {
+                mSrc.removeCallbacks(mTriggerLongPress);
+            }
+
+            if (mDisallowIntercept != null) {
+                mSrc.removeCallbacks(mDisallowIntercept);
+            }
+        }
+
+        private void onLongPress() {
+            clearCallbacks();
+
+            final View src = mSrc;
+            if (!src.isEnabled()) {
+                return;
+            }
+
+            if (!onForwardingStarted()) {
+                return;
+            }
+
+            // Don't let the parent intercept our events.
+            mSrc.getParent().requestDisallowInterceptTouchEvent(true);
+
+            // Make sure we cancel any ongoing source event stream.
+            final long now = SystemClock.uptimeMillis();
+            final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
+            mSrc.onTouchEvent(e);
+            e.recycle();
+
+            mForwarding = true;
+            mWasLongPress = true;
+        }
+
+        /**
+         * Handled forwarded motion events and determines when to stop forwarding.
+         *
+         * @param srcEvent motion event in source view coordinates
+         * @return true to continue forwarding motion events, false to cancel
+         */
+        private boolean onTouchForwarded(MotionEvent srcEvent) {
+            final View src = mSrc;
+            final ListPopupWindow popup = getPopup();
+            if (popup == null || !popup.isShowing()) {
+                return false;
+            }
+
+            final DropDownListView dst = popup.mDropDownList;
+            if (dst == null || !dst.isShown()) {
+                return false;
+            }
+
+            // Convert event to destination-local coordinates.
+            final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent);
+            toGlobalMotionEvent(src, dstEvent);
+            toLocalMotionEvent(dst, dstEvent);
+
+            // Forward converted event to destination view, then recycle it.
+            final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId);
+            dstEvent.recycle();
+
+            // Always cancel forwarding when the touch stream ends.
+            final int action = MotionEventCompat.getActionMasked(srcEvent);
+            final boolean keepForwarding = action != MotionEvent.ACTION_UP
+                    && action != MotionEvent.ACTION_CANCEL;
+
+            return handled && keepForwarding;
+        }
+
+        private static boolean pointInView(View view, float localX, float localY, float slop) {
+            return localX >= -slop && localY >= -slop &&
+                    localX < ((view.getRight() - view.getLeft()) + slop) &&
+                    localY < ((view.getBottom() - view.getTop()) + slop);
+        }
+
+        /**
+         * Emulates View.toLocalMotionEvent(). This implementation does not handle transformations
+         * (scaleX, scaleY, etc).
+         */
+        private boolean toLocalMotionEvent(View view, MotionEvent event) {
+            final int[] loc = mTmpLocation;
+            view.getLocationOnScreen(loc);
+            event.offsetLocation(-loc[0], -loc[1]);
+            return true;
+        }
+
+        /**
+         * Emulates View.toGlobalMotionEvent(). This implementation does not handle transformations
+         * (scaleX, scaleY, etc).
+         */
+        private boolean toGlobalMotionEvent(View view, MotionEvent event) {
+            final int[] loc = mTmpLocation;
+            view.getLocationOnScreen(loc);
+            event.offsetLocation(loc[0], loc[1]);
+            return true;
+        }
+
+        private class DisallowIntercept implements Runnable {
+            @Override
+            public void run() {
+                final ViewParent parent = mSrc.getParent();
+                parent.requestDisallowInterceptTouchEvent(true);
+            }
+        }
+
+        private class TriggerLongPress implements Runnable {
+            @Override
+            public void run() {
+                onLongPress();
+            }
+        }
+    }
+
+    /**
+     * <p>Wrapper class for a ListView. This wrapper can hijack the focus to
+     * make sure the list uses the appropriate drawables and states when
+     * displayed on screen within a drop down. The focus is never actually
+     * passed to the drop down in this mode; the list only looks focused.</p>
+     */
+    private static class DropDownListView extends ListViewCompat {
+
+        /*
+        * WARNING: This is a workaround for a touch mode issue.
+        *
+        * Touch mode is propagated lazily to windows. This causes problems in
+        * the following scenario:
+        * - Type something in the AutoCompleteTextView and get some results
+        * - Move down with the d-pad to select an item in the list
+        * - Move up with the d-pad until the selection disappears
+        * - Type more text in the AutoCompleteTextView *using the soft keyboard*
+        *   and get new results; you are now in touch mode
+        * - The selection comes back on the first item in the list, even though
+        *   the list is supposed to be in touch mode
+        *
+        * Using the soft keyboard triggers the touch mode change but that change
+        * is propagated to our window only after the first list layout, therefore
+        * after the list attempts to resurrect the selection.
+        *
+        * The trick to work around this issue is to pretend the list is in touch
+        * mode when we know that the selection should not appear, that is when
+        * we know the user moved the selection away from the list.
+        *
+        * This boolean is set to true whenever we explicitly hide the list's
+        * selection and reset to false whenever we know the user moved the
+        * selection back to the list.
+        *
+        * When this boolean is true, isInTouchMode() returns true, otherwise it
+        * returns super.isInTouchMode().
+        */
+        private boolean mListSelectionHidden;
+
+        /**
+         * True if this wrapper should fake focus.
+         */
+        private boolean mHijackFocus;
+
+        /** Whether to force drawing of the pressed state selector. */
+        private boolean mDrawsInPressedState;
+
+        /** Current drag-to-open click animation, if any. */
+        private ViewPropertyAnimatorCompat mClickAnimation;
+
+        /** Helper for drag-to-open auto scrolling. */
+        private ListViewAutoScrollHelper mScrollHelper;
+
+
+        /**
+         * <p>Creates a new list view wrapper.</p>
+         *
+         * @param context this view's context
+         */
+        public DropDownListView(Context context, boolean hijackFocus) {
+            super(context, null, R.attr.dropDownListViewStyle);
+            mHijackFocus = hijackFocus;
+            setCacheColorHint(0); // Transparent, since the background drawable could be anything.
+        }
+
+        /**
+         * Handles forwarded events.
+         *
+         * @param activePointerId id of the pointer that activated forwarding
+         * @return whether the event was handled
+         */
+        public boolean onForwardedEvent(MotionEvent event, int activePointerId) {
+            boolean handledEvent = true;
+            boolean clearPressedItem = false;
+
+            final int actionMasked = MotionEventCompat.getActionMasked(event);
+            switch (actionMasked) {
+                case MotionEvent.ACTION_CANCEL:
+                    handledEvent = false;
+                    break;
+                case MotionEvent.ACTION_UP:
+                    handledEvent = false;
+                    // $FALL-THROUGH$
+                case MotionEvent.ACTION_MOVE:
+                    final int activeIndex = event.findPointerIndex(activePointerId);
+                    if (activeIndex < 0) {
+                        handledEvent = false;
+                        break;
+                    }
+
+                    final int x = (int) event.getX(activeIndex);
+                    final int y = (int) event.getY(activeIndex);
+                    final int position = pointToPosition(x, y);
+                    if (position == INVALID_POSITION) {
+                        clearPressedItem = true;
+                        break;
+                    }
+
+                    final View child = getChildAt(position - getFirstVisiblePosition());
+                    setPressedItem(child, position, x, y);
+                    handledEvent = true;
+
+                    if (actionMasked == MotionEvent.ACTION_UP) {
+                        clickPressedItem(child, position);
+                    }
+                    break;
+            }
+
+            // Failure to handle the event cancels forwarding.
+            if (!handledEvent || clearPressedItem) {
+                clearPressedItem();
+            }
+
+            // Manage automatic scrolling.
+            if (handledEvent) {
+                if (mScrollHelper == null) {
+                    mScrollHelper = new ListViewAutoScrollHelper(this);
+                }
+                mScrollHelper.setEnabled(true);
+                mScrollHelper.onTouch(this, event);
+            } else if (mScrollHelper != null) {
+                mScrollHelper.setEnabled(false);
+            }
+
+            return handledEvent;
+        }
+
+        /**
+         * Starts an alpha animation on the selector. When the animation ends,
+         * the list performs a click on the item.
+         */
+        private void clickPressedItem(final View child, final int position) {
+            final long id = getItemIdAtPosition(position);
+            performItemClick(child, position, id);
+        }
+
+        private void clearPressedItem() {
+            mDrawsInPressedState = false;
+            setPressed(false);
+            // This will call through to updateSelectorState()
+            drawableStateChanged();
+
+            if (mClickAnimation != null) {
+                mClickAnimation.cancel();
+                mClickAnimation = null;
+            }
+        }
+
+        private void setPressedItem(View child, int position, float x, float y) {
+            mDrawsInPressedState = true;
+
+            // Ordering is essential. First update the pressed state and layout
+            // the children. This will ensure the selector actually gets drawn.
+            setPressed(true);
+            layoutChildren();
+
+            // Ensure that keyboard focus starts from the last touched position.
+            setSelection(position);
+            positionSelectorLikeTouchCompat(position, child, x, y);
+
+            // Refresh the drawable state to reflect the new pressed state,
+            // which will also update the selector state.
+            refreshDrawableState();
+        }
+
+        @Override
+        protected boolean touchModeDrawsInPressedStateCompat() {
+            return mDrawsInPressedState || super.touchModeDrawsInPressedStateCompat();
+        }
+
+        @Override
+        public boolean isInTouchMode() {
+            // WARNING: Please read the comment where mListSelectionHidden is declared
+            return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode();
+        }
+
+        /**
+         * <p>Returns the focus state in the drop down.</p>
+         *
+         * @return true always if hijacking focus
+         */
+        @Override
+        public boolean hasWindowFocus() {
+            return mHijackFocus || super.hasWindowFocus();
+        }
+
+        /**
+         * <p>Returns the focus state in the drop down.</p>
+         *
+         * @return true always if hijacking focus
+         */
+        @Override
+        public boolean isFocused() {
+            return mHijackFocus || super.isFocused();
+        }
+
+        /**
+         * <p>Returns the focus state in the drop down.</p>
+         *
+         * @return true always if hijacking focus
+         */
+        @Override
+        public boolean hasFocus() {
+            return mHijackFocus || super.hasFocus();
+        }
+
+    }
+
+    private class PopupDataSetObserver extends DataSetObserver {
+        @Override
+        public void onChanged() {
+            if (isShowing()) {
+                // Resize the popup to fit new content
+                show();
+            }
+        }
+
+        @Override
+        public void onInvalidated() {
+            dismiss();
+        }
+    }
+
+    private class ListSelectorHider implements Runnable {
+        public void run() {
+            clearListSelection();
+        }
+    }
+
+    private class ResizePopupRunnable implements Runnable {
+        public void run() {
+            if (mDropDownList != null && mDropDownList.getCount() > mDropDownList.getChildCount() &&
+                    mDropDownList.getChildCount() <= mListItemExpandMaximum) {
+                mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+                show();
+            }
+        }
+    }
+
+    private class PopupTouchInterceptor implements OnTouchListener {
+        public boolean onTouch(View v, MotionEvent event) {
+            final int action = event.getAction();
+            final int x = (int) event.getX();
+            final int y = (int) event.getY();
+
+            if (action == MotionEvent.ACTION_DOWN &&
+                    mPopup != null && mPopup.isShowing() &&
+                    (x >= 0 && x < mPopup.getWidth() && y >= 0 && y < mPopup.getHeight())) {
+                mHandler.postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT);
+            } else if (action == MotionEvent.ACTION_UP) {
+                mHandler.removeCallbacks(mResizePopupRunnable);
+            }
+            return false;
+        }
+    }
+
+    private class PopupScrollListener implements ListView.OnScrollListener {
+        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+                int totalItemCount) {
+
+        }
+
+        public void onScrollStateChanged(AbsListView view, int scrollState) {
+            if (scrollState == SCROLL_STATE_TOUCH_SCROLL &&
+                    !isInputMethodNotNeeded() && mPopup.getContentView() != null) {
+                mHandler.removeCallbacks(mResizePopupRunnable);
+                mResizePopupRunnable.run();
+            }
+        }
+    }
+
+    private static boolean isConfirmKey(int keyCode) {
+        return keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_DPAD_CENTER;
+    }
+
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/PopupMenu.java b/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
index 2f561d1..14813b1 100644
--- a/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
+++ b/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2014 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,15 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.support.v7.widget;
 
 
 import android.content.Context;
+import android.support.annotation.MenuRes;
 import android.support.v7.internal.view.SupportMenuInflater;
 import android.support.v7.internal.view.menu.MenuBuilder;
 import android.support.v7.internal.view.menu.MenuPopupHelper;
 import android.support.v7.internal.view.menu.MenuPresenter;
 import android.support.v7.internal.view.menu.SubMenuBuilder;
+import android.view.Gravity;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -41,6 +44,7 @@
     private MenuPopupHelper mPopup;
     private OnMenuItemClickListener mMenuItemClickListener;
     private OnDismissListener mDismissListener;
+    private View.OnTouchListener mDragListener;
 
     /**
      * Callback interface used to notify the application that the menu has closed.
@@ -62,15 +66,71 @@
      *               is room, or above it if there is not.
      */
     public PopupMenu(Context context, View anchor) {
+        this(context, anchor, Gravity.NO_GRAVITY);
+    }
+
+    /**
+     * Construct a new PopupMenu.
+     *
+     * @param context Context for the PopupMenu.
+     * @param anchor Anchor view for this popup. The popup will appear below the anchor if there
+     *               is room, or above it if there is not.
+     * @param gravity The {@link Gravity} value for aligning the popup with its anchor
+     */
+    public PopupMenu(Context context, View anchor, int gravity) {
+        // TODO Theme?
         mContext = context;
         mMenu = new MenuBuilder(context);
         mMenu.setCallback(this);
         mAnchor = anchor;
         mPopup = new MenuPopupHelper(context, mMenu, anchor);
+        mPopup.setGravity(gravity);
         mPopup.setCallback(this);
     }
 
     /**
+     * Returns an {@link android.view.View.OnTouchListener} that can be added to the anchor view
+     * to implement drag-to-open behavior.
+     * <p>
+     * When the listener is set on a view, touching that view and dragging
+     * outside of its bounds will open the popup window. Lifting will select the
+     * currently touched list item.
+     * <p>
+     * Example usage:
+     * <pre>
+     * PopupMenu myPopup = new PopupMenu(context, myAnchor);
+     * myAnchor.setOnTouchListener(myPopup.getDragToOpenListener());
+     * </pre>
+     *
+     * @return a touch listener that controls drag-to-open behavior
+     */
+    public View.OnTouchListener getDragToOpenListener() {
+        if (mDragListener == null) {
+            mDragListener = new ListPopupWindow.ForwardingListener(mAnchor) {
+                @Override
+                protected boolean onForwardingStarted() {
+                    show();
+                    return true;
+                }
+
+                @Override
+                protected boolean onForwardingStopped() {
+                    dismiss();
+                    return true;
+                }
+
+                @Override
+                public ListPopupWindow getPopup() {
+                    // This will be null until show() is called.
+                    return mPopup.getPopup();
+                }
+            };
+        }
+
+        return mDragListener;
+    }
+
+    /**
      * @return the {@link Menu} associated with this popup. Populate the returned Menu with
      * items before calling {@link #show()}.
      *
@@ -96,7 +156,7 @@
      * popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu()).
      * @param menuRes Menu resource to inflate
      */
-    public void inflate(int menuRes) {
+    public void inflate(@MenuRes int menuRes) {
         getMenuInflater().inflate(menuRes, mMenu);
     }
 
diff --git a/v7/appcompat/src/android/support/v7/widget/SearchView.java b/v7/appcompat/src/android/support/v7/widget/SearchView.java
index e548298..06efb49 100644
--- a/v7/appcompat/src/android/support/v7/widget/SearchView.java
+++ b/v7/appcompat/src/android/support/v7/widget/SearchView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2014 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
 
 package android.support.v7.widget;
 
+import android.annotation.TargetApi;
 import android.app.PendingIntent;
 import android.app.SearchManager;
 import android.app.SearchableInfo;
@@ -36,9 +37,11 @@
 import android.os.Bundle;
 import android.os.ResultReceiver;
 import android.speech.RecognizerIntent;
+import android.support.v4.content.ContextCompat;
 import android.support.v4.view.KeyEventCompat;
 import android.support.v4.widget.CursorAdapter;
 import android.support.v7.appcompat.R;
+import android.support.v7.internal.widget.ViewUtils;
 import android.support.v7.view.CollapsibleActionView;
 import android.text.Editable;
 import android.text.InputType;
@@ -49,7 +52,6 @@
 import android.text.style.ImageSpan;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -61,7 +63,6 @@
 import android.widget.AdapterView.OnItemSelectedListener;
 import android.widget.AutoCompleteTextView;
 import android.widget.ImageView;
-import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
@@ -100,35 +101,46 @@
  *
  * @see android.support.v4.view.MenuItemCompat#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
  */
-public class SearchView extends LinearLayout implements CollapsibleActionView {
+public class SearchView extends LinearLayoutCompat implements CollapsibleActionView {
 
     private static final boolean DBG = false;
     private static final String LOG_TAG = "SearchView";
 
+    private static final boolean IS_AT_LEAST_FROYO = Build.VERSION.SDK_INT >= 8;
+
     /**
      * Private constant for removing the microphone in the keyboard.
      */
     private static final String IME_OPTION_NO_MICROPHONE = "nm";
 
+    private final SearchAutoComplete mQueryTextView;
+    private final View mSearchEditFrame;
+    private final View mSearchPlate;
+    private final View mSubmitArea;
+    private final ImageView mSearchButton;
+    private final ImageView mSubmitButton;
+    private final ImageView mCloseButton;
+    private final ImageView mVoiceButton;
+    private final ImageView mSearchHintIcon;
+    private final View mDropDownAnchor;
+    private final int mSearchIconResId;
+
+    // Resources used by SuggestionsAdapter to display suggestions.
+    private final int mSuggestionRowLayout;
+    private final int mSuggestionCommitIconResId;
+
+    // Intents used for voice searching.
+    private final Intent mVoiceWebSearchIntent;
+    private final Intent mVoiceAppSearchIntent;
     private OnQueryTextListener mOnQueryChangeListener;
     private OnCloseListener mOnCloseListener;
-    private View.OnFocusChangeListener mOnQueryTextFocusChangeListener;
+    private OnFocusChangeListener mOnQueryTextFocusChangeListener;
     private OnSuggestionListener mOnSuggestionListener;
     private OnClickListener mOnSearchClickListener;
 
     private boolean mIconifiedByDefault;
     private boolean mIconified;
     private CursorAdapter mSuggestionsAdapter;
-    private View mSearchButton;
-    private View mSubmitButton;
-    private View mSearchPlate;
-    private View mSubmitArea;
-    private ImageView mCloseButton;
-    private View mSearchEditFrame;
-    private View mVoiceButton;
-    private SearchAutoComplete mQueryTextView;
-    private View mDropDownAnchor;
-    private ImageView mSearchHintIcon;
     private boolean mSubmitButtonEnabled;
     private CharSequence mQueryHint;
     private boolean mQueryRefinement;
@@ -160,7 +172,7 @@
         }
     };
 
-    private Runnable mUpdateDrawableStateRunnable = new Runnable() {
+    private final Runnable mUpdateDrawableStateRunnable = new Runnable() {
         public void run() {
             updateFocusedState();
         }
@@ -174,10 +186,6 @@
         }
     };
 
-    // For voice searching
-    private final Intent mVoiceWebSearchIntent;
-    private final Intent mVoiceAppSearchIntent;
-
     // A weak map of drawables we've gotten from other packages, so we don't load them
     // more than once.
     private final WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache =
@@ -255,23 +263,42 @@
     }
 
     public SearchView(Context context, AttributeSet attrs) {
-        super(context, attrs);
+        this(context, attrs, R.attr.searchViewStyle);
+    }
 
-        LayoutInflater inflater = (LayoutInflater) context
-                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        inflater.inflate(R.layout.abc_search_view, this, true);
+    public SearchView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
 
-        mSearchButton = findViewById(R.id.search_button);
+        final TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.SearchView, defStyleAttr, 0);
+        final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+        final int layoutResId = a.getResourceId(R.styleable.SearchView_layout, 0);
+        inflater.inflate(layoutResId, this, true);
         mQueryTextView = (SearchAutoComplete) findViewById(R.id.search_src_text);
         mQueryTextView.setSearchView(this);
 
         mSearchEditFrame = findViewById(R.id.search_edit_frame);
         mSearchPlate = findViewById(R.id.search_plate);
         mSubmitArea = findViewById(R.id.submit_area);
-        mSubmitButton = findViewById(R.id.search_go_btn);
+        mSearchButton = (ImageView) findViewById(R.id.search_button);
+        mSubmitButton = (ImageView) findViewById(R.id.search_go_btn);
         mCloseButton = (ImageView) findViewById(R.id.search_close_btn);
-        mVoiceButton = findViewById(R.id.search_voice_btn);
+        mVoiceButton = (ImageView) findViewById(R.id.search_voice_btn);
         mSearchHintIcon = (ImageView) findViewById(R.id.search_mag_icon);
+        // Set up icons and backgrounds.
+        mSearchPlate.setBackgroundDrawable(a.getDrawable(R.styleable.SearchView_queryBackground));
+        mSubmitArea.setBackgroundDrawable(a.getDrawable(R.styleable.SearchView_submitBackground));
+        mSearchIconResId = a.getResourceId(R.styleable.SearchView_searchIcon, 0);
+        mSearchButton.setImageResource(mSearchIconResId);
+        mSubmitButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_goIcon));
+        mCloseButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_closeIcon));
+        mVoiceButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_voiceIcon));
+        mSearchHintIcon.setImageDrawable(a.getDrawable(R.styleable.SearchView_searchIcon));
+
+        // Extract dropdown layout resource IDs for later use.
+        mSuggestionRowLayout = a.getResourceId(R.styleable.SearchView_suggestionRowLayout, 0);
+        mSuggestionCommitIconResId = a.getResourceId(R.styleable.SearchView_commitIcon, 0);
 
         mSearchButton.setOnClickListener(mOnClickListener);
         mCloseButton.setOnClickListener(mOnClickListener);
@@ -293,35 +320,27 @@
                 }
             }
         });
-
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SearchView, 0, 0);
         setIconifiedByDefault(a.getBoolean(R.styleable.SearchView_iconifiedByDefault, true));
-        int maxWidth = a.getDimensionPixelSize(R.styleable.SearchView_android_maxWidth, -1);
+
+        final int maxWidth = a.getDimensionPixelSize(R.styleable.SearchView_android_maxWidth, -1);
         if (maxWidth != -1) {
             setMaxWidth(maxWidth);
         }
-        CharSequence queryHint = a.getText(R.styleable.SearchView_queryHint);
+        final CharSequence queryHint = a.getText(R.styleable.SearchView_queryHint);
         if (!TextUtils.isEmpty(queryHint)) {
             setQueryHint(queryHint);
         }
-        int imeOptions = a.getInt(R.styleable.SearchView_android_imeOptions, -1);
+        final int imeOptions = a.getInt(R.styleable.SearchView_android_imeOptions, -1);
         if (imeOptions != -1) {
             setImeOptions(imeOptions);
         }
-        int inputType = a.getInt(R.styleable.SearchView_android_inputType, -1);
+        final int inputType = a.getInt(R.styleable.SearchView_android_inputType, -1);
         if (inputType != -1) {
             setInputType(inputType);
         }
 
         a.recycle();
 
-        boolean focusable = true;
-
-        a = context.obtainStyledAttributes(attrs, R.styleable.View, 0, 0);
-        focusable = a.getBoolean(R.styleable.View_android_focusable, focusable);
-        a.recycle();
-        setFocusable(focusable);
-
         // Save voice intent for later queries/launching
         mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
         mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -344,6 +363,7 @@
         updateQueryHint();
     }
 
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
     private void addOnLayoutChangeListenerToDropDownAnchorSDK11() {
         mDropDownAnchor.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
             @Override
@@ -364,6 +384,14 @@
                 });
     }
 
+    int getSuggestionRowLayout() {
+        return mSuggestionRowLayout;
+    }
+
+    int getSuggestionCommitIconResId() {
+        return mSuggestionCommitIconResId;
+    }
+
     /**
      * Sets the SearchableInfo for this SearchView. Properties in the SearchableInfo are used
      * to display labels, hints, suggestions, create intents for launching search results screens
@@ -375,11 +403,13 @@
     public void setSearchableInfo(SearchableInfo searchable) {
         mSearchable = searchable;
         if (mSearchable != null) {
-            updateSearchAutoComplete();
+            if (IS_AT_LEAST_FROYO) {
+                updateSearchAutoComplete();
+            }
             updateQueryHint();
         }
         // Cache the voice search capability
-        mVoiceButtonEnabled = hasVoiceSearch();
+        mVoiceButtonEnabled = IS_AT_LEAST_FROYO && hasVoiceSearch();
 
         if (mVoiceButtonEnabled) {
             // Disable the microphone on the keyboard, as a mic is displayed near the text box
@@ -403,8 +433,6 @@
      *
      * @see TextView#setImeOptions(int)
      * @param imeOptions the options to set on the query text field
-     *
-     * @attr ref android.R.styleable#SearchView_imeOptions
      */
     public void setImeOptions(int imeOptions) {
         mQueryTextView.setImeOptions(imeOptions);
@@ -414,8 +442,6 @@
      * Returns the IME options set on the query text field.
      * @return the ime options
      * @see TextView#setImeOptions(int)
-     *
-     * @attr ref android.R.styleable#SearchView_imeOptions
      */
     public int getImeOptions() {
         return mQueryTextView.getImeOptions();
@@ -426,8 +452,6 @@
      *
      * @see TextView#setInputType(int)
      * @param inputType the input type to set on the query text field
-     *
-     * @attr ref android.R.styleable#SearchView_inputType
      */
     public void setInputType(int inputType) {
         mQueryTextView.setInputType(inputType);
@@ -436,8 +460,6 @@
     /**
      * Returns the input type set on the query text field.
      * @return the input type
-     *
-     * @attr ref android.R.styleable#SearchView_inputType
      */
     public int getInputType() {
         return mQueryTextView.getInputType();
@@ -556,8 +578,6 @@
      * in the SearchableInfo.
      *
      * @param hint the hint text to display
-     *
-     * @attr ref android.R.styleable#SearchView_queryHint
      */
     public void setQueryHint(CharSequence hint) {
         mQueryHint = hint;
@@ -567,13 +587,11 @@
     /**
      * Gets the hint text to display in the query text field.
      * @return the query hint text, if specified, null otherwise.
-     *
-     * @attr ref android.R.styleable#SearchView_queryHint
      */
     public CharSequence getQueryHint() {
         if (mQueryHint != null) {
             return mQueryHint;
-        } else if (mSearchable != null) {
+        } else if (IS_AT_LEAST_FROYO && mSearchable != null) {
             CharSequence hint = null;
             int hintId = mSearchable.getHintId();
             if (hintId != 0) {
@@ -593,8 +611,6 @@
      * <p>The default value is true.</p>
      *
      * @param iconified whether the search field should be iconified by default
-     *
-     * @attr ref android.R.styleable#SearchView_iconifiedByDefault
      */
     public void setIconifiedByDefault(boolean iconified) {
         if (mIconifiedByDefault == iconified) return;
@@ -606,8 +622,6 @@
     /**
      * Returns the default iconified state of the search field.
      * @return
-     *
-     * @attr ref android.R.styleable#SearchView_iconifiedByDefault
      */
     public boolean isIconfiedByDefault() {
         return mIconifiedByDefault;
@@ -715,8 +729,6 @@
 
     /**
      * Makes the view at most this many pixels wide
-     *
-     * @attr ref android.R.styleable#SearchView_maxWidth
      */
     public void setMaxWidth(int maxpixels) {
         mMaxWidth = maxpixels;
@@ -728,8 +740,6 @@
      * Gets the specified maximum width in pixels, if set. Returns zero if
      * no maximum width was specified.
      * @return the maximum width of the view
-     *
-     * @attr ref android.R.styleable#SearchView_maxWidth
      */
     public int getMaxWidth() {
         return mMaxWidth;
@@ -791,8 +801,10 @@
         updateSubmitArea();
     }
 
+    @TargetApi(Build.VERSION_CODES.FROYO)
     private boolean hasVoiceSearch() {
-        if (mSearchable != null && mSearchable.getVoiceSearchEnabled()) {
+        if (mSearchable != null &&
+                mSearchable.getVoiceSearchEnabled()) {
             Intent testIntent = null;
             if (mSearchable.getVoiceSearchLaunchWebSearch()) {
                 testIntent = mVoiceWebSearchIntent;
@@ -846,8 +858,8 @@
 
     private void updateFocusedState() {
         boolean focused = mQueryTextView.hasFocus();
-        mSearchPlate.getBackground().setState(focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET);
-        mSubmitArea.getBackground().setState(focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET);
+        mSearchPlate.getBackground().setState(focused ? ENABLED_FOCUSED_STATE_SET : EMPTY_STATE_SET);
+        mSubmitArea.getBackground().setState(focused ? ENABLED_FOCUSED_STATE_SET : EMPTY_STATE_SET);
         invalidate();
     }
 
@@ -890,7 +902,9 @@
             } else if (v == mSubmitButton) {
                 onSubmitQuery();
             } else if (v == mVoiceButton) {
-                onVoiceClicked();
+                if (IS_AT_LEAST_FROYO) {
+                    onVoiceClicked();
+                }
             } else if (v == mQueryTextView) {
                 forceSuggestionQuery();
             }
@@ -898,24 +912,6 @@
     };
 
     /**
-     * Handles the key down event for dealing with action keys.
-     *
-     * @param keyCode This is the keycode of the typed key, and is the same value as
-     *        found in the KeyEvent parameter.
-     * @param event The complete event record for the typed key
-     *
-     * @return true if the event was handled here, or false if not.
-     */
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (mSearchable == null) {
-            return false;
-        }
-
-        return super.onKeyDown(keyCode, event);
-    }
-
-    /**
      * React to the user typing "enter" or other hardwired keys while typing in
      * the search box. This handles these special keys while the edit box has
      * focus.
@@ -984,6 +980,8 @@
             if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
                 // give "focus" to text editor, with cursor at the beginning if
                 // left key, at end if right key
+                // TODO: Reverse left/right for right-to-left languages, e.g.
+                // Arabic
                 int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? 0 : mQueryTextView
                         .length();
                 mQueryTextView.setSelection(selPoint);
@@ -1004,21 +1002,18 @@
         return false;
     }
 
-    private int getSearchIconId() {
-        TypedValue outValue = new TypedValue();
-        getContext().getTheme().resolveAttribute(R.attr.searchViewSearchIcon, outValue, true);
-        return outValue.resourceId;
-    }
-
     private CharSequence getDecoratedHint(CharSequence hintText) {
         // If the field is always expanded, then don't add the search icon to the hint
-        if (!mIconifiedByDefault) return hintText;
+        if (!mIconifiedByDefault) {
+            return hintText;
+        }
 
-        SpannableStringBuilder ssb = new SpannableStringBuilder("   "); // for the icon
-        ssb.append(hintText);
-        Drawable searchIcon = getContext().getResources().getDrawable(getSearchIconId());
-        int textSize = (int) (mQueryTextView.getTextSize() * 1.25);
+        final Drawable searchIcon = ContextCompat.getDrawable(getContext(), mSearchIconResId);
+        final int textSize = (int) (mQueryTextView.getTextSize() * 1.25);
         searchIcon.setBounds(0, 0, textSize, textSize);
+
+        final SpannableStringBuilder ssb = new SpannableStringBuilder("   "); // for the icon
+        ssb.append(hintText);
         ssb.setSpan(new ImageSpan(searchIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
         return ssb;
     }
@@ -1026,7 +1021,7 @@
     private void updateQueryHint() {
         if (mQueryHint != null) {
             mQueryTextView.setHint(getDecoratedHint(mQueryHint));
-        } else if (mSearchable != null) {
+        } else if (IS_AT_LEAST_FROYO && mSearchable != null) {
             CharSequence hint = null;
             int hintId = mSearchable.getHintId();
             if (hintId != 0) {
@@ -1043,6 +1038,7 @@
     /**
      * Updates the auto-complete text view.
      */
+    @TargetApi(Build.VERSION_CODES.FROYO)
     private void updateSearchAutoComplete() {
         mQueryTextView.setThreshold(mSearchable.getSuggestThreshold());
         mQueryTextView.setImeOptions(mSearchable.getImeOptions());
@@ -1127,8 +1123,8 @@
                     || !mOnQueryChangeListener.onQueryTextSubmit(query.toString())) {
                 if (mSearchable != null) {
                     launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, query.toString());
-                    setImeVisibility(false);
                 }
+                setImeVisibility(false);
                 dismissSuggestions();
             }
         }
@@ -1167,6 +1163,7 @@
         }
     }
 
+    @TargetApi(Build.VERSION_CODES.FROYO)
     private void onVoiceClicked() {
         // guard against possible race conditions
         if (mSearchable == null) {
@@ -1212,6 +1209,7 @@
      */
     @Override
     public void onActionViewCollapsed() {
+        setQuery("", false);
         clearFocus();
         updateViewsVisibility(true);
         mQueryTextView.setImeOptions(mCollapsedImeOptions);
@@ -1238,12 +1236,18 @@
             Resources res = getContext().getResources();
             int anchorPadding = mSearchPlate.getPaddingLeft();
             Rect dropDownPadding = new Rect();
+            final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this);
             int iconOffset = mIconifiedByDefault
                     ? res.getDimensionPixelSize(R.dimen.abc_dropdownitem_icon_width)
                     + res.getDimensionPixelSize(R.dimen.abc_dropdownitem_text_padding_left)
                     : 0;
             mQueryTextView.getDropDownBackground().getPadding(dropDownPadding);
-            int offset = anchorPadding - (dropDownPadding.left + iconOffset);
+            int offset;
+            if (isLayoutRtl) {
+                offset = - dropDownPadding.left;
+            } else {
+                offset = anchorPadding - (dropDownPadding.left + iconOffset);
+            }
             mQueryTextView.setDropDownHorizontalOffset(offset);
             final int width = mDropDownAnchor.getWidth() + dropDownPadding.left
                     + dropDownPadding.right + iconOffset - anchorPadding;
@@ -1420,13 +1424,16 @@
             intent.putExtra(SearchManager.ACTION_KEY, actionKey);
             intent.putExtra(SearchManager.ACTION_MSG, actionMsg);
         }
-        intent.setComponent(mSearchable.getSearchActivity());
+        if (IS_AT_LEAST_FROYO) {
+            intent.setComponent(mSearchable.getSearchActivity());
+        }
         return intent;
     }
 
     /**
      * Create and return an Intent that can launch the voice search activity for web search.
      */
+    @TargetApi(Build.VERSION_CODES.FROYO)
     private Intent createVoiceWebSearchIntent(Intent baseIntent, SearchableInfo searchable) {
         Intent voiceIntent = new Intent(baseIntent);
         ComponentName searchActivity = searchable.getSearchActivity();
@@ -1442,6 +1449,7 @@
      * @param baseIntent The voice app search intent to start from
      * @return A completely-configured intent ready to send to the voice search activity
      */
+    @TargetApi(Build.VERSION_CODES.FROYO)
     private Intent createVoiceAppSearchIntent(Intent baseIntent, SearchableInfo searchable) {
         ComponentName searchActivity = searchable.getSearchActivity();
 
@@ -1473,18 +1481,20 @@
         String language = null;
         int maxResults = 1;
 
-        Resources resources = getResources();
-        if (searchable.getVoiceLanguageModeId() != 0) {
-            languageModel = resources.getString(searchable.getVoiceLanguageModeId());
-        }
-        if (searchable.getVoicePromptTextId() != 0) {
-            prompt = resources.getString(searchable.getVoicePromptTextId());
-        }
-        if (searchable.getVoiceLanguageId() != 0) {
-            language = resources.getString(searchable.getVoiceLanguageId());
-        }
-        if (searchable.getVoiceMaxResults() != 0) {
-            maxResults = searchable.getVoiceMaxResults();
+        if (Build.VERSION.SDK_INT >= 8) {
+            Resources resources = getResources();
+            if (searchable.getVoiceLanguageModeId() != 0) {
+                languageModel = resources.getString(searchable.getVoiceLanguageModeId());
+            }
+            if (searchable.getVoicePromptTextId() != 0) {
+                prompt = resources.getString(searchable.getVoicePromptTextId());
+            }
+            if (searchable.getVoiceLanguageId() != 0) {
+                language = resources.getString(searchable.getVoiceLanguageId());
+            }
+            if (searchable.getVoiceMaxResults() != 0) {
+                maxResults = searchable.getVoiceMaxResults();
+            }
         }
         voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel);
         voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
@@ -1518,7 +1528,7 @@
             // use specific action if supplied, or default action if supplied, or fixed default
             String action = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
 
-            if (action == null) {
+            if (action == null && Build.VERSION.SDK_INT >= 8) {
                 action = mSearchable.getSuggestIntentAction();
             }
             if (action == null) {
@@ -1527,7 +1537,7 @@
 
             // use specific data if supplied, or default data if supplied
             String data = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA);
-            if (data == null) {
+            if (IS_AT_LEAST_FROYO && data == null) {
                 data = mSearchable.getSuggestIntentData();
             }
             // then, if an ID was provided, append it.
diff --git a/v7/appcompat/src/android/support/v7/widget/ShareActionProvider.java b/v7/appcompat/src/android/support/v7/widget/ShareActionProvider.java
index 6448e07..c6d9cbd 100644
--- a/v7/appcompat/src/android/support/v7/widget/ShareActionProvider.java
+++ b/v7/appcompat/src/android/support/v7/widget/ShareActionProvider.java
@@ -21,6 +21,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
+import android.support.v4.content.ContextCompat;
 import android.support.v4.view.ActionProvider;
 import android.support.v7.appcompat.R;
 import android.support.v7.internal.widget.ActivityChooserModel;
@@ -184,7 +185,7 @@
         // Lookup and set the expand action icon.
         TypedValue outTypedValue = new TypedValue();
         mContext.getTheme().resolveAttribute(R.attr.actionModeShareDrawable, outTypedValue, true);
-        Drawable drawable = mContext.getResources().getDrawable(outTypedValue.resourceId);
+        Drawable drawable = ContextCompat.getDrawable(mContext, outTypedValue.resourceId);
         activityChooserView.setExpandActivityOverflowButtonDrawable(drawable);
         activityChooserView.setProvider(this);
 
diff --git a/v7/appcompat/src/android/support/v7/widget/Toolbar.java b/v7/appcompat/src/android/support/v7/widget/Toolbar.java
new file mode 100644
index 0000000..5a4f2c0
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/Toolbar.java
@@ -0,0 +1,1830 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.MarginLayoutParamsCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.app.ActionBar;
+import android.support.v7.appcompat.R;
+import android.support.v7.internal.view.SupportMenuInflater;
+import android.support.v7.internal.view.menu.MenuBuilder;
+import android.support.v7.internal.view.menu.MenuItemImpl;
+import android.support.v7.internal.view.menu.MenuPresenter;
+import android.support.v7.internal.view.menu.MenuView;
+import android.support.v7.internal.view.menu.SubMenuBuilder;
+import android.support.v7.internal.widget.DecorToolbar;
+import android.support.v7.internal.widget.RtlSpacingHelper;
+import android.support.v7.internal.widget.ToolbarWidgetWrapper;
+import android.support.v7.internal.widget.ViewUtils;
+import android.support.v7.view.CollapsibleActionView;
+import android.text.Layout;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A standard toolbar for use within application content.
+ *
+ * <p>A Toolbar is a generalization of {@link ActionBar action bars} for use
+ * within application layouts. While an action bar is traditionally part of an
+ * {@link android.app.Activity Activity's} opaque window decor controlled by the framework,
+ * a Toolbar may be placed at any arbitrary level of nesting within a view hierarchy.
+ * An application may choose to designate a Toolbar as the action bar for an Activity
+ * using the {@link android.support.v7.app.ActionBarActivity#setSupportActionBar(Toolbar)
+ * setSupportActionBar()} method.</p>
+ *
+ * <p>Toolbar supports a more focused feature set than ActionBar. From start to end, a toolbar
+ * may contain a combination of the following optional elements:
+ *
+ * <ul>
+ *     <li><em>A navigation button.</em> This may be an Up arrow, navigation menu toggle, close,
+ *     collapse, done or another glyph of the app's choosing. This button should always be used
+ *     to access other navigational destinations within the container of the Toolbar and
+ *     its signified content or otherwise leave the current context signified by the Toolbar.</li>
+ *     <li><em>A branded logo image.</em> This may extend to the height of the bar and can be
+ *     arbitrarily wide.</li>
+ *     <li><em>A title and subtitle.</em> The title should be a signpost for the Toolbar's current
+ *     position in the navigation hierarchy and the content contained there. The subtitle,
+ *     if present should indicate any extended information about the current content.
+ *     If an app uses a logo image it should strongly consider omitting a title and subtitle.</li>
+ *     <li><em>One or more custom views.</em> The application may add arbitrary child views
+ *     to the Toolbar. They will appear at this position within the layout. If a child view's
+ *     {@link LayoutParams} indicates a {@link Gravity} value of
+ *     {@link Gravity#CENTER_HORIZONTAL CENTER_HORIZONTAL} the view will attempt to center
+ *     within the available space remaining in the Toolbar after all other elements have been
+ *     measured.</li>
+ *     <li><em>An {@link ActionMenuView action menu}.</em> The menu of actions will pin to the
+ *     end of the Toolbar offering a few
+ *     <a href="http://developer.android.com/design/patterns/actionbar.html#ActionButtons">
+ *         frequent, important or typical</a> actions along with an optional overflow menu for
+ *         additional actions.</li>
+ * </ul>
+ * </p>
+ *
+ * <p>In modern Android UIs developers should lean more on a visually distinct color scheme for
+ * toolbars than on their application icon. The use of application icon plus title as a standard
+ * layout is discouraged on API 21 devices and newer.</p>
+ */
+public class Toolbar extends ViewGroup {
+    private static final String TAG = "Toolbar";
+
+    private ActionMenuView mMenuView;
+    private TextView mTitleTextView;
+    private TextView mSubtitleTextView;
+    private ImageButton mNavButtonView;
+    private ImageView mLogoView;
+
+    private Drawable mCollapseIcon;
+    private ImageButton mCollapseButtonView;
+    View mExpandedActionView;
+
+    /** Context against which to inflate popup menus. */
+    private Context mPopupContext;
+
+    /** Theme resource against which to inflate popup menus. */
+    private int mPopupTheme;
+
+    private int mTitleTextAppearance;
+    private int mSubtitleTextAppearance;
+
+    private int mButtonGravity;
+
+    private int mMaxButtonHeight;
+
+    private int mTitleMarginStart;
+    private int mTitleMarginEnd;
+    private int mTitleMarginTop;
+    private int mTitleMarginBottom;
+
+    private final RtlSpacingHelper mContentInsets = new RtlSpacingHelper();
+
+    private int mGravity = GravityCompat.START | Gravity.CENTER_VERTICAL;
+
+    private CharSequence mTitleText;
+    private CharSequence mSubtitleText;
+
+    private int mTitleTextColor;
+    private int mSubtitleTextColor;
+
+    // Clear me after use.
+    private final ArrayList<View> mTempViews = new ArrayList<View>();
+
+    private final int[] mTempMargins = new int[2];
+
+    private OnMenuItemClickListener mOnMenuItemClickListener;
+
+    private final ActionMenuView.OnMenuItemClickListener mMenuViewItemClickListener =
+            new ActionMenuView.OnMenuItemClickListener() {
+                @Override
+                public boolean onMenuItemClick(MenuItem item) {
+                    if (mOnMenuItemClickListener != null) {
+                        return mOnMenuItemClickListener.onMenuItemClick(item);
+                    }
+                    return false;
+                }
+            };
+
+    private ToolbarWidgetWrapper mWrapper;
+    private ActionMenuPresenter mOuterActionMenuPresenter;
+    private ExpandedActionViewMenuPresenter mExpandedMenuPresenter;
+
+    private boolean mCollapsible;
+
+    public Toolbar(Context context) {
+        this(context, null);
+    }
+
+    public Toolbar(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.toolbarStyle);
+    }
+
+    public Toolbar(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Toolbar,
+                defStyleAttr, 0);
+
+        mTitleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, 0);
+        mSubtitleTextAppearance = a.getResourceId(R.styleable.Toolbar_subtitleTextAppearance, 0);
+        mGravity = a.getInteger(R.styleable.Toolbar_android_gravity, mGravity);
+        mButtonGravity = a.getInteger(R.styleable.Toolbar_buttonGravity, Gravity.TOP);
+        mTitleMarginStart = mTitleMarginEnd = mTitleMarginTop = mTitleMarginBottom =
+                a.getDimensionPixelOffset(R.styleable.Toolbar_titleMargins, 0);
+
+        final int marginStart = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginStart, -1);
+        if (marginStart >= 0) {
+            mTitleMarginStart = marginStart;
+        }
+
+        final int marginEnd = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginEnd, -1);
+        if (marginEnd >= 0) {
+            mTitleMarginEnd = marginEnd;
+        }
+
+        final int marginTop = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginTop, -1);
+        if (marginTop >= 0) {
+            mTitleMarginTop = marginTop;
+        }
+
+        final int marginBottom = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginBottom,
+                -1);
+        if (marginBottom >= 0) {
+            mTitleMarginBottom = marginBottom;
+        }
+
+        mMaxButtonHeight = a.getDimensionPixelSize(R.styleable.Toolbar_maxButtonHeight, -1);
+
+        final int contentInsetStart =
+                a.getDimensionPixelOffset(R.styleable.Toolbar_contentInsetStart,
+                        RtlSpacingHelper.UNDEFINED);
+        final int contentInsetEnd =
+                a.getDimensionPixelOffset(R.styleable.Toolbar_contentInsetEnd,
+                        RtlSpacingHelper.UNDEFINED);
+        final int contentInsetLeft =
+                a.getDimensionPixelSize(R.styleable.Toolbar_contentInsetLeft, 0);
+        final int contentInsetRight =
+                a.getDimensionPixelSize(R.styleable.Toolbar_contentInsetRight, 0);
+
+        mContentInsets.setAbsolute(contentInsetLeft, contentInsetRight);
+
+        if (contentInsetStart != RtlSpacingHelper.UNDEFINED ||
+                contentInsetEnd != RtlSpacingHelper.UNDEFINED) {
+            mContentInsets.setRelative(contentInsetStart, contentInsetEnd);
+        }
+
+        mCollapseIcon = a.getDrawable(R.styleable.Toolbar_collapseIcon);
+
+        final CharSequence title = a.getText(R.styleable.Toolbar_title);
+        if (!TextUtils.isEmpty(title)) {
+            setTitle(title);
+        }
+
+        final CharSequence subtitle = a.getText(R.styleable.Toolbar_subtitle);
+        if (!TextUtils.isEmpty(subtitle)) {
+            setSubtitle(subtitle);
+        }
+        // Set the default context, since setPopupTheme() may be a no-op.
+        mPopupContext = getContext();
+        setPopupTheme(a.getResourceId(R.styleable.Toolbar_popupTheme, 0));
+        a.recycle();
+
+    }
+
+    /**
+     * Specifies the theme to use when inflating popup menus. By default, uses
+     * the same theme as the toolbar itself.
+     *
+     * @param resId theme used to inflate popup menus
+     * @see #getPopupTheme()
+     */
+    public void setPopupTheme(int resId) {
+        if (mPopupTheme != resId) {
+            mPopupTheme = resId;
+            if (resId == 0) {
+                mPopupContext = getContext();
+            } else {
+                mPopupContext = new ContextThemeWrapper(getContext(), resId);
+            }
+        }
+    }
+
+    /**
+     * @return resource identifier of the theme used to inflate popup menus, or
+     *         0 if menus are inflated against the toolbar theme
+     * @see #setPopupTheme(int)
+     */
+    public int getPopupTheme() {
+        return mPopupTheme;
+    }
+
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        if (Build.VERSION.SDK_INT >= 17) {
+            super.onRtlPropertiesChanged(layoutDirection);
+        }
+        mContentInsets.setDirection(layoutDirection ==  ViewCompat.LAYOUT_DIRECTION_RTL);
+    }
+
+    /**
+     * Set a logo drawable from a resource id.
+     *
+     * <p>This drawable should generally take the place of title text. The logo cannot be
+     * clicked. Apps using a logo should also supply a description using
+     * {@link #setLogoDescription(int)}.</p>
+     *
+     * @param resId ID of a drawable resource
+     */
+    public void setLogo(int resId) {
+        setLogo(ContextCompat.getDrawable(getContext(), resId));
+    }
+
+    /** @hide */
+    public boolean canShowOverflowMenu() {
+        return getVisibility() == VISIBLE && mMenuView != null && mMenuView.isOverflowReserved();
+    }
+
+    /**
+     * Check whether the overflow menu is currently showing. This may not reflect
+     * a pending show operation in progress.
+     *
+     * @return true if the overflow menu is currently showing
+     */
+    public boolean isOverflowMenuShowing() {
+        return mMenuView != null && mMenuView.isOverflowMenuShowing();
+    }
+
+    /** @hide */
+    public boolean isOverflowMenuShowPending() {
+        return mMenuView != null && mMenuView.isOverflowMenuShowPending();
+    }
+
+    /**
+     * Show the overflow items from the associated menu.
+     *
+     * @return true if the menu was able to be shown, false otherwise
+     */
+    public boolean showOverflowMenu() {
+        return mMenuView != null && mMenuView.showOverflowMenu();
+    }
+
+    /**
+     * Hide the overflow items from the associated menu.
+     *
+     * @return true if the menu was able to be hidden, false otherwise
+     */
+    public boolean hideOverflowMenu() {
+        return mMenuView != null && mMenuView.hideOverflowMenu();
+    }
+
+    /** @hide */
+    public void setMenu(MenuBuilder menu, ActionMenuPresenter outerPresenter) {
+        if (menu == null && mMenuView == null) {
+            return;
+        }
+
+        ensureMenuView();
+        final MenuBuilder oldMenu = mMenuView.peekMenu();
+        if (oldMenu == menu) {
+            return;
+        }
+
+        if (oldMenu != null) {
+            oldMenu.removeMenuPresenter(mOuterActionMenuPresenter);
+            oldMenu.removeMenuPresenter(mExpandedMenuPresenter);
+        }
+
+        if (mExpandedMenuPresenter == null) {
+            mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
+        }
+
+        outerPresenter.setExpandedActionViewsExclusive(true);
+        if (menu != null) {
+            menu.addMenuPresenter(outerPresenter, mPopupContext);
+            menu.addMenuPresenter(mExpandedMenuPresenter, mPopupContext);
+        } else {
+            outerPresenter.initForMenu(mPopupContext, null);
+            mExpandedMenuPresenter.initForMenu(mPopupContext, null);
+            outerPresenter.updateMenuView(true);
+            mExpandedMenuPresenter.updateMenuView(true);
+        }
+        mMenuView.setPopupTheme(mPopupTheme);
+        mMenuView.setPresenter(outerPresenter);
+        mOuterActionMenuPresenter = outerPresenter;
+    }
+
+    /**
+     * Dismiss all currently showing popup menus, including overflow or submenus.
+     */
+    public void dismissPopupMenus() {
+        if (mMenuView != null) {
+            mMenuView.dismissPopupMenus();
+        }
+    }
+
+    /** @hide */
+    public boolean isTitleTruncated() {
+        if (mTitleTextView == null) {
+            return false;
+        }
+
+        final Layout titleLayout = mTitleTextView.getLayout();
+        if (titleLayout == null) {
+            return false;
+        }
+
+        final int lineCount = titleLayout.getLineCount();
+        for (int i = 0; i < lineCount; i++) {
+            if (titleLayout.getEllipsisCount(i) > 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Set a logo drawable.
+     *
+     * <p>This drawable should generally take the place of title text. The logo cannot be
+     * clicked. Apps using a logo should also supply a description using
+     * {@link #setLogoDescription(int)}.</p>
+     *
+     * @param drawable Drawable to use as a logo
+     */
+    public void setLogo(Drawable drawable) {
+        if (drawable != null) {
+            ensureLogoView();
+            if (mLogoView.getParent() == null) {
+                addSystemView(mLogoView);
+            }
+        } else if (mLogoView != null && mLogoView.getParent() != null) {
+            removeView(mLogoView);
+        }
+        if (mLogoView != null) {
+            mLogoView.setImageDrawable(drawable);
+        }
+    }
+
+    /**
+     * Return the current logo drawable.
+     *
+     * @return The current logo drawable
+     * @see #setLogo(int)
+     * @see #setLogo(android.graphics.drawable.Drawable)
+     */
+    public Drawable getLogo() {
+        return mLogoView != null ? mLogoView.getDrawable() : null;
+    }
+
+    /**
+     * Set a description of the toolbar's logo.
+     *
+     * <p>This description will be used for accessibility or other similar descriptions
+     * of the UI.</p>
+     *
+     * @param resId String resource id
+     */
+    public void setLogoDescription(int resId) {
+        setLogoDescription(getContext().getText(resId));
+    }
+
+    /**
+     * Set a description of the toolbar's logo.
+     *
+     * <p>This description will be used for accessibility or other similar descriptions
+     * of the UI.</p>
+     *
+     * @param description Description to set
+     */
+    public void setLogoDescription(CharSequence description) {
+        if (!TextUtils.isEmpty(description)) {
+            ensureLogoView();
+        }
+        if (mLogoView != null) {
+            mLogoView.setContentDescription(description);
+        }
+    }
+
+    /**
+     * Return the description of the toolbar's logo.
+     *
+     * @return A description of the logo
+     */
+    public CharSequence getLogoDescription() {
+        return mLogoView != null ? mLogoView.getContentDescription() : null;
+    }
+
+    private void ensureLogoView() {
+        if (mLogoView == null) {
+            mLogoView = new ImageView(getContext());
+        }
+    }
+
+    /**
+     * Check whether this Toolbar is currently hosting an expanded action view.
+     *
+     * <p>An action view may be expanded either directly from the
+     * {@link android.view.MenuItem MenuItem} it belongs to or by user action. If the Toolbar
+     * has an expanded action view it can be collapsed using the {@link #collapseActionView()}
+     * method.</p>
+     *
+     * @return true if the Toolbar has an expanded action view
+     */
+    public boolean hasExpandedActionView() {
+        return mExpandedMenuPresenter != null &&
+                mExpandedMenuPresenter.mCurrentExpandedItem != null;
+    }
+
+    /**
+     * Collapse a currently expanded action view. If this Toolbar does not have an
+     * expanded action view this method has no effect.
+     *
+     * <p>An action view may be expanded either directly from the
+     * {@link android.view.MenuItem MenuItem} it belongs to or by user action.</p>
+     *
+     * @see #hasExpandedActionView()
+     */
+    public void collapseActionView() {
+        final MenuItemImpl item = mExpandedMenuPresenter == null ? null :
+                mExpandedMenuPresenter.mCurrentExpandedItem;
+        if (item != null) {
+            item.collapseActionView();
+        }
+    }
+
+    /**
+     * Returns the title of this toolbar.
+     *
+     * @return The current title.
+     */
+    public CharSequence getTitle() {
+        return mTitleText;
+    }
+
+    /**
+     * Set the title of this toolbar.
+     *
+     * <p>A title should be used as the anchor for a section of content. It should
+     * describe or name the content being viewed.</p>
+     *
+     * @param resId Resource ID of a string to set as the title
+     */
+    public void setTitle(int resId) {
+        setTitle(getContext().getText(resId));
+    }
+
+    /**
+     * Set the title of this toolbar.
+     *
+     * <p>A title should be used as the anchor for a section of content. It should
+     * describe or name the content being viewed.</p>
+     *
+     * @param title Title to set
+     */
+    public void setTitle(CharSequence title) {
+        if (!TextUtils.isEmpty(title)) {
+            if (mTitleTextView == null) {
+                final Context context = getContext();
+                mTitleTextView = new TextView(context);
+                mTitleTextView.setSingleLine();
+                mTitleTextView.setEllipsize(TextUtils.TruncateAt.END);
+                if (mTitleTextAppearance != 0) {
+                    mTitleTextView.setTextAppearance(context, mTitleTextAppearance);
+                }
+                if (mTitleTextColor != 0) {
+                    mTitleTextView.setTextColor(mTitleTextColor);
+                }
+            }
+            if (mTitleTextView.getParent() == null) {
+                addSystemView(mTitleTextView);
+            }
+        } else if (mTitleTextView != null && mTitleTextView.getParent() != null) {
+            removeView(mTitleTextView);
+        }
+        if (mTitleTextView != null) {
+            mTitleTextView.setText(title);
+        }
+        mTitleText = title;
+    }
+
+    /**
+     * Return the subtitle of this toolbar.
+     *
+     * @return The current subtitle
+     */
+    public CharSequence getSubtitle() {
+        return mSubtitleText;
+    }
+
+    /**
+     * Set the subtitle of this toolbar.
+     *
+     * <p>Subtitles should express extended information about the current content.</p>
+     *
+     * @param resId String resource ID
+     */
+    public void setSubtitle(int resId) {
+        setSubtitle(getContext().getText(resId));
+    }
+
+    /**
+     * Set the subtitle of this toolbar.
+     *
+     * <p>Subtitles should express extended information about the current content.</p>
+     *
+     * @param subtitle Subtitle to set
+     */
+    public void setSubtitle(CharSequence subtitle) {
+        if (!TextUtils.isEmpty(subtitle)) {
+            if (mSubtitleTextView == null) {
+                final Context context = getContext();
+                mSubtitleTextView = new TextView(context);
+                mSubtitleTextView.setSingleLine();
+                mSubtitleTextView.setEllipsize(TextUtils.TruncateAt.END);
+                if (mSubtitleTextAppearance != 0) {
+                    mSubtitleTextView.setTextAppearance(context, mSubtitleTextAppearance);
+                }
+                if (mSubtitleTextColor != 0) {
+                    mSubtitleTextView.setTextColor(mSubtitleTextColor);
+                }
+            }
+            if (mSubtitleTextView.getParent() == null) {
+                addSystemView(mSubtitleTextView);
+            }
+        } else if (mSubtitleTextView != null && mSubtitleTextView.getParent() != null) {
+            removeView(mSubtitleTextView);
+        }
+        if (mSubtitleTextView != null) {
+            mSubtitleTextView.setText(subtitle);
+        }
+        mSubtitleText = subtitle;
+    }
+
+    /**
+     * Sets the text color, size, style, hint color, and highlight color
+     * from the specified TextAppearance resource.
+     */
+    public void setTitleTextAppearance(Context context, int resId) {
+        mTitleTextAppearance = resId;
+        if (mTitleTextView != null) {
+            mTitleTextView.setTextAppearance(context, resId);
+        }
+    }
+
+    /**
+     * Sets the text color, size, style, hint color, and highlight color
+     * from the specified TextAppearance resource.
+     */
+    public void setSubtitleTextAppearance(Context context, int resId) {
+        mSubtitleTextAppearance = resId;
+        if (mSubtitleTextView != null) {
+            mSubtitleTextView.setTextAppearance(context, resId);
+        }
+    }
+
+    /**
+     * Sets the text color of the title, if present.
+     *
+     * @param color The new text color in 0xAARRGGBB format
+     */
+    public void setTitleTextColor(int color) {
+        mTitleTextColor = color;
+        if (mTitleTextView != null) {
+            mTitleTextView.setTextColor(color);
+        }
+    }
+
+    /**
+     * Sets the text color of the subtitle, if present.
+     *
+     * @param color The new text color in 0xAARRGGBB format
+     */
+    public void setSubtitleTextColor(int color) {
+        mSubtitleTextColor = color;
+        if (mSubtitleTextView != null) {
+            mSubtitleTextView.setTextColor(color);
+        }
+    }
+
+    /**
+     * Retrieve the currently configured content description for the navigation button view.
+     * This will be used to describe the navigation action to users through mechanisms such
+     * as screen readers or tooltips.
+     *
+     * @return The navigation button's content description
+     */
+    @Nullable
+    public CharSequence getNavigationContentDescription() {
+        return mNavButtonView != null ? mNavButtonView.getContentDescription() : null;
+    }
+
+    /**
+     * Set a content description for the navigation button if one is present. The content
+     * description will be read via screen readers or other accessibility systems to explain
+     * the action of the navigation button.
+     *
+     * @param resId Resource ID of a content description string to set, or 0 to
+     *              clear the description
+     */
+    public void setNavigationContentDescription(int resId) {
+        setNavigationContentDescription(resId != 0 ? getContext().getText(resId) : null);
+    }
+
+    /**
+     * Set a content description for the navigation button if one is present. The content
+     * description will be read via screen readers or other accessibility systems to explain
+     * the action of the navigation button.
+     *
+     * @param description Content description to set, or <code>null</code> to
+     *                    clear the content description
+     */
+    public void setNavigationContentDescription(@Nullable CharSequence description) {
+        if (!TextUtils.isEmpty(description)) {
+            ensureNavButtonView();
+        }
+        if (mNavButtonView != null) {
+            mNavButtonView.setContentDescription(description);
+        }
+    }
+    
+    /**
+     * Set the icon to use for the toolbar's navigation button.
+     *
+     * <p>The navigation button appears at the start of the toolbar if present. Setting an icon
+     * will make the navigation button visible.</p>
+     *
+     * <p>If you use a navigation icon you should also set a description for its action using
+     * {@link #setNavigationContentDescription(int)}. This is used for accessibility and
+     * tooltips.</p>
+     *
+     * @param resId Resource ID of a drawable to set
+     */
+    public void setNavigationIcon(int resId) {
+        setNavigationIcon(ContextCompat.getDrawable(getContext(), resId));
+    }
+
+    /**
+     * Set the icon to use for the toolbar's navigation button.
+     *
+     * <p>The navigation button appears at the start of the toolbar if present. Setting an icon
+     * will make the navigation button visible.</p>
+     *
+     * <p>If you use a navigation icon you should also set a description for its action using
+     * {@link #setNavigationContentDescription(int)}. This is used for accessibility and
+     * tooltips.</p>
+     *
+     * @param icon Drawable to set, may be null to clear the icon
+     */
+    public void setNavigationIcon(@Nullable Drawable icon) {
+        if (icon != null) {
+            ensureNavButtonView();
+            if (mNavButtonView.getParent() == null) {
+                addSystemView(mNavButtonView);
+            }
+        } else if (mNavButtonView != null && mNavButtonView.getParent() != null) {
+            removeView(mNavButtonView);
+        }
+        if (mNavButtonView != null) {
+            mNavButtonView.setImageDrawable(icon);
+        }
+    }
+
+    /**
+     * Return the current drawable used as the navigation icon.
+     *
+     * @return The navigation icon drawable
+     */
+    @Nullable
+    public Drawable getNavigationIcon() {
+        return mNavButtonView != null ? mNavButtonView.getDrawable() : null;
+    }
+
+    /**
+     * Set a listener to respond to navigation events.
+     *
+     * <p>This listener will be called whenever the user clicks the navigation button
+     * at the start of the toolbar. An icon must be set for the navigation button to appear.</p>
+     *
+     * @param listener Listener to set
+     * @see #setNavigationIcon(android.graphics.drawable.Drawable)
+     */
+    public void setNavigationOnClickListener(OnClickListener listener) {
+        ensureNavButtonView();
+        mNavButtonView.setOnClickListener(listener);
+    }
+
+    /**
+     * Return the Menu shown in the toolbar.
+     *
+     * <p>Applications that wish to populate the toolbar's menu can do so from here. To use
+     * an XML menu resource, use {@link #inflateMenu(int)}.</p>
+     *
+     * @return The toolbar's Menu
+     */
+    public Menu getMenu() {
+        ensureMenu();
+        return mMenuView.getMenu();
+    }
+
+    private void ensureMenu() {
+        ensureMenuView();
+        if (mMenuView.peekMenu() == null) {
+            // Initialize a new menu for the first time.
+            final MenuBuilder menu = (MenuBuilder) mMenuView.getMenu();
+            if (mExpandedMenuPresenter == null) {
+                mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
+            }
+            mMenuView.setExpandedActionViewsExclusive(true);
+            menu.addMenuPresenter(mExpandedMenuPresenter, mPopupContext);
+        }
+    }
+
+    private void ensureMenuView() {
+        if (mMenuView == null) {
+            mMenuView = new ActionMenuView(getContext());
+            mMenuView.setPopupTheme(mPopupTheme);
+            mMenuView.setOnMenuItemClickListener(mMenuViewItemClickListener);
+            final LayoutParams lp = generateDefaultLayoutParams();
+            lp.gravity = GravityCompat.END | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
+            mMenuView.setLayoutParams(lp);
+            addSystemView(mMenuView);
+        }
+    }
+
+    private MenuInflater getMenuInflater() {
+        return new SupportMenuInflater(getContext());
+    }
+
+    /**
+     * Inflate a menu resource into this toolbar.
+     *
+     * <p>Inflate an XML menu resource into this toolbar. Existing items in the menu will not
+     * be modified or removed.</p>
+     *
+     * @param resId ID of a menu resource to inflate
+     */
+    public void inflateMenu(int resId) {
+        getMenuInflater().inflate(resId, getMenu());
+    }
+
+    /**
+     * Set a listener to respond to menu item click events.
+     *
+     * <p>This listener will be invoked whenever a user selects a menu item from
+     * the action buttons presented at the end of the toolbar or the associated overflow.</p>
+     *
+     * @param listener Listener to set
+     */
+    public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
+        mOnMenuItemClickListener = listener;
+    }
+
+    /**
+     * Set the content insets for this toolbar relative to layout direction.
+     *
+     * <p>The content inset affects the valid area for Toolbar content other than
+     * the navigation button and menu. Insets define the minimum margin for these components
+     * and can be used to effectively align Toolbar content along well-known gridlines.</p>
+     *
+     * @param contentInsetStart Content inset for the toolbar starting edge
+     * @param contentInsetEnd Content inset for the toolbar ending edge
+     *
+     * @see #setContentInsetsAbsolute(int, int)
+     * @see #getContentInsetStart()
+     * @see #getContentInsetEnd()
+     * @see #getContentInsetLeft()
+     * @see #getContentInsetRight()
+     */
+    public void setContentInsetsRelative(int contentInsetStart, int contentInsetEnd) {
+        mContentInsets.setRelative(contentInsetStart, contentInsetEnd);
+    }
+
+    /**
+     * Get the starting content inset for this toolbar.
+     *
+     * <p>The content inset affects the valid area for Toolbar content other than
+     * the navigation button and menu. Insets define the minimum margin for these components
+     * and can be used to effectively align Toolbar content along well-known gridlines.</p>
+     *
+     * @return The starting content inset for this toolbar
+     *
+     * @see #setContentInsetsRelative(int, int)
+     * @see #setContentInsetsAbsolute(int, int)
+     * @see #getContentInsetEnd()
+     * @see #getContentInsetLeft()
+     * @see #getContentInsetRight()
+     */
+    public int getContentInsetStart() {
+        return mContentInsets.getStart();
+    }
+
+    /**
+     * Get the ending content inset for this toolbar.
+     *
+     * <p>The content inset affects the valid area for Toolbar content other than
+     * the navigation button and menu. Insets define the minimum margin for these components
+     * and can be used to effectively align Toolbar content along well-known gridlines.</p>
+     *
+     * @return The ending content inset for this toolbar
+     *
+     * @see #setContentInsetsRelative(int, int)
+     * @see #setContentInsetsAbsolute(int, int)
+     * @see #getContentInsetStart()
+     * @see #getContentInsetLeft()
+     * @see #getContentInsetRight()
+     */
+    public int getContentInsetEnd() {
+        return mContentInsets.getEnd();
+    }
+
+    /**
+     * Set the content insets for this toolbar.
+     *
+     * <p>The content inset affects the valid area for Toolbar content other than
+     * the navigation button and menu. Insets define the minimum margin for these components
+     * and can be used to effectively align Toolbar content along well-known gridlines.</p>
+     *
+     * @param contentInsetLeft Content inset for the toolbar's left edge
+     * @param contentInsetRight Content inset for the toolbar's right edge
+     *
+     * @see #setContentInsetsAbsolute(int, int)
+     * @see #getContentInsetStart()
+     * @see #getContentInsetEnd()
+     * @see #getContentInsetLeft()
+     * @see #getContentInsetRight()
+     */
+    public void setContentInsetsAbsolute(int contentInsetLeft, int contentInsetRight) {
+        mContentInsets.setAbsolute(contentInsetLeft, contentInsetRight);
+    }
+
+    /**
+     * Get the left content inset for this toolbar.
+     *
+     * <p>The content inset affects the valid area for Toolbar content other than
+     * the navigation button and menu. Insets define the minimum margin for these components
+     * and can be used to effectively align Toolbar content along well-known gridlines.</p>
+     *
+     * @return The left content inset for this toolbar
+     *
+     * @see #setContentInsetsRelative(int, int)
+     * @see #setContentInsetsAbsolute(int, int)
+     * @see #getContentInsetStart()
+     * @see #getContentInsetEnd()
+     * @see #getContentInsetRight()
+     */
+    public int getContentInsetLeft() {
+        return mContentInsets.getLeft();
+    }
+
+    /**
+     * Get the right content inset for this toolbar.
+     *
+     * <p>The content inset affects the valid area for Toolbar content other than
+     * the navigation button and menu. Insets define the minimum margin for these components
+     * and can be used to effectively align Toolbar content along well-known gridlines.</p>
+     *
+     * @return The right content inset for this toolbar
+     *
+     * @see #setContentInsetsRelative(int, int)
+     * @see #setContentInsetsAbsolute(int, int)
+     * @see #getContentInsetStart()
+     * @see #getContentInsetEnd()
+     * @see #getContentInsetLeft()
+     */
+    public int getContentInsetRight() {
+        return mContentInsets.getRight();
+    }
+
+    private void ensureNavButtonView() {
+        if (mNavButtonView == null) {
+            mNavButtonView = new ImageButton(getContext(), null,
+                    R.attr.toolbarNavigationButtonStyle);
+            final LayoutParams lp = generateDefaultLayoutParams();
+            lp.gravity = GravityCompat.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
+            mNavButtonView.setLayoutParams(lp);
+        }
+    }
+
+    private void ensureCollapseButtonView() {
+        if (mCollapseButtonView == null) {
+            mCollapseButtonView = new ImageButton(getContext(), null,
+                    R.attr.toolbarNavigationButtonStyle);
+            mCollapseButtonView.setImageDrawable(mCollapseIcon);
+            final LayoutParams lp = generateDefaultLayoutParams();
+            lp.gravity = GravityCompat.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
+            lp.mViewType = LayoutParams.EXPANDED;
+            mCollapseButtonView.setLayoutParams(lp);
+            mCollapseButtonView.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    collapseActionView();
+                }
+            });
+        }
+    }
+
+    private void addSystemView(View v) {
+        final LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT,
+                LayoutParams.WRAP_CONTENT);
+        lp.mViewType = LayoutParams.SYSTEM;
+        addView(v, lp);
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        SavedState state = new SavedState(super.onSaveInstanceState());
+        return state;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        final SavedState ss = (SavedState) state;
+        super.onRestoreInstanceState(ss.getSuperState());
+    }
+
+    private void measureChildConstrained(View child, int parentWidthSpec, int widthUsed,
+            int parentHeightSpec, int heightUsed, int heightConstraint) {
+        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+
+        int childWidthSpec = getChildMeasureSpec(parentWidthSpec,
+                getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin
+                        + widthUsed, lp.width);
+        int childHeightSpec = getChildMeasureSpec(parentHeightSpec,
+                getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin
+                        + heightUsed, lp.height);
+
+        final int childHeightMode = MeasureSpec.getMode(childHeightSpec);
+        if (childHeightMode != MeasureSpec.EXACTLY && heightConstraint >= 0) {
+            final int size = childHeightMode != MeasureSpec.UNSPECIFIED ?
+                    Math.min(MeasureSpec.getSize(childHeightSpec), heightConstraint) :
+                    heightConstraint;
+            childHeightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
+        }
+        child.measure(childWidthSpec, childHeightSpec);
+    }
+
+    /**
+     * Returns the width + uncollapsed margins
+     */
+    private int measureChildCollapseMargins(View child,
+            int parentWidthMeasureSpec, int widthUsed,
+            int parentHeightMeasureSpec, int heightUsed, int[] collapsingMargins) {
+        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+
+        final int leftDiff = lp.leftMargin - collapsingMargins[0];
+        final int rightDiff = lp.rightMargin - collapsingMargins[1];
+        final int leftMargin = Math.max(0, leftDiff);
+        final int rightMargin = Math.max(0, rightDiff);
+        final int hMargins = leftMargin + rightMargin;
+        collapsingMargins[0] = Math.max(0, -leftDiff);
+        collapsingMargins[1] = Math.max(0, -rightDiff);
+
+        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
+                getPaddingLeft() + getPaddingRight() + hMargins + widthUsed, lp.width);
+        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
+                getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin
+                        + heightUsed, lp.height);
+
+        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+        return child.getMeasuredWidth() + hMargins;
+    }
+
+    /**
+     * Returns true if the Toolbar is collapsible and has no child views with a measured size > 0.
+     */
+    private boolean shouldCollapse() {
+        if (!mCollapsible) return false;
+
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            if (shouldLayout(child) && child.getMeasuredWidth() > 0 &&
+                    child.getMeasuredHeight() > 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int width = 0;
+        int height = 0;
+        int childState = 0;
+
+        final int[] collapsingMargins = mTempMargins;
+        final int marginStartIndex;
+        final int marginEndIndex;
+        if (ViewUtils.isLayoutRtl(this)) {
+            marginStartIndex = 1;
+            marginEndIndex = 0;
+        } else {
+            marginStartIndex = 0;
+            marginEndIndex = 1;
+        }
+
+        // System views measure first.
+
+        int navWidth = 0;
+        if (shouldLayout(mNavButtonView)) {
+            measureChildConstrained(mNavButtonView, widthMeasureSpec, width, heightMeasureSpec, 0,
+                    mMaxButtonHeight);
+            navWidth = mNavButtonView.getMeasuredWidth() + getHorizontalMargins(mNavButtonView);
+            height = Math.max(height, mNavButtonView.getMeasuredHeight() +
+                    getVerticalMargins(mNavButtonView));
+            childState = ViewUtils.combineMeasuredStates(childState,
+                    ViewCompat.getMeasuredState(mNavButtonView));
+        }
+
+        if (shouldLayout(mCollapseButtonView)) {
+            measureChildConstrained(mCollapseButtonView, widthMeasureSpec, width,
+                    heightMeasureSpec, 0, mMaxButtonHeight);
+            navWidth = mCollapseButtonView.getMeasuredWidth() +
+                    getHorizontalMargins(mCollapseButtonView);
+            height = Math.max(height, mCollapseButtonView.getMeasuredHeight() +
+                    getVerticalMargins(mCollapseButtonView));
+            childState = ViewUtils.combineMeasuredStates(childState,
+                    ViewCompat.getMeasuredState(mCollapseButtonView));
+        }
+
+        final int contentInsetStart = getContentInsetStart();
+        width += Math.max(contentInsetStart, navWidth);
+        collapsingMargins[marginStartIndex] = Math.max(0, contentInsetStart - navWidth);
+
+        int menuWidth = 0;
+        if (shouldLayout(mMenuView)) {
+            measureChildConstrained(mMenuView, widthMeasureSpec, width, heightMeasureSpec, 0,
+                    mMaxButtonHeight);
+            menuWidth = mMenuView.getMeasuredWidth() + getHorizontalMargins(mMenuView);
+            height = Math.max(height, mMenuView.getMeasuredHeight() +
+                    getVerticalMargins(mMenuView));
+            childState = ViewUtils.combineMeasuredStates(childState,
+                    ViewCompat.getMeasuredState(mMenuView));
+        }
+
+        final int contentInsetEnd = getContentInsetEnd();
+        width += Math.max(contentInsetEnd, menuWidth);
+        collapsingMargins[marginEndIndex] = Math.max(0, contentInsetEnd - menuWidth);
+
+        if (shouldLayout(mExpandedActionView)) {
+            width += measureChildCollapseMargins(mExpandedActionView, widthMeasureSpec, width,
+                    heightMeasureSpec, 0, collapsingMargins);
+            height = Math.max(height, mExpandedActionView.getMeasuredHeight() +
+                    getVerticalMargins(mExpandedActionView));
+            childState = ViewUtils.combineMeasuredStates(childState,
+                    ViewCompat.getMeasuredState(mExpandedActionView));
+        }
+
+        if (shouldLayout(mLogoView)) {
+            width += measureChildCollapseMargins(mLogoView, widthMeasureSpec, width,
+                    heightMeasureSpec, 0, collapsingMargins);
+            height = Math.max(height, mLogoView.getMeasuredHeight() +
+                    getVerticalMargins(mLogoView));
+            childState = ViewUtils.combineMeasuredStates(childState,
+                    ViewCompat.getMeasuredState(mLogoView));
+        }
+
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            if (lp.mViewType != LayoutParams.CUSTOM || !shouldLayout(child)) {
+                // We already got all system views above. Skip them and GONE views.
+                continue;
+            }
+
+            width += measureChildCollapseMargins(child, widthMeasureSpec, width,
+                    heightMeasureSpec, 0, collapsingMargins);
+            height = Math.max(height, child.getMeasuredHeight() + getVerticalMargins(child));
+            childState = ViewUtils.combineMeasuredStates(childState,
+                    ViewCompat.getMeasuredState(child));
+        }
+
+        int titleWidth = 0;
+        int titleHeight = 0;
+        final int titleVertMargins = mTitleMarginTop + mTitleMarginBottom;
+        final int titleHorizMargins = mTitleMarginStart + mTitleMarginEnd;
+        if (shouldLayout(mTitleTextView)) {
+            titleWidth = measureChildCollapseMargins(mTitleTextView, widthMeasureSpec,
+                    width + titleHorizMargins, heightMeasureSpec, titleVertMargins,
+                    collapsingMargins);
+            titleWidth = mTitleTextView.getMeasuredWidth() + getHorizontalMargins(mTitleTextView);
+            titleHeight = mTitleTextView.getMeasuredHeight() + getVerticalMargins(mTitleTextView);
+            childState = ViewUtils.combineMeasuredStates(childState,
+                    ViewCompat.getMeasuredState(mTitleTextView));
+        }
+        if (shouldLayout(mSubtitleTextView)) {
+            titleWidth = Math.max(titleWidth, measureChildCollapseMargins(mSubtitleTextView,
+                    widthMeasureSpec, width + titleHorizMargins,
+                    heightMeasureSpec, titleHeight + titleVertMargins,
+                    collapsingMargins));
+            titleHeight += mSubtitleTextView.getMeasuredHeight() +
+                    getVerticalMargins(mSubtitleTextView);
+            childState = ViewUtils.combineMeasuredStates(childState,
+                    ViewCompat.getMeasuredState(mSubtitleTextView));
+        }
+
+        width += titleWidth;
+        height = Math.max(height, titleHeight);
+
+        // Measurement already took padding into account for available space for the children,
+        // add it in for the final size.
+        width += getPaddingLeft() + getPaddingRight();
+        height += getPaddingTop() + getPaddingBottom();
+
+        final int measuredWidth = ViewCompat.resolveSizeAndState(
+                Math.max(width, getSuggestedMinimumWidth()),
+                widthMeasureSpec, childState & ViewCompat.MEASURED_STATE_MASK);
+        final int measuredHeight = ViewCompat.resolveSizeAndState(
+                Math.max(height, getSuggestedMinimumHeight()),
+                heightMeasureSpec, childState << ViewCompat.MEASURED_HEIGHT_STATE_SHIFT);
+
+        setMeasuredDimension(measuredWidth, shouldCollapse() ? 0 : measuredHeight);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        final boolean isRtl =  ViewCompat.getLayoutDirection(this) ==  ViewCompat.LAYOUT_DIRECTION_RTL;
+        final int width = getWidth();
+        final int height = getHeight();
+        final int paddingLeft = getPaddingLeft();
+        final int paddingRight = getPaddingRight();
+        final int paddingTop = getPaddingTop();
+        final int paddingBottom = getPaddingBottom();
+        int left = paddingLeft;
+        int right = width - paddingRight;
+
+        final int[] collapsingMargins = mTempMargins;
+        collapsingMargins[0] = collapsingMargins[1] = 0;
+
+        if (shouldLayout(mNavButtonView)) {
+            if (isRtl) {
+                right = layoutChildRight(mNavButtonView, right, collapsingMargins);
+            } else {
+                left = layoutChildLeft(mNavButtonView, left, collapsingMargins);
+            }
+        }
+
+        if (shouldLayout(mCollapseButtonView)) {
+            if (isRtl) {
+                right = layoutChildRight(mCollapseButtonView, right, collapsingMargins);
+            } else {
+                left = layoutChildLeft(mCollapseButtonView, left, collapsingMargins);
+            }
+        }
+
+        if (shouldLayout(mMenuView)) {
+            if (isRtl) {
+                left = layoutChildLeft(mMenuView, left, collapsingMargins);
+            } else {
+                right = layoutChildRight(mMenuView, right, collapsingMargins);
+            }
+        }
+
+        collapsingMargins[0] = Math.max(0, getContentInsetLeft() - left);
+        collapsingMargins[1] = Math.max(0, getContentInsetRight() - (width - paddingRight - right));
+        left = Math.max(left, getContentInsetLeft());
+        right = Math.min(right, width - paddingRight - getContentInsetRight());
+
+        if (shouldLayout(mExpandedActionView)) {
+            if (isRtl) {
+                right = layoutChildRight(mExpandedActionView, right, collapsingMargins);
+            } else {
+                left = layoutChildLeft(mExpandedActionView, left, collapsingMargins);
+            }
+        }
+
+        if (shouldLayout(mLogoView)) {
+            if (isRtl) {
+                right = layoutChildRight(mLogoView, right, collapsingMargins);
+            } else {
+                left = layoutChildLeft(mLogoView, left, collapsingMargins);
+            }
+        }
+
+        final boolean layoutTitle = shouldLayout(mTitleTextView);
+        final boolean layoutSubtitle = shouldLayout(mSubtitleTextView);
+        int titleHeight = 0;
+        if (layoutTitle) {
+            final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
+            titleHeight += lp.topMargin + mTitleTextView.getMeasuredHeight() + lp.bottomMargin;
+        }
+        if (layoutSubtitle) {
+            final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams();
+            titleHeight += lp.topMargin + mSubtitleTextView.getMeasuredHeight() + lp.bottomMargin;
+        }
+
+        if (layoutTitle || layoutSubtitle) {
+            int titleTop;
+            final View topChild = layoutTitle ? mTitleTextView : mSubtitleTextView;
+            final View bottomChild = layoutSubtitle ? mSubtitleTextView : mTitleTextView;
+            final LayoutParams toplp = (LayoutParams) topChild.getLayoutParams();
+            final LayoutParams bottomlp = (LayoutParams) bottomChild.getLayoutParams();
+
+            switch (mGravity & Gravity.VERTICAL_GRAVITY_MASK) {
+                case Gravity.TOP:
+                    titleTop = getPaddingTop() + toplp.topMargin + mTitleMarginTop;
+                    break;
+                default:
+                case Gravity.CENTER_VERTICAL:
+                    final int space = height - paddingTop - paddingBottom;
+                    int spaceAbove = (space - titleHeight) / 2;
+                    if (spaceAbove < toplp.topMargin + mTitleMarginTop) {
+                        spaceAbove = toplp.topMargin + mTitleMarginTop;
+                    } else {
+                        final int spaceBelow = height - paddingBottom - titleHeight -
+                                spaceAbove - paddingTop;
+                        if (spaceBelow < toplp.bottomMargin + mTitleMarginBottom) {
+                            spaceAbove = Math.max(0, spaceAbove -
+                                    (bottomlp.bottomMargin + mTitleMarginBottom - spaceBelow));
+                        }
+                    }
+                    titleTop = paddingTop + spaceAbove;
+                    break;
+                case Gravity.BOTTOM:
+                    titleTop = height - paddingBottom - bottomlp.bottomMargin - mTitleMarginBottom -
+                            titleHeight;
+                    break;
+            }
+            if (isRtl) {
+                final int rd = mTitleMarginStart - collapsingMargins[1];
+                right -= Math.max(0, rd);
+                collapsingMargins[1] = Math.max(0, -rd);
+                int titleRight = right;
+                int subtitleRight = right;
+
+                if (layoutTitle) {
+                    final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
+                    final int titleLeft = titleRight - mTitleTextView.getMeasuredWidth();
+                    final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight();
+                    mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom);
+                    titleRight = titleLeft - mTitleMarginEnd;
+                    titleTop = titleBottom + lp.bottomMargin;
+                }
+                if (layoutSubtitle) {
+                    final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams();
+                    titleTop += lp.topMargin;
+                    final int subtitleLeft = subtitleRight - mSubtitleTextView.getMeasuredWidth();
+                    final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight();
+                    mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom);
+                    subtitleRight = subtitleRight - mTitleMarginEnd;
+                    titleTop = subtitleBottom + lp.bottomMargin;
+                }
+                right = Math.min(titleRight, subtitleRight);
+            } else {
+                final int ld = mTitleMarginStart - collapsingMargins[0];
+                left += Math.max(0, ld);
+                collapsingMargins[0] = Math.max(0, -ld);
+                int titleLeft = left;
+                int subtitleLeft = left;
+
+                if (layoutTitle) {
+                    final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
+                    final int titleRight = titleLeft + mTitleTextView.getMeasuredWidth();
+                    final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight();
+                    mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom);
+                    titleLeft = titleRight + mTitleMarginEnd;
+                    titleTop = titleBottom + lp.bottomMargin;
+                }
+                if (layoutSubtitle) {
+                    final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams();
+                    titleTop += lp.topMargin;
+                    final int subtitleRight = subtitleLeft + mSubtitleTextView.getMeasuredWidth();
+                    final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight();
+                    mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom);
+                    subtitleLeft = subtitleRight + mTitleMarginEnd;
+                    titleTop = subtitleBottom + lp.bottomMargin;
+                }
+                left = Math.max(titleLeft, subtitleLeft);
+            }
+        }
+
+        // Get all remaining children sorted for layout. This is all prepared
+        // such that absolute layout direction can be used below.
+
+        addCustomViewsWithGravity(mTempViews, Gravity.LEFT);
+        final int leftViewsCount = mTempViews.size();
+        for (int i = 0; i < leftViewsCount; i++) {
+            left = layoutChildLeft(mTempViews.get(i), left, collapsingMargins);
+        }
+
+        addCustomViewsWithGravity(mTempViews, Gravity.RIGHT);
+        final int rightViewsCount = mTempViews.size();
+        for (int i = 0; i < rightViewsCount; i++) {
+            right = layoutChildRight(mTempViews.get(i), right, collapsingMargins);
+        }
+
+        // Centered views try to center with respect to the whole bar, but views pinned
+        // to the left or right can push the mass of centered views to one side or the other.
+        addCustomViewsWithGravity(mTempViews, Gravity.CENTER_HORIZONTAL);
+        final int centerViewsWidth = getViewListMeasuredWidth(mTempViews, collapsingMargins);
+        final int parentCenter = paddingLeft + (width - paddingLeft - paddingRight) / 2;
+        final int halfCenterViewsWidth = centerViewsWidth / 2;
+        int centerLeft = parentCenter - halfCenterViewsWidth;
+        final int centerRight = centerLeft + centerViewsWidth;
+        if (centerLeft < left) {
+            centerLeft = left;
+        } else if (centerRight > right) {
+            centerLeft -= centerRight - right;
+        }
+
+        final int centerViewsCount = mTempViews.size();
+        for (int i = 0; i < centerViewsCount; i++) {
+            centerLeft = layoutChildLeft(mTempViews.get(i), centerLeft, collapsingMargins);
+        }
+        mTempViews.clear();
+    }
+
+    private int getViewListMeasuredWidth(List<View> views, int[] collapsingMargins) {
+        int collapseLeft = collapsingMargins[0];
+        int collapseRight = collapsingMargins[1];
+        int width = 0;
+        final int count = views.size();
+        for (int i = 0; i < count; i++) {
+            final View v = views.get(i);
+            final LayoutParams lp = (LayoutParams) v.getLayoutParams();
+            final int l = lp.leftMargin - collapseLeft;
+            final int r = lp.rightMargin - collapseRight;
+            final int leftMargin = Math.max(0, l);
+            final int rightMargin = Math.max(0, r);
+            collapseLeft = Math.max(0, -l);
+            collapseRight = Math.max(0, -r);
+            width += leftMargin + v.getMeasuredWidth() + rightMargin;
+        }
+        return width;
+    }
+
+    private int layoutChildLeft(View child, int left, int[] collapsingMargins) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        final int l = lp.leftMargin - collapsingMargins[0];
+        left += Math.max(0, l);
+        collapsingMargins[0] = Math.max(0, -l);
+        final int top = getChildTop(child);
+        final int childWidth = child.getMeasuredWidth();
+        child.layout(left, top, left + childWidth, top + child.getMeasuredHeight());
+        left += childWidth + lp.rightMargin;
+        return left;
+    }
+
+    private int layoutChildRight(View child, int right, int[] collapsingMargins) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        final int r = lp.rightMargin - collapsingMargins[1];
+        right -= Math.max(0, r);
+        collapsingMargins[1] = Math.max(0, -r);
+        final int top = getChildTop(child);
+        final int childWidth = child.getMeasuredWidth();
+        child.layout(right - childWidth, top, right, top + child.getMeasuredHeight());
+        right -= childWidth + lp.leftMargin;
+        return right;
+    }
+
+    private int getChildTop(View child) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        switch (getChildVerticalGravity(lp.gravity)) {
+            case Gravity.TOP:
+                return getPaddingTop();
+
+            case Gravity.BOTTOM:
+                return getHeight() - getPaddingBottom() -
+                        child.getMeasuredHeight() - lp.bottomMargin;
+
+            default:
+            case Gravity.CENTER_VERTICAL:
+                final int paddingTop = getPaddingTop();
+                final int paddingBottom = getPaddingBottom();
+                final int height = getHeight();
+                final int childHeight = child.getMeasuredHeight();
+                final int space = height - paddingTop - paddingBottom;
+                int spaceAbove = (space - childHeight) / 2;
+                if (spaceAbove < lp.topMargin) {
+                    spaceAbove = lp.topMargin;
+                } else {
+                    final int spaceBelow = height - paddingBottom - childHeight -
+                            spaceAbove - paddingTop;
+                    if (spaceBelow < lp.bottomMargin) {
+                        spaceAbove = Math.max(0, spaceAbove - (lp.bottomMargin - spaceBelow));
+                    }
+                }
+                return paddingTop + spaceAbove;
+        }
+    }
+
+    private int getChildVerticalGravity(int gravity) {
+        final int vgrav = gravity & Gravity.VERTICAL_GRAVITY_MASK;
+        switch (vgrav) {
+            case Gravity.TOP:
+            case Gravity.BOTTOM:
+            case Gravity.CENTER_VERTICAL:
+                return vgrav;
+            default:
+                return mGravity & Gravity.VERTICAL_GRAVITY_MASK;
+        }
+    }
+
+    /**
+     * Prepare a list of non-SYSTEM child views. If the layout direction is RTL
+     * this will be in reverse child order.
+     *
+     * @param views List to populate. It will be cleared before use.
+     * @param gravity Horizontal gravity to match against
+     */
+    private void addCustomViewsWithGravity(List<View> views, int gravity) {
+        final boolean isRtl =  ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
+        final int childCount = getChildCount();
+        final int absGrav = GravityCompat.getAbsoluteGravity(gravity,
+                ViewCompat.getLayoutDirection(this));
+
+        views.clear();
+
+        if (isRtl) {
+            for (int i = childCount - 1; i >= 0; i--) {
+                final View child = getChildAt(i);
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if (lp.mViewType == LayoutParams.CUSTOM && shouldLayout(child) &&
+                        getChildHorizontalGravity(lp.gravity) == absGrav) {
+                    views.add(child);
+                }
+            }
+        } else {
+            for (int i = 0; i < childCount; i++) {
+                final View child = getChildAt(i);
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if (lp.mViewType == LayoutParams.CUSTOM && shouldLayout(child) &&
+                        getChildHorizontalGravity(lp.gravity) == absGrav) {
+                    views.add(child);
+                }
+            }
+        }
+    }
+
+    private int getChildHorizontalGravity(int gravity) {
+        final int ld =  ViewCompat.getLayoutDirection(this);
+        final int absGrav = GravityCompat.getAbsoluteGravity(gravity, ld);
+        final int hGrav = absGrav & Gravity.HORIZONTAL_GRAVITY_MASK;
+        switch (hGrav) {
+            case Gravity.LEFT:
+            case Gravity.RIGHT:
+            case Gravity.CENTER_HORIZONTAL:
+                return hGrav;
+            default:
+                return ld == ViewCompat.LAYOUT_DIRECTION_RTL ? Gravity.RIGHT : Gravity.LEFT;
+        }
+    }
+
+    private boolean shouldLayout(View view) {
+        return view != null && view.getParent() == this && view.getVisibility() != GONE;
+    }
+
+    private int getHorizontalMargins(View v) {
+        final MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams();
+        return MarginLayoutParamsCompat.getMarginStart(mlp) +
+                MarginLayoutParamsCompat.getMarginEnd(mlp);
+    }
+
+    private int getVerticalMargins(View v) {
+        final MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams();
+        return mlp.topMargin + mlp.bottomMargin;
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new LayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        if (p instanceof LayoutParams) {
+            return new LayoutParams((LayoutParams) p);
+        } else if (p instanceof ActionBar.LayoutParams) {
+            return new LayoutParams((ActionBar.LayoutParams) p);
+        } else if (p instanceof MarginLayoutParams) {
+            return new LayoutParams((MarginLayoutParams) p);
+        } else {
+            return new LayoutParams(p);
+        }
+    }
+
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return super.checkLayoutParams(p) && p instanceof LayoutParams;
+    }
+
+    private static boolean isCustomView(View child) {
+        return ((LayoutParams) child.getLayoutParams()).mViewType == LayoutParams.CUSTOM;
+    }
+
+    /** @hide */
+    public DecorToolbar getWrapper() {
+        if (mWrapper == null) {
+            mWrapper = new ToolbarWidgetWrapper(this, true);
+        }
+        return mWrapper;
+    }
+
+    private void setChildVisibilityForExpandedActionView(boolean expand) {
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            if (lp.mViewType != LayoutParams.EXPANDED && child != mMenuView) {
+                child.setVisibility(expand ? GONE : VISIBLE);
+            }
+        }
+    }
+
+    /**
+     * Force the toolbar to collapse to zero-height during measurement if
+     * it could be considered "empty" (no visible elements with nonzero measured size)
+     * @hide
+     */
+    public void setCollapsible(boolean collapsible) {
+        mCollapsible = collapsible;
+        requestLayout();
+    }
+
+    /**
+     * Interface responsible for receiving menu item click events if the items themselves
+     * do not have individual item click listeners.
+     */
+    public interface OnMenuItemClickListener {
+        /**
+         * This method will be invoked when a menu item is clicked if the item itself did
+         * not already handle the event.
+         *
+         * @param item {@link MenuItem} that was clicked
+         * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
+         */
+        public boolean onMenuItemClick(MenuItem item);
+    }
+
+    /**
+     * Layout information for child views of Toolbars.
+     *
+     * <p>Toolbar.LayoutParams extends ActionBar.LayoutParams for compatibility with existing
+     * ActionBar API. See
+     * {@link android.support.v7.app.ActionBarActivity#setSupportActionBar(Toolbar)
+     * ActionBarActivity.setActionBar}
+     * for more info on how to use a Toolbar as your Activity's ActionBar.</p>
+     */
+    public static class LayoutParams extends ActionBar.LayoutParams {
+        static final int CUSTOM = 0;
+        static final int SYSTEM = 1;
+        static final int EXPANDED = 2;
+
+        int mViewType = CUSTOM;
+
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+        }
+
+        public LayoutParams(int width, int height) {
+            super(width, height);
+            this.gravity = Gravity.CENTER_VERTICAL | GravityCompat.START;
+        }
+
+        public LayoutParams(int width, int height, int gravity) {
+            super(width, height);
+            this.gravity = gravity;
+        }
+
+        public LayoutParams(int gravity) {
+            this(WRAP_CONTENT, MATCH_PARENT, gravity);
+        }
+
+        public LayoutParams(LayoutParams source) {
+            super(source);
+
+            mViewType = source.mViewType;
+        }
+
+        public LayoutParams(ActionBar.LayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(MarginLayoutParams source) {
+            super(source);
+            // ActionBar.LayoutParams doesn't have a MarginLayoutParams constructor.
+            // Fake it here and copy over the relevant data.
+            copyMarginsFromCompat(source);
+        }
+
+        public LayoutParams(ViewGroup.LayoutParams source) {
+            super(source);
+        }
+
+        void copyMarginsFromCompat(MarginLayoutParams source) {
+            this.leftMargin = source.leftMargin;
+            this.topMargin = source.topMargin;
+            this.rightMargin = source.rightMargin;
+            this.bottomMargin = source.bottomMargin;
+        }
+    }
+
+    static class SavedState extends BaseSavedState {
+        public SavedState(Parcel source) {
+            super(source);
+        }
+
+        public SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+        }
+
+        public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
+
+            @Override
+            public SavedState createFromParcel(Parcel source) {
+                return new SavedState(source);
+            }
+
+            @Override
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    private class ExpandedActionViewMenuPresenter implements MenuPresenter {
+        MenuBuilder mMenu;
+        MenuItemImpl mCurrentExpandedItem;
+
+        @Override
+        public void initForMenu(Context context, MenuBuilder menu) {
+            // Clear the expanded action view when menus change.
+            if (mMenu != null && mCurrentExpandedItem != null) {
+                mMenu.collapseItemActionView(mCurrentExpandedItem);
+            }
+            mMenu = menu;
+        }
+
+        @Override
+        public MenuView getMenuView(ViewGroup root) {
+            return null;
+        }
+
+        @Override
+        public void updateMenuView(boolean cleared) {
+            // Make sure the expanded item we have is still there.
+            if (mCurrentExpandedItem != null) {
+                boolean found = false;
+
+                if (mMenu != null) {
+                    final int count = mMenu.size();
+                    for (int i = 0; i < count; i++) {
+                        final MenuItem item = mMenu.getItem(i);
+                        if (item == mCurrentExpandedItem) {
+                            found = true;
+                            break;
+                        }
+                    }
+                }
+
+                if (!found) {
+                    // The item we had expanded disappeared. Collapse.
+                    collapseItemActionView(mMenu, mCurrentExpandedItem);
+                }
+            }
+        }
+
+        @Override
+        public void setCallback(Callback cb) {
+        }
+
+        @Override
+        public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+            return false;
+        }
+
+        @Override
+        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+        }
+
+        @Override
+        public boolean flagActionItems() {
+            return false;
+        }
+
+        @Override
+        public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+            ensureCollapseButtonView();
+            if (mCollapseButtonView.getParent() != Toolbar.this) {
+                addView(mCollapseButtonView);
+            }
+            mExpandedActionView = item.getActionView();
+            mCurrentExpandedItem = item;
+            if (mExpandedActionView.getParent() != Toolbar.this) {
+                final LayoutParams lp = generateDefaultLayoutParams();
+                lp.gravity = GravityCompat.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
+                lp.mViewType = LayoutParams.EXPANDED;
+                mExpandedActionView.setLayoutParams(lp);
+                addView(mExpandedActionView);
+            }
+
+            setChildVisibilityForExpandedActionView(true);
+            requestLayout();
+            item.setActionViewExpanded(true);
+
+            if (mExpandedActionView instanceof CollapsibleActionView) {
+                ((CollapsibleActionView) mExpandedActionView).onActionViewExpanded();
+            }
+
+            return true;
+        }
+
+        @Override
+        public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+            // Do this before detaching the actionview from the hierarchy, in case
+            // it needs to dismiss the soft keyboard, etc.
+            if (mExpandedActionView instanceof CollapsibleActionView) {
+                ((CollapsibleActionView) mExpandedActionView).onActionViewCollapsed();
+            }
+
+            removeView(mExpandedActionView);
+            removeView(mCollapseButtonView);
+            mExpandedActionView = null;
+
+            setChildVisibilityForExpandedActionView(false);
+            mCurrentExpandedItem = null;
+            requestLayout();
+            item.setActionViewExpanded(false);
+
+            return true;
+        }
+
+        @Override
+        public int getId() {
+            return 0;
+        }
+
+        @Override
+        public Parcelable onSaveInstanceState() {
+            return null;
+        }
+
+        @Override
+        public void onRestoreInstanceState(Parcelable state) {
+        }
+    }
+}
diff --git a/v7/cardview/Android.mk b/v7/cardview/Android.mk
new file mode 100644
index 0000000..5c8da8c
--- /dev/null
+++ b/v7/cardview/Android.mk
@@ -0,0 +1,75 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+# Build the resources using the current SDK version.
+# We do this here because the final static library must be compiled with an older
+# SDK version than the resources.  The resources library and the R class that it
+# contains will not be linked into the final static library.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v7-cardview-res
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_JAR_EXCLUDE_FILES := none
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# A helper sub-library to resolve cyclic dependencies between CardView and platform dependent
+# implementations
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v7-cardview-base
+LOCAL_SDK_VERSION := 7
+LOCAL_SRC_FILES := $(call all-java-files-under, base)
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# A helper sub-library that makes direct use of Eclair MR1 APIs
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v7-cardview-eclair-mr1
+LOCAL_SDK_VERSION := 7
+LOCAL_SRC_FILES := $(call all-java-files-under, eclair-mr1)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-cardview-base
+LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# A helper sub-library that makes direct use of JB MR1 APIs
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v7-cardview-jellybean-mr1
+LOCAL_SDK_VERSION := 17
+LOCAL_SRC_FILES := $(call all-java-files-under, jellybean-mr1)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-cardview-eclair-mr1
+LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# A helper sub-library that makes direct use of L APIs
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v7-cardview-api21
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, api21)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-cardview-base \
+    android-support-v7-cardview-jellybean-mr1
+LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Here is the final static library that apps can link against.
+# The R class is automatically excluded from the generated library.
+# Applications that use this library must specify LOCAL_RESOURCE_DIR
+# in their makefiles to include the resources in their package.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v7-cardview
+LOCAL_SDK_VERSION := 7
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-cardview-api21
+LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v7/cardview/AndroidManifest.xml b/v7/cardview/AndroidManifest.xml
new file mode 100644
index 0000000..3ef02dc
--- /dev/null
+++ b/v7/cardview/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.support.v7.cardview">
+    <uses-sdk android:minSdkVersion="7"/>
+    <application />
+</manifest>
diff --git a/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java b/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
new file mode 100644
index 0000000..450ff46
--- /dev/null
+++ b/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.View;
+import android.support.v7.cardview.R;
+
+class CardViewApi21 implements CardViewImpl {
+
+    @Override
+    public void initialize(CardViewDelegate cardView, Context context, int backgroundColor,
+            float radius, float elevation, float maxElevation/*ignored*/) {
+        cardView.setBackgroundDrawable(new RoundRectDrawable(backgroundColor, radius));
+        View view = (View) cardView;
+        view.setClipToOutline(true);
+        view.setElevation(elevation);
+    }
+
+    @Override
+    public void setRadius(CardViewDelegate cardView, float radius) {
+        ((RoundRectDrawable) (cardView.getBackground())).setRadius(radius);
+    }
+
+    @Override
+    public void initStatic() {
+    }
+
+    @Override
+    public void setMaxElevation(CardViewDelegate cardView, float maxElevation) {
+        // no op
+    }
+
+    @Override
+    public float getMaxElevation(CardViewDelegate cardView) {
+        return 0;
+    }
+
+    @Override
+    public float getMinWidth(CardViewDelegate cardView) {
+        return getRadius(cardView) * 2;
+    }
+
+    @Override
+    public float getMinHeight(CardViewDelegate cardView) {
+        return getRadius(cardView) * 2;
+    }
+
+    @Override
+    public float getRadius(CardViewDelegate cardView) {
+        return ((RoundRectDrawable) (cardView.getBackground())).getRadius();
+    }
+
+    @Override
+    public void setElevation(CardViewDelegate cardView, float elevation) {
+        ((View) cardView).setElevation(elevation);
+    }
+
+    @Override
+    public float getElevation(CardViewDelegate cardView) {
+        return ((View) cardView).getElevation();
+    }
+
+}
\ No newline at end of file
diff --git a/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java b/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java
new file mode 100644
index 0000000..93bd954
--- /dev/null
+++ b/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+
+/**
+ * Very simple drawable that draws a rounded rectangle background with arbitrary corners and also
+ * reports proper outline for L.
+ * <p>
+ * Simpler and uses less resources compared to GradientDrawable or ShapeDrawable.
+ */
+class RoundRectDrawable extends Drawable {
+    float mRadius;
+    final Paint mPaint;
+    final RectF mBounds;
+
+    public RoundRectDrawable(int backgroundColor, float radius) {
+        mRadius = radius;
+        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
+        mPaint.setColor(backgroundColor);
+        mBounds = new RectF();
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        canvas.drawRoundRect(mBounds, mRadius, mRadius, mPaint);
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        super.onBoundsChange(bounds);
+        mBounds.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
+    }
+
+    @Override
+    public void getOutline(Outline outline) {
+        outline.setRoundRect(getBounds(), mRadius);
+    }
+
+    public void setRadius(float radius) {
+        if (radius == mRadius) {
+            return;
+        }
+        mRadius = radius;
+        invalidateSelf();
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        // not supported because older versions do not support
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter cf) {
+        // not supported because older versions do not support
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.OPAQUE;
+    }
+
+    public float getRadius() {
+        return mRadius;
+    }
+}
diff --git a/v7/cardview/base/android/support/v7/widget/CardViewDelegate.java b/v7/cardview/base/android/support/v7/widget/CardViewDelegate.java
new file mode 100644
index 0000000..beedbd1
--- /dev/null
+++ b/v7/cardview/base/android/support/v7/widget/CardViewDelegate.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * Interface provided by CardView to implementations.
+ * <p>
+ * Necessary to resolve circular dependency between base CardView and platform implementations.
+ */
+interface CardViewDelegate {
+    void setBackgroundDrawable(Drawable paramDrawable);
+    Drawable getBackground();
+}
\ No newline at end of file
diff --git a/v7/cardview/base/android/support/v7/widget/CardViewImpl.java b/v7/cardview/base/android/support/v7/widget/CardViewImpl.java
new file mode 100644
index 0000000..5ac0861
--- /dev/null
+++ b/v7/cardview/base/android/support/v7/widget/CardViewImpl.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+
+/**
+ * Interface for platform specific CardView implementations.
+ */
+interface CardViewImpl {
+    void initialize(CardViewDelegate cardView, Context context, int backgroundColor, float radius,
+            float elevation, float maxElevation);
+
+    void setRadius(CardViewDelegate cardView, float radius);
+
+    float getRadius(CardViewDelegate cardView);
+
+    void setElevation(CardViewDelegate cardView, float elevation);
+
+    float getElevation(CardViewDelegate cardView);
+
+    void initStatic();
+
+    void setMaxElevation(CardViewDelegate cardView, float maxElevation);
+
+    float getMaxElevation(CardViewDelegate cardView);
+
+    float getMinWidth(CardViewDelegate cardView);
+
+    float getMinHeight(CardViewDelegate cardView);
+}
diff --git a/v7/cardview/build.gradle b/v7/cardview/build.gradle
new file mode 100644
index 0000000..b0d4401
--- /dev/null
+++ b/v7/cardview/build.gradle
@@ -0,0 +1,98 @@
+apply plugin: 'android-library'
+
+archivesBaseName = 'cardview-v7'
+
+android {
+    // WARNING: should be 7
+    compileSdkVersion 'current'
+
+    buildToolsVersion "19.0.1"
+
+    defaultConfig {
+        minSdkVersion 7
+        // TODO: get target from branch
+        //targetSdkVersion 19
+    }
+
+    sourceSets {
+        main.manifest.srcFile 'AndroidManifest.xml'
+        main.java.srcDirs = ['base', 'eclair-mr1', 'jellybean-mr1', 'api21', 'src']
+        main.aidl.srcDirs = ['base', 'eclair-mr1', 'jellybean-mr1', 'api21', 'src']
+        main.res.srcDirs = ['res']
+
+        androidTest.setRoot('tests')
+        androidTest.java.srcDir 'tests/java'
+    }
+
+    lintOptions {
+        // TODO: fix errors and reenable.
+        abortOnError false
+    }
+}
+
+android.libraryVariants.all { variant ->
+    def name = variant.buildType.name
+
+    if (name.equals(com.android.builder.BuilderConstants.DEBUG)) {
+        return; // Skip debug builds.
+    }
+    def suffix = name.capitalize()
+
+    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
+        dependsOn variant.javaCompile
+        from variant.javaCompile.destinationDir
+        from 'LICENSE.txt'
+    }
+    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
+        source android.sourceSets.main.allJava
+        classpath = files(variant.javaCompile.classpath.files) + files(
+                "${android.plugin.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
+    }
+
+    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
+        classifier = 'javadoc'
+        from 'build/docs/javadoc'
+    }
+
+    def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
+        classifier = 'sources'
+        from android.sourceSets.main.allSource
+    }
+
+    artifacts.add('archives', javadocJarTask);
+    artifacts.add('archives', sourcesJarTask);
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri(rootProject.ext.supportRepoOut)) {
+            }
+
+            pom.project {
+                name 'Android Support CardView v7'
+                description "Android Support CardView v7"
+                url 'http://developer.android.com/tools/extras/support-library.html'
+                inceptionYear '2011'
+
+                licenses {
+                    license {
+                        name 'The Apache Software License, Version 2.0'
+                        url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+                        distribution 'repo'
+                    }
+                }
+
+                scm {
+                    url "http://source.android.com"
+                    connection "scm:git:https://android.googlesource.com/platform/frameworks/support"
+                }
+                developers {
+                    developer {
+                        name 'The Android Open Source Project'
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/v7/cardview/dummy/Dummy.java b/v7/cardview/dummy/Dummy.java
new file mode 100644
index 0000000..be16dc7
--- /dev/null
+++ b/v7/cardview/dummy/Dummy.java
@@ -0,0 +1 @@
+// Dummy java file used to build the resource library.
diff --git a/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java b/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java
new file mode 100644
index 0000000..86190ec
--- /dev/null
+++ b/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.View;
+import android.view.ViewGroup;
+
+class CardViewEclairMr1 implements CardViewImpl {
+
+    final RectF sCornerRect = new RectF();
+
+    @Override
+    public void initStatic() {
+        // Draws a round rect using 7 draw operations. This is faster than using
+        // canvas.drawRoundRect before JBMR1 because API 11-16 used alpha mask textures to draw
+        // shapes.
+        RoundRectDrawableWithShadow.sRoundRectHelper
+                = new RoundRectDrawableWithShadow.RoundRectHelper() {
+            @Override
+            public void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius,
+                    Paint paint) {
+                final float twoRadius = cornerRadius * 2;
+                final float innerWidth = bounds.width() - twoRadius;
+                final float innerHeight = bounds.height() - twoRadius;
+                sCornerRect.set(bounds.left, bounds.top,
+                        bounds.left + cornerRadius * 2, bounds.top + cornerRadius * 2);
+
+                canvas.drawArc(sCornerRect, 180, 90, true, paint);
+                sCornerRect.offset(innerWidth, 0);
+                canvas.drawArc(sCornerRect, 270, 90, true, paint);
+                sCornerRect.offset(0, innerHeight);
+                canvas.drawArc(sCornerRect, 0, 90, true, paint);
+                sCornerRect.offset(-innerWidth, 0);
+                canvas.drawArc(sCornerRect, 90, 90, true, paint);
+
+                //draw top and bottom pieces
+                canvas.drawRect(bounds.left + cornerRadius, bounds.top,
+                        bounds.right - cornerRadius, bounds.top + cornerRadius,
+                        paint);
+                canvas.drawRect(bounds.left + cornerRadius,
+                        bounds.bottom - cornerRadius, bounds.right - cornerRadius,
+                        bounds.bottom, paint);
+
+                //center
+                canvas.drawRect(bounds.left, bounds.top + cornerRadius,
+                        bounds.right, bounds.bottom - cornerRadius, paint);
+            }
+        };
+    }
+
+    @Override
+    public void initialize(CardViewDelegate cardView, Context context, int backgroundColor,
+            float radius, float elevation, float maxElevation) {
+        RoundRectDrawableWithShadow background = createBackground(context, backgroundColor, radius,
+                elevation, maxElevation);
+        cardView.setBackgroundDrawable(background);
+        updatePadding(cardView);
+    }
+
+    RoundRectDrawableWithShadow createBackground(Context context, int backgroundColor,
+            float radius, float elevation, float maxElevation) {
+        return new RoundRectDrawableWithShadow(context.getResources(), backgroundColor, radius,
+                elevation, maxElevation);
+    }
+
+    private void updatePadding(CardViewDelegate cardView) {
+        Rect shadowPadding = new Rect();
+        getShadowBackground(cardView).getMaxShadowAndCornerPadding(shadowPadding);
+        ((View)cardView).setPadding(shadowPadding.left, shadowPadding.top, shadowPadding.right,
+                shadowPadding.bottom);
+        ((View)cardView).setMinimumHeight((int) Math.ceil(getMinHeight(cardView)));
+        ((View)cardView).setMinimumWidth((int) Math.ceil(getMinWidth(cardView)));
+    }
+
+    @Override
+    public void setRadius(CardViewDelegate cardView, float radius) {
+        getShadowBackground(cardView).setCornerRadius(radius);
+        updatePadding(cardView);
+    }
+
+    @Override
+    public float getRadius(CardViewDelegate cardView) {
+        return getShadowBackground(cardView).getCornerRadius();
+    }
+
+    @Override
+    public void setElevation(CardViewDelegate cardView, float elevation) {
+        getShadowBackground(cardView).setShadowSize(elevation);
+    }
+
+    @Override
+    public float getElevation(CardViewDelegate cardView) {
+        return getShadowBackground(cardView).getShadowSize();
+    }
+
+    @Override
+    public void setMaxElevation(CardViewDelegate cardView, float maxElevation) {
+        getShadowBackground(cardView).setMaxShadowSize(maxElevation);
+        updatePadding(cardView);
+    }
+
+    @Override
+    public float getMaxElevation(CardViewDelegate cardView) {
+        return getShadowBackground(cardView).getMaxShadowSize();
+    }
+
+    @Override
+    public float getMinWidth(CardViewDelegate cardView) {
+        return getShadowBackground(cardView).getMinWidth();
+    }
+
+    @Override
+    public float getMinHeight(CardViewDelegate cardView) {
+        return getShadowBackground(cardView).getMinHeight();
+    }
+
+    private RoundRectDrawableWithShadow getShadowBackground(CardViewDelegate cardView) {
+        return ((RoundRectDrawableWithShadow) cardView.getBackground());
+    }
+}
\ No newline at end of file
diff --git a/v7/cardview/eclair-mr1/android/support/v7/widget/RoundRectDrawableWithShadow.java b/v7/cardview/eclair-mr1/android/support/v7/widget/RoundRectDrawableWithShadow.java
new file mode 100644
index 0000000..fd54985
--- /dev/null
+++ b/v7/cardview/eclair-mr1/android/support/v7/widget/RoundRectDrawableWithShadow.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.RadialGradient;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
+import android.support.v7.cardview.R;
+import android.util.Log;
+
+/**
+ * A rounded rectangle drawable which also includes a shadow around.
+ */
+class RoundRectDrawableWithShadow extends Drawable {
+    // used to calculate content padding
+    final static double COS_45 = Math.cos(Math.toRadians(45));
+
+    final static float SHADOW_MULTIPLIER = 1.5f;
+
+    final float mInsetShadow; // extra shadow to avoid gaps between card and shadow
+
+    /*
+    * This helper is set by CardView implementations.
+    * <p>
+    * Prior to API 17, canvas.drawRoundRect is expensive; which is why we need this interface
+    * to draw efficient rounded rectangles before 17.
+    * */
+    static RoundRectHelper sRoundRectHelper;
+
+    Paint mPaint;
+
+    Paint mCornerShadowPaint;
+
+    Paint mEdgeShadowPaint;
+
+    final RectF mCardBounds;
+
+    float mCornerRadius;
+
+    Path mCornerShadowPath;
+
+    // updated value with inset
+    float mMaxShadowSize;
+
+    // actual value set by developer
+    float mRawMaxShadowSize;
+
+    // multiplied value to account for shadow offset
+    float mShadowSize;
+
+    // actual value set by developer
+    float mRawShadowSize;
+
+    private boolean mDirty = true;
+
+    private final int mShadowStartColor;
+
+    private final int mShadowEndColor;
+
+    /**
+     * If shadow size is set to a value above max shadow, we print a warning
+     */
+    private boolean mPrintedShadowClipWarning = false;
+
+    RoundRectDrawableWithShadow(Resources resources, int backgroundColor, float radius,
+            float shadowSize, float maxShadowSize) {
+        mShadowStartColor = resources.getColor(R.color.cardview_shadow_start_color);
+        mShadowEndColor = resources.getColor(R.color.cardview_shadow_end_color);
+        mInsetShadow = resources.getDimension(R.dimen.cardview_compat_inset_shadow);
+        setShadowSize(shadowSize, maxShadowSize);
+        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
+        mPaint.setColor(backgroundColor);
+        mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
+        mCornerShadowPaint.setStyle(Paint.Style.FILL);
+        mCornerShadowPaint.setDither(true);
+        mCornerRadius = radius;
+        mCardBounds = new RectF();
+        mEdgeShadowPaint = new Paint(mCornerShadowPaint);
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mPaint.setAlpha(alpha);
+        mCornerShadowPaint.setAlpha(alpha);
+        mEdgeShadowPaint.setAlpha(alpha);
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        super.onBoundsChange(bounds);
+        mDirty = true;
+    }
+
+    void setShadowSize(float shadowSize, float maxShadowSize) {
+        if (shadowSize < 0 || maxShadowSize < 0) {
+            throw new IllegalArgumentException("invalid shadow size");
+        }
+        if (shadowSize > maxShadowSize) {
+            shadowSize = maxShadowSize;
+            if (!mPrintedShadowClipWarning) {
+                Log.w("CardView", "Shadow size is being clipped by the max shadow size. See "
+                        + "{CardView#setMaxCardElevation}.");
+                mPrintedShadowClipWarning = true;
+            }
+        }
+        if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) {
+            return;
+        }
+        mRawShadowSize = shadowSize;
+        mRawMaxShadowSize = maxShadowSize;
+        mShadowSize = shadowSize * SHADOW_MULTIPLIER + mInsetShadow;
+        mMaxShadowSize = maxShadowSize + mInsetShadow;
+        mDirty = true;
+        invalidateSelf();
+    }
+
+    @Override
+    public boolean getPadding(Rect padding) {
+        int verticalOffset = (int) Math
+                .round(mRawMaxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * mCornerRadius);
+        int horizontalOffset = (int) Math.round(mRawMaxShadowSize + (1 - COS_45) * mCornerRadius);
+        padding.set(horizontalOffset, verticalOffset, horizontalOffset, verticalOffset);
+        return true;
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter cf) {
+        mPaint.setColorFilter(cf);
+        mCornerShadowPaint.setColorFilter(cf);
+        mEdgeShadowPaint.setColorFilter(cf);
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.OPAQUE;
+    }
+
+    void setCornerRadius(float radius) {
+        if (mCornerRadius == radius) {
+            return;
+        }
+        mCornerRadius = radius;
+        mDirty = true;
+        invalidateSelf();
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mDirty) {
+            buildComponents(getBounds());
+            mDirty = false;
+        }
+        canvas.translate(0, mRawShadowSize / 2);
+        drawShadow(canvas);
+        canvas.translate(0, -mRawShadowSize / 2);
+        sRoundRectHelper.drawRoundRect(canvas, mCardBounds, mCornerRadius, mPaint);
+    }
+
+    private void drawShadow(Canvas canvas) {
+        final float edgeShadowTop = -mCornerRadius - mShadowSize;
+        final float inset = mCornerRadius + mInsetShadow + mRawShadowSize / 2;
+        final boolean drawHorizontalEdges = mCardBounds.width() - 2 * inset > 0;
+        final boolean drawVerticalEdges = mCardBounds.height() - 2 * inset > 0;
+        // LT
+        int saved = canvas.save();
+        canvas.translate(mCardBounds.left + inset, mCardBounds.top + inset);
+        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
+        if (drawHorizontalEdges) {
+            canvas.drawRect(0, edgeShadowTop,
+                    mCardBounds.width() - 2 * inset, -mCornerRadius,
+                    mEdgeShadowPaint);
+        }
+        canvas.restoreToCount(saved);
+        // RB
+        saved = canvas.save();
+        canvas.translate(mCardBounds.right - inset, mCardBounds.bottom - inset);
+        canvas.rotate(180f);
+        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
+        if (drawHorizontalEdges) {
+            canvas.drawRect(0, edgeShadowTop,
+                    mCardBounds.width() - 2 * inset, -mCornerRadius + mShadowSize,
+                    mEdgeShadowPaint);
+        }
+        canvas.restoreToCount(saved);
+        // LB
+        saved = canvas.save();
+        canvas.translate(mCardBounds.left + inset, mCardBounds.bottom - inset);
+        canvas.rotate(270f);
+        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
+        if (drawVerticalEdges) {
+            canvas.drawRect(0, edgeShadowTop,
+                    mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
+        }
+        canvas.restoreToCount(saved);
+        // RT
+        saved = canvas.save();
+        canvas.translate(mCardBounds.right - inset, mCardBounds.top + inset);
+        canvas.rotate(90f);
+        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
+        if (drawVerticalEdges) {
+            canvas.drawRect(0, edgeShadowTop,
+                    mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
+        }
+        canvas.restoreToCount(saved);
+    }
+
+    private void buildShadowCorners() {
+        RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
+        RectF outerBounds = new RectF(innerBounds);
+        outerBounds.inset(-mShadowSize, -mShadowSize);
+
+        if (mCornerShadowPath == null) {
+            mCornerShadowPath = new Path();
+        } else {
+            mCornerShadowPath.reset();
+        }
+        mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
+        mCornerShadowPath.moveTo(-mCornerRadius, 0);
+        mCornerShadowPath.rLineTo(-mShadowSize, 0);
+        // outer arc
+        mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
+        // inner arc
+        mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
+        mCornerShadowPath.close();
+
+        float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
+        mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
+                new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
+                new float[]{0f, startRatio, 1f}
+                , Shader.TileMode.CLAMP));
+
+        // we offset the content shadowSize/2 pixels up to make it more realistic.
+        // this is why edge shadow shader has some extra space
+        // When drawing bottom edge shadow, we use that extra space.
+        mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0,
+                -mCornerRadius - mShadowSize,
+                new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
+                new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
+    }
+
+    private void buildComponents(Rect bounds) {
+        // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift.
+        // We could have different top-bottom offsets to avoid extra gap above but in that case
+        // center aligning Views inside the CardView would be problematic.
+        final float verticalOffset = mMaxShadowSize * SHADOW_MULTIPLIER;
+        mCardBounds.set(bounds.left + mMaxShadowSize, bounds.top + verticalOffset,
+                bounds.right - mMaxShadowSize, bounds.bottom - verticalOffset);
+        buildShadowCorners();
+    }
+
+    float getCornerRadius() {
+        return mCornerRadius;
+    }
+
+    void getMaxShadowAndCornerPadding(Rect into) {
+        getPadding(into);
+    }
+
+    void setShadowSize(float size) {
+        setShadowSize(size, mRawMaxShadowSize);
+    }
+
+    void setMaxShadowSize(float size) {
+        setShadowSize(mRawShadowSize, size);
+    }
+
+    float getShadowSize() {
+        return mRawShadowSize;
+    }
+
+    float getMaxShadowSize() {
+        return mRawMaxShadowSize;
+    }
+
+    float getMinWidth() {
+        final float content = 2 *
+                Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow + mRawMaxShadowSize / 2);
+        return content + (mRawMaxShadowSize + mInsetShadow) * 2;
+    }
+
+    float getMinHeight() {
+        final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow
+                        + mRawMaxShadowSize * SHADOW_MULTIPLIER / 2);
+        return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2;
+    }
+
+    static interface RoundRectHelper {
+        void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius, Paint paint);
+    }
+}
\ No newline at end of file
diff --git a/v7/cardview/jellybean-mr1/android/support/v7/widget/CardViewJellybeanMr1.java b/v7/cardview/jellybean-mr1/android/support/v7/widget/CardViewJellybeanMr1.java
new file mode 100644
index 0000000..bb38a3c
--- /dev/null
+++ b/v7/cardview/jellybean-mr1/android/support/v7/widget/CardViewJellybeanMr1.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+
+class CardViewJellybeanMr1 extends CardViewEclairMr1 {
+
+    @Override
+    public void initStatic() {
+        RoundRectDrawableWithShadow.sRoundRectHelper
+                = new RoundRectDrawableWithShadow.RoundRectHelper() {
+            @Override
+            public void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius,
+                    Paint paint) {
+                canvas.drawRoundRect(bounds, cornerRadius, cornerRadius, paint);
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/v7/cardview/res/values/attrs.xml b/v7/cardview/res/values/attrs.xml
new file mode 100644
index 0000000..c370c95
--- /dev/null
+++ b/v7/cardview/res/values/attrs.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <declare-styleable name="CardView">
+        <!-- Background color for CardView. -->
+        <attr name="cardBackgroundColor" format="color" />
+        <!-- Corner radius for CardView. -->
+        <attr name="cardCornerRadius" format="dimension" />
+        <!-- Elevation for CardView. -->
+        <attr name="cardElevation" format="dimension" />
+        <!-- Maximum Elevation for CardView. -->
+        <attr name="cardMaxElevation" format="dimension" />
+    </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/v7/cardview/res/values/colors.xml b/v7/cardview/res/values/colors.xml
new file mode 100644
index 0000000..3ed7087
--- /dev/null
+++ b/v7/cardview/res/values/colors.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <!-- Background color for light CardView. -->
+    <color name="cardview_light_background">#FFFAFAFA</color>
+	<!-- Background color for dark CardView. -->
+	<color name="cardview_dark_background">#FF202020</color>
+    <!-- Shadow color for the first pixels around CardView. -->
+    <color name="cardview_shadow_start_color">#37000000</color>
+    <!-- Shadow color for the furthest pixels around CardView. -->
+    <color name="cardview_shadow_end_color">#03000000</color>
+</resources>
\ No newline at end of file
diff --git a/v7/cardview/res/values/dimens.xml b/v7/cardview/res/values/dimens.xml
new file mode 100644
index 0000000..ebfbb3a
--- /dev/null
+++ b/v7/cardview/res/values/dimens.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <!-- Default radius for CardView corners. -->
+    <dimen name="cardview_default_radius">2dp</dimen>
+    <!-- Elevation value to use for CardViews. Pre-L, it is equal to shadow size. -->
+    <dimen name="cardview_default_elevation">2dp</dimen>
+    <!-- Inset shadow for RoundRectDrawableWithShadow. It is used to avoid gaps between the card
+     and the shadow. -->
+    <dimen name="cardview_compat_inset_shadow">1dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/v7/cardview/res/values/styles.xml b/v7/cardview/res/values/styles.xml
new file mode 100644
index 0000000..165b7c8
--- /dev/null
+++ b/v7/cardview/res/values/styles.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+    <style name="CardView">
+        <item name="cardBackgroundColor">@color/cardview_light_background</item>
+        <item name="cardCornerRadius">@dimen/cardview_default_radius</item>
+        <item name="cardElevation">@dimen/cardview_default_elevation</item>
+        <item name="cardMaxElevation">@dimen/cardview_default_elevation</item>
+    </style>
+    <style name="CardView.Light">
+        <item name="cardBackgroundColor">@color/cardview_light_background</item>
+    </style>
+    <style name="CardView.Dark">
+        <item name="cardBackgroundColor">@color/cardview_dark_background</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/v7/cardview/src/.readme b/v7/cardview/src/.readme
new file mode 100644
index 0000000..4bcebad
--- /dev/null
+++ b/v7/cardview/src/.readme
@@ -0,0 +1,2 @@
+This hidden file is there to ensure there is an src folder.
+Once we support binary library this will go away.
\ No newline at end of file
diff --git a/v7/cardview/src/android/support/v7/widget/CardView.java b/v7/cardview/src/android/support/v7/widget/CardView.java
new file mode 100644
index 0000000..0fcbe05
--- /dev/null
+++ b/v7/cardview/src/android/support/v7/widget/CardView.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.os.Build;
+import android.support.v7.cardview.R;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+/**
+ * A FrameLayout with a rounded corner background and shadow.
+ * <p>
+ * CardView uses <code>elevation</code> property on L for shadows and falls back to a custom shadow
+ * implementation on older platforms.
+ * <p>
+ * Due to expensive nature of rounded corner clipping, on platforms before L, CardView does not
+ * clip its children that intersect with rounded corners. Instead, it adds padding to avoid such
+ * intersection.
+ * <p>
+ * Before L, CardView adds padding to its content and draws shadows to that area. This padding
+ * amount is equal to {@link #getMaxCardElevation()} on the sides and
+ * <code>1.5 * {@link #getMaxCardElevation()}</code> on top and bottom. Since padding is used to
+ * offset content for shadows, you cannot set custom padding on CardView.
+ * <p>
+ * To change CardView's elevation in a backward compatible way, use
+ * {@link #setCardElevation(float)}. CardView will use elevation API on L and before L, it will
+ * change the shadow size. To avoid moving the View while shadow size is changing, shadow size is
+ * clamped by {@link #getMaxCardElevation()}. If you want to change elevation dynamically, you
+ * should call {@link #setMaxCardElevation(float)} when CardView is initialized.
+ *
+ * @attr ref android.support.v7.cardview.R.styleable#CardView_cardBackgroundColor
+ * @attr ref android.support.v7.cardview.R.styleable#CardView_cardCornerRadius
+ * @attr ref android.support.v7.cardview.R.styleable#CardView_cardElevation
+ * @attr ref android.support.v7.cardview.R.styleable#CardView_cardMaxElevation
+ */
+public class CardView extends FrameLayout implements CardViewDelegate {
+
+    private final static CardViewImpl IMPL;
+
+    static {
+        if ("L".equals(Build.VERSION.CODENAME) || Build.VERSION.SDK_INT >= 21) {
+            IMPL = new CardViewApi21();
+        } else if (Build.VERSION.SDK_INT >= 17) {
+            IMPL = new CardViewJellybeanMr1();
+        } else {
+            IMPL = new CardViewEclairMr1();
+        }
+        IMPL.initStatic();
+    }
+
+    public CardView(Context context) {
+        super(context);
+        initialize(context, null, 0);
+    }
+
+    public CardView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initialize(context, attrs, 0);
+    }
+
+    public CardView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initialize(context, attrs, defStyleAttr);
+    }
+
+    private Rect mShadowRect = new Rect();
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (IMPL instanceof CardViewApi21 == false) {
+            final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+            int extraWidth = 0;
+            int extraHeight = 0;
+            switch (widthMode) {
+                case MeasureSpec.EXACTLY:
+                case MeasureSpec.AT_MOST:
+                    final int minWidth = (int) Math.ceil(IMPL.getMinWidth(this));
+                    widthMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(minWidth,
+                            MeasureSpec.getSize(widthMeasureSpec)), widthMode);
+                    break;
+                case MeasureSpec.UNSPECIFIED:
+                    extraWidth = mShadowRect.left + mShadowRect.right;
+                    break;
+            }
+
+            final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+            switch (heightMode) {
+                case MeasureSpec.EXACTLY:
+                case MeasureSpec.AT_MOST:
+                    final int minHeight = (int) Math.ceil(IMPL.getMinHeight(this));
+                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(minHeight,
+                            MeasureSpec.getSize(heightMeasureSpec)), heightMode);
+                    break;
+                case MeasureSpec.UNSPECIFIED:
+                    extraHeight = mShadowRect.top + mShadowRect.bottom;
+                    break;
+            }
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            if (extraWidth != 0 || extraHeight != 0) {
+                setMeasuredDimension(getMeasuredWidth() + extraWidth,
+                        getMeasuredHeight() + extraHeight);
+            }
+        } else {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+
+    }
+
+    private void initialize(Context context, AttributeSet attrs, int defStyleAttr) {
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CardView, defStyleAttr,
+                R.style.CardView_Light);
+        int backgroundColor = a.getColor(R.styleable.CardView_cardBackgroundColor, 0);
+        float radius = a.getDimension(R.styleable.CardView_cardCornerRadius, 0);
+        float elevation = a.getDimension(R.styleable.CardView_cardElevation, 0);
+        float maxElevation = a.getDimension(R.styleable.CardView_cardMaxElevation, 0);
+        if (elevation > maxElevation) {
+            maxElevation = elevation;
+        }
+        a.recycle();
+
+        IMPL.initialize(this, context, backgroundColor, radius, elevation, maxElevation);
+    }
+
+    /**
+     * Updates the corner radius of the CardView.
+     *
+     * @param radius The radius in pixels of the corners of the rectangle shape
+     * @attr ref android.support.v7.cardview.R.styleable#CardView_cardCornerRadius
+     * @see #setRadius(float)
+     */
+    public void setRadius(float radius) {
+        IMPL.setRadius(this, radius);
+    }
+
+    /**
+     * Returns the corner radius of the CardView.
+     *
+     * @return Corner radius of the CardView
+     * @see #getRadius()
+     */
+    public float getRadius() {
+        return IMPL.getRadius(this);
+    }
+
+    /**
+     * Updates the backward compatible elevation of the CardView.
+     *
+     * @param radius The backward compatible elevation in pixels.
+     * @attr ref android.support.v7.cardview.R.styleable#CardView_cardElevation
+     * @see #getCardElevation()
+     * @see #setMaxCardElevation(float)
+     */
+    public void setCardElevation(float radius) {
+        IMPL.setElevation(this, radius);
+    }
+
+    /**
+     * Returns the backward compatible elevation of the CardView.
+     *
+     * @return Elevation of the CardView
+     * @see #setCardElevation(float)
+     * @see #getMaxCardElevation()
+     */
+    public float getCardElevation() {
+        return IMPL.getElevation(this);
+    }
+
+    /**
+     * Updates the backward compatible elevation of the CardView.
+     * <p>
+     * Calling this method has no effect if device OS version is L or newer.
+     *
+     * @param radius The backward compatible elevation in pixels.
+     * @attr ref android.support.v7.cardview.R.styleable#CardView_cardElevation
+     * @see #setCardElevation(float)
+     * @see #getMaxCardElevation()
+     */
+    public void setMaxCardElevation(float radius) {
+        IMPL.setMaxElevation(this, radius);
+    }
+
+    /**
+     * Returns the backward compatible elevation of the CardView.
+     * <p>
+     * If device OS version is L or newer, this method returns 0.
+     *
+     * @return Elevation of the CardView
+     * @see #setMaxCardElevation(float)
+     * @see #getCardElevation()
+     */
+    public float getMaxCardElevation() {
+        return IMPL.getMaxElevation(this);
+    }
+}
diff --git a/v7/gridlayout/build.gradle b/v7/gridlayout/build.gradle
index 8e6d942..bb586d1 100644
--- a/v7/gridlayout/build.gradle
+++ b/v7/gridlayout/build.gradle
@@ -6,10 +6,8 @@
     compile project(':support-v4')
 }
 
-
 android {
-    compileSdkVersion 19
-    buildToolsVersion "19.0.1"
+    compileSdkVersion 'current'
 
     sourceSets {
         main.manifest.srcFile 'AndroidManifest.xml'
diff --git a/v7/gridlayout/res/values/attrs.xml b/v7/gridlayout/res/values/attrs.xml
index ab3bc23..90dc702 100644
--- a/v7/gridlayout/res/values/attrs.xml
+++ b/v7/gridlayout/res/values/attrs.xml
@@ -124,6 +124,9 @@
         See {@link android.widget.GridLayout.Spec}.
         -->
         <attr name="layout_rowSpan" format="integer" min="1" />
+        <!-- The relative proportion of horizontal space that should be allocated to this view
+        during excess space distribution. -->
+        <attr name="layout_rowWeight" format="float" />
         <!--
         The column boundary delimiting the left of the group of cells
         occupied by this view.
@@ -136,6 +139,9 @@
         See {@link android.widget.GridLayout.Spec}.
         -->
         <attr name="layout_columnSpan" format="integer" min="1" />
+        <!-- The relative proportion of vertical space that should be allocated to this view
+        during excess space distribution. -->
+        <attr name="layout_columnWeight" format="float" />
         <!--
         Gravity specifies how a component should be placed in its group of cells.
         The default is LEFT | BASELINE.
diff --git a/v7/gridlayout/src/android/support/v7/widget/GridLayout.java b/v7/gridlayout/src/android/support/v7/widget/GridLayout.java
index a0e6ca7..b5cb092 100644
--- a/v7/gridlayout/src/android/support/v7/widget/GridLayout.java
+++ b/v7/gridlayout/src/android/support/v7/widget/GridLayout.java
@@ -102,14 +102,15 @@
  *
  * <h4>Excess Space Distribution</h4>
  *
- * GridLayout's distribution of excess space is based on <em>priority</em>
- * rather than <em>weight</em>.
+ * GridLayout's distribution of excess space accommodates the principle of weight.
+ * In the event that no weights are specified, columns and rows are taken as
+ * flexible if their views specify some form of alignment within their groups.
  * <p>
- * A child's ability to stretch is inferred from the alignment properties of
- * its row and column groups (which are typically set by setting the
- * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters).
- * If alignment was defined along a given axis then the component
- * is taken as <em>flexible</em> in that direction. If no alignment was set,
+ * The flexibility of a view is therefore influenced by its alignment which is,
+ * in turn, typically defined by setting the
+ * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters.
+ * If either a weight or alignment were defined along a given axis then the component
+ * is taken as <em>flexible</em> in that direction. If no weight or alignment was set,
  * the component is instead assumed to be <em>inflexible</em>.
  * <p>
  * Multiple components in the same row or column group are
@@ -120,12 +121,16 @@
  * elements is flexible if <em>one</em> of its elements is flexible.
  * <p>
  * To make a column stretch, make sure all of the components inside it define a
- * gravity. To prevent a column from stretching, ensure that one of the components
- * in the column does not define a gravity.
+ * weight or a gravity. To prevent a column from stretching, ensure that one of the components
+ * in the column does not define a weight or a gravity.
  * <p>
  * When the principle of flexibility does not provide complete disambiguation,
  * GridLayout's algorithms favour rows and columns that are closer to its <em>right</em>
- * and <em>bottom</em> edges.
+ * and <em>bottom</em> edges. To be more precise, GridLayout treats each of its layout
+ * parameters as a constraint in the a set of variables that define the grid-lines along a
+ * given axis. During layout, GridLayout solves the constraints so as to return the unique
+ * solution to those constraints for which all variables are less-than-or-equal-to
+ * the corresponding value in any other valid solution.
  *
  * <h4>Interpretation of GONE</h4>
  *
@@ -138,18 +143,6 @@
  * had never been added to it.
  * These statements apply equally to rows as well as columns, and to groups of rows or columns.
  *
- * <h5>Limitations</h5>
- *
- * GridLayout does not provide support for the principle of <em>weight</em>, as defined in
- * {@link LinearLayout.LayoutParams#weight}. In general, it is not therefore possible
- * to configure a GridLayout to distribute excess space between multiple components.
- * <p>
- * Some common use-cases may nevertheless be accommodated as follows.
- * To place equal amounts of space around a component in a cell group;
- * use {@link #CENTER} alignment (or {@link LayoutParams#setGravity(int) gravity}).
- * For complete control over excess space distribution in a row or column;
- * use a {@link LinearLayout} subview to hold the components in the associated cell group.
- * When using either of these techniques, bear in mind that cell groups may be defined to overlap.
  * <p>
  * See {@link GridLayout.LayoutParams} for a full description of the
  * layout parameters used by GridLayout.
@@ -161,7 +154,7 @@
  * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
  * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
  */
-public class GridLayout extends android.view.ViewGroup {
+public class GridLayout extends ViewGroup {
 
     // Public constants
 
@@ -909,6 +902,8 @@
             LayoutParams lp = getLayoutParams(c);
             if (firstPass) {
                 measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height);
+                mHorizontalAxis.recordOriginalMeasurement(i);
+                mVerticalAxis.recordOriginalMeasurement(i);
             } else {
                 boolean horizontal = (mOrientation == HORIZONTAL);
                 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
@@ -1124,6 +1119,11 @@
         public int[] locations;
         public boolean locationsValid = false;
 
+        public boolean hasWeights;
+        public boolean hasWeightsValid = false;
+        public int[] originalMeasurements;
+        public int[] deltas;
+
         boolean orderPreserved = DEFAULT_ORDER_PRESERVED;
 
         private MutableInt parentMin = new MutableInt(0);
@@ -1200,7 +1200,10 @@
                 // we must include views that are GONE here, see introductory javadoc
                 LayoutParams lp = getLayoutParams(c);
                 Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
-                groupBounds.getValue(i).include(GridLayout.this, c, spec, this);
+                int size = (spec.weight == 0) ?
+                        getMeasurementIncludingMargin(c, horizontal) :
+                        getOriginalMeasurements()[i] + getDeltas()[i];
+                groupBounds.getValue(i).include(GridLayout.this, c, spec, this, size);
             }
         }
 
@@ -1572,8 +1575,94 @@
             return trailingMargins;
         }
 
-        private void computeLocations(int[] a) {
+        private void solve(int[] a) {
             solve(getArcs(), a);
+        }
+
+        private boolean computeHasWeights() {
+            for (int i = 0, N = getChildCount(); i < N; i++) {
+                LayoutParams lp = getLayoutParams(getChildAt(i));
+                Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
+                if (spec.weight != 0) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        private boolean hasWeights() {
+            if (!hasWeightsValid) {
+                hasWeights = computeHasWeights();
+                hasWeightsValid = true;
+            }
+            return hasWeights;
+        }
+
+        public int[] getOriginalMeasurements() {
+            if (originalMeasurements == null) {
+                originalMeasurements = new int[getChildCount()];
+            }
+            return originalMeasurements;
+        }
+
+        private void recordOriginalMeasurement(int i) {
+            if (hasWeights()) {
+                getOriginalMeasurements()[i] = getMeasurementIncludingMargin(getChildAt(i), horizontal);
+            }
+        }
+
+        public int[] getDeltas() {
+            if (deltas == null) {
+                deltas = new int[getChildCount()];
+            }
+            return deltas;
+        }
+
+        private void shareOutDelta() {
+            int totalDelta = 0;
+            float totalWeight = 0;
+            for (int i = 0, N = getChildCount(); i < N; i++) {
+                View c = getChildAt(i);
+                LayoutParams lp = getLayoutParams(c);
+                Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
+                float weight = spec.weight;
+                if (weight != 0) {
+                    int delta = getMeasurement(c, horizontal) - getOriginalMeasurements()[i];
+                    totalDelta += delta;
+                    totalWeight += weight;
+                }
+            }
+            for (int i = 0, N = getChildCount(); i < N; i++) {
+                LayoutParams lp = getLayoutParams(getChildAt(i));
+                Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
+                float weight = spec.weight;
+                if (weight != 0) {
+                    int delta = Math.round((weight * totalDelta / totalWeight));
+                    deltas[i] = delta;
+                    // the two adjustments below are to counter the above rounding and avoid off-by-ones at the end
+                    totalDelta -= delta;
+                    totalWeight -= weight;
+                }
+            }
+        }
+
+        private void solveAndDistributeSpace(int[] a) {
+            Arrays.fill(getDeltas(), 0);
+            solve(a);
+            shareOutDelta();
+            arcsValid = false;
+            forwardLinksValid = false;
+            backwardLinksValid = false;
+            groupBoundsValid = false;
+            solve(a);
+        }
+
+        private void computeLocations(int[] a) {
+            if (!hasWeights()) {
+                solve(a);
+            } else {
+                solveAndDistributeSpace(a);
+            }
             if (!orderPreserved) {
                 // Solve returns the smallest solution to the constraint system for which all
                 // values are positive. One value is therefore zero - though if the row/col
@@ -1656,6 +1745,10 @@
 
             locations = null;
 
+            originalMeasurements = null;
+            deltas = null;
+            hasWeightsValid = false;
+
             invalidateValues();
         }
 
@@ -1689,6 +1782,9 @@
      * both aspects of alignment within the cell group. It is also possible to specify a child's
      * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)}
      * method.
+     * <p>
+     * The weight property is also included in Spec and specifies the proportion of any
+     * excess space that is due to the associated view.
      *
      * <h4>WRAP_CONTENT and MATCH_PARENT</h4>
      *
@@ -1730,9 +1826,11 @@
      *     <li>{@link #rowSpec}<code>.row</code> = {@link #UNDEFINED} </li>
      *     <li>{@link #rowSpec}<code>.rowSpan</code> = 1 </li>
      *     <li>{@link #rowSpec}<code>.alignment</code> = {@link #BASELINE} </li>
+     *     <li>{@link #rowSpec}<code>.weight</code> = 0 </li>
      *     <li>{@link #columnSpec}<code>.column</code> = {@link #UNDEFINED} </li>
      *     <li>{@link #columnSpec}<code>.columnSpan</code> = 1 </li>
      *     <li>{@link #columnSpec}<code>.alignment</code> = {@link #START} </li>
+     *     <li>{@link #columnSpec}<code>.weight</code> = 0 </li>
      * </ul>
      *
      * See {@link GridLayout} for a more complete description of the conventions
@@ -1740,8 +1838,10 @@
      *
      * @attr ref android.R.styleable#GridLayout_Layout_layout_row
      * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan
+     * @attr ref android.R.styleable#GridLayout_Layout_layout_rowWeight
      * @attr ref android.R.styleable#GridLayout_Layout_layout_column
      * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan
+     * @attr ref android.R.styleable#GridLayout_Layout_layout_columnWeight
      * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
      */
     public static class LayoutParams extends MarginLayoutParams {
@@ -1766,9 +1866,11 @@
 
         private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column;
         private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan;
+        private static final int COLUMN_WEIGHT = R.styleable.GridLayout_Layout_layout_columnWeight;
 
         private static final int ROW = R.styleable.GridLayout_Layout_layout_row;
         private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan;
+        private static final int ROW_WEIGHT = R.styleable.GridLayout_Layout_layout_rowWeight;
 
         private static final int GRAVITY = R.styleable.GridLayout_Layout_layout_gravity;
 
@@ -1852,12 +1954,16 @@
         }
 
         /**
-         * {@inheritDoc}
+         * Copy constructor. Clones the width, height, margin values, row spec,
+         * and column spec of the source.
+         *
+         * @param source The layout params to copy from.
          */
-        public LayoutParams(LayoutParams that) {
-            super(that);
-            this.rowSpec = that.rowSpec;
-            this.columnSpec = that.columnSpec;
+        public LayoutParams(LayoutParams source) {
+            super(source);
+
+            this.rowSpec = source.rowSpec;
+            this.columnSpec = source.columnSpec;
         }
 
         // AttributeSet constructors
@@ -1907,11 +2013,13 @@
 
                 int column = a.getInt(COLUMN, DEFAULT_COLUMN);
                 int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE);
-                this.columnSpec = spec(column, colSpan, getAlignment(gravity, true));
+                float colWeight = a.getFloat(COLUMN_WEIGHT, Spec.DEFAULT_WEIGHT);
+                this.columnSpec = spec(column, colSpan, getAlignment(gravity, true), colWeight);
 
                 int row = a.getInt(ROW, DEFAULT_ROW);
                 int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE);
-                this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false));
+                float rowWeight = a.getFloat(ROW_WEIGHT, Spec.DEFAULT_WEIGHT);
+                this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false), rowWeight);
             } finally {
                 a.recycle();
             }
@@ -2146,10 +2254,9 @@
             return before - a.getAlignmentValue(c, size, ViewGroupCompat.getLayoutMode(gl));
         }
 
-        protected final void include(GridLayout gl, View c, Spec spec, Axis axis) {
+        protected final void include(GridLayout gl, View c, Spec spec, Axis axis, int size) {
             this.flexibility &= spec.getFlexibility();
             boolean horizontal = axis.horizontal;
-            int size = gl.getMeasurementIncludingMargin(c, horizontal);
             Alignment alignment = gl.getAlignment(spec.alignment, horizontal);
             // todo test this works correctly when the returned value is UNDEFINED
             int before = alignment.getAlignmentValue(c, size, ViewGroupCompat.getLayoutMode(gl));
@@ -2274,36 +2381,43 @@
      *   <li>{@link #spec(int, int)}</li>
      *   <li>{@link #spec(int, Alignment)}</li>
      *   <li>{@link #spec(int, int, Alignment)}</li>
+     *   <li>{@link #spec(int, float)}</li>
+     *   <li>{@link #spec(int, int, float)}</li>
+     *   <li>{@link #spec(int, Alignment, float)}</li>
+     *   <li>{@link #spec(int, int, Alignment, float)}</li>
      * </ul>
      *
      */
     public static class Spec {
         static final Spec UNDEFINED = spec(GridLayout.UNDEFINED);
+        static final float DEFAULT_WEIGHT = 0;
 
         final boolean startDefined;
         final Interval span;
         final Alignment alignment;
+        final float weight;
 
-        private Spec(boolean startDefined, Interval span, Alignment alignment) {
+        private Spec(boolean startDefined, Interval span, Alignment alignment, float weight) {
             this.startDefined = startDefined;
             this.span = span;
             this.alignment = alignment;
+            this.weight = weight;
         }
 
-        private Spec(boolean startDefined, int start, int size, Alignment alignment) {
-            this(startDefined, new Interval(start, start + size), alignment);
+        private Spec(boolean startDefined, int start, int size, Alignment alignment, float weight) {
+            this(startDefined, new Interval(start, start + size), alignment, weight);
         }
 
         final Spec copyWriteSpan(Interval span) {
-            return new Spec(startDefined, span, alignment);
+            return new Spec(startDefined, span, alignment, weight);
         }
 
         final Spec copyWriteAlignment(Alignment alignment) {
-            return new Spec(startDefined, span, alignment);
+            return new Spec(startDefined, span, alignment, weight);
         }
 
         final int getFlexibility() {
-            return (alignment == UNDEFINED_ALIGNMENT) ? INFLEXIBLE : CAN_STRETCH;
+            return (alignment == UNDEFINED_ALIGNMENT && weight == 0) ? INFLEXIBLE : CAN_STRETCH;
         }
 
         /**
@@ -2351,6 +2465,7 @@
      * <ul>
      *     <li> {@code spec.span = [start, start + size]} </li>
      *     <li> {@code spec.alignment = alignment} </li>
+     *     <li> {@code spec.weight = weight} </li>
      * </ul>
      * <p>
      * To leave the start index undefined, use the value {@link #UNDEFINED}.
@@ -2358,9 +2473,55 @@
      * @param start     the start
      * @param size      the size
      * @param alignment the alignment
+     * @param weight    the weight
+     */
+    public static Spec spec(int start, int size, Alignment alignment, float weight) {
+        return new Spec(start != UNDEFINED, start, size, alignment, weight);
+    }
+
+    /**
+     * Equivalent to: {@code spec(start, 1, alignment, weight)}.
+     *
+     * @param start     the start
+     * @param alignment the alignment
+     * @param weight    the weight
+     */
+    public static Spec spec(int start, Alignment alignment, float weight) {
+        return spec(start, 1, alignment, weight);
+    }
+
+    /**
+     * Equivalent to: {@code spec(start, 1, default_alignment, weight)} -
+     * where {@code default_alignment} is specified in
+     * {@link android.widget.GridLayout.LayoutParams}.
+     *
+     * @param start  the start
+     * @param size   the size
+     * @param weight the weight
+     */
+    public static Spec spec(int start, int size, float weight) {
+        return spec(start, size, UNDEFINED_ALIGNMENT, weight);
+    }
+
+    /**
+     * Equivalent to: {@code spec(start, 1, weight)}.
+     *
+     * @param start  the start
+     * @param weight the weight
+     */
+    public static Spec spec(int start, float weight) {
+        return spec(start, 1, weight);
+    }
+
+    /**
+     * Equivalent to: {@code spec(start, size, alignment, 0f)}.
+     *
+     * @param start     the start
+     * @param size      the size
+     * @param alignment the alignment
      */
     public static Spec spec(int start, int size, Alignment alignment) {
-        return new Spec(start != UNDEFINED, start, size, alignment);
+        return spec(start, size, alignment, Spec.DEFAULT_WEIGHT);
     }
 
     /**
diff --git a/v7/mediarouter/build.gradle b/v7/mediarouter/build.gradle
index 4fee3bf..9ad1915 100644
--- a/v7/mediarouter/build.gradle
+++ b/v7/mediarouter/build.gradle
@@ -32,7 +32,7 @@
     jellybeanmr1Compile getAndroidPrebuilt('17')
     jellybeanmr1Compile sourceSets.jellybean.output
 
-    jellybeanmr2Compile getAndroidPrebuilt('18')
+    jellybeanmr2Compile getAndroidPrebuilt('current')
     jellybeanmr2Compile sourceSets.jellybean.output
     jellybeanmr2Compile sourceSets.jellybeanmr1.output
 
@@ -40,8 +40,7 @@
 }
 
 android {
-    compileSdkVersion 19
-    buildToolsVersion "19.0.1"
+    compileSdkVersion 'current'
 
     sourceSets {
         main.manifest.srcFile 'AndroidManifest.xml'
diff --git a/v7/mediarouter/project.properties b/v7/mediarouter/project.properties
index dfa4dd0..484dab0 100644
--- a/v7/mediarouter/project.properties
+++ b/v7/mediarouter/project.properties
@@ -11,5 +11,5 @@
 #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
 
 # Project target.
-target=android-16
+target=android-17
 android.library=true
diff --git a/v7/mediarouter/res/values-af/strings.xml b/v7/mediarouter/res/values-af/strings.xml
new file mode 100644
index 0000000..885af10
--- /dev/null
+++ b/v7/mediarouter/res/values-af/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Stelsel"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Toestelle"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Media-uitvoer"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Koppel aan toestel"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Soek tans vir toestelle…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Ontkoppel"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-am/strings.xml b/v7/mediarouter/res/values-am/strings.xml
new file mode 100644
index 0000000..0027737
--- /dev/null
+++ b/v7/mediarouter/res/values-am/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"ስርዓት"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"መሣሪያዎች"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"የሚዲያ ውፅዓት"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"ከመሳሪያ ጋር ያገናኙ"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"መሳሪያዎችን በመፈለግ ላይ…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"ግንኙነት አቋርጥ"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-ar/strings.xml b/v7/mediarouter/res/values-ar/strings.xml
new file mode 100644
index 0000000..9289a35
--- /dev/null
+++ b/v7/mediarouter/res/values-ar/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"النظام"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"الأجهزة"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"المنفذ الإعلامي"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"الاتصال بجهاز"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"جارٍ البحث عن الأجهزة…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"قطع الاتصال"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-bg/strings.xml b/v7/mediarouter/res/values-bg/strings.xml
new file mode 100644
index 0000000..ff1401e
--- /dev/null
+++ b/v7/mediarouter/res/values-bg/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Система"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Устройства"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Изходяща мултимедия"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Свързване с устройство"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Търсят се устройства…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Прекратяване на връзката"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-bn-rBD/strings.xml b/v7/mediarouter/res/values-bn-rBD/strings.xml
new file mode 100644
index 0000000..d0b2c32
--- /dev/null
+++ b/v7/mediarouter/res/values-bn-rBD/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"সিস্টেম"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"ডিভাইসগুলি"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"মিডিয়া আউটপুট"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"ডিভাইসে সংযোগ করুন"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"ডিভাইসগুলি অনুসন্ধান করা হচ্ছে…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"সংযোগ বিচ্ছিন্ন করুন"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-ca/strings.xml b/v7/mediarouter/res/values-ca/strings.xml
new file mode 100644
index 0000000..dd485de
--- /dev/null
+++ b/v7/mediarouter/res/values-ca/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositius"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Sortida de contingut multimèdia"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Connecta al dispositiu"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"S\'estan cercant dispositius…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Desconnecta"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-cs/strings.xml b/v7/mediarouter/res/values-cs/strings.xml
new file mode 100644
index 0000000..4687100
--- /dev/null
+++ b/v7/mediarouter/res/values-cs/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Systém"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Zařízení"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Výstup médií"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Připojení k zařízení"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Vyhledávání zařízení…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Odpojit"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-da/strings.xml b/v7/mediarouter/res/values-da/strings.xml
new file mode 100644
index 0000000..fd3b0fb
--- /dev/null
+++ b/v7/mediarouter/res/values-da/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Enheder"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Medieudgang"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Opret forbindelse til enheden"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Søger efter enheder..."</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Afbryd forbindelsen"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-de/strings.xml b/v7/mediarouter/res/values-de/strings.xml
new file mode 100644
index 0000000..9df0ebf
--- /dev/null
+++ b/v7/mediarouter/res/values-de/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Geräte"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Medienausgabe"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Mit Gerät verbinden"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Geräte werden gesucht…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Verbindung aufheben"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-el/strings.xml b/v7/mediarouter/res/values-el/strings.xml
new file mode 100644
index 0000000..5a61395
--- /dev/null
+++ b/v7/mediarouter/res/values-el/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Σύστημα"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Συσκευές"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Έξοδος μέσων"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Σύνδεση με τη συσκευή"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Αναζήτηση συσκευών…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Αποσύνδεση"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-en-rGB/strings.xml b/v7/mediarouter/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..b9af4bf
--- /dev/null
+++ b/v7/mediarouter/res/values-en-rGB/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Devices"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Media output"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Connect to device"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Searching for devices…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Disconnect"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-en-rIN/strings.xml b/v7/mediarouter/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..b9af4bf
--- /dev/null
+++ b/v7/mediarouter/res/values-en-rIN/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Devices"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Media output"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Connect to device"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Searching for devices…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Disconnect"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-es-rUS/strings.xml b/v7/mediarouter/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..211b400
--- /dev/null
+++ b/v7/mediarouter/res/values-es-rUS/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivos"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Salida multimedia"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Conectar al dispositivo"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Buscando dispositivos…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Desconectar"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-es/strings.xml b/v7/mediarouter/res/values-es/strings.xml
new file mode 100644
index 0000000..d3a1639
--- /dev/null
+++ b/v7/mediarouter/res/values-es/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivos"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Salida multimedia"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Conectar a dispositivo"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Buscando dispositivos…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Desconectar"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-et-rEE/strings.xml b/v7/mediarouter/res/values-et-rEE/strings.xml
new file mode 100644
index 0000000..7dbdf74
--- /dev/null
+++ b/v7/mediarouter/res/values-et-rEE/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Süsteem"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Seadmed"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Meediaväljund"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Seadmega ühendamine"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Seadmete otsimine …"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Katkesta ühendus"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-eu-rES/strings.xml b/v7/mediarouter/res/values-eu-rES/strings.xml
new file mode 100644
index 0000000..728672d
--- /dev/null
+++ b/v7/mediarouter/res/values-eu-rES/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Gailuak"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Multimedia-irteera"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Konektatu gailura"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Gailuak bilatzen…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Deskonektatu"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-fa/strings.xml b/v7/mediarouter/res/values-fa/strings.xml
new file mode 100644
index 0000000..2ffed50
--- /dev/null
+++ b/v7/mediarouter/res/values-fa/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"سیستم"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"دستگاه‌ها"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"خروجی رسانه"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"برقراری ارتباط با دستگاه"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"در حال جستجو برای دستگاه‌ها..."</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"قطع ارتباط"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-fi/strings.xml b/v7/mediarouter/res/values-fi/strings.xml
new file mode 100644
index 0000000..0692c2f
--- /dev/null
+++ b/v7/mediarouter/res/values-fi/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Järjestelmä"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Laitteet"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Median äänentoisto"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Yhdistä laitteeseen"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Etsitään laitteita…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Katkaise yhteys"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-fr-rCA/strings.xml b/v7/mediarouter/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..9fa3c9c
--- /dev/null
+++ b/v7/mediarouter/res/values-fr-rCA/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Système"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Appareils"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Sortie multimédia"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Connexion au périphérique"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Recherche d\'appareils en cours…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Déconnecter"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-fr/strings.xml b/v7/mediarouter/res/values-fr/strings.xml
new file mode 100644
index 0000000..5607a1c
--- /dev/null
+++ b/v7/mediarouter/res/values-fr/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Système"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Appareils"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Sortie multimédia"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Connecter à l\'appareil"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Recherche d\'appareils en cours…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Déconnecter"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-gl-rES/strings.xml b/v7/mediarouter/res/values-gl-rES/strings.xml
new file mode 100644
index 0000000..d700c14
--- /dev/null
+++ b/v7/mediarouter/res/values-gl-rES/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivos"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Saída multimedia"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Conectar co dispositivo"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Buscando dispositivos…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Desconectar"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-hi/strings.xml b/v7/mediarouter/res/values-hi/strings.xml
new file mode 100644
index 0000000..8acc2bb
--- /dev/null
+++ b/v7/mediarouter/res/values-hi/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"सिस्टम"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"उपकरण"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"मीडिया आउटपुट"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"उपकरण से कनेक्ट करें"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"उपकरणों की खोज हो रही है…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"डिस्कनेक्ट करें"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-hr/strings.xml b/v7/mediarouter/res/values-hr/strings.xml
new file mode 100644
index 0000000..2946433
--- /dev/null
+++ b/v7/mediarouter/res/values-hr/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sustav"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Uređaji"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Medijski izlaz"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Povezivanje s uređajem"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Traženje uređaja…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Prekini vezu"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-hu/strings.xml b/v7/mediarouter/res/values-hu/strings.xml
new file mode 100644
index 0000000..b68fe16
--- /dev/null
+++ b/v7/mediarouter/res/values-hu/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Rendszer"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Eszközök"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Médiakimenet"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Csatlakozás adott eszközhöz"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Eszközkeresés…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Leválasztás"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-hy-rAM/strings.xml b/v7/mediarouter/res/values-hy-rAM/strings.xml
new file mode 100644
index 0000000..77f1136
--- /dev/null
+++ b/v7/mediarouter/res/values-hy-rAM/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Համակարգ"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Սարքեր"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Մեդիա արտածում"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Միանալ սարքին"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Որոնվում են սարքեր..."</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Անջատել"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-in/strings.xml b/v7/mediarouter/res/values-in/strings.xml
new file mode 100644
index 0000000..1d3b387
--- /dev/null
+++ b/v7/mediarouter/res/values-in/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Perangkat"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Keluaran media"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Sambungkan ke perangkat"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Menelusuri perangkat…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Putuskan sambungan"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-is-rIS/strings.xml b/v7/mediarouter/res/values-is-rIS/strings.xml
new file mode 100644
index 0000000..45d7329
--- /dev/null
+++ b/v7/mediarouter/res/values-is-rIS/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Kerfi"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Tæki"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Margmiðlunarúttak"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Tengjast tæki"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Leitar að tækjum…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Aftengja"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-it/strings.xml b/v7/mediarouter/res/values-it/strings.xml
new file mode 100644
index 0000000..bd58755
--- /dev/null
+++ b/v7/mediarouter/res/values-it/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivi"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Uscita media"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Connetti al dispositivo"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Ricerca di dispositivi…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Disconnetti"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-iw/strings.xml b/v7/mediarouter/res/values-iw/strings.xml
new file mode 100644
index 0000000..59753b4
--- /dev/null
+++ b/v7/mediarouter/res/values-iw/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"מערכת"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"מכשירים"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"פלט מדיה"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"התחבר למכשיר"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"מחפש מכשירים…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"התנתק"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-ja/strings.xml b/v7/mediarouter/res/values-ja/strings.xml
new file mode 100644
index 0000000..1367489
--- /dev/null
+++ b/v7/mediarouter/res/values-ja/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"システム"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"端末"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"メディア出力"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"端末に接続"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"端末を検索しています…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"接続を解除"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-ka-rGE/strings.xml b/v7/mediarouter/res/values-ka-rGE/strings.xml
new file mode 100644
index 0000000..413257e
--- /dev/null
+++ b/v7/mediarouter/res/values-ka-rGE/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"სისტემა"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"მოწყობილობები"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"მედია გამოსასვლელი"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"მოწყობილობასთან დაკავშირება"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"მოწყობილობების ძიება…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"კავშირის გაწყვეტა"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-kk-rKZ/strings.xml b/v7/mediarouter/res/values-kk-rKZ/strings.xml
new file mode 100644
index 0000000..e8da02a
--- /dev/null
+++ b/v7/mediarouter/res/values-kk-rKZ/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Жүйе"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Құрылғылар"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Meдиа құрылғылары"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Құрылғыға жалғау"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Құрылғыларды іздеуде…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Ажырату"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-km-rKH/strings.xml b/v7/mediarouter/res/values-km-rKH/strings.xml
new file mode 100644
index 0000000..e001dde
--- /dev/null
+++ b/v7/mediarouter/res/values-km-rKH/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"ប្រព័ន្ធ"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"ឧបករណ៍"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"លទ្ធផល​មេឌៀ"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"ភ្ជាប់​ឧបករណ៍"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"កំពុង​ស្វែងរក​ឧបករណ៍..."</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"ផ្ដាច់"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-kn-rIN/strings.xml b/v7/mediarouter/res/values-kn-rIN/strings.xml
new file mode 100644
index 0000000..147ebc8
--- /dev/null
+++ b/v7/mediarouter/res/values-kn-rIN/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"ಸಿಸ್ಟಂ"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"ಸಾಧನಗಳು"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"ಮಾಧ್ಯಮ ಔಟ್‌ಪುಟ್"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"ಸಾಧನಕ್ಕೆ ಸಂಪರ್ಕಪಡಿಸಿ"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"ಸಾಧನಗಳನ್ನು ಹುಡುಕಲಾಗುತ್ತಿದೆ…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸು"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-ko/strings.xml b/v7/mediarouter/res/values-ko/strings.xml
new file mode 100644
index 0000000..21f82a0
--- /dev/null
+++ b/v7/mediarouter/res/values-ko/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"시스템"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"기기"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"미디어 출력"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"기기에 연결"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"기기 검색 중…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"연결 해제"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-ky-rKG/strings.xml b/v7/mediarouter/res/values-ky-rKG/strings.xml
new file mode 100644
index 0000000..4a587ac
--- /dev/null
+++ b/v7/mediarouter/res/values-ky-rKG/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Систем"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Түзмөктөр"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Медиа чыгаруу"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Түзмөккө туташуу"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Түзмөктөр изделүүдө..."</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Ажыратуу"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-lo-rLA/strings.xml b/v7/mediarouter/res/values-lo-rLA/strings.xml
new file mode 100644
index 0000000..31a03cd
--- /dev/null
+++ b/v7/mediarouter/res/values-lo-rLA/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"ລະບົບ"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"ອຸປະກອນ"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"ມີເດຍເອົ້າພຸດ"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"ເຊື່ອມຕໍ່ຫາອຸປະກອນ"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"ກຳລັງຊອກຫາອຸປະກອນ..."</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"ຕັດການເຊື່ອມຕໍ່"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-lt/strings.xml b/v7/mediarouter/res/values-lt/strings.xml
new file mode 100644
index 0000000..ead3b73
--- /dev/null
+++ b/v7/mediarouter/res/values-lt/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Įrenginiai"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Medijos išvestis"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Prijungimas prie įrenginio"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Ieškoma įrenginių…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Atjungti"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-lv/strings.xml b/v7/mediarouter/res/values-lv/strings.xml
new file mode 100644
index 0000000..0914990
--- /dev/null
+++ b/v7/mediarouter/res/values-lv/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistēma"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Ierīces"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Multivides izeja"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Savienojuma izveide ar ierīci"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Notiek ierīču meklēšana..."</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Atvienot"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-mk-rMK/strings.xml b/v7/mediarouter/res/values-mk-rMK/strings.xml
new file mode 100644
index 0000000..363f16b
--- /dev/null
+++ b/v7/mediarouter/res/values-mk-rMK/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Систем"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Уреди"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Излез за медиум"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Поврзи се со уредот"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Се пребаруваат уреди..."</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Исклучи се"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-ml-rIN/strings.xml b/v7/mediarouter/res/values-ml-rIN/strings.xml
new file mode 100644
index 0000000..d20ba1d
--- /dev/null
+++ b/v7/mediarouter/res/values-ml-rIN/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"സിസ്റ്റം"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"ഉപകരണങ്ങൾ"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"മീഡിയ ഔട്ട്പുട്ട്"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"ഉപകരണത്തിലേക്ക് കണക്റ്റുചെയ്യുക"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"ഉപകരണങ്ങൾക്കായി തിരയുന്നു…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"വിച്ഛേദിക്കുക"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-mn-rMN/strings.xml b/v7/mediarouter/res/values-mn-rMN/strings.xml
new file mode 100644
index 0000000..4eecdb4
--- /dev/null
+++ b/v7/mediarouter/res/values-mn-rMN/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Систем"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Төхөөрөмжүүд"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Медиа гаралт"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Төхөөрөмжтэй холбох"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Төхөөрөмжүүдийг хайж байна…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Салгах"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-mr-rIN/strings.xml b/v7/mediarouter/res/values-mr-rIN/strings.xml
new file mode 100644
index 0000000..9187b5d
--- /dev/null
+++ b/v7/mediarouter/res/values-mr-rIN/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"सिस्टम"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"डिव्हाइसेस"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"माध्यम आउटपुट"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"डिव्हाइसला कनेक्ट करा"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"डिव्‍हाइसेस शोधत आहे…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"‍डिस्कनेक्ट करा"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-ms-rMY/strings.xml b/v7/mediarouter/res/values-ms-rMY/strings.xml
new file mode 100644
index 0000000..dadaa30
--- /dev/null
+++ b/v7/mediarouter/res/values-ms-rMY/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Peranti"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Output media"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Sambung kepada peranti"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Mencari peranti..."</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Putuskan sambungan"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-my-rMM/strings.xml b/v7/mediarouter/res/values-my-rMM/strings.xml
new file mode 100644
index 0000000..c417d57
--- /dev/null
+++ b/v7/mediarouter/res/values-my-rMM/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"စနစ်"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"စက်ပစ္စည်းများ"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"မီဒီယာထွက်ပေါက်"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"စက်တစ်ခုကို ချိတ်ဆက်ပါ"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"စက်ပစ္စည်းများကို ရှာဖွေနေပါသည်"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"ချိတ်ဆက်ခြင်းရပ်တန့်ရန်"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-nb/strings.xml b/v7/mediarouter/res/values-nb/strings.xml
new file mode 100644
index 0000000..fa4d9a4
--- /dev/null
+++ b/v7/mediarouter/res/values-nb/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Enheter"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Medieutgang"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Koble til enheten"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Søker etter enheter …"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Koble fra"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-ne-rNP/strings.xml b/v7/mediarouter/res/values-ne-rNP/strings.xml
new file mode 100644
index 0000000..3fe9ac3
--- /dev/null
+++ b/v7/mediarouter/res/values-ne-rNP/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"प्रणाली"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"उपकरणहरू"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"मिडियाको उत्पादन"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"उपकरणसँग जडान गर्नुहोस्"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"उपकरणहरूका लागि खोजी गरिँदै..."</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"विच्छेदन गर्नुहोस्"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-nl/strings.xml b/v7/mediarouter/res/values-nl/strings.xml
new file mode 100644
index 0000000..5572449
--- /dev/null
+++ b/v7/mediarouter/res/values-nl/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Systeem"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Apparaten"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Media-uitvoer"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Verbinding maken met apparaat"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Zoeken naar apparaten…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Verbinding verbreken"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-pl/strings.xml b/v7/mediarouter/res/values-pl/strings.xml
new file mode 100644
index 0000000..95a1d03
--- /dev/null
+++ b/v7/mediarouter/res/values-pl/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Urządzenia"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Wyjście multimediów"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Połącz z urządzeniem"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Szukam urządzeń…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Rozłącz"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-pt-rPT/strings.xml b/v7/mediarouter/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..54b1dfc
--- /dev/null
+++ b/v7/mediarouter/res/values-pt-rPT/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivos"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Saída de som multimédia"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Ligar ao dispositivo"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"A pesquisar dispositivos…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Desassociar"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-pt/strings.xml b/v7/mediarouter/res/values-pt/strings.xml
new file mode 100644
index 0000000..3ce1c38
--- /dev/null
+++ b/v7/mediarouter/res/values-pt/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistema"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispositivos"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Saída de mídia"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Conectar ao dispositivo"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Procurando dispositivos…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Desconectar"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-ro/strings.xml b/v7/mediarouter/res/values-ro/strings.xml
new file mode 100644
index 0000000..4c9e4b9
--- /dev/null
+++ b/v7/mediarouter/res/values-ro/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Dispozitive"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Rezultate media"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Conectați-vă la dispozitiv"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Se caută dispozitive..."</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Deconectați-vă"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-ru/strings.xml b/v7/mediarouter/res/values-ru/strings.xml
new file mode 100644
index 0000000..5cc2bba
--- /dev/null
+++ b/v7/mediarouter/res/values-ru/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Система"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Устройства"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Перенаправлять поток мультимедиа"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Подключение к устройству"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Поиск устройств…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Отключить"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-si-rLK/strings.xml b/v7/mediarouter/res/values-si-rLK/strings.xml
new file mode 100644
index 0000000..2eba3c8
--- /dev/null
+++ b/v7/mediarouter/res/values-si-rLK/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"පද්ධතිය"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"උපාංග"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"මාධ්‍ය ප්‍රතිදානය"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"උපාංගයට සම්බන්ධ වන්න"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"උපාංග සඳහා සොයමින්…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"විසන්ධි කරන්න"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-sk/strings.xml b/v7/mediarouter/res/values-sk/strings.xml
new file mode 100644
index 0000000..668800f
--- /dev/null
+++ b/v7/mediarouter/res/values-sk/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Systém"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Zariadenia"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Výstup médií"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Pripojenie k zariadeniu"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Prebieha vyhľadávanie zariadení…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Odpojiť"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-sl/strings.xml b/v7/mediarouter/res/values-sl/strings.xml
new file mode 100644
index 0000000..3e3e8bb
--- /dev/null
+++ b/v7/mediarouter/res/values-sl/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Naprave"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Izhod za predstavnost"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Povezovanje z napravo"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Iskanje naprav …"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Prekini povezavo"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-sr/strings.xml b/v7/mediarouter/res/values-sr/strings.xml
new file mode 100644
index 0000000..320f3e8
--- /dev/null
+++ b/v7/mediarouter/res/values-sr/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Систем"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Уређаји"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Излаз медија"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Повежите са уређајем"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Претраживање уређаја…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Прекини везу"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-sv/strings.xml b/v7/mediarouter/res/values-sv/strings.xml
new file mode 100644
index 0000000..910c6f1
--- /dev/null
+++ b/v7/mediarouter/res/values-sv/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Enheter"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Medieuppspelning"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Anslut till enhet"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Söker efter enheter ..."</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Koppla från"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-sw/strings.xml b/v7/mediarouter/res/values-sw/strings.xml
new file mode 100644
index 0000000..fcbc590
--- /dev/null
+++ b/v7/mediarouter/res/values-sw/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Mfumo"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Vifaa"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Towe la vyombo vya habari"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Unganisha kwenye kifaa"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Inatafuta vifaa..."</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Tenganisha"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-ta-rIN/strings.xml b/v7/mediarouter/res/values-ta-rIN/strings.xml
new file mode 100644
index 0000000..d5d1386
--- /dev/null
+++ b/v7/mediarouter/res/values-ta-rIN/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"அமைப்பு"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"சாதனங்கள்"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"மீடியா வெளியீடு"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"சாதனத்துடன் இணைக்கவும்"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"சாதனங்களைத் தேடுகிறது..."</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"துண்டி"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-te-rIN/strings.xml b/v7/mediarouter/res/values-te-rIN/strings.xml
new file mode 100644
index 0000000..9fa6e90
--- /dev/null
+++ b/v7/mediarouter/res/values-te-rIN/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"సిస్టమ్"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"పరికరాలు"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"మీడియా అవుట్‌పుట్"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"పరికరానికి కనెక్ట్ చేయండి"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"పరికరాల కోసం శోధిస్తోంది…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"డిస్‌కనెక్ట్ చేయి"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-th/strings.xml b/v7/mediarouter/res/values-th/strings.xml
new file mode 100644
index 0000000..78e5a73
--- /dev/null
+++ b/v7/mediarouter/res/values-th/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"ระบบ"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"อุปกรณ์"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"เอาต์พุตสื่อ"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"เชื่อมต่อกับอุปกรณ์"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"กำลังค้นหาอุปกรณ์…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"ยกเลิกการเชื่อมต่อ"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-tl/strings.xml b/v7/mediarouter/res/values-tl/strings.xml
new file mode 100644
index 0000000..0953787
--- /dev/null
+++ b/v7/mediarouter/res/values-tl/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"System"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Mga Device"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Output ng media"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Kumonekta sa device"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Naghahanap ng mga device…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Idiskonekta"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-tr/strings.xml b/v7/mediarouter/res/values-tr/strings.xml
new file mode 100644
index 0000000..12faaa6
--- /dev/null
+++ b/v7/mediarouter/res/values-tr/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Cihazlar"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Medya çıkışı"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Cihaza bağlanın"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Cihaz arayın…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Bağlantıyı kes"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-uk/strings.xml b/v7/mediarouter/res/values-uk/strings.xml
new file mode 100644
index 0000000..b036dea
--- /dev/null
+++ b/v7/mediarouter/res/values-uk/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Система"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Пристрої"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Вивід медіа-даних"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Під’єднатися до пристрою"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Пошук пристроїв…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Від’єднатися"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-ur-rPK/strings.xml b/v7/mediarouter/res/values-ur-rPK/strings.xml
new file mode 100644
index 0000000..bce0e0c
--- /dev/null
+++ b/v7/mediarouter/res/values-ur-rPK/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"سسٹم"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"آلات"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"میڈیا آؤٹ پٹ"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"آلہ سے مربوط ہوں"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"آلات تلاش کر رہا ہے…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"غیر مربوط کریں"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-uz-rUZ/strings.xml b/v7/mediarouter/res/values-uz-rUZ/strings.xml
new file mode 100644
index 0000000..f191fd9
--- /dev/null
+++ b/v7/mediarouter/res/values-uz-rUZ/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Tizim"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Qurilmalar"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Media chiqish"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Qurilmaga ulanish"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Qurilmalar izlanmoqda…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Uzish"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-vi/strings.xml b/v7/mediarouter/res/values-vi/strings.xml
new file mode 100644
index 0000000..a58d0e4
--- /dev/null
+++ b/v7/mediarouter/res/values-vi/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Hệ thống"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Thiết bị"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Đầu ra phương tiện"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Kết nối với thiết bị"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Đang tìm kiếm thiết bị…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Ngắt kết nối"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-zh-rCN/strings.xml b/v7/mediarouter/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..71c4407
--- /dev/null
+++ b/v7/mediarouter/res/values-zh-rCN/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"系统"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"设备"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"媒体输出线路"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"连接到设备"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"正在搜索设备…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"断开连接"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-zh-rHK/strings.xml b/v7/mediarouter/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..f499169
--- /dev/null
+++ b/v7/mediarouter/res/values-zh-rHK/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"系統"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"裝置"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"媒體輸出"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"連線至裝置"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"正在搜尋裝置…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"中斷連線"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-zh-rTW/strings.xml b/v7/mediarouter/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..a847615
--- /dev/null
+++ b/v7/mediarouter/res/values-zh-rTW/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"系統"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"裝置"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"媒體輸出"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"連線至裝置"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"正在搜尋裝置..."</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"中斷連線"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-zu/strings.xml b/v7/mediarouter/res/values-zu/strings.xml
new file mode 100644
index 0000000..be195be
--- /dev/null
+++ b/v7/mediarouter/res/values-zu/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="mr_system_route_name" msgid="5441529851481176817">"Isistimu"</string>
+    <string name="mr_user_route_category_name" msgid="7498112907524977311">"Amadivayisi"</string>
+    <string name="mr_media_route_button_content_description" msgid="4271159405637008602">"Okukhiphayo kwabezindaba"</string>
+    <string name="mr_media_route_chooser_title" msgid="7106830097177242655">"Xhumeka kudivayisi"</string>
+    <string name="mr_media_route_chooser_searching" msgid="7553005460920830010">"Iseshela amadivayisi…"</string>
+    <string name="mr_media_route_controller_disconnect" msgid="109793632378378069">"Nqamula"</string>
+</resources>
diff --git a/v7/mediarouter/res/values/strings.xml b/v7/mediarouter/res/values/strings.xml
index 5160c33..b8944b6 100644
--- a/v7/mediarouter/res/values/strings.xml
+++ b/v7/mediarouter/res/values/strings.xml
@@ -21,8 +21,9 @@
     <!-- Name for the user route category created when publishing routes to the system in Jellybean and above. [CHAR LIMIT=30] -->
     <string name="mr_user_route_category_name">Devices</string>
 
-    <!-- Content description of a MediaRouteButton for accessibility support. [CHAR LIMIT=50] -->
-    <string name="mr_media_route_button_content_description">Media output</string>
+    <!-- Content description of a MediaRouteButton for accessibility support.
+        Cast is the standard android verb for sending content to a remote device. [CHAR LIMIT=50] -->
+    <string name="mr_media_route_button_content_description">Cast</string>
 
     <!-- Title of the media route chooser dialog. [CHAR LIMIT=30] -->
     <string name="mr_media_route_chooser_title">Connect to device</string>
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteActionProvider.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteActionProvider.java
index 2fd2488..3b14e2b 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteActionProvider.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteActionProvider.java
@@ -17,6 +17,8 @@
 package android.support.v7.app;
 
 import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.view.ActionProvider;
 import android.support.v7.media.MediaRouter;
 import android.support.v7.media.MediaRouteSelector;
@@ -151,6 +153,7 @@
      *
      * @return The selector, never null.
      */
+    @NonNull
     public MediaRouteSelector getRouteSelector() {
         return mSelector;
     }
@@ -161,7 +164,7 @@
      *
      * @param selector The selector, must not be null.
      */
-    public void setRouteSelector(MediaRouteSelector selector) {
+    public void setRouteSelector(@NonNull MediaRouteSelector selector) {
         if (selector == null) {
             throw new IllegalArgumentException("selector must not be null");
         }
@@ -195,6 +198,7 @@
      *
      * @return The dialog factory, never null.
      */
+    @NonNull
     public MediaRouteDialogFactory getDialogFactory() {
         return mDialogFactory;
     }
@@ -205,7 +209,7 @@
      *
      * @param factory The dialog factory, must not be null.
      */
-    public void setDialogFactory(MediaRouteDialogFactory factory) {
+    public void setDialogFactory(@NonNull MediaRouteDialogFactory factory) {
         if (factory == null) {
             throw new IllegalArgumentException("factory must not be null");
         }
@@ -222,6 +226,7 @@
     /**
      * Gets the associated media route button, or null if it has not yet been created.
      */
+    @Nullable
     public MediaRouteButton getMediaRouteButton() {
         return mButton;
     }
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
index c3d34ec..f5103fa 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
@@ -23,6 +23,7 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
 import android.support.v4.app.FragmentActivity;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.graphics.drawable.DrawableCompat;
@@ -144,6 +145,7 @@
      *
      * @return The selector, never null.
      */
+    @NonNull
     public MediaRouteSelector getRouteSelector() {
         return mSelector;
     }
@@ -179,6 +181,7 @@
      *
      * @return The dialog factory, never null.
      */
+    @NonNull
     public MediaRouteDialogFactory getDialogFactory() {
         return mDialogFactory;
     }
@@ -189,7 +192,7 @@
      *
      * @param factory The dialog factory, must not be null.
      */
-    public void setDialogFactory(MediaRouteDialogFactory factory) {
+    public void setDialogFactory(@NonNull MediaRouteDialogFactory factory) {
         if (factory == null) {
             throw new IllegalArgumentException("factory must not be null");
         }
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
index ec53317..3a87f02 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
@@ -19,6 +19,7 @@
 import android.app.Dialog;
 import android.content.Context;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
 import android.support.v7.media.MediaRouter;
 import android.support.v7.media.MediaRouteSelector;
 import android.support.v7.mediarouter.R;
@@ -73,6 +74,7 @@
      *
      * @return The selector, never null.
      */
+    @NonNull
     public MediaRouteSelector getRouteSelector() {
         return mSelector;
     }
@@ -82,7 +84,7 @@
      *
      * @param selector The selector, must not be null.
      */
-    public void setRouteSelector(MediaRouteSelector selector) {
+    public void setRouteSelector(@NonNull MediaRouteSelector selector) {
         if (selector == null) {
             throw new IllegalArgumentException("selector must not be null");
         }
@@ -109,7 +111,7 @@
      *
      * @param routes The list of routes to filter in-place, never null.
      */
-    public void onFilterRoutes(List<MediaRouter.RouteInfo> routes) {
+    public void onFilterRoutes(@NonNull List<MediaRouter.RouteInfo> routes) {
         for (int i = routes.size(); i-- > 0; ) {
             if (!onFilterRoute(routes.get(i))) {
                 routes.remove(i);
@@ -128,7 +130,7 @@
      * @param route The route to consider, never null.
      * @return True if the route should be included in the chooser dialog.
      */
-    public boolean onFilterRoute(MediaRouter.RouteInfo route) {
+    public boolean onFilterRoute(@NonNull MediaRouter.RouteInfo route) {
         return !route.isDefault() && route.isEnabled() && route.matchesSelector(mSelector);
     }
 
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteDialogFactory.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteDialogFactory.java
index 834b50d..1ae284f 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteDialogFactory.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteDialogFactory.java
@@ -16,6 +16,8 @@
 
 package android.support.v7.app;
 
+import android.support.annotation.NonNull;
+
 /**
  * The media route dialog factory is responsible for creating the media route
  * chooser and controller dialogs as needed.
@@ -39,6 +41,7 @@
      *
      * @return The default media route dialog factory, never null.
      */
+    @NonNull
     public static MediaRouteDialogFactory getDefault() {
         return sDefault;
     }
@@ -51,6 +54,7 @@
      *
      * @return The media route chooser dialog fragment, must not be null.
      */
+    @NonNull
     public MediaRouteChooserDialogFragment onCreateChooserDialogFragment() {
         return new MediaRouteChooserDialogFragment();
     }
@@ -63,6 +67,7 @@
      *
      * @return The media route controller dialog fragment, must not be null.
      */
+    @NonNull
     public MediaRouteControllerDialogFragment onCreateControllerDialogFragment() {
         return new MediaRouteControllerDialogFragment();
     }
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteDiscoveryFragment.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteDiscoveryFragment.java
index dfb8481..4e97d57 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteDiscoveryFragment.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteDiscoveryFragment.java
@@ -91,8 +91,7 @@
 
             if (mCallback != null) {
                 mRouter.removeCallback(mCallback);
-                mRouter.addCallback(mSelector, mCallback,
-                        MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
+                mRouter.addCallback(mSelector, mCallback, onPrepareCallbackFlags());
             }
         }
     }
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
index 54596b1..e011877 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
@@ -21,6 +21,8 @@
 import android.content.Intent;
 import android.os.Handler;
 import android.os.Message;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v7.media.MediaRouter.ControlRequestCallback;
 
 /**
@@ -73,7 +75,7 @@
      *
      * @param context The context.
      */
-    public MediaRouteProvider(Context context) {
+    public MediaRouteProvider(@NonNull Context context) {
         this(context, null);
     }
 
@@ -116,7 +118,7 @@
      *
      * @param callback The callback to use, or null if none.
      */
-    public final void setCallback(Callback callback) {
+    public final void setCallback(@Nullable Callback callback) {
         MediaRouter.checkCallingThread();
         mCallback = callback;
     }
@@ -129,6 +131,7 @@
      *
      * @see #onDiscoveryRequestChanged
      */
+    @Nullable
     public final MediaRouteDiscoveryRequest getDiscoveryRequest() {
         return mDiscoveryRequest;
     }
@@ -184,7 +187,7 @@
      *
      * @see MediaRouter#addCallback
      */
-    public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) {
+    public void onDiscoveryRequestChanged(@Nullable MediaRouteDiscoveryRequest request) {
     }
 
     /**
@@ -199,6 +202,7 @@
      *
      * @see Callback#onDescriptorChanged
      */
+    @Nullable
     public final MediaRouteProviderDescriptor getDescriptor() {
         return mDescriptor;
     }
@@ -214,7 +218,7 @@
      *
      * @see Callback#onDescriptorChanged
      */
-    public final void setDescriptor(MediaRouteProviderDescriptor descriptor) {
+    public final void setDescriptor(@Nullable MediaRouteProviderDescriptor descriptor) {
         MediaRouter.checkCallingThread();
 
         if (mDescriptor != descriptor) {
@@ -245,6 +249,7 @@
      * @return The route controller.  Returns null if there is no such route or if the route
      * cannot be controlled using the route controller interface.
      */
+    @Nullable
     public RouteController onCreateRouteController(String routeId) {
         return null;
     }
@@ -354,7 +359,7 @@
          *
          * @see MediaControlIntent
          */
-        public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
+        public boolean onControlRequest(Intent intent, @Nullable ControlRequestCallback callback) {
             return false;
         }
     }
@@ -369,8 +374,8 @@
          * @param provider The media route provider that changed, never null.
          * @param descriptor The new media route provider descriptor, or null if none.
          */
-        public void onDescriptorChanged(MediaRouteProvider provider,
-                MediaRouteProviderDescriptor descriptor) {
+        public void onDescriptorChanged(@NonNull MediaRouteProvider provider,
+                @Nullable MediaRouteProviderDescriptor descriptor) {
         }
     }
 
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteSelector.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteSelector.java
index c6869f3..4323d69 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteSelector.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteSelector.java
@@ -17,6 +17,8 @@
 
 import android.content.IntentFilter;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -200,7 +202,7 @@
      * @param bundle The bundle, or null if none.
      * @return The new instance, or null if the bundle was null.
      */
-    public static MediaRouteSelector fromBundle(Bundle bundle) {
+    public static MediaRouteSelector fromBundle(@Nullable Bundle bundle) {
         return bundle != null ? new MediaRouteSelector(bundle, null) : null;
     }
 
@@ -220,7 +222,7 @@
          * Creates a media route selector descriptor builder whose initial contents are
          * copied from an existing selector.
          */
-        public Builder(MediaRouteSelector selector) {
+        public Builder(@NonNull MediaRouteSelector selector) {
             if (selector == null) {
                 throw new IllegalArgumentException("selector must not be null");
             }
@@ -238,7 +240,8 @@
          * {@link MediaControlIntent#CATEGORY_LIVE_AUDIO}.
          * @return The builder instance for chaining.
          */
-        public Builder addControlCategory(String category) {
+        @NonNull
+        public Builder addControlCategory(@NonNull String category) {
             if (category == null) {
                 throw new IllegalArgumentException("category must not be null");
             }
@@ -259,7 +262,8 @@
          * such as {@link MediaControlIntent#CATEGORY_LIVE_AUDIO}.
          * @return The builder instance for chaining.
          */
-        public Builder addControlCategories(Collection<String> categories) {
+        @NonNull
+        public Builder addControlCategories(@NonNull Collection<String> categories) {
             if (categories == null) {
                 throw new IllegalArgumentException("categories must not be null");
             }
@@ -278,7 +282,8 @@
          * @param selector The media route selector whose contents are to be added.
          * @return The builder instance for chaining.
          */
-        public Builder addSelector(MediaRouteSelector selector) {
+        @NonNull
+        public Builder addSelector(@NonNull MediaRouteSelector selector) {
             if (selector == null) {
                 throw new IllegalArgumentException("selector must not be null");
             }
@@ -290,6 +295,7 @@
         /**
          * Builds the {@link MediaRouteSelector media route selector}.
          */
+        @NonNull
         public MediaRouteSelector build() {
             if (mControlCategories == null) {
                 return EMPTY;
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
index 7b83cc0..127dda2 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
@@ -16,6 +16,7 @@
 
 package android.support.v7.media;
 
+import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -27,11 +28,17 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.ActivityManagerCompat;
 import android.support.v4.hardware.display.DisplayManagerCompat;
 import android.support.v7.media.MediaRouteProvider.ProviderMetadata;
 import android.util.Log;
 import android.view.Display;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -70,6 +77,17 @@
     final Context mContext;
     final ArrayList<CallbackRecord> mCallbackRecords = new ArrayList<CallbackRecord>();
 
+    /** @hide */
+    @IntDef(flag = true,
+            value = {
+                    CALLBACK_FLAG_PERFORM_ACTIVE_SCAN,
+                    CALLBACK_FLAG_REQUEST_DISCOVERY,
+                    CALLBACK_FLAG_UNFILTERED_EVENTS
+            }
+    )
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface CallbackFlags {}
+
     /**
      * Flag for {@link #addCallback}: Actively scan for routes while this callback
      * is registered.
@@ -103,8 +121,8 @@
     public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 1 << 1;
 
     /**
-     * Flag for {@link #addCallback}: Request that route discovery be performed while this
-     * callback is registered.
+     * Flag for {@link #addCallback}: Request passive route discovery while this
+     * callback is registered, except on {@link ActivityManager#isLowRamDevice low-RAM devices}.
      * <p>
      * When this flag is specified, the media router will try to discover routes.
      * Although route discovery is intended to be efficient, checking for new routes may
@@ -118,6 +136,10 @@
      * method and remove it in the {@link android.app.Activity#onStop onStop} method.
      * The {@link android.support.v7.app.MediaRouteDiscoveryFragment} fragment may
      * also be used for this purpose.
+     * </p><p class="note">
+     * On {@link ActivityManager#isLowRamDevice low-RAM devices} this flag
+     * will be ignored.  Refer to
+     * {@link #addCallback(MediaRouteSelector, Callback, int) addCallback} for details.
      * </p>
      *
      * @see android.support.v7.app.MediaRouteDiscoveryFragment
@@ -125,6 +147,21 @@
     public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 1 << 2;
 
     /**
+     * Flag for {@link #addCallback}: Request passive route discovery while this
+     * callback is registered, even on {@link ActivityManager#isLowRamDevice low-RAM devices}.
+     * <p class="note">
+     * This flag has a significant performance impact on low-RAM devices
+     * since it may cause many media route providers to be started simultaneously.
+     * It is much better to use {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} instead to avoid
+     * performing passive discovery on these devices altogether.  Refer to
+     * {@link #addCallback(MediaRouteSelector, Callback, int) addCallback} for details.
+     * </p>
+     *
+     * @see android.support.v7.app.MediaRouteDiscoveryFragment
+     */
+    public static final int CALLBACK_FLAG_FORCE_DISCOVERY = 1 << 3;
+
+    /**
      * Flag for {@link #isRouteAvailable}: Ignore the default route.
      * <p>
      * This flag is used to determine whether a matching non-default route is available.
@@ -135,6 +172,21 @@
      */
     public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1 << 0;
 
+    /**
+     * Flag for {@link #isRouteAvailable}: Require an actual route to be matched.
+     * <p>
+     * If this flag is not set, then {@link #isRouteAvailable} will return true
+     * if it is possible to discover a matching route even if discovery is not in
+     * progress or if no matching route has yet been found.  This feature is used to
+     * save resources by removing the need to perform passive route discovery on
+     * {@link ActivityManager#isLowRamDevice low-RAM devices}.
+     * </p><p>
+     * If this flag is set, then {@link #isRouteAvailable} will only return true if
+     * a matching route has actually been discovered.
+     * </p>
+     */
+    public static final int AVAILABILITY_FLAG_REQUIRE_MATCH = 1 << 1;
+
     MediaRouter(Context context) {
         mContext = context;
     }
@@ -156,7 +208,7 @@
      * @return The media router instance for the context.  The application must hold
      * a strong reference to this object as long as it is in use.
      */
-    public static MediaRouter getInstance(Context context) {
+    public static MediaRouter getInstance(@NonNull Context context) {
         if (context == null) {
             throw new IllegalArgumentException("context must not be null");
         }
@@ -195,6 +247,7 @@
      *
      * @return The default route, which is guaranteed to never be null.
      */
+    @NonNull
     public RouteInfo getDefaultRoute() {
         checkCallingThread();
         return sGlobal.getDefaultRoute();
@@ -245,6 +298,7 @@
      * @see RouteInfo#supportsControlCategory
      * @see RouteInfo#supportsControlRequest
      */
+    @NonNull
     public RouteInfo getSelectedRoute() {
         checkCallingThread();
         return sGlobal.getSelectedRoute();
@@ -262,7 +316,8 @@
      * @see RouteInfo#matchesSelector
      * @see RouteInfo#isDefault
      */
-    public RouteInfo updateSelectedRoute(MediaRouteSelector selector) {
+    @NonNull
+    public RouteInfo updateSelectedRoute(@NonNull MediaRouteSelector selector) {
         if (selector == null) {
             throw new IllegalArgumentException("selector must not be null");
         }
@@ -284,7 +339,7 @@
      *
      * @param route The route to select.
      */
-    public void selectRoute(RouteInfo route) {
+    public void selectRoute(@NonNull RouteInfo route) {
         if (route == null) {
             throw new IllegalArgumentException("route must not be null");
         }
@@ -303,14 +358,20 @@
      * regardless of whether they are enabled or disabled.  If the
      * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} flag is specified, then
      * the method will only consider non-default routes.
+     * </p><p class="note">
+     * On {@link ActivityManager#isLowRamDevice low-RAM devices} this method
+     * will return true if it is possible to discover a matching route even if
+     * discovery is not in progress or if no matching route has yet been found.
+     * Use {@link #AVAILABILITY_FLAG_REQUIRE_MATCH} to require an actual match.
      * </p>
      *
      * @param selector The selector to match.
      * @param flags Flags to control the determination of whether a route may be available.
-     * May be zero or {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE}.
+     * May be zero or some combination of {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE}
+     * and {@link #AVAILABILITY_FLAG_REQUIRE_MATCH}.
      * @return True if a matching route may be available.
      */
-    public boolean isRouteAvailable(MediaRouteSelector selector, int flags) {
+    public boolean isRouteAvailable(@NonNull MediaRouteSelector selector, int flags) {
         if (selector == null) {
             throw new IllegalArgumentException("selector must not be null");
         }
@@ -353,6 +414,28 @@
      * By default, the callback will only be invoked for events that affect routes
      * that match the specified selector.  Event filtering may be disabled by specifying
      * the {@link #CALLBACK_FLAG_UNFILTERED_EVENTS} flag when the callback is registered.
+     * </p><p>
+     * Applications should use the {@link #isRouteAvailable} method to determine
+     * whether is it possible to discover a route with the desired capabilities
+     * and therefore whether the media route button should be shown to the user.
+     * </p><p>
+     * The {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} flag should be used while the application
+     * is in the foreground to request that passive discovery be performed if there are
+     * sufficient resources to allow continuous passive discovery.
+     * On {@link ActivityManager#isLowRamDevice low-RAM devices} this flag will be
+     * ignored to conserve resources.
+     * </p><p>
+     * The {@link #CALLBACK_FLAG_FORCE_DISCOVERY} flag should be used when
+     * passive discovery absolutely must be performed, even on low-RAM devices.
+     * This flag has a significant performance impact on low-RAM devices
+     * since it may cause many media route providers to be started simultaneously.
+     * It is much better to use {@link #CALLBACK_FLAG_REQUEST_DISCOVERY} instead to avoid
+     * performing passive discovery on these devices altogether.
+     * </p><p>
+     * The {@link #CALLBACK_FLAG_PERFORM_ACTIVE_SCAN} flag should be used when the
+     * media route chooser dialog is showing to confirm the presence of available
+     * routes that the user may connect to.  This flag may use substantially more
+     * power.
      * </p>
      *
      * <h3>Example</h3>
@@ -407,7 +490,8 @@
      * {@link #CALLBACK_FLAG_UNFILTERED_EVENTS}.
      * @see #removeCallback
      */
-    public void addCallback(MediaRouteSelector selector, Callback callback, int flags) {
+    public void addCallback(@NonNull MediaRouteSelector selector, @NonNull Callback callback,
+            @CallbackFlags int flags) {
         if (selector == null) {
             throw new IllegalArgumentException("selector must not be null");
         }
@@ -452,7 +536,7 @@
      * @param callback The callback to remove.
      * @see #addCallback
      */
-    public void removeCallback(Callback callback) {
+    public void removeCallback(@NonNull Callback callback) {
         if (callback == null) {
             throw new IllegalArgumentException("callback must not be null");
         }
@@ -491,7 +575,7 @@
      * @see MediaRouteProvider
      * @see #removeCallback
      */
-    public void addProvider(MediaRouteProvider providerInstance) {
+    public void addProvider(@NonNull MediaRouteProvider providerInstance) {
         if (providerInstance == null) {
             throw new IllegalArgumentException("providerInstance must not be null");
         }
@@ -515,7 +599,7 @@
      * @see MediaRouteProvider
      * @see #addCallback
      */
-    public void removeProvider(MediaRouteProvider providerInstance) {
+    public void removeProvider(@NonNull MediaRouteProvider providerInstance) {
         if (providerInstance == null) {
             throw new IllegalArgumentException("providerInstance must not be null");
         }
@@ -538,7 +622,7 @@
      *
      * @param remoteControlClient The {@link android.media.RemoteControlClient} to register.
      */
-    public void addRemoteControlClient(Object remoteControlClient) {
+    public void addRemoteControlClient(@NonNull Object remoteControlClient) {
         if (remoteControlClient == null) {
             throw new IllegalArgumentException("remoteControlClient must not be null");
         }
@@ -555,7 +639,7 @@
      *
      * @param remoteControlClient The {@link android.media.RemoteControlClient} to register.
      */
-    public void removeRemoteControlClient(Object remoteControlClient) {
+    public void removeRemoteControlClient(@NonNull Object remoteControlClient) {
         if (remoteControlClient == null) {
             throw new IllegalArgumentException("remoteControlClient must not be null");
         }
@@ -608,6 +692,11 @@
         private Bundle mExtras;
         private MediaRouteDescriptor mDescriptor;
 
+        /** @hide */
+        @IntDef({PLAYBACK_TYPE_LOCAL,PLAYBACK_TYPE_REMOTE})
+        @Retention(RetentionPolicy.SOURCE)
+        private @interface PlaybackType {}
+
         /**
          * The default playback type, "local", indicating the presentation of the media
          * is happening on the same device (e.g. a phone, a tablet) as where it is
@@ -625,6 +714,11 @@
          */
         public static final int PLAYBACK_TYPE_REMOTE = 1;
 
+        /** @hide */
+        @IntDef({PLAYBACK_VOLUME_FIXED,PLAYBACK_VOLUME_VARIABLE})
+        @Retention(RetentionPolicy.SOURCE)
+        private @interface PlaybackVolume {}
+
         /**
          * Playback information indicating the playback volume is fixed, i.e. it cannot be
          * controlled from this object. An example of fixed playback volume is a remote player,
@@ -670,6 +764,7 @@
          *
          * @return The unique id of the route, never null.
          */
+        @NonNull
         public String getId() {
             return mUniqueId;
         }
@@ -697,6 +792,7 @@
          *
          * @return The description of the route, or null if none.
          */
+        @Nullable
         public String getDescription() {
             return mDescription;
         }
@@ -768,7 +864,7 @@
          * @return True if the route supports at least one of the capabilities
          * described in the media route selector.
          */
-        public boolean matchesSelector(MediaRouteSelector selector) {
+        public boolean matchesSelector(@NonNull MediaRouteSelector selector) {
             if (selector == null) {
                 throw new IllegalArgumentException("selector must not be null");
             }
@@ -794,7 +890,7 @@
          * @see MediaControlIntent
          * @see #getControlFilters
          */
-        public boolean supportsControlCategory(String category) {
+        public boolean supportsControlCategory(@NonNull String category) {
             if (category == null) {
                 throw new IllegalArgumentException("category must not be null");
             }
@@ -829,7 +925,7 @@
          * @see MediaControlIntent
          * @see #getControlFilters
          */
-        public boolean supportsControlAction(String category, String action) {
+        public boolean supportsControlAction(@NonNull String category, @NonNull String action) {
             if (category == null) {
                 throw new IllegalArgumentException("category must not be null");
             }
@@ -862,7 +958,7 @@
          * @see MediaControlIntent
          * @see #getControlFilters
          */
-        public boolean supportsControlRequest(Intent intent) {
+        public boolean supportsControlRequest(@NonNull Intent intent) {
             if (intent == null) {
                 throw new IllegalArgumentException("intent must not be null");
             }
@@ -895,7 +991,8 @@
          *
          * @see MediaControlIntent
          */
-        public void sendControlRequest(Intent intent, ControlRequestCallback callback) {
+        public void sendControlRequest(@NonNull Intent intent,
+                @Nullable ControlRequestCallback callback) {
             if (intent == null) {
                 throw new IllegalArgumentException("intent must not be null");
             }
@@ -910,6 +1007,7 @@
          * @return The type of playback associated with this route: {@link #PLAYBACK_TYPE_LOCAL}
          * or {@link #PLAYBACK_TYPE_REMOTE}.
          */
+        @PlaybackType
         public int getPlaybackType() {
             return mPlaybackType;
         }
@@ -929,6 +1027,7 @@
          * @return How volume is handled on the route: {@link #PLAYBACK_VOLUME_FIXED}
          * or {@link #PLAYBACK_VOLUME_VARIABLE}.
          */
+        @PlaybackVolume
         public int getVolumeHandling() {
             return mVolumeHandling;
         }
@@ -1012,6 +1111,7 @@
          * @see MediaControlIntent#CATEGORY_LIVE_VIDEO
          * @see android.app.Presentation
          */
+        @Nullable
         public Display getPresentationDisplay() {
             checkCallingThread();
             if (mPresentationDisplayId >= 0 && mPresentationDisplay == null) {
@@ -1024,6 +1124,7 @@
          * Gets a collection of extra properties about this route that were supplied
          * by its media route provider, or null if none.
          */
+        @Nullable
         public Bundle getExtras() {
             return mExtras;
         }
@@ -1393,6 +1494,7 @@
         private final CallbackHandler mCallbackHandler = new CallbackHandler();
         private final DisplayManagerCompat mDisplayManager;
         private final SystemMediaRouteProvider mSystemProvider;
+        private final boolean mLowRam;
 
         private RegisteredMediaRouteProviderWatcher mRegisteredProviderWatcher;
         private RouteInfo mDefaultRoute;
@@ -1403,6 +1505,9 @@
         GlobalMediaRouter(Context applicationContext) {
             mApplicationContext = applicationContext;
             mDisplayManager = DisplayManagerCompat.getInstance(applicationContext);
+            mLowRam = ActivityManagerCompat.isLowRamDevice(
+                    (ActivityManager)applicationContext.getSystemService(
+                            Context.ACTIVITY_SERVICE));
 
             // Add the system media route provider for interoperating with
             // the framework media router.  This one is special and receives
@@ -1522,6 +1627,15 @@
         }
 
         public boolean isRouteAvailable(MediaRouteSelector selector, int flags) {
+            if (selector.isEmpty()) {
+                return false;
+            }
+
+            // On low-RAM devices, do not rely on actual discovery results unless asked to.
+            if ((flags & AVAILABILITY_FLAG_REQUIRE_MATCH) == 0 && mLowRam) {
+                return true;
+            }
+
             // Check whether any existing routes match the selector.
             final int routeCount = mRoutes.size();
             for (int i = 0; i < routeCount; i++) {
@@ -1558,6 +1672,11 @@
                             discover = true; // perform active scan implies request discovery
                         }
                         if ((callback.mFlags & CALLBACK_FLAG_REQUEST_DISCOVERY) != 0) {
+                            if (!mLowRam) {
+                                discover = true;
+                            }
+                        }
+                        if ((callback.mFlags & CALLBACK_FLAG_FORCE_DISCOVERY) != 0) {
                             discover = true;
                         }
                     }
@@ -1584,6 +1703,12 @@
             if (DEBUG) {
                 Log.d(TAG, "Updated discovery request: " + mDiscoveryRequest);
             }
+            if (discover && !activeScan && mLowRam) {
+                Log.i(TAG, "Forcing passive route discovery on a low-RAM device, "
+                        + "system performance may be affected.  Please consider using "
+                        + "CALLBACK_FLAG_REQUEST_DISCOVERY instead of "
+                        + "CALLBACK_FLAG_FORCE_DISCOVERY.");
+            }
 
             // Notify providers.
             final int providerCount = mProviders.size();
diff --git a/v7/palette/.classpath b/v7/palette/.classpath
new file mode 100644
index 0000000..43cb38c
--- /dev/null
+++ b/v7/palette/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+    <classpathentry kind="src" path="src"/>
+    <classpathentry kind="src" path="gen"/>
+    <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+    <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+    <classpathentry kind="output" path="bin/classes"/>
+</classpath>
diff --git a/v7/palette/.project b/v7/palette/.project
new file mode 100644
index 0000000..76e11a6
--- /dev/null
+++ b/v7/palette/.project
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+    <name>android-support-v7-palette</name>
+    <comment></comment>
+    <projects>
+    </projects>
+    <buildSpec>
+        <buildCommand>
+            <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+            <arguments>
+            </arguments>
+        </buildCommand>
+        <buildCommand>
+            <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+            <arguments>
+            </arguments>
+        </buildCommand>
+        <buildCommand>
+            <name>org.eclipse.jdt.core.javabuilder</name>
+            <arguments>
+            </arguments>
+        </buildCommand>
+        <buildCommand>
+            <name>com.android.ide.eclipse.adt.ApkBuilder</name>
+            <arguments>
+            </arguments>
+        </buildCommand>
+    </buildSpec>
+    <natures>
+        <nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+        <nature>org.eclipse.jdt.core.javanature</nature>
+    </natures>
+</projectDescription>
diff --git a/v7/palette/Android.mk b/v7/palette/Android.mk
new file mode 100644
index 0000000..877e518
--- /dev/null
+++ b/v7/palette/Android.mk
@@ -0,0 +1,23 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+# Here is the final static library that apps can link against.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v7-palette
+LOCAL_SDK_VERSION := 7
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_JAVA_LIBRARIES += android-support-v4
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v7/palette/AndroidManifest.xml b/v7/palette/AndroidManifest.xml
new file mode 100644
index 0000000..20e14c2
--- /dev/null
+++ b/v7/palette/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.support.v7.palette">
+    <uses-sdk android:minSdkVersion="7"/>
+    <application>
+    </application>
+</manifest>
diff --git a/v7/palette/README.txt b/v7/palette/README.txt
new file mode 100644
index 0000000..4a8b5e4
--- /dev/null
+++ b/v7/palette/README.txt
@@ -0,0 +1 @@
+Library Project including Palette for color extraction from images
diff --git a/v7/palette/build.gradle b/v7/palette/build.gradle
new file mode 100644
index 0000000..863028a
--- /dev/null
+++ b/v7/palette/build.gradle
@@ -0,0 +1,93 @@
+apply plugin: 'android-library'
+
+archivesBaseName = 'palette-v7'
+
+dependencies {
+    compile project(':support-v4')
+}
+
+android {
+    compileSdkVersion 7
+    buildToolsVersion "19.0.1"
+
+    defaultConfig {
+        minSdkVersion 7
+    }
+
+    sourceSets {
+        main.manifest.srcFile 'AndroidManifest.xml'
+        main.java.srcDir 'src'
+    }
+
+    lintOptions {
+        // TODO: fix errors and reenable.
+        abortOnError false
+    }
+}
+
+android.libraryVariants.all { variant ->
+    def name = variant.buildType.name
+
+    if (name.equals(com.android.builder.BuilderConstants.DEBUG)) {
+        return; // Skip debug builds.
+    }
+    def suffix = name.capitalize()
+
+    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
+        dependsOn variant.javaCompile
+        from variant.javaCompile.destinationDir
+        from 'LICENSE.txt'
+    }
+    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
+        source android.sourceSets.main.allJava
+        classpath = files(variant.javaCompile.classpath.files) + files(
+                "${android.plugin.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
+    }
+
+    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
+        classifier = 'javadoc'
+        from 'build/docs/javadoc'
+    }
+
+    def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
+        classifier = 'sources'
+        from android.sourceSets.main.allSource
+    }
+
+    artifacts.add('archives', javadocJarTask);
+    artifacts.add('archives', sourcesJarTask);
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri(rootProject.ext.supportRepoOut)) {
+            }
+
+            pom.project {
+                name 'Android Support Palette v7'
+                description "Android Support for extracting color palettes from images"
+                url 'http://developer.android.com/tools/extras/support-library.html'
+                inceptionYear '2011'
+
+                licenses {
+                    license {
+                        name 'The Apache Software License, Version 2.0'
+                        url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+                        distribution 'repo'
+                    }
+                }
+
+                scm {
+                    url "http://source.android.com"
+                    connection "scm:git:https://android.googlesource.com/platform/frameworks/support"
+                }
+                developers {
+                    developer {
+                        name 'The Android Open Source Project'
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/v7/palette/project.properties b/v7/palette/project.properties
new file mode 100644
index 0000000..1e106c3
--- /dev/null
+++ b/v7/palette/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-7
+android.library=true
\ No newline at end of file
diff --git a/v7/palette/src/android/support/v7/graphics/ColorCutQuantizer.java b/v7/palette/src/android/support/v7/graphics/ColorCutQuantizer.java
new file mode 100644
index 0000000..8fbd87c
--- /dev/null
+++ b/v7/palette/src/android/support/v7/graphics/ColorCutQuantizer.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.graphics;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.support.v7.graphics.Palette.Swatch;
+import android.util.SparseIntArray;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.PriorityQueue;
+
+/**
+ * An color quantizer based on the Median-cut algorithm, but optimized for picking out distinct
+ * colors rather than representation colors.
+ *
+ * The color space is represented as a 3-dimensional cube with each dimension being an RGB
+ * component. The cube is then repeatedly divided until we have reduced the color space to the
+ * requested number of colors. An average color is then generated from each cube.
+ *
+ * What makes this different to median-cut is that median-cut divided cubes so that all of the cubes
+ * have roughly the same population, where this quantizer divides boxes based on their color volume.
+ * This means that the color space is divided into distinct colors, rather than representative
+ * colors.
+ */
+final class ColorCutQuantizer {
+
+    private static final String LOG_TAG = ColorCutQuantizer.class.getSimpleName();
+
+    private final float[] mTempHsl = new float[3];
+
+    private static final float BLACK_MAX_LIGHTNESS = 0.05f;
+    private static final float WHITE_MIN_LIGHTNESS = 0.95f;
+
+    private static final int COMPONENT_RED = -3;
+    private static final int COMPONENT_GREEN = -2;
+    private static final int COMPONENT_BLUE = -1;
+
+    private final int[] mColors;
+    private final SparseIntArray mColorPopulations;
+
+    private final List<Swatch> mQuantizedColors;
+
+    /**
+     * Factory-method to generate a {@link ColorCutQuantizer} from a {@link Bitmap} object.
+     *
+     * @param bitmap Bitmap to extract the pixel data from
+     * @param maxColors The maximum number of colors that should be in the result palette.
+     */
+    static ColorCutQuantizer fromBitmap(Bitmap bitmap, int maxColors) {
+        final int width = bitmap.getWidth();
+        final int height = bitmap.getHeight();
+
+        final int[] pixels = new int[width * height];
+        bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
+
+        return new ColorCutQuantizer(new ColorHistogram(pixels), maxColors);
+    }
+
+    /**
+     * Private constructor.
+     *
+     * @param colorHistogram histogram representing an image's pixel data
+     * @param maxColors The maximum number of colors that should be in the result palette.
+     */
+    private ColorCutQuantizer(ColorHistogram colorHistogram, int maxColors) {
+        final int rawColorCount = colorHistogram.getNumberOfColors();
+        final int[] rawColors = colorHistogram.getColors();
+        final int[] rawColorCounts = colorHistogram.getColorCounts();
+
+        // First, lets pack the populations into a SparseIntArray so that they can be easily
+        // retrieved without knowing a color's index
+        mColorPopulations = new SparseIntArray(rawColorCount);
+        for (int i = 0; i < rawColors.length; i++) {
+            mColorPopulations.append(rawColors[i], rawColorCounts[i]);
+        }
+
+        // Now go through all of the colors and keep those which we do not want to ignore
+        mColors = new int[rawColorCount];
+        int validColorCount = 0;
+        for (int color : rawColors) {
+            if (!shouldIgnoreColor(color)) {
+                mColors[validColorCount++] = color;
+            }
+        }
+
+        if (validColorCount <= maxColors) {
+            // The image has fewer colors than the maximum requested, so just return the colors
+            mQuantizedColors = new ArrayList<Swatch>();
+            for (final int color : mColors) {
+                mQuantizedColors.add(new Swatch(color, mColorPopulations.get(color)));
+            }
+        } else {
+            // We need use quantization to reduce the number of colors
+            mQuantizedColors = quantizePixels(validColorCount - 1, maxColors);
+        }
+    }
+
+    /**
+     * @return the list of quantized colors
+     */
+    List<Swatch> getQuantizedColors() {
+        return mQuantizedColors;
+    }
+
+    private List<Swatch> quantizePixels(int maxColorIndex, int maxColors) {
+        // Create the priority queue which is sorted by volume descending. This means we always
+        // split the largest box in the queue
+        final PriorityQueue<Vbox> pq = new PriorityQueue<Vbox>(maxColors, VBOX_COMPARATOR_VOLUME);
+
+        // To start, offer a box which contains all of the colors
+        pq.offer(new Vbox(0, maxColorIndex));
+
+        // Now go through the boxes, splitting them until we have reached maxColors or there are no
+        // more boxes to split
+        splitBoxes(pq, maxColors);
+
+        // Finally, return the average colors of the color boxes
+        return generateAverageColors(pq);
+    }
+
+    /**
+     * Iterate through the {@link java.util.Queue}, popping
+     * {@link ColorCutQuantizer.Vbox} objects from the queue
+     * and splitting them. Once split, the new box and the remaining box are offered back to the
+     * queue.
+     *
+     * @param queue {@link java.util.PriorityQueue} to poll for boxes
+     * @param maxSize Maximum amount of boxes to split
+     */
+    private void splitBoxes(final PriorityQueue<Vbox> queue, final int maxSize) {
+        while (queue.size() < maxSize) {
+            final Vbox vbox = queue.poll();
+
+            if (vbox != null && vbox.canSplit()) {
+                // First split the box, and offer the result
+                queue.offer(vbox.splitBox());
+                // Then offer the box back
+                queue.offer(vbox);
+            } else {
+                // If we get here then there are no more boxes to split, so return
+                return;
+            }
+        }
+    }
+
+    private List<Swatch> generateAverageColors(Collection<Vbox> vboxes) {
+        ArrayList<Swatch> colors = new ArrayList<Swatch>(vboxes.size());
+        for (Vbox vbox : vboxes) {
+            Swatch color = vbox.getAverageColor();
+            if (!shouldIgnoreColor(color)) {
+                // As we're averaging a color box, we can still get colors which we do not want, so
+                // we check again here
+                colors.add(color);
+            }
+        }
+        return colors;
+    }
+
+    /**
+     * Represents a tightly fitting box around a color space.
+     */
+    private class Vbox {
+        // lower and upper index are inclusive
+        private int mLowerIndex;
+        private int mUpperIndex;
+
+        private int mMinRed, mMaxRed;
+        private int mMinGreen, mMaxGreen;
+        private int mMinBlue, mMaxBlue;
+
+        Vbox(int lowerIndex, int upperIndex) {
+            mLowerIndex = lowerIndex;
+            mUpperIndex = upperIndex;
+            fitBox();
+        }
+
+        int getVolume() {
+            return (mMaxRed - mMinRed + 1) * (mMaxGreen - mMinGreen + 1) *
+                    (mMaxBlue - mMinBlue + 1);
+        }
+
+        boolean canSplit() {
+            return getColorCount() > 1;
+        }
+
+        int getColorCount() {
+            return mUpperIndex - mLowerIndex + 1;
+        }
+
+        /**
+         * Recomputes the boundaries of this box to tightly fit the colors within the box.
+         */
+        void fitBox() {
+            // Reset the min and max to opposite values
+            mMinRed = mMinGreen = mMinBlue = 0xFF;
+            mMaxRed = mMaxGreen = mMaxBlue = 0x0;
+
+            for (int i = mLowerIndex; i <= mUpperIndex; i++) {
+                final int color = mColors[i];
+                final int r = Color.red(color);
+                final int g = Color.green(color);
+                final int b = Color.blue(color);
+                if (r > mMaxRed) {
+                    mMaxRed = r;
+                }
+                if (r < mMinRed) {
+                    mMinRed = r;
+                }
+                if (g > mMaxGreen) {
+                    mMaxGreen = g;
+                }
+                if (g < mMinGreen) {
+                    mMinGreen = g;
+                }
+                if (b > mMaxBlue) {
+                    mMaxBlue = b;
+                }
+                if (b < mMinBlue) {
+                    mMinBlue = b;
+                }
+            }
+        }
+
+        /**
+         * Split this color box at the mid-point along it's longest dimension
+         *
+         * @return the new ColorBox
+         */
+        Vbox splitBox() {
+            if (!canSplit()) {
+                throw new IllegalStateException("Can not split a box with only 1 color");
+            }
+
+            // find median along the longest dimension
+            final int splitPoint = findSplitPoint();
+
+            Vbox newBox = new Vbox(splitPoint + 1, mUpperIndex);
+
+            // Now change this box's upperIndex and recompute the color boundaries
+            mUpperIndex = splitPoint;
+            fitBox();
+
+            return newBox;
+        }
+
+        /**
+         * @return the dimension which this box is largest in
+         */
+        int getLongestColorDimension() {
+            final int redLength = mMaxRed - mMinRed;
+            final int greenLength = mMaxGreen - mMinGreen;
+            final int blueLength = mMaxBlue - mMinBlue;
+
+            if (redLength >= greenLength && redLength >= blueLength) {
+                return COMPONENT_RED;
+            } else if (greenLength >= redLength && greenLength >= blueLength) {
+                return COMPONENT_GREEN;
+            } else {
+                return COMPONENT_BLUE;
+            }
+        }
+
+        /**
+         * Finds the point within this box's lowerIndex and upperIndex index of where to split.
+         *
+         * This is calculated by finding the longest color dimension, and then sorting the
+         * sub-array based on that dimension value in each color. The colors are then iterated over
+         * until a color is found with at least the midpoint of the whole box's dimension midpoint.
+         *
+         * @return the index of the colors array to split from
+         */
+        int findSplitPoint() {
+            final int longestDimension = getLongestColorDimension();
+
+            // We need to sort the colors in this box based on the longest color dimension.
+            // As we can't use a Comparator to define the sort logic, we modify each color so that
+            // it's most significant is the desired dimension
+            modifySignificantOctet(longestDimension, mLowerIndex, mUpperIndex);
+
+            // Now sort... Arrays.sort uses a exclusive toIndex so we need to add 1
+            Arrays.sort(mColors, mLowerIndex, mUpperIndex + 1);
+
+            // Now revert all of the colors so that they are packed as RGB again
+            modifySignificantOctet(longestDimension, mLowerIndex, mUpperIndex);
+
+            final int dimensionMidPoint = midPoint(longestDimension);
+
+            for (int i = mLowerIndex; i <= mUpperIndex; i++)  {
+                final int color = mColors[i];
+
+                switch (longestDimension) {
+                    case COMPONENT_RED:
+                        if (Color.red(color) >= dimensionMidPoint) {
+                            return i;
+                        }
+                        break;
+                    case COMPONENT_GREEN:
+                        if (Color.green(color) >= dimensionMidPoint) {
+                            return i;
+                        }
+                        break;
+                    case COMPONENT_BLUE:
+                        if (Color.blue(color) > dimensionMidPoint) {
+                            return i;
+                        }
+                        break;
+                }
+            }
+
+            return mLowerIndex;
+        }
+
+        /**
+         * @return the average color of this box.
+         */
+        Swatch getAverageColor() {
+            int redSum = 0;
+            int greenSum = 0;
+            int blueSum = 0;
+            int totalPopulation = 0;
+
+            for (int i = mLowerIndex; i <= mUpperIndex; i++) {
+                final int color = mColors[i];
+                final int colorPopulation = mColorPopulations.get(color);
+
+                totalPopulation += colorPopulation;
+                redSum += colorPopulation * Color.red(color);
+                greenSum += colorPopulation * Color.green(color);
+                blueSum += colorPopulation * Color.blue(color);
+            }
+
+            final int redAverage = Math.round(redSum / (float) totalPopulation);
+            final int greenAverage = Math.round(greenSum / (float) totalPopulation);
+            final int blueAverage = Math.round(blueSum / (float) totalPopulation);
+
+            return new Swatch(redAverage, greenAverage, blueAverage, totalPopulation);
+        }
+
+        /**
+         * @return the midpoint of this box in the given {@code dimension}
+         */
+        int midPoint(int dimension) {
+            switch (dimension) {
+                case COMPONENT_RED:
+                default:
+                    return (mMinRed + mMaxRed) / 2;
+                case COMPONENT_GREEN:
+                    return (mMinGreen + mMaxGreen) / 2;
+                case COMPONENT_BLUE:
+                    return (mMinBlue + mMaxBlue) / 2;
+            }
+        }
+    }
+
+    /**
+     * Modify the significant octet in a packed color int. Allows sorting based on the value of a
+     * single color component.
+     *
+     * @see Vbox#findSplitPoint()
+     */
+    private void modifySignificantOctet(final int dimension, int lowerIndex, int upperIndex) {
+        switch (dimension) {
+            case COMPONENT_RED:
+                // Already in RGB, no need to do anything
+                break;
+            case COMPONENT_GREEN:
+                // We need to do a RGB to GRB swap, or vice-versa
+                for (int i = lowerIndex; i <= upperIndex; i++) {
+                    final int color = mColors[i];
+                    mColors[i] = Color.rgb((color >> 8) & 0xFF, (color >> 16) & 0xFF, color & 0xFF);
+                }
+                break;
+            case COMPONENT_BLUE:
+                // We need to do a RGB to BGR swap, or vice-versa
+                for (int i = lowerIndex; i <= upperIndex; i++) {
+                    final int color = mColors[i];
+                    mColors[i] = Color.rgb(color & 0xFF, (color >> 8) & 0xFF, (color >> 16) & 0xFF);
+                }
+                break;
+        }
+    }
+
+    private boolean shouldIgnoreColor(int color) {
+        ColorUtils.RGBtoHSL(Color.red(color), Color.green(color), Color.blue(color), mTempHsl);
+        return shouldIgnoreColor(mTempHsl);
+    }
+
+    private static boolean shouldIgnoreColor(Swatch color) {
+        return shouldIgnoreColor(color.getHsl());
+    }
+
+    private static boolean shouldIgnoreColor(float[] hslColor) {
+        return isWhite(hslColor) || isBlack(hslColor) || isNearRedILine(hslColor);
+    }
+
+    /**
+     * @return true if the color represents a color which is close to black.
+     */
+    private static boolean isBlack(float[] hslColor) {
+        return hslColor[2] <= BLACK_MAX_LIGHTNESS;
+    }
+
+    /**
+     * @return true if the color represents a color which is close to white.
+     */
+    private static boolean isWhite(float[] hslColor) {
+        return hslColor[2] >= WHITE_MIN_LIGHTNESS;
+    }
+
+    /**
+     * @return true if the color lies close to the red side of the I line.
+     */
+    private static boolean isNearRedILine(float[] hslColor) {
+        return hslColor[0] >= 10f && hslColor[0] <= 37f && hslColor[1] <= 0.82f;
+    }
+
+    /**
+     * Comparator which sorts {@link Vbox} instances based on their volume, in descending order
+     */
+    private static final Comparator<Vbox> VBOX_COMPARATOR_VOLUME = new Comparator<Vbox>() {
+        @Override
+        public int compare(Vbox lhs, Vbox rhs) {
+            return rhs.getVolume() - lhs.getVolume();
+        }
+    };
+
+}
diff --git a/v7/palette/src/android/support/v7/graphics/ColorHistogram.java b/v7/palette/src/android/support/v7/graphics/ColorHistogram.java
new file mode 100644
index 0000000..bcc43fe
--- /dev/null
+++ b/v7/palette/src/android/support/v7/graphics/ColorHistogram.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.graphics;
+
+import java.util.Arrays;
+
+/**
+ * Class which provides a histogram for RGB values.
+ */
+final class ColorHistogram {
+
+    private final int[] mColors;
+    private final int[] mColorCounts;
+    private final int mNumberColors;
+
+    /**
+     * A new {@link ColorHistogram} instance.
+     *
+     * @param pixels array of image contents
+     */
+    ColorHistogram(final int[] pixels) {
+        // Sort the pixels to enable counting below
+        Arrays.sort(pixels);
+
+        // Count number of distinct colors
+        mNumberColors = countDistinctColors(pixels);
+
+        // Create arrays
+        mColors = new int[mNumberColors];
+        mColorCounts = new int[mNumberColors];
+
+        // Finally count the frequency of each color
+        countFrequencies(pixels);
+    }
+
+    /**
+     * @return number of distinct colors in the image.
+     */
+    int getNumberOfColors() {
+        return mNumberColors;
+    }
+
+    /**
+     * @return an array containing all of the distinct colors in the image.
+     */
+    int[] getColors() {
+        return mColors;
+    }
+
+    /**
+     * @return an array containing the frequency of a distinct colors within the image.
+     */
+    int[] getColorCounts() {
+        return mColorCounts;
+    }
+
+    private static int countDistinctColors(final int[] pixels) {
+        if (pixels.length < 2) {
+            // If we have less than 2 pixels we can stop here
+            return pixels.length;
+        }
+
+        // If we have at least 2 pixels, we have a minimum of 1 color...
+        int colorCount = 1;
+        int currentColor = pixels[0];
+
+        // Now iterate from the second pixel to the end, counting distinct colors
+        for (int i = 1; i < pixels.length; i++) {
+            // If we encounter a new color, increase the population
+            if (pixels[i] != currentColor) {
+                currentColor = pixels[i];
+                colorCount++;
+            }
+        }
+
+        return colorCount;
+    }
+
+    private void countFrequencies(final int[] pixels) {
+        if (pixels.length == 0) {
+            return;
+        }
+
+        int currentColorIndex = 0;
+        int currentColor = pixels[0];
+
+        mColors[currentColorIndex] = currentColor;
+        mColorCounts[currentColorIndex] = 1;
+
+        if (pixels.length == 1) {
+            // If we only have one pixel, we can stop here
+            return;
+        }
+
+        // Now iterate from the second pixel to the end, population distinct colors
+        for (int i = 1; i < pixels.length; i++) {
+            if (pixels[i] == currentColor) {
+                // We've hit the same color as before, increase population
+                mColorCounts[currentColorIndex]++;
+            } else {
+                // We've hit a new color, increase index
+                currentColor = pixels[i];
+
+                currentColorIndex++;
+                mColors[currentColorIndex] = currentColor;
+                mColorCounts[currentColorIndex] = 1;
+            }
+        }
+    }
+
+}
diff --git a/v7/palette/src/android/support/v7/graphics/ColorUtils.java b/v7/palette/src/android/support/v7/graphics/ColorUtils.java
new file mode 100644
index 0000000..84d330b
--- /dev/null
+++ b/v7/palette/src/android/support/v7/graphics/ColorUtils.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.graphics;
+
+import android.graphics.Color;
+
+final class ColorUtils {
+
+    private static final int MIN_ALPHA_SEARCH_MAX_ITERATIONS = 10;
+    private static final int MIN_ALPHA_SEARCH_PRECISION = 10;
+
+    private ColorUtils() {}
+
+    /**
+     * Composite two potentially translucent colors over each other and returns the result.
+     */
+    private static int compositeColors(int fg, int bg) {
+        final float alpha1 = Color.alpha(fg) / 255f;
+        final float alpha2 = Color.alpha(bg) / 255f;
+
+        float a = (alpha1 + alpha2) * (1f - alpha1);
+        float r = (Color.red(fg) * alpha1) + (Color.red(bg) * alpha2 * (1f - alpha1));
+        float g = (Color.green(fg) * alpha1) + (Color.green(bg) * alpha2 * (1f - alpha1));
+        float b = (Color.blue(fg) * alpha1) + (Color.blue(bg) * alpha2 * (1f - alpha1));
+
+        return Color.argb((int) a, (int) r, (int) g, (int) b);
+    }
+
+    /**
+     * Returns the luminance of a color.
+     *
+     * Formula defined here: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
+     */
+    private static double calculateLuminance(int color) {
+        double red = Color.red(color) / 255d;
+        red = red < 0.03928 ? red / 12.92 : Math.pow((red + 0.055) / 1.055, 2.4);
+
+        double green = Color.green(color) / 255d;
+        green = green < 0.03928 ? green / 12.92 : Math.pow((green + 0.055) / 1.055, 2.4);
+
+        double blue = Color.blue(color) / 255d;
+        blue = blue < 0.03928 ? blue / 12.92 : Math.pow((blue + 0.055) / 1.055, 2.4);
+
+        return (0.2126 * red) + (0.7152 * green) + (0.0722 * blue);
+    }
+
+    /**
+     * Returns the contrast ratio between two colors.
+     *
+     * Formula defined here: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
+     */
+    private static double calculateContrast(int foreground, int background) {
+        if (Color.alpha(background) != 255) {
+            throw new IllegalArgumentException("background can not be translucent");
+        }
+        if (Color.alpha(foreground) < 255) {
+            // If the foreground is translucent, composite the foreground over the background
+            foreground = compositeColors(foreground, background);
+        }
+
+        final double luminance1 = calculateLuminance(foreground) + 0.05;
+        final double luminance2 = calculateLuminance(background) + 0.05;
+
+        // Now return the lighter luminance divided by the darker luminance
+        return Math.max(luminance1, luminance2) / Math.min(luminance1, luminance2);
+    }
+
+    /**
+     * Finds the minimum alpha value which can be applied to {@code foreground} so that is has a
+     * contrast value of at least {@code minContrastRatio} when compared to background.
+     *
+     * @return the alpha value in the range 0-255.
+     */
+    private static int findMinimumAlpha(int foreground, int background, double minContrastRatio) {
+        if (Color.alpha(background) != 255) {
+            throw new IllegalArgumentException("background can not be translucent");
+        }
+
+        // First lets check that a fully opaque foreground has sufficient contrast
+        int testForeground = modifyAlpha(foreground, 255);
+        double testRatio = calculateContrast(testForeground, background);
+        if (testRatio < minContrastRatio) {
+            // Fully opaque foreground does not have sufficient contrast, return error
+            return -1;
+        }
+
+        // Binary search to find a value with the minimum value which provides sufficient contrast
+        int numIterations = 0;
+        int minAlpha = 0;
+        int maxAlpha = 255;
+
+        while (numIterations <= MIN_ALPHA_SEARCH_MAX_ITERATIONS &&
+                (maxAlpha - minAlpha) > MIN_ALPHA_SEARCH_PRECISION) {
+            final int testAlpha = (minAlpha + maxAlpha) / 2;
+
+            testForeground = modifyAlpha(foreground, testAlpha);
+            testRatio = calculateContrast(testForeground, background);
+
+            if (testRatio < minContrastRatio) {
+                minAlpha = testAlpha;
+            } else {
+                maxAlpha = testAlpha;
+            }
+
+            numIterations++;
+        }
+
+        // Conservatively return the max of the range of possible alphas, which is known to pass.
+        return maxAlpha;
+    }
+
+    static int getTextColorForBackground(int backgroundColor, float minContrastRatio) {
+        // First we will check white as most colors will be dark
+        final int whiteMinAlpha = ColorUtils
+                .findMinimumAlpha(Color.WHITE, backgroundColor, minContrastRatio);
+
+        if (whiteMinAlpha >= 0) {
+            return ColorUtils.modifyAlpha(Color.WHITE, whiteMinAlpha);
+        }
+
+        // If we hit here then there is not an translucent white which provides enough contrast,
+        // so check black
+        final int blackMinAlpha = ColorUtils
+                .findMinimumAlpha(Color.BLACK, backgroundColor, minContrastRatio);
+
+        if (blackMinAlpha >= 0) {
+            return ColorUtils.modifyAlpha(Color.BLACK, blackMinAlpha);
+        }
+
+        // This should not happen!
+        return -1;
+    }
+
+    static void RGBtoHSL(int r, int g, int b, float[] hsl) {
+        final float rf = r / 255f;
+        final float gf = g / 255f;
+        final float bf = b / 255f;
+
+        final float max = Math.max(rf, Math.max(gf, bf));
+        final float min = Math.min(rf, Math.min(gf, bf));
+        final float deltaMaxMin = max - min;
+
+        float h, s;
+        float l = (max + min) / 2f;
+
+        if (max == min) {
+            // Monochromatic
+            h = s = 0f;
+        } else {
+            if (max == rf) {
+                h = ((gf - bf) / deltaMaxMin) % 6f;
+            } else if (max == gf) {
+                h = ((bf - rf) / deltaMaxMin) + 2f;
+            } else {
+                h = ((rf - gf) / deltaMaxMin) + 4f;
+            }
+
+            s =  deltaMaxMin / (1f - Math.abs(2f * l - 1f));
+        }
+
+        hsl[0] = (h * 60f) % 360f;
+        hsl[1] = s;
+        hsl[2] = l;
+    }
+
+    static int HSLtoRGB (float[] hsl) {
+        final float h = hsl[0];
+        final float s = hsl[1];
+        final float l = hsl[2];
+
+        final float c = (1f - Math.abs(2 * l - 1f)) * s;
+        final float m = l - 0.5f * c;
+        final float x = c * (1f - Math.abs((h / 60f % 2f) - 1f));
+
+        final int hueSegment = (int) h / 60;
+
+        int r = 0, g = 0, b = 0;
+
+        switch (hueSegment) {
+            case 0:
+                r = Math.round(255 * (c + m));
+                g = Math.round(255 * (x + m));
+                b = Math.round(255 * m);
+                break;
+            case 1:
+                r = Math.round(255 * (x + m));
+                g = Math.round(255 * (c + m));
+                b = Math.round(255 * m);
+                break;
+            case 2:
+                r = Math.round(255 * m);
+                g = Math.round(255 * (c + m));
+                b = Math.round(255 * (x + m));
+                break;
+            case 3:
+                r = Math.round(255 * m);
+                g = Math.round(255 * (x + m));
+                b = Math.round(255 * (c + m));
+                break;
+            case 4:
+                r = Math.round(255 * (x + m));
+                g = Math.round(255 * m);
+                b = Math.round(255 * (c + m));
+                break;
+            case 5:
+            case 6:
+                r = Math.round(255 * (c + m));
+                g = Math.round(255 * m);
+                b = Math.round(255 * (x + m));
+                break;
+        }
+
+        r = Math.max(0, Math.min(255, r));
+        g = Math.max(0, Math.min(255, g));
+        b = Math.max(0, Math.min(255, b));
+
+        return Color.rgb(r, g, b);
+    }
+
+    /**
+     * Set the alpha component of {@code color} to be {@code alpha}.
+     */
+    static int modifyAlpha(int color, int alpha) {
+        return (color & 0x00ffffff) | (alpha << 24);
+    }
+
+}
diff --git a/v7/palette/src/android/support/v7/graphics/Palette.java b/v7/palette/src/android/support/v7/graphics/Palette.java
new file mode 100644
index 0000000..0831b75
--- /dev/null
+++ b/v7/palette/src/android/support/v7/graphics/Palette.java
@@ -0,0 +1,646 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.graphics;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.os.AsyncTask;
+import android.support.v4.os.AsyncTaskCompat;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A helper class to extract prominent colors from an image.
+ * <p>
+ * A number of colors with different profiles are extracted from the image:
+ * <ul>
+ *     <li>Vibrant</li>
+ *     <li>Vibrant Dark</li>
+ *     <li>Vibrant Light</li>
+ *     <li>Muted</li>
+ *     <li>Muted Dark</li>
+ *     <li>Muted Light</li>
+ * </ul>
+ * These can be retrieved from the appropriate getter method.
+ *
+ * <p>
+ * Instances can be created with the synchronous factory methods {@link #generate(Bitmap)} and
+ * {@link #generate(Bitmap, int)}.
+ * <p>
+ * These should be called on a background thread, ideally the one in
+ * which you load your images on. Sometimes that is not possible, so asynchronous factory methods
+ * have also been provided: {@link #generateAsync(Bitmap, PaletteAsyncListener)} and
+ * {@link #generateAsync(Bitmap, int, PaletteAsyncListener)}. These can be used as so:
+ *
+ * <pre>
+ * Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() {
+ *     public void onGenerated(Palette palette) {
+ *         // Do something with colors...
+ *     }
+ * });
+ * </pre>
+ */
+public final class Palette {
+
+    /**
+     * Listener to be used with {@link #generateAsync(Bitmap, PaletteAsyncListener)} or
+     * {@link #generateAsync(Bitmap, int, PaletteAsyncListener)}
+     */
+    public interface PaletteAsyncListener {
+
+        /**
+         * Called when the {@link Palette} has been generated.
+         */
+        void onGenerated(Palette palette);
+    }
+
+    private static final int CALCULATE_BITMAP_MIN_DIMENSION = 100;
+    private static final int DEFAULT_CALCULATE_NUMBER_COLORS = 16;
+
+    private static final float TARGET_DARK_LUMA = 0.26f;
+    private static final float MAX_DARK_LUMA = 0.45f;
+
+    private static final float MIN_LIGHT_LUMA = 0.55f;
+    private static final float TARGET_LIGHT_LUMA = 0.74f;
+
+    private static final float MIN_NORMAL_LUMA = 0.3f;
+    private static final float TARGET_NORMAL_LUMA = 0.5f;
+    private static final float MAX_NORMAL_LUMA = 0.7f;
+
+    private static final float TARGET_MUTED_SATURATION = 0.3f;
+    private static final float MAX_MUTED_SATURATION = 0.4f;
+
+    private static final float TARGET_VIBRANT_SATURATION = 1f;
+    private static final float MIN_VIBRANT_SATURATION = 0.35f;
+
+    private static final float WEIGHT_SATURATION = 3f;
+    private static final float WEIGHT_LUMA = 6f;
+    private static final float WEIGHT_POPULATION = 1f;
+
+    private static final float MIN_CONTRAST_TITLE_TEXT = 3.0f;
+    private static final float MIN_CONTRAST_BODY_TEXT = 4.5f;
+
+    private final List<Swatch> mSwatches;
+    private final int mHighestPopulation;
+
+    private Swatch mVibrantSwatch;
+    private Swatch mMutedSwatch;
+
+    private Swatch mDarkVibrantSwatch;
+    private Swatch mDarkMutedSwatch;
+
+    private Swatch mLightVibrantSwatch;
+    private Swatch mLightMutedColor;
+
+    /**
+     * Generate a {@link Palette} from a {@link Bitmap} using the default number of colors.
+     */
+    public static Palette generate(Bitmap bitmap) {
+        return generate(bitmap, DEFAULT_CALCULATE_NUMBER_COLORS);
+    }
+
+    /**
+     * Generate a {@link Palette} from a {@link Bitmap} using the specified {@code numColors}.
+     * Good values for {@code numColors} depend on the source image type.
+     * For landscapes, a good values are in the range 12-16. For images which are largely made up
+     * of people's faces then this value should be increased to 24-32.
+     *
+     * @param numColors The maximum number of colors in the generated palette. Increasing this
+     *                  number will increase the time needed to compute the values.
+     */
+    public static Palette generate(Bitmap bitmap, int numColors) {
+        checkBitmapParam(bitmap);
+        checkNumberColorsParam(numColors);
+
+        // First we'll scale down the bitmap so it's shortest dimension is 100px
+        final Bitmap scaledBitmap = scaleBitmapDown(bitmap);
+
+        // Now generate a quantizer from the Bitmap
+        ColorCutQuantizer quantizer = ColorCutQuantizer.fromBitmap(scaledBitmap, numColors);
+
+        // If created a new bitmap, recycle it
+        if (scaledBitmap != bitmap) {
+            scaledBitmap.recycle();
+        }
+
+        // Now return a ColorExtractor instance
+        return new Palette(quantizer.getQuantizedColors());
+    }
+
+    /**
+     * Generate a {@link Palette} asynchronously. {@link PaletteAsyncListener#onGenerated(Palette)}
+     * will be called with the created instance. The resulting {@link Palette} is the same as
+     * what would be created by calling {@link #generate(Bitmap)}.
+     *
+     * @param listener Listener to be invoked when the {@link Palette} has been generated.
+     *
+     * @return the {@link android.os.AsyncTask} used to asynchronously generate the instance.
+     */
+    public static AsyncTask<Bitmap, Void, Palette> generateAsync(
+            Bitmap bitmap, PaletteAsyncListener listener) {
+        return generateAsync(bitmap, DEFAULT_CALCULATE_NUMBER_COLORS, listener);
+    }
+
+    /**
+     * Generate a {@link Palette} asynchronously. {@link PaletteAsyncListener#onGenerated(Palette)}
+     * will be called with the created instance. The resulting {@link Palette} is the same as what
+     * would be created by calling {@link #generate(Bitmap, int)}.
+     *
+     * @param listener Listener to be invoked when the {@link Palette} has been generated.
+     *
+     * @return the {@link android.os.AsyncTask} used to asynchronously generate the instance.
+     */
+    public static AsyncTask<Bitmap, Void, Palette> generateAsync(
+            final Bitmap bitmap, final int numColors, final PaletteAsyncListener listener) {
+        checkBitmapParam(bitmap);
+        checkNumberColorsParam(numColors);
+        checkAsyncListenerParam(listener);
+
+        return AsyncTaskCompat.executeParallel(
+                new AsyncTask<Bitmap, Void, Palette>() {
+                    @Override
+                    protected Palette doInBackground(Bitmap... params) {
+                        return generate(params[0], numColors);
+                    }
+
+                    @Override
+                    protected void onPostExecute(Palette colorExtractor) {
+                        listener.onGenerated(colorExtractor);
+                    }
+                }, bitmap);
+    }
+
+    private Palette(List<Swatch> swatches) {
+        mSwatches = swatches;
+        mHighestPopulation = findMaxPopulation();
+
+        mVibrantSwatch = findColor(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA,
+                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
+
+        mLightVibrantSwatch = findColor(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f,
+                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
+
+        mDarkVibrantSwatch = findColor(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA,
+                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
+
+        mMutedSwatch = findColor(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA,
+                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
+
+        mLightMutedColor = findColor(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f,
+                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
+
+        mDarkMutedSwatch = findColor(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA,
+                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
+
+        // Now try and generate any missing colors
+        generateEmptySwatches();
+    }
+
+    /**
+     * Returns all of the swatches which make up the palette.
+     */
+    public List<Swatch> getSwatches() {
+        return Collections.unmodifiableList(mSwatches);
+    }
+
+    /**
+     * Returns the most vibrant swatch in the palette. Might be null.
+     */
+    public Swatch getVibrantSwatch() {
+        return mVibrantSwatch;
+    }
+
+    /**
+     * Returns a light and vibrant swatch from the palette. Might be null.
+     */
+    public Swatch getLightVibrantSwatch() {
+        return mLightVibrantSwatch;
+    }
+
+    /**
+     * Returns a dark and vibrant swatch from the palette. Might be null.
+     */
+    public Swatch getDarkVibrantSwatch() {
+        return mDarkVibrantSwatch;
+    }
+
+    /**
+     * Returns a muted swatch from the palette. Might be null.
+     */
+    public Swatch getMutedSwatch() {
+        return mMutedSwatch;
+    }
+
+    /**
+     * Returns a muted and light swatch from the palette. Might be null.
+     */
+    public Swatch getLightMutedSwatch() {
+        return mLightMutedColor;
+    }
+
+    /**
+     * Returns a muted and dark swatch from the palette. Might be null.
+     */
+    public Swatch getDarkMutedSwatch() {
+        return mDarkMutedSwatch;
+    }
+
+    /**
+     * Returns the most vibrant color in the palette as an RGB packed int.
+     *
+     * @param defaultColor value to return if the swatch isn't available
+     */
+    public int getVibrantColor(int defaultColor) {
+        return mVibrantSwatch != null ? mVibrantSwatch.getRgb() : defaultColor;
+    }
+
+    /**
+     * Returns a light and vibrant color from the palette as an RGB packed int.
+     *
+     * @param defaultColor value to return if the swatch isn't available
+     */
+    public int getLightVibrantColor(int defaultColor) {
+        return mLightVibrantSwatch != null ? mLightVibrantSwatch.getRgb() : defaultColor;
+    }
+
+    /**
+     * Returns a dark and vibrant color from the palette as an RGB packed int.
+     *
+     * @param defaultColor value to return if the swatch isn't available
+     */
+    public int getDarkVibrantColor(int defaultColor) {
+        return mDarkVibrantSwatch != null ? mDarkVibrantSwatch.getRgb() : defaultColor;
+    }
+
+    /**
+     * Returns a muted color from the palette as an RGB packed int.
+     *
+     * @param defaultColor value to return if the swatch isn't available
+     */
+    public int getMutedColor(int defaultColor) {
+        return mMutedSwatch != null ? mMutedSwatch.getRgb() : defaultColor;
+    }
+
+    /**
+     * Returns a muted and light color from the palette as an RGB packed int.
+     *
+     * @param defaultColor value to return if the swatch isn't available
+     */
+    public int getLightMutedColor(int defaultColor) {
+        return mLightMutedColor != null ? mLightMutedColor.getRgb() : defaultColor;
+    }
+
+    /**
+     * Returns a muted and dark color from the palette as an RGB packed int.
+     *
+     * @param defaultColor value to return if the swatch isn't available
+     */
+    public int getDarkMutedColor(int defaultColor) {
+        return mDarkMutedSwatch != null ? mDarkMutedSwatch.getRgb() : defaultColor;
+    }
+
+    /**
+     * @return true if we have already selected {@code swatch}
+     */
+    private boolean isAlreadySelected(Swatch swatch) {
+        return mVibrantSwatch == swatch || mDarkVibrantSwatch == swatch ||
+                mLightVibrantSwatch == swatch || mMutedSwatch == swatch ||
+                mDarkMutedSwatch == swatch || mLightMutedColor == swatch;
+    }
+
+    private Swatch findColor(float targetLuma, float minLuma, float maxLuma,
+                             float targetSaturation, float minSaturation, float maxSaturation) {
+        Swatch max = null;
+        float maxValue = 0f;
+
+        for (Swatch swatch : mSwatches) {
+            final float sat = swatch.getHsl()[1];
+            final float luma = swatch.getHsl()[2];
+
+            if (sat >= minSaturation && sat <= maxSaturation &&
+                    luma >= minLuma && luma <= maxLuma &&
+                    !isAlreadySelected(swatch)) {
+                float thisValue = createComparisonValue(sat, targetSaturation, luma, targetLuma,
+                        swatch.getPopulation(), mHighestPopulation);
+                if (max == null || thisValue > maxValue) {
+                    max = swatch;
+                    maxValue = thisValue;
+                }
+            }
+        }
+
+        return max;
+    }
+
+    /**
+     * Try and generate any missing swatches from the swatches we did find.
+     */
+    private void generateEmptySwatches() {
+        if (mVibrantSwatch == null) {
+            // If we do not have a vibrant color...
+            if (mDarkVibrantSwatch != null) {
+                // ...but we do have a dark vibrant, generate the value by modifying the luma
+                final float[] newHsl = copyHslValues(mDarkVibrantSwatch);
+                newHsl[2] = TARGET_NORMAL_LUMA;
+                mVibrantSwatch = new Swatch(ColorUtils.HSLtoRGB(newHsl), 0);
+            }
+        }
+
+        if (mDarkVibrantSwatch == null) {
+            // If we do not have a dark vibrant color...
+            if (mVibrantSwatch != null) {
+                // ...but we do have a vibrant, generate the value by modifying the luma
+                final float[] newHsl = copyHslValues(mVibrantSwatch);
+                newHsl[2] = TARGET_DARK_LUMA;
+                mDarkVibrantSwatch = new Swatch(ColorUtils.HSLtoRGB(newHsl), 0);
+            }
+        }
+    }
+
+    /**
+     * Find the {@link Swatch} with the highest population value and return the population.
+     */
+    private int findMaxPopulation() {
+        int population = 0;
+        for (Swatch swatch : mSwatches) {
+            population = Math.max(population, swatch.getPopulation());
+        }
+        return population;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        Palette palette = (Palette) o;
+
+        if (mSwatches != null ? !mSwatches.equals(palette.mSwatches) : palette.mSwatches != null) {
+            return false;
+        }
+        if (mDarkMutedSwatch != null ? !mDarkMutedSwatch.equals(palette.mDarkMutedSwatch)
+                : palette.mDarkMutedSwatch != null) {
+            return false;
+        }
+        if (mDarkVibrantSwatch != null ? !mDarkVibrantSwatch.equals(palette.mDarkVibrantSwatch)
+                : palette.mDarkVibrantSwatch != null) {
+            return false;
+        }
+        if (mLightMutedColor != null ? !mLightMutedColor.equals(palette.mLightMutedColor)
+                : palette.mLightMutedColor != null) {
+            return false;
+        }
+        if (mLightVibrantSwatch != null ? !mLightVibrantSwatch.equals(palette.mLightVibrantSwatch)
+                : palette.mLightVibrantSwatch != null) {
+            return false;
+        }
+        if (mMutedSwatch != null ? !mMutedSwatch.equals(palette.mMutedSwatch)
+                : palette.mMutedSwatch != null) {
+            return false;
+        }
+        if (mVibrantSwatch != null ? !mVibrantSwatch.equals(palette.mVibrantSwatch)
+                : palette.mVibrantSwatch != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mSwatches != null ? mSwatches.hashCode() : 0;
+        result = 31 * result + (mVibrantSwatch != null ? mVibrantSwatch.hashCode() : 0);
+        result = 31 * result + (mMutedSwatch != null ? mMutedSwatch.hashCode() : 0);
+        result = 31 * result + (mDarkVibrantSwatch != null ? mDarkVibrantSwatch.hashCode() : 0);
+        result = 31 * result + (mDarkMutedSwatch != null ? mDarkMutedSwatch.hashCode() : 0);
+        result = 31 * result + (mLightVibrantSwatch != null ? mLightVibrantSwatch.hashCode() : 0);
+        result = 31 * result + (mLightMutedColor != null ? mLightMutedColor.hashCode() : 0);
+        return result;
+    }
+
+    /**
+     * Scale the bitmap down so that it's smallest dimension is
+     * {@value #CALCULATE_BITMAP_MIN_DIMENSION}px. If {@code bitmap} is smaller than this, than it
+     * is returned.
+     */
+    private static Bitmap scaleBitmapDown(Bitmap bitmap) {
+        final int minDimension = Math.min(bitmap.getWidth(), bitmap.getHeight());
+
+        if (minDimension <= CALCULATE_BITMAP_MIN_DIMENSION) {
+            // If the bitmap is small enough already, just return it
+            return bitmap;
+        }
+
+        final float scaleRatio = CALCULATE_BITMAP_MIN_DIMENSION / (float) minDimension;
+        return Bitmap.createScaledBitmap(bitmap,
+                Math.round(bitmap.getWidth() * scaleRatio),
+                Math.round(bitmap.getHeight() * scaleRatio),
+                false);
+    }
+
+    private static float createComparisonValue(float saturation, float targetSaturation,
+            float luma, float targetLuma,
+            int population, int highestPopulation) {
+        return weightedMean(
+                invertDiff(saturation, targetSaturation), WEIGHT_SATURATION,
+                invertDiff(luma, targetLuma), WEIGHT_LUMA,
+                population / (float) highestPopulation, WEIGHT_POPULATION
+        );
+    }
+
+    /**
+     * Copy a {@link Swatch}'s HSL values into a new float[].
+     */
+    private static float[] copyHslValues(Swatch color) {
+        final float[] newHsl = new float[3];
+        System.arraycopy(color.getHsl(), 0, newHsl, 0, 3);
+        return newHsl;
+    }
+
+    /**
+     * Returns a value in the range 0-1. 1 is returned when {@code value} equals the
+     * {@code targetValue} and then decreases as the absolute difference between {@code value} and
+     * {@code targetValue} increases.
+     *
+     * @param value the item's value
+     * @param targetValue the value which we desire
+     */
+    private static float invertDiff(float value, float targetValue) {
+        return 1f - Math.abs(value - targetValue);
+    }
+
+    private static float weightedMean(float... values) {
+        float sum = 0f;
+        float sumWeight = 0f;
+
+        for (int i = 0; i < values.length; i += 2) {
+            float value = values[i];
+            float weight = values[i + 1];
+
+            sum += (value * weight);
+            sumWeight += weight;
+        }
+
+        return sum / sumWeight;
+    }
+
+    private static void checkBitmapParam(Bitmap bitmap) {
+        if (bitmap == null) {
+            throw new IllegalArgumentException("bitmap can not be null");
+        }
+        if (bitmap.isRecycled()) {
+            throw new IllegalArgumentException("bitmap can not be recycled");
+        }
+    }
+
+    private static void checkNumberColorsParam(int numColors) {
+        if (numColors < 1) {
+            throw new IllegalArgumentException("numColors must be 1 of greater");
+        }
+    }
+
+    private static void checkAsyncListenerParam(PaletteAsyncListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener can not be null");
+        }
+    }
+
+    /**
+     * Represents a color swatch generated from an image's palette. The RGB color can be retrieved
+     * by calling {@link #getRgb()}.
+     */
+    public static final class Swatch {
+        private final int mRed, mGreen, mBlue;
+        private final int mRgb;
+        private final int mPopulation;
+
+        private boolean mGeneratedTextColors;
+        private int mTitleTextColor;
+        private int mBodyTextColor;
+
+        private float[] mHsl;
+
+        Swatch(int rgbColor, int population) {
+            mRed = Color.red(rgbColor);
+            mGreen = Color.green(rgbColor);
+            mBlue = Color.blue(rgbColor);
+            mRgb = rgbColor;
+            mPopulation = population;
+        }
+
+        Swatch(int red, int green, int blue, int population) {
+            mRed = red;
+            mGreen = green;
+            mBlue = blue;
+            mRgb = Color.rgb(red, green, blue);
+            mPopulation = population;
+        }
+
+        /**
+         * @return this swatch's RGB color value
+         */
+        public int getRgb() {
+            return mRgb;
+        }
+
+        /**
+         * Return this swatch's HSL values.
+         *     hsv[0] is Hue [0 .. 360)
+         *     hsv[1] is Saturation [0...1]
+         *     hsv[2] is Lightness [0...1]
+         */
+        public float[] getHsl() {
+            if (mHsl == null) {
+                // Lazily generate HSL values from RGB
+                mHsl = new float[3];
+                ColorUtils.RGBtoHSL(mRed, mGreen, mBlue, mHsl);
+            }
+            return mHsl;
+        }
+
+        /**
+         * @return the number of pixels represented by this swatch
+         */
+        public int getPopulation() {
+            return mPopulation;
+        }
+
+        /**
+         * Returns an appropriate color to use for any 'title' text which is displayed over this
+         * {@link Swatch}'s color. This color is guaranteed to have sufficient contrast.
+         */
+        public int getTitleTextColor() {
+            ensureTextColorsGenerated();
+            return mTitleTextColor;
+        }
+
+        /**
+         * Returns an appropriate color to use for any 'body' text which is displayed over this
+         * {@link Swatch}'s color. This color is guaranteed to have sufficient contrast.
+         */
+        public int getBodyTextColor() {
+            ensureTextColorsGenerated();
+            return mBodyTextColor;
+        }
+
+        private void ensureTextColorsGenerated() {
+            if (!mGeneratedTextColors) {
+                mTitleTextColor = ColorUtils.getTextColorForBackground(mRgb,
+                        MIN_CONTRAST_TITLE_TEXT);
+                mBodyTextColor = ColorUtils.getTextColorForBackground(mRgb,
+                        MIN_CONTRAST_BODY_TEXT);
+                mGeneratedTextColors = true;
+            }
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder(getClass().getSimpleName())
+                    .append(" [RGB: #").append(Integer.toHexString(getRgb())).append(']')
+                    .append(" [HSL: ").append(Arrays.toString(getHsl())).append(']')
+                    .append(" [Population: ").append(mPopulation).append(']')
+                    .append(" [Title Text: #").append(Integer.toHexString(mTitleTextColor)).append(']')
+                    .append(" [Body Text: #").append(Integer.toHexString(mBodyTextColor)).append(']')
+                    .toString();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            Swatch swatch = (Swatch) o;
+            return mPopulation == swatch.mPopulation && mRgb == swatch.mRgb;
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * mRgb + mPopulation;
+        }
+    }
+
+}
diff --git a/v7/recyclerview/Android.mk b/v7/recyclerview/Android.mk
new file mode 100644
index 0000000..b3da0bd
--- /dev/null
+++ b/v7/recyclerview/Android.mk
@@ -0,0 +1,67 @@
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+# # Build the resources using the current SDK version.
+# # We do this here because the final static library must be compiled with an older
+# # SDK version than the resources.  The resources library and the R class that it
+# # contains will not be linked into the final static library.
+# include $(CLEAR_VARS)
+# LOCAL_MODULE := android-support-v7-recyclerview-res
+# LOCAL_SDK_VERSION := current
+# LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
+# LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+# LOCAL_AAPT_FLAGS := \
+# 	--auto-add-overlay
+# LOCAL_JAR_EXCLUDE_FILES := none
+# include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# # A helper sub-library that makes direct use of JellyBean APIs.
+# include $(CLEAR_VARS)
+# LOCAL_MODULE := android-support-v7-recyclerview-jellybean
+# LOCAL_SDK_VERSION := 16
+# LOCAL_SRC_FILES := $(call all-java-files-under, jellybean)
+# include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# # A helper sub-library that makes direct use of JellyBean MR1 APIs.
+# include $(CLEAR_VARS)
+# LOCAL_MODULE := android-support-v7-recyclerview-jellybean-mr1
+# LOCAL_SDK_VERSION := 17
+# LOCAL_SRC_FILES := $(call all-java-files-under, jellybean-mr1)
+# LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-recyclerview-jellybean
+# include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# # A helper sub-library that makes direct use of JellyBean MR2 APIs.
+# include $(CLEAR_VARS)
+# LOCAL_MODULE := android-support-v7-recyclerview-jellybean-mr2
+# LOCAL_SDK_VERSION := current
+# LOCAL_SRC_FILES := $(call all-java-files-under, jellybean-mr2)
+# LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-recyclerview-jellybean-mr1
+# include $(BUILD_STATIC_JAVA_LIBRARY)
+
+
+# Here is the final static library that apps can link against.
+# The R class is automatically excluded from the generated library.
+# Applications that use this library must specify LOCAL_RESOURCE_DIR
+# in their makefiles to include the resources in their package.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v7-recyclerview
+LOCAL_SDK_VERSION := 7
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+# LOCAL_JAVA_LIBRARIES := android-support-v4 android-support-v7-recyclerview-res
+LOCAL_JAVA_LIBRARIES := \
+        android-support-v4 \
+        android-support-annotations
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v7/recyclerview/AndroidManifest.xml b/v7/recyclerview/AndroidManifest.xml
new file mode 100644
index 0000000..4258b9b
--- /dev/null
+++ b/v7/recyclerview/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.support.v7.recyclerview">
+    <uses-sdk android:minSdkVersion="7"/>
+    <!-- Until test manifest can be customized, we need this definition here. issue id: 57819 -->
+    <application>
+        <activity android:name="android.support.v7.widget.TestActivity"
+                  android:label="RecyclerViewTestActivity"/>
+    </application>
+</manifest>
diff --git a/v7/recyclerview/README.txt b/v7/recyclerview/README.txt
new file mode 100644
index 0000000..3c9de34
--- /dev/null
+++ b/v7/recyclerview/README.txt
@@ -0,0 +1 @@
+Library Project including RecyclerView and associated utilities.
diff --git a/v7/recyclerview/build.gradle b/v7/recyclerview/build.gradle
new file mode 100644
index 0000000..40a58ad
--- /dev/null
+++ b/v7/recyclerview/build.gradle
@@ -0,0 +1,95 @@
+apply plugin: 'android-library'
+
+archivesBaseName = 'recyclerview-v7'
+
+dependencies {
+    compile project(':support-v4')
+}
+
+android {
+    compileSdkVersion 7
+    buildToolsVersion "19.0.1"
+
+    defaultConfig {
+        minSdkVersion 7
+    }
+
+    sourceSets {
+        main.manifest.srcFile 'AndroidManifest.xml'
+        main.java.srcDir 'src'
+        androidTest.setRoot('tests')
+        androidTest.java.srcDir 'tests/src'
+    }
+
+    lintOptions {
+        // TODO: fix errors and reenable.
+        abortOnError false
+    }
+}
+
+android.libraryVariants.all { variant ->
+    def name = variant.buildType.name
+
+    if (name.equals(com.android.builder.BuilderConstants.DEBUG)) {
+        return; // Skip debug builds.
+    }
+    def suffix = name.capitalize()
+
+    def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
+        dependsOn variant.javaCompile
+        from variant.javaCompile.destinationDir
+        from 'LICENSE.txt'
+    }
+    def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
+        source android.sourceSets.main.allJava
+        classpath = files(variant.javaCompile.classpath.files) + files(
+                "${android.plugin.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
+    }
+
+    def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
+        classifier = 'javadoc'
+        from 'build/docs/javadoc'
+    }
+
+    def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
+        classifier = 'sources'
+        from android.sourceSets.main.allSource
+    }
+
+    artifacts.add('archives', javadocJarTask);
+    artifacts.add('archives', sourcesJarTask);
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri(rootProject.ext.supportRepoOut)) {
+            }
+
+            pom.project {
+                name 'Android Support RecyclerView v7'
+                description "Android Support RecyclerView v7"
+                url 'http://developer.android.com/tools/extras/support-library.html'
+                inceptionYear '2011'
+
+                licenses {
+                    license {
+                        name 'The Apache Software License, Version 2.0'
+                        url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+                        distribution 'repo'
+                    }
+                }
+
+                scm {
+                    url "http://source.android.com"
+                    connection "scm:git:https://android.googlesource.com/platform/frameworks/support"
+                }
+                developers {
+                    developer {
+                        name 'The Android Open Source Project'
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java b/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
new file mode 100644
index 0000000..51d9164
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
@@ -0,0 +1,735 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.support.v4.util.Pools;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static android.support.v7.widget.RecyclerView.*;
+
+/**
+ * Helper class that can enqueue and process adapter update operations.
+ * <p>
+ * To support animations, RecyclerView presents an older version the Adapter to best represent
+ * previous state of the layout. Sometimes, this is not trivial when items are removed that were
+ * not laid out, in which case, RecyclerView has no way of providing that item's view for
+ * animations.
+ * <p>
+ * AdapterHelper creates an UpdateOp for each adapter data change then pre-processes them. During
+ * pre processing, AdapterHelper finds out which UpdateOps can be deferred to second layout pass
+ * and which cannot. For the UpdateOps that cannot be deferred, AdapterHelper will change them
+ * according to previously deferred operation and dispatch them before the first layout pass. It
+ * also takes care of updating deferred UpdateOps since order of operations is changed by this
+ * process.
+ * <p>
+ * Although operations may be forwarded to LayoutManager in different orders, resulting data set
+ * is guaranteed to be the consistent.
+ */
+class AdapterHelper {
+
+    final static int POSITION_TYPE_INVISIBLE = 0;
+
+    final static int POSITION_TYPE_NEW_OR_LAID_OUT = 1;
+
+    private static final boolean DEBUG = false;
+
+    private static final String TAG = "AHT";
+
+    private Pools.Pool<UpdateOp> mUpdateOpPool = new Pools.SimplePool<UpdateOp>(UpdateOp.POOL_SIZE);
+
+    final ArrayList<UpdateOp> mPendingUpdates = new ArrayList<UpdateOp>();
+
+    final ArrayList<UpdateOp> mPostponedList = new ArrayList<UpdateOp>();
+
+    final Callback mCallback;
+
+    Runnable mOnItemProcessedCallback;
+
+    final boolean mDisableRecycler;
+
+    AdapterHelper(Callback callback) {
+        this(callback, false);
+    }
+
+    AdapterHelper(Callback callback, boolean disableRecycler) {
+        mCallback = callback;
+        mDisableRecycler = disableRecycler;
+    }
+
+    AdapterHelper addUpdateOp(UpdateOp... ops) {
+        Collections.addAll(mPendingUpdates, ops);
+        return this;
+    }
+
+    void reset() {
+        recycleUpdateOpsAndClearList(mPendingUpdates);
+        recycleUpdateOpsAndClearList(mPostponedList);
+    }
+
+    void preProcess() {
+        pruneInvalidMoveOps();
+        final int count = mPendingUpdates.size();
+        for (int i = 0; i < count; i++) {
+            UpdateOp op = mPendingUpdates.get(i);
+            switch (op.cmd) {
+                case UpdateOp.ADD:
+                    applyAdd(op);
+                    break;
+                case UpdateOp.REMOVE:
+                    applyRemove(op);
+                    break;
+                case UpdateOp.UPDATE:
+                    applyUpdate(op);
+                    break;
+                case UpdateOp.MOVE:
+                    applyMove(op);
+                    break;
+            }
+            if (mOnItemProcessedCallback != null) {
+                mOnItemProcessedCallback.run();
+            }
+        }
+        mPendingUpdates.clear();
+    }
+
+    /**
+     * When an item is moved, we still represent it in pre-layout even if we don't have a
+     * ViewHolder for it. This may be a problem if item is removed in the same layout pass.
+     * This is why we make sure item is not removed and if it is removed, we replace MOVE op
+     * with a REMOVE op and update the remaining UpdateOps accordingly.
+     */
+    private void pruneInvalidMoveOps() {
+        for (int i = mPendingUpdates.size() - 1; i >= 0; i--) {
+            UpdateOp op = mPendingUpdates.get(i);
+            if (op.cmd != UpdateOp.MOVE) {
+                continue;
+            }
+            // IF MOVE(from) is a newly added item, we defer it gracefully.
+            // IF MOVE(to) is removed, we have to invalidate MOVE
+            final int count = mPendingUpdates.size();
+            int to = op.itemCount;
+            int removedIndex = -1;
+            for (int j = i + 1; j < count; j++) {
+                UpdateOp other = mPendingUpdates.get(j);
+                if (other.cmd == UpdateOp.ADD && other.positionStart <= to) {
+                    to += other.itemCount;
+                } else if (other.cmd == UpdateOp.REMOVE && other.positionStart <= to) {
+                    if (other.positionStart + other.itemCount > to) {
+                        removedIndex = j;
+                        break;
+                    } else {
+                        to -= other.itemCount;
+                    }
+                } else if (other.cmd == UpdateOp.MOVE) {
+                    if (to == other.positionStart) {
+                        to = other.itemCount;
+                    } else {
+                        if (to > other.positionStart) {
+                            to--;
+                        }
+                        if (to >= other.itemCount) {
+                            to++;
+                        }
+                    }
+                }
+            }
+            if (removedIndex != -1) {
+                if (DEBUG) {
+                    Log.d(TAG,
+                            "detected a move that has been removed at" + removedIndex + ":" + op);
+                }
+                to = op.itemCount;
+                op.itemCount = 1;
+                op.cmd = UpdateOp.REMOVE;
+                createFakeAddForRemovedMove(to, i + 1);
+            }
+        }
+        if (DEBUG) {
+            Log.d(TAG, "after pruning is complete.");
+            for (UpdateOp op : mPendingUpdates) {
+                Log.d(TAG, op.toString());
+            }
+            Log.d(TAG, "------");
+        }
+    }
+
+    /**
+     * when a Move op is removed in the same layout pass, we convert it into a delete op and create
+     * another add op. This way, we can keep using the same code path w/o the complexity of move.
+     *
+     * @param adapterIndex Target position of the move operation
+     * @param pendingUpdateIndex The UpdateOp index of the move operation
+     */
+    void createFakeAddForRemovedMove(int adapterIndex, int pendingUpdateIndex) {
+        UpdateOp fakeAdd = obtainUpdateOp(UpdateOp.ADD, adapterIndex, 1);
+        mPendingUpdates.add(pendingUpdateIndex, fakeAdd);
+    }
+
+    void consumePostponedUpdates() {
+        final int count = mPostponedList.size();
+        for (int i = 0; i < count; i++) {
+            mCallback.onDispatchSecondPass(mPostponedList.get(i));
+        }
+        recycleUpdateOpsAndClearList(mPostponedList);
+    }
+
+    private void applyMove(UpdateOp op) {
+        // MOVE ops are pre-processed so at this point, we know that item is still in the adapter.
+        // otherwise, it would be converted into a REMOVE operation
+        mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
+        postpone(op);
+    }
+
+    private void applyRemove(UpdateOp op) {
+        int tmpStart = op.positionStart;
+        int tmpCount = 0;
+        int tmpEnd = op.positionStart + op.itemCount;
+        int type = -1;
+        for (int position = op.positionStart; position < tmpEnd; position++) {
+            boolean typeChanged = false;
+            ViewHolder vh = mCallback.findViewHolder(position);
+            if (vh != null || canFindInPreLayout(position)) {
+                // If a ViewHolder exists or this is a newly added item, we can defer this update
+                // to post layout stage.
+                // * For existing ViewHolders, we'll fake its existence in the pre-layout phase.
+                // * For items that are added and removed in the same process cycle, they won't
+                // have any effect in pre-layout since their add ops are already deferred to
+                // post-layout pass.
+                if (type == POSITION_TYPE_INVISIBLE) {
+                    // Looks like we have other updates that we cannot merge with this one.
+                    // Create an UpdateOp and dispatch it to LayoutManager.
+                    UpdateOp newOp = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount);
+                    dispatch(newOp);
+                    mCallback.offsetPositionsForRemovingInvisible(newOp.positionStart,
+                            newOp.itemCount);
+                    typeChanged = true;
+                }
+                type = POSITION_TYPE_NEW_OR_LAID_OUT;
+            } else {
+                // This update cannot be recovered because we don't have a ViewHolder representing
+                // this position. Instead, post it to LayoutManager immediately
+                if (type == POSITION_TYPE_NEW_OR_LAID_OUT) {
+                    // Looks like we have other updates that we cannot merge with this one.
+                    // Create UpdateOp op and dispatch it to LayoutManager.
+                    UpdateOp newOp = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount);
+                    mCallback.offsetPositionsForRemovingLaidOutOrNewView(newOp.positionStart,
+                            newOp.itemCount);
+                    postpone(newOp);
+                    typeChanged = true;
+                }
+                type = POSITION_TYPE_INVISIBLE;
+            }
+            if (typeChanged) {
+                position -= tmpCount; // also equal to tmpStart
+                tmpEnd -= tmpCount;
+                tmpCount = 1;
+            } else {
+                tmpCount++;
+            }
+        }
+        if (tmpCount != op.itemCount) { // all 1 effect
+            recycleUpdateOp(op);
+            op = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount);
+        }
+        if (type == POSITION_TYPE_INVISIBLE) {
+            dispatch(op);
+            mCallback.offsetPositionsForRemovingInvisible(op.positionStart, op.itemCount);
+        } else {
+            mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart, op.itemCount);
+            postpone(op);
+        }
+    }
+
+    private void applyUpdate(UpdateOp op) {
+        int tmpStart = op.positionStart;
+        int tmpCount = 0;
+        int tmpEnd = op.positionStart + op.itemCount;
+        int type = -1;
+        for (int position = op.positionStart; position < tmpEnd; position++) {
+            ViewHolder vh = mCallback.findViewHolder(position);
+            if (vh != null || canFindInPreLayout(position)) { // deferred
+                if (type == POSITION_TYPE_INVISIBLE) {
+                    UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount);
+                    dispatch(newOp);
+                    mCallback.markViewHoldersUpdated(newOp.positionStart, newOp.itemCount);
+
+                    // tmpStart is still same since dispatch already shifts elements
+                    position -= newOp.itemCount; // also equal to tmpStart
+                    tmpEnd -= newOp.itemCount;
+                    tmpCount = 0;
+                }
+                type = POSITION_TYPE_NEW_OR_LAID_OUT;
+            } else { // applied
+                if (type == POSITION_TYPE_NEW_OR_LAID_OUT) {
+                    UpdateOp newOp = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount);
+                    mCallback.markViewHoldersUpdated(newOp.positionStart, newOp.itemCount);
+                    postpone(newOp);
+                    // both type-new and type-laid-out are deferred. This is why we are
+                    // resetting out position to here.
+                    position -= newOp.itemCount; // also equal to tmpStart
+                    tmpEnd -= newOp.itemCount;
+                    tmpCount = 0;
+                }
+                type = POSITION_TYPE_INVISIBLE;
+            }
+            tmpCount++;
+        }
+        if (tmpCount != op.itemCount) { // all 1 effect
+            recycleUpdateOp(op);
+            op = obtainUpdateOp(UpdateOp.REMOVE, tmpStart, tmpCount);
+        }
+        if (type == POSITION_TYPE_INVISIBLE) {
+            dispatch(op);
+        } else {
+            postpone(op);
+        }
+        mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount);
+    }
+
+    private void dispatch(UpdateOp op) {
+        // tricky part.
+        // traverse all postpones and revert their changes on this op if necessary, apply updated
+        // dispatch to them since now they are after this op.
+        if (op.cmd == UpdateOp.ADD || op.cmd == UpdateOp.MOVE) {
+            throw new IllegalArgumentException("should not dispatch add or move for pre layout");
+        }
+        if (DEBUG) {
+            Log.d(TAG, "dispat (pre)" + op);
+            Log.d(TAG, "postponed state before:");
+            for (UpdateOp updateOp : mPostponedList) {
+                Log.d(TAG, updateOp.toString());
+            }
+            Log.d(TAG, "----");
+        }
+
+        // handle each pos 1 by 1 to ensure continuity. If it breaks, dispatch partial
+        int tmpStart = updatePositionWithPostponed(op.positionStart, op.cmd);
+        if (DEBUG) {
+            Log.d(TAG, "pos:" + op.positionStart + ",updatedPos:" + tmpStart);
+        }
+        int tmpCnt = 1;
+        for (int p = 1; p < op.itemCount; p++) {
+            int pos = -1;
+            switch (op.cmd) {
+                case UpdateOp.UPDATE:
+                    pos = op.positionStart + p;
+                    break;
+                case UpdateOp.REMOVE:
+                    pos = op.positionStart;
+            }
+            int updatedPos = updatePositionWithPostponed(pos, op.cmd);
+            if (DEBUG) {
+                Log.d(TAG, "pos:" + pos + ",updatedPos:" + updatedPos);
+            }
+            boolean continuous = false;
+            switch (op.cmd) {
+                case UpdateOp.UPDATE:
+                    continuous = updatedPos == tmpStart + 1;
+                    break;
+                case UpdateOp.REMOVE:
+                    continuous = updatedPos == tmpStart;
+                    break;
+            }
+            if (continuous) {
+                tmpCnt++;
+            } else {
+                // need to dispatch this separately
+                UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt);
+                if (DEBUG) {
+                    Log.d(TAG, "need to dispatch separately " + tmp);
+                }
+                mCallback.onDispatchFirstPass(tmp);
+                recycleUpdateOp(tmp);
+                tmpStart = updatedPos;// need to remove previously dispatched
+                tmpCnt = 1;
+            }
+        }
+        recycleUpdateOp(op);
+        if (tmpCnt > 0) {
+            UpdateOp tmp = obtainUpdateOp(op.cmd, tmpStart, tmpCnt);
+            if (DEBUG) {
+                Log.d(TAG, "dispatching:" + tmp);
+            }
+            mCallback.onDispatchFirstPass(tmp);
+            recycleUpdateOp(tmp);
+        }
+        if (DEBUG) {
+            Log.d(TAG, "post dispatch");
+            Log.d(TAG, "postponed state after:");
+            for (UpdateOp updateOp : mPostponedList) {
+                Log.d(TAG, updateOp.toString());
+            }
+            Log.d(TAG, "----");
+        }
+    }
+
+    private int updatePositionWithPostponed(int pos, int cmd) {
+        final int count = mPostponedList.size();
+        for (int i = count - 1; i >= 0; i--) {
+            UpdateOp postponed = mPostponedList.get(i);
+            if (postponed.cmd == UpdateOp.MOVE) {
+                int start, end;
+                if (postponed.positionStart < postponed.itemCount) {
+                    start = postponed.positionStart;
+                    end = postponed.itemCount;
+                } else {
+                    start = postponed.itemCount;
+                    end = postponed.positionStart;
+                }
+                if (pos >= start && pos <= end) {
+                    //i'm affected
+                    if (start == postponed.positionStart) {
+                        if (cmd == UpdateOp.ADD) {
+                            postponed.itemCount++;
+                        } else if (cmd == UpdateOp.REMOVE) {
+                            postponed.itemCount--;
+                        }
+                        // op moved to left, move it right to revert
+                        pos++;
+                    } else {
+                        if (cmd == UpdateOp.ADD) {
+                            postponed.positionStart++;
+                        } else if (cmd == UpdateOp.REMOVE) {
+                            postponed.positionStart--;
+                        }
+                        // op was moved right, move left to revert
+                        pos--;
+                    }
+                } else if (pos < postponed.positionStart) {
+                    // postponed MV is outside the dispatched OP. if it is before, offset
+                    if (cmd == UpdateOp.ADD) {
+                        postponed.positionStart++;
+                        postponed.itemCount++;
+                    } else if (cmd == UpdateOp.REMOVE) {
+                        postponed.positionStart--;
+                        postponed.itemCount--;
+                    }
+                }
+            } else {
+                if (postponed.positionStart <= pos) {
+                    if (postponed.cmd == UpdateOp.ADD) {
+                        pos -= postponed.itemCount;
+                    } else if (postponed.cmd == UpdateOp.REMOVE) {
+                        pos += postponed.itemCount;
+                    }
+                } else {
+                    if (cmd == UpdateOp.ADD) {
+                        postponed.positionStart++;
+                    } else if (cmd == UpdateOp.REMOVE) {
+                        postponed.positionStart--;
+                    }
+                }
+            }
+            if (DEBUG) {
+                Log.d(TAG, "dispath (step" + i + ")");
+                Log.d(TAG, "postponed state:" + i + ", pos:" + pos);
+                for (UpdateOp updateOp : mPostponedList) {
+                    Log.d(TAG, updateOp.toString());
+                }
+                Log.d(TAG, "----");
+            }
+        }
+        for (int i = mPostponedList.size() - 1; i >= 0; i--) {
+            UpdateOp op = mPostponedList.get(i);
+            if (op.cmd == UpdateOp.MOVE) {
+                if (op.itemCount == op.positionStart || op.itemCount < 0) {
+                    mPostponedList.remove(i);
+                    recycleUpdateOp(op);
+                }
+            } else if (op.itemCount <= 0) {
+                mPostponedList.remove(i);
+                recycleUpdateOp(op);
+            }
+        }
+        return pos;
+    }
+
+    private boolean canFindInPreLayout(int position) {
+        final int count = mPostponedList.size();
+        for (int i = 0; i < count; i++) {
+            UpdateOp op = mPostponedList.get(i);
+            if (op.cmd == UpdateOp.MOVE) {
+                if (op.positionStart == position) {
+                    return true;
+                }
+            } else if (op.cmd == UpdateOp.ADD) {
+                // TODO optimize.
+                final int end = op.positionStart + op.itemCount;
+                for (int pos = op.positionStart; pos < end; pos++) {
+                    if (findPositionOffset(pos, i + 1) == position) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    private void applyAdd(UpdateOp op) {
+        mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
+        postpone(op);
+    }
+
+    private void postpone(UpdateOp op) {
+        if (DEBUG) {
+            Log.d(TAG, "postponing " + op);
+        }
+        mPostponedList.add(op);
+    }
+
+    boolean hasPendingUpdates() {
+        return mPendingUpdates.size() > 0;
+    }
+
+    int findPositionOffset(int position) {
+        return findPositionOffset(position, 0);
+    }
+
+    int findPositionOffset(int position, int firstPostponedItem) {
+        int count = mPostponedList.size();
+        for (int i = firstPostponedItem; i < count; ++i) {
+            UpdateOp op = mPostponedList.get(i);
+            if (op.cmd == UpdateOp.MOVE) {
+                if (op.positionStart == position) {
+                    position = op.itemCount;
+                } else {
+                    if (op.positionStart < position) {
+                        position--; // like a remove
+                    }
+                    if (op.itemCount <= position) {
+                        position++; // like an add
+                    }
+                }
+            } else if (op.positionStart <= position) {
+                if (op.cmd == UpdateOp.REMOVE) {
+                    if (position < op.positionStart + op.itemCount) {
+                        return -1;
+                    }
+                    position -= op.itemCount;
+                } else if (op.cmd == UpdateOp.ADD) {
+                    position += op.itemCount;
+                }
+            }
+        }
+        return position;
+    }
+
+    /**
+     * @return True if updates should be processed.
+     */
+    boolean onItemRangeChanged(int positionStart, int itemCount) {
+        mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount));
+        return mPendingUpdates.size() == 1;
+    }
+
+    /**
+     * @return True if updates should be processed.
+     */
+    boolean onItemRangeInserted(int positionStart, int itemCount) {
+        mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount));
+        return mPendingUpdates.size() == 1;
+    }
+
+    /**
+     * @return True if updates should be processed.
+     */
+    boolean onItemRangeRemoved(int positionStart, int itemCount) {
+        mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount));
+        return mPendingUpdates.size() == 1;
+    }
+
+    /**
+     * @return True if updates should be processed.
+     */
+    boolean onItemRangeMoved(int from, int to, int itemCount) {
+        if (from == to) {
+            return false;//no-op
+        }
+        if (itemCount != 1) {
+            throw new IllegalArgumentException("Moving more than 1 item is not supported yet");
+        }
+        mPendingUpdates.add(obtainUpdateOp(UpdateOp.MOVE, from, to));
+        return mPendingUpdates.size() == 1;
+    }
+
+    /**
+     * Skips pre-processing and applies all updates in one pass.
+     */
+    void consumeUpdatesInOnePass() {
+        // we still consume postponed updates (if there is) in case there was a pre-process call
+        // w/o a matching consumePostponedUpdates.
+        consumePostponedUpdates();
+        final int count = mPendingUpdates.size();
+        for (int i = 0; i < count; i++) {
+            UpdateOp op = mPendingUpdates.get(i);
+            switch (op.cmd) {
+                case UpdateOp.ADD:
+                    mCallback.onDispatchSecondPass(op);
+                    mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
+                    break;
+                case UpdateOp.REMOVE:
+                    mCallback.onDispatchSecondPass(op);
+                    mCallback.offsetPositionsForRemovingInvisible(op.positionStart, op.itemCount);
+                    break;
+                case UpdateOp.UPDATE:
+                    mCallback.onDispatchSecondPass(op);
+                    mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount);
+                    break;
+                case UpdateOp.MOVE:
+                    mCallback.onDispatchSecondPass(op);
+                    mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
+                    break;
+            }
+            if (mOnItemProcessedCallback != null) {
+                mOnItemProcessedCallback.run();
+            }
+        }
+        recycleUpdateOpsAndClearList(mPendingUpdates);
+    }
+
+    /**
+     * Queued operation to happen when child views are updated.
+     */
+    static class UpdateOp {
+
+        static final int ADD = 0;
+
+        static final int REMOVE = 1;
+
+        static final int UPDATE = 2;
+
+        static final int MOVE = 3;
+
+        static final int POOL_SIZE = 30;
+
+        int cmd;
+
+        int positionStart;
+
+        // holds the target position if this is a MOVE
+        int itemCount;
+
+        UpdateOp(int cmd, int positionStart, int itemCount) {
+            this.cmd = cmd;
+            this.positionStart = positionStart;
+            this.itemCount = itemCount;
+        }
+
+        String cmdToString() {
+            switch (cmd) {
+                case ADD:
+                    return "add";
+                case REMOVE:
+                    return "rm";
+                case UPDATE:
+                    return "up";
+                case MOVE:
+                    return "mv";
+            }
+            return "??";
+        }
+
+        @Override
+        public String toString() {
+            return "[" + cmdToString() + ",s:" + positionStart + "c:" + itemCount + "]";
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            UpdateOp op = (UpdateOp) o;
+
+            if (cmd != op.cmd) {
+                return false;
+            }
+            if (itemCount != op.itemCount) {
+                return false;
+            }
+            if (positionStart != op.positionStart) {
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = cmd;
+            result = 31 * result + positionStart;
+            result = 31 * result + itemCount;
+            return result;
+        }
+    }
+
+    UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount) {
+        UpdateOp op = mUpdateOpPool.acquire();
+        if (op == null) {
+            op = new UpdateOp(cmd, positionStart, itemCount);
+        } else {
+            op.cmd = cmd;
+            op.positionStart = positionStart;
+            op.itemCount = itemCount;
+        }
+        return op;
+    }
+
+    void recycleUpdateOp(UpdateOp op) {
+        if (!mDisableRecycler) {
+            mUpdateOpPool.release(op);
+        }
+    }
+
+    void recycleUpdateOpsAndClearList(List<UpdateOp> ops) {
+        final int count = ops.size();
+        for (int i = 0; i < count; i++) {
+            recycleUpdateOp(ops.get(i));
+        }
+        ops.clear();
+    }
+
+    /**
+     * Contract between AdapterHelper and RecyclerView.
+     */
+    static interface Callback {
+
+        ViewHolder findViewHolder(int position);
+
+        void offsetPositionsForRemovingInvisible(int positionStart, int itemCount);
+
+        void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount);
+
+        void markViewHoldersUpdated(int positionStart, int itemCount);
+
+        void onDispatchFirstPass(UpdateOp updateOp);
+
+        void onDispatchSecondPass(UpdateOp updateOp);
+
+        void offsetPositionsForAdd(int positionStart, int itemCount);
+
+        void offsetPositionsForMove(int from, int to);
+    }
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/ChildHelper.java b/v7/recyclerview/src/android/support/v7/widget/ChildHelper.java
new file mode 100644
index 0000000..6fb964c
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/widget/ChildHelper.java
@@ -0,0 +1,474 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class to manage children.
+ * <p>
+ * It wraps a RecyclerView and adds ability to hide some children. There are two sets of methods
+ * provided by this class. <b>Regular</b> methods are the ones that replicate ViewGroup methods
+ * like getChildAt, getChildCount etc. These methods ignore hidden children.
+ * <p>
+ * When RecyclerView needs direct access to the view group children, it can call unfiltered
+ * methods like get getUnfilteredChildCount or getUnfilteredChildAt.
+ */
+class ChildHelper {
+
+    private static final boolean DEBUG = false;
+
+    private static final String TAG = "ChildrenHelper";
+
+    final Callback mCallback;
+
+    final Bucket mBucket;
+
+    final List<View> mHiddenViews;
+
+    ChildHelper(Callback callback) {
+        mCallback = callback;
+        mBucket = new Bucket();
+        mHiddenViews = new ArrayList<View>();
+    }
+
+    /**
+     * Adds a view to the ViewGroup
+     *
+     * @param child  View to add.
+     * @param hidden If set to true, this item will be invisible from regular methods.
+     */
+    void addView(View child, boolean hidden) {
+        addView(child, -1, hidden);
+    }
+
+    /**
+     * Add a view to the ViewGroup at an index
+     *
+     * @param child  View to add.
+     * @param index  Index of the child from the regular perspective (excluding hidden views).
+     *               ChildHelper offsets this index to actual ViewGroup index.
+     * @param hidden If set to true, this item will be invisible from regular methods.
+     */
+    void addView(View child, int index, boolean hidden) {
+        final int offset;
+        if (index < 0) {
+            offset = mCallback.getChildCount();
+        } else {
+            offset = getOffset(index);
+        }
+        mCallback.addView(child, offset);
+        mBucket.insert(offset, hidden);
+        if (hidden) {
+            mHiddenViews.add(child);
+        }
+        if (DEBUG) {
+            Log.d(TAG, "addViewAt " + index + ",h:" + hidden + ", " + this);
+        }
+    }
+
+    private int getOffset(int index) {
+        if (index < 0) {
+            return -1; //anything below 0 won't work as diff will be undefined.
+        }
+        final int limit = mCallback.getChildCount();
+        int offset = index;
+        while (offset < limit) {
+            final int removedBefore = mBucket.countOnesBefore(offset);
+            final int diff = index - (offset - removedBefore);
+            if (diff == 0) {
+                while (mBucket.get(offset)) { // ensure this offset is not hidden
+                    offset ++;
+                }
+                return offset;
+            } else {
+                offset += diff;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Removes the provided View from underlying RecyclerView.
+     *
+     * @param view The view to remove.
+     */
+    void removeView(View view) {
+        int index = mCallback.indexOfChild(view);
+        if (index < 0) {
+            return;
+        }
+        mCallback.removeViewAt(index);
+        if (mBucket.remove(index)) {
+            mHiddenViews.remove(view);
+        }
+        if (DEBUG) {
+            Log.d(TAG, "remove View off:" + index + "," + this);
+        }
+    }
+
+    /**
+     * Removes the view at the provided index from RecyclerView.
+     *
+     * @param index Index of the child from the regular perspective (excluding hidden views).
+     *              ChildHelper offsets this index to actual ViewGroup index.
+     */
+    void removeViewAt(int index) {
+        final int offset = getOffset(index);
+        final View view = mCallback.getChildAt(offset);
+        if (view == null) {
+            return;
+        }
+        mCallback.removeViewAt(offset);
+        if (mBucket.remove(offset)) {
+            mHiddenViews.remove(view);
+        }
+        if (DEBUG) {
+            Log.d(TAG, "removeViewAt " + index + ", off:" + offset + ", " + this);
+        }
+    }
+
+    /**
+     * Returns the child at provided index.
+     *
+     * @param index Index of the child to return in regular perspective.
+     */
+    View getChildAt(int index) {
+        final int offset = getOffset(index);
+        return mCallback.getChildAt(offset);
+    }
+
+    /**
+     * Removes all views from the ViewGroup including the hidden ones.
+     */
+    void removeAllViewsUnfiltered() {
+        mCallback.removeAllViews();
+        mBucket.reset();
+        mHiddenViews.clear();
+        if (DEBUG) {
+            Log.d(TAG, "removeAllViewsUnfiltered");
+        }
+    }
+
+    /**
+     * This can be used to find a disappearing view by position.
+     *
+     * @param position The adapter position of the item.
+     * @param type     View type, can be {@link RecyclerView#INVALID_TYPE}.
+     * @return         A hidden view with a valid ViewHolder that matches the position and type.
+     */
+    View findHiddenNonRemovedView(int position, int type) {
+        final int count = mHiddenViews.size();
+        for (int i = 0; i < count; i++) {
+            final View view = mHiddenViews.get(i);
+            RecyclerView.ViewHolder holder = mCallback.getChildViewHolder(view);
+            if (holder.getPosition() == position && !holder.isInvalid() &&
+                    (type == RecyclerView.INVALID_TYPE || holder.getItemViewType() == type)) {
+                return view;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Attaches the provided view to the underlying ViewGroup.
+     *
+     * @param child        Child to attach.
+     * @param index        Index of the child to attach in regular perspective.
+     * @param layoutParams LayoutParams for the child.
+     * @param hidden       If set to true, this item will be invisible to the regular methods.
+     */
+    void attachViewToParent(View child, int index, ViewGroup.LayoutParams layoutParams,
+            boolean hidden) {
+        final int offset;
+        if (index < 0) {
+            offset = mCallback.getChildCount();
+        } else {
+            offset = getOffset(index);
+        }
+        mCallback.attachViewToParent(child, offset, layoutParams);
+        mBucket.insert(offset, hidden);
+        if (DEBUG) {
+            Log.d(TAG, "attach view to parent index:" + index + ",off:" + offset + "," +
+                    "h:" + hidden + ", " + this);
+        }
+    }
+
+    /**
+     * Returns the number of children that are not hidden.
+     *
+     * @return Number of children that are not hidden.
+     * @see #getChildAt(int)
+     */
+    int getChildCount() {
+        return mCallback.getChildCount() - mHiddenViews.size();
+    }
+
+    /**
+     * Returns the total number of children.
+     *
+     * @return The total number of children including the hidden views.
+     * @see #getUnfilteredChildAt(int)
+     */
+    int getUnfilteredChildCount() {
+        return mCallback.getChildCount();
+    }
+
+    /**
+     * Returns a child by ViewGroup offset. ChildHelper won't offset this index.
+     *
+     * @param index ViewGroup index of the child to return.
+     * @return The view in the provided index.
+     */
+    View getUnfilteredChildAt(int index) {
+        return mCallback.getChildAt(index);
+    }
+
+    /**
+     * Detaches the view at the provided index.
+     *
+     * @param index Index of the child to return in regular perspective.
+     */
+    void detachViewFromParent(int index) {
+        final int offset = getOffset(index);
+        mCallback.detachViewFromParent(offset);
+        mBucket.remove(offset);
+        if (DEBUG) {
+            Log.d(TAG, "detach view from parent " + index + ", off:" + offset);
+        }
+    }
+
+    /**
+     * Returns the index of the child in regular perspective.
+     *
+     * @param child The child whose index will be returned.
+     * @return The regular perspective index of the child or -1 if it does not exists.
+     */
+    int indexOfChild(View child) {
+        final int index = mCallback.indexOfChild(child);
+        if (index == -1) {
+            return -1;
+        }
+        if (mBucket.get(index)) {
+            if (DEBUG) {
+                throw new IllegalArgumentException("cannot get index of a hidden child");
+            } else {
+                return -1;
+            }
+        }
+        // reverse the index
+        return index - mBucket.countOnesBefore(index);
+    }
+
+    /**
+     * Marks a child view as hidden.
+     *
+     * @param view The view to hide.
+     */
+    void hide(View view) {
+        final int offset = mCallback.indexOfChild(view);
+        if (offset < 0) {
+            throw new IllegalArgumentException("view is not a child, cannot hide " + view);
+        }
+        if (DEBUG && mBucket.get(offset)) {
+            throw new RuntimeException("trying to hide same view twice, how come ? " + view);
+        }
+        mBucket.set(offset);
+        mHiddenViews.add(view);
+        if (DEBUG) {
+            Log.d(TAG, "hiding child " + view + " at offset " + offset+ ", " + this);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return mBucket.toString();
+    }
+
+    /**
+     * Removes a view from the ViewGroup if it is hidden.
+     *
+     * @param view The view to remove.
+     * @return True if the View is found and it is hidden. False otherwise.
+     */
+    boolean removeViewIfHidden(View view) {
+        final int index = mCallback.indexOfChild(view);
+        if (index == -1) {
+            if (mHiddenViews.remove(view) && DEBUG) {
+                throw new IllegalStateException("view is in hidden list but not in view group");
+            }
+            return true;
+        }
+        if (mBucket.get(index)) {
+            mBucket.remove(index);
+            mCallback.removeViewAt(index);
+            if (!mHiddenViews.remove(view) && DEBUG) {
+                throw new IllegalStateException(
+                        "removed a hidden view but it is not in hidden views list");
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Bitset implementation that provides methods to offset indices.
+     */
+    static class Bucket {
+
+        final static int BITS_PER_WORD = Long.SIZE;
+
+        final static long LAST_BIT = 1L << (Long.SIZE - 1);
+
+        long mData = 0;
+
+        Bucket next;
+
+        void set(int index) {
+            if (index >= BITS_PER_WORD) {
+                ensureNext();
+                next.set(index - BITS_PER_WORD);
+            } else {
+                mData |= 1L << index;
+            }
+        }
+
+        private void ensureNext() {
+            if (next == null) {
+                next = new Bucket();
+            }
+        }
+
+        void clear(int index) {
+            if (index >= BITS_PER_WORD) {
+                if (next != null) {
+                    next.clear(index - BITS_PER_WORD);
+                }
+            } else {
+                mData &= ~(1L << index);
+            }
+
+        }
+
+        boolean get(int index) {
+            if (index >= BITS_PER_WORD) {
+                ensureNext();
+                return next.get(index - BITS_PER_WORD);
+            } else {
+                return (mData & (1L << index)) != 0;
+            }
+        }
+
+        void reset() {
+            mData = 0;
+            if (next != null) {
+                next.reset();
+            }
+        }
+
+        void insert(int index, boolean value) {
+            if (index >= BITS_PER_WORD) {
+                ensureNext();
+                next.insert(index - BITS_PER_WORD, value);
+            } else {
+                final boolean lastBit = (mData & LAST_BIT) != 0;
+                long mask = (1L << index) - 1;
+                final long before = mData & mask;
+                final long after = ((mData & ~mask)) << 1;
+                mData = before | after;
+                if (value) {
+                    set(index);
+                } else {
+                    clear(index);
+                }
+                if (lastBit || next != null) {
+                    ensureNext();
+                    next.insert(0, lastBit);
+                }
+            }
+        }
+
+        boolean remove(int index) {
+            if (index >= BITS_PER_WORD) {
+                ensureNext();
+                return next.remove(index - BITS_PER_WORD);
+            } else {
+                long mask = (1L << index);
+                final boolean value = (mData & mask) != 0;
+                mData &= ~mask;
+                mask = mask - 1;
+                final long before = mData & mask;
+                // cannot use >> because it adds one.
+                final long after = Long.rotateRight(mData & ~mask, 1);
+                mData = before | after;
+                if (next != null) {
+                    if (next.get(0)) {
+                        set(BITS_PER_WORD - 1);
+                    }
+                    next.remove(0);
+                }
+                return value;
+            }
+        }
+
+        int countOnesBefore(int index) {
+            if (next == null) {
+                if (index >= BITS_PER_WORD) {
+                    return Long.bitCount(mData);
+                }
+                return Long.bitCount(mData & ((1L << index) - 1));
+            }
+            if (index < BITS_PER_WORD) {
+                return Long.bitCount(mData & ((1L << index) - 1));
+            } else {
+                return next.countOnesBefore(index - BITS_PER_WORD) + Long.bitCount(mData);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return next == null ? Long.toBinaryString(mData)
+                    : next.toString() + "xx" + Long.toBinaryString(mData);
+        }
+    }
+
+    static interface Callback {
+
+        int getChildCount();
+
+        void addView(View child, int index);
+
+        int indexOfChild(View view);
+
+        void removeViewAt(int index);
+
+        View getChildAt(int offset);
+
+        void removeAllViews();
+
+        RecyclerView.ViewHolder getChildViewHolder(View view);
+
+        void attachViewToParent(View child, int index, ViewGroup.LayoutParams layoutParams);
+
+        void detachViewFromParent(int offset);
+    }
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java b/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
new file mode 100644
index 0000000..7ae1bce
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
@@ -0,0 +1,620 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewPropertyAnimatorCompat;
+import android.support.v4.view.ViewPropertyAnimatorListener;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This implementation of {@link RecyclerView.ItemAnimator} provides basic
+ * animations on remove, add, and move events that happen to the items in
+ * a RecyclerView. RecyclerView uses a DefaultItemAnimator by default.
+ *
+ * @see RecyclerView#setItemAnimator(RecyclerView.ItemAnimator)
+ */
+public class DefaultItemAnimator extends RecyclerView.ItemAnimator {
+    private static final boolean DEBUG = false;
+
+    private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<ViewHolder>();
+    private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<ViewHolder>();
+    private ArrayList<MoveInfo> mPendingMoves = new ArrayList<MoveInfo>();
+    private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<ChangeInfo>();
+
+    private ArrayList<ArrayList<ViewHolder>> mAdditionsList =
+            new ArrayList<ArrayList<ViewHolder>>();
+    private ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<ArrayList<MoveInfo>>();
+    private ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<ArrayList<ChangeInfo>>();
+
+    private ArrayList<ViewHolder> mAddAnimations = new ArrayList<ViewHolder>();
+    private ArrayList<ViewHolder> mMoveAnimations = new ArrayList<ViewHolder>();
+    private ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<ViewHolder>();
+    private ArrayList<ViewHolder> mChangeAnimations = new ArrayList<ViewHolder>();
+
+    private static class MoveInfo {
+        public ViewHolder holder;
+        public int fromX, fromY, toX, toY;
+
+        private MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) {
+            this.holder = holder;
+            this.fromX = fromX;
+            this.fromY = fromY;
+            this.toX = toX;
+            this.toY = toY;
+        }
+    }
+
+    private static class ChangeInfo {
+        public ViewHolder oldHolder, newHolder;
+        public int fromX, fromY, toX, toY;
+        private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) {
+            this.oldHolder = oldHolder;
+            this.newHolder = newHolder;
+        }
+
+        private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder,
+                int fromX, int fromY, int toX, int toY) {
+            this(oldHolder, newHolder);
+            this.fromX = fromX;
+            this.fromY = fromY;
+            this.toX = toX;
+            this.toY = toY;
+        }
+
+        @Override
+        public String toString() {
+            return "ChangeInfo{" +
+                    "oldHolder=" + oldHolder +
+                    ", newHolder=" + newHolder +
+                    ", fromX=" + fromX +
+                    ", fromY=" + fromY +
+                    ", toX=" + toX +
+                    ", toY=" + toY +
+                    '}';
+        }
+    }
+
+    @Override
+    public void runPendingAnimations() {
+        boolean removalsPending = !mPendingRemovals.isEmpty();
+        boolean movesPending = !mPendingMoves.isEmpty();
+        boolean changesPending = !mPendingChanges.isEmpty();
+        boolean additionsPending = !mPendingAdditions.isEmpty();
+        if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
+            // nothing to animate
+            return;
+        }
+        // First, remove stuff
+        for (ViewHolder holder : mPendingRemovals) {
+            animateRemoveImpl(holder);
+        }
+        mPendingRemovals.clear();
+        // Next, move stuff
+        if (movesPending) {
+            final ArrayList<MoveInfo> moves = new ArrayList<MoveInfo>();
+            moves.addAll(mPendingMoves);
+            mMovesList.add(moves);
+            mPendingMoves.clear();
+            Runnable mover = new Runnable() {
+                @Override
+                public void run() {
+                    for (MoveInfo moveInfo : moves) {
+                        animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
+                                moveInfo.toX, moveInfo.toY);
+                    }
+                    moves.clear();
+                    mMovesList.remove(moves);
+                }
+            };
+            if (removalsPending) {
+                View view = moves.get(0).holder.itemView;
+                ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
+            } else {
+                mover.run();
+            }
+        }
+        // Next, change stuff, to run in parallel with move animations
+        if (changesPending) {
+            final ArrayList<ChangeInfo> changes = new ArrayList<ChangeInfo>();
+            changes.addAll(mPendingChanges);
+            mChangesList.add(changes);
+            mPendingChanges.clear();
+            Runnable changer = new Runnable() {
+                @Override
+                public void run() {
+                    for (ChangeInfo change : changes) {
+                        animateChangeImpl(change);
+                    }
+                    changes.clear();
+                    mChangesList.remove(changes);
+                }
+            };
+            if (removalsPending) {
+                ViewHolder holder = changes.get(0).oldHolder;
+                ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
+            } else {
+                changer.run();
+            }
+        }
+        // Next, add stuff
+        if (additionsPending) {
+            final ArrayList<ViewHolder> additions = new ArrayList<ViewHolder>();
+            additions.addAll(mPendingAdditions);
+            mAdditionsList.add(additions);
+            mPendingAdditions.clear();
+            Runnable adder = new Runnable() {
+                public void run() {
+                    for (ViewHolder holder : additions) {
+                        animateAddImpl(holder);
+                    }
+                    additions.clear();
+                    mAdditionsList.remove(additions);
+                }
+            };
+            if (removalsPending || movesPending) {
+                long removeDuration = removalsPending ? getRemoveDuration() : 0;
+                long moveDuration = movesPending ? getMoveDuration() : 0;
+                long changeDuration = changesPending ? getChangeDuration() : 0;
+                long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
+                View view = additions.get(0).itemView;
+                ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
+            } else {
+                adder.run();
+            }
+        }
+    }
+
+    @Override
+    public boolean animateRemove(final ViewHolder holder) {
+        endAnimation(holder);
+        mPendingRemovals.add(holder);
+        return true;
+    }
+
+    private void animateRemoveImpl(final ViewHolder holder) {
+        final View view = holder.itemView;
+        ViewCompat.animate(view).setDuration(getRemoveDuration()).
+                alpha(0).setListener(new VpaListenerAdapter() {
+            @Override
+            public void onAnimationStart(View view) {
+                dispatchRemoveStarting(holder);
+            }
+            @Override
+            public void onAnimationEnd(View view) {
+                ViewCompat.setAlpha(view, 1);
+                dispatchRemoveFinished(holder);
+                mRemoveAnimations.remove(holder);
+                dispatchFinishedWhenDone();
+            }
+        }).start();
+        mRemoveAnimations.add(holder);
+    }
+
+    @Override
+    public boolean animateAdd(final ViewHolder holder) {
+        endAnimation(holder);
+        ViewCompat.setAlpha(holder.itemView, 0);
+        mPendingAdditions.add(holder);
+        return true;
+    }
+
+    private void animateAddImpl(final ViewHolder holder) {
+        final View view = holder.itemView;
+        mAddAnimations.add(holder);
+        ViewCompat.animate(view).alpha(1).setDuration(getAddDuration()).
+                setListener(new VpaListenerAdapter() {
+                    @Override
+                    public void onAnimationStart(View view) {
+                        dispatchAddStarting(holder);
+                    }
+                    @Override
+                    public void onAnimationCancel(View view) {
+                        ViewCompat.setAlpha(view, 1);
+                    }
+
+                    @Override
+                    public void onAnimationEnd(View view) {
+                        dispatchAddFinished(holder);
+                        mAddAnimations.remove(holder);
+                        dispatchFinishedWhenDone();
+                    }
+                }).start();
+    }
+
+    @Override
+    public boolean animateMove(final ViewHolder holder, int fromX, int fromY,
+            int toX, int toY) {
+        final View view = holder.itemView;
+        fromX += ViewCompat.getTranslationX(holder.itemView);
+        fromY += ViewCompat.getTranslationY(holder.itemView);
+        endAnimation(holder);
+        int deltaX = toX - fromX;
+        int deltaY = toY - fromY;
+        if (deltaX == 0 && deltaY == 0) {
+            dispatchMoveFinished(holder);
+            return false;
+        }
+        if (deltaX != 0) {
+            ViewCompat.setTranslationX(view, -deltaX);
+        }
+        if (deltaY != 0) {
+            ViewCompat.setTranslationY(view, -deltaY);
+        }
+        mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
+        return true;
+    }
+
+    private void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) {
+        final View view = holder.itemView;
+        final int deltaX = toX - fromX;
+        final int deltaY = toY - fromY;
+        if (deltaX != 0) {
+            ViewCompat.animate(view).translationX(0);
+        }
+        if (deltaY != 0) {
+            ViewCompat.animate(view).translationY(0);
+        }
+        // TODO: make EndActions end listeners instead, since end actions aren't called when
+        // vpas are canceled (and can't end them. why?)
+        // need listener functionality in VPACompat for this. Ick.
+        mMoveAnimations.add(holder);
+        ViewCompat.animate(view).setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() {
+            @Override
+            public void onAnimationStart(View view) {
+                dispatchMoveStarting(holder);
+            }
+            @Override
+            public void onAnimationCancel(View view) {
+                if (deltaX != 0) {
+                    ViewCompat.setTranslationX(view, 0);
+                }
+                if (deltaY != 0) {
+                    ViewCompat.setTranslationY(view, 0);
+                }
+            }
+            @Override
+            public void onAnimationEnd(View view) {
+                dispatchMoveFinished(holder);
+                mMoveAnimations.remove(holder);
+                dispatchFinishedWhenDone();
+            }
+        }).start();
+    }
+
+    @Override
+    public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder,
+            int fromX, int fromY, int toX, int toY) {
+        final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView);
+        final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView);
+        final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
+        endAnimation(oldHolder);
+        int deltaX = (int) (toX - fromX - prevTranslationX);
+        int deltaY = (int) (toY - fromY - prevTranslationY);
+        // recover prev translation state after ending animation
+        ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX);
+        ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY);
+        ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
+        if (newHolder != null && newHolder.itemView != null) {
+            // carry over translation values
+            endAnimation(newHolder);
+            ViewCompat.setTranslationX(newHolder.itemView, -deltaX);
+            ViewCompat.setTranslationY(newHolder.itemView, -deltaY);
+            ViewCompat.setAlpha(newHolder.itemView, 0);
+        }
+        mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
+        return true;
+    }
+
+    private void animateChangeImpl(final ChangeInfo changeInfo) {
+        final ViewHolder holder = changeInfo.oldHolder;
+        final View view = holder.itemView;
+        final ViewHolder newHolder = changeInfo.newHolder;
+        final View newView = newHolder != null ? newHolder.itemView : null;
+        mChangeAnimations.add(changeInfo.oldHolder);
+
+        ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration(
+                getChangeDuration());
+        oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
+        oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
+        oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() {
+            @Override
+            public void onAnimationStart(View view) {
+                dispatchChangeStarting(changeInfo.oldHolder, true);
+            }
+            @Override
+            public void onAnimationEnd(View view) {
+                ViewCompat.setAlpha(view, 1);
+                ViewCompat.setTranslationX(view, 0);
+                ViewCompat.setTranslationY(view, 0);
+                dispatchChangeFinished(changeInfo.oldHolder, true);
+                mChangeAnimations.remove(changeInfo.oldHolder);
+                dispatchFinishedWhenDone();
+            }
+        }).start();
+        if (newView != null) {
+            mChangeAnimations.add(changeInfo.newHolder);
+            ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView);
+            newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).
+                    alpha(1).setListener(new VpaListenerAdapter() {
+                @Override
+                public void onAnimationStart(View view) {
+                    dispatchChangeStarting(changeInfo.newHolder, false);
+                }
+                @Override
+                public void onAnimationEnd(View view) {
+                    ViewCompat.setAlpha(newView, 1);
+                    ViewCompat.setTranslationX(newView, 0);
+                    ViewCompat.setTranslationY(newView, 0);
+                    dispatchChangeFinished(changeInfo.newHolder, false);
+                    mChangeAnimations.remove(changeInfo.newHolder);
+                    dispatchFinishedWhenDone();
+                }
+            }).start();
+        }
+    }
+
+    private void endChangeAnimation(List<ChangeInfo> infoList, ViewHolder item) {
+        for (int i = infoList.size() - 1; i >= 0; i--) {
+            ChangeInfo changeInfo = infoList.get(i);
+            if (endChangeAnimationIfNecessary(changeInfo, item)) {
+                if (changeInfo.oldHolder == null && changeInfo.newHolder == null) {
+                    infoList.remove(changeInfo);
+                }
+            }
+        }
+    }
+
+    private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) {
+        if (changeInfo.oldHolder != null) {
+            endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder);
+        }
+        if (changeInfo.newHolder != null) {
+            endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder);
+        }
+    }
+    private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, ViewHolder item) {
+        boolean oldItem = false;
+        if (changeInfo.newHolder == item) {
+            changeInfo.newHolder = null;
+        } else if (changeInfo.oldHolder == item) {
+            changeInfo.oldHolder = null;
+            oldItem = true;
+        } else {
+            return false;
+        }
+        ViewCompat.setAlpha(item.itemView, 1);
+        ViewCompat.setTranslationX(item.itemView, 0);
+        ViewCompat.setTranslationY(item.itemView, 0);
+        dispatchChangeFinished(item, oldItem);
+        return true;
+    }
+
+    @Override
+    public void endAnimation(ViewHolder item) {
+        final View view = item.itemView;
+        // this will trigger end callback which should set properties to their target values.
+        ViewCompat.animate(view).cancel();
+        // TODO if some other animations are chained to end, how do we cancel them as well?
+        for (int i = mPendingMoves.size() - 1; i >= 0; i--) {
+            MoveInfo moveInfo = mPendingMoves.get(i);
+            if (moveInfo.holder == item) {
+                ViewCompat.setTranslationY(view, 0);
+                ViewCompat.setTranslationX(view, 0);
+                dispatchMoveFinished(item);
+                mPendingMoves.remove(item);
+            }
+        }
+        endChangeAnimation(mPendingChanges, item);
+        if (mPendingRemovals.remove(item)) {
+            ViewCompat.setAlpha(view, 1);
+            dispatchRemoveFinished(item);
+        }
+        if (mPendingAdditions.remove(item)) {
+            ViewCompat.setAlpha(view, 1);
+            dispatchAddFinished(item);
+        }
+
+        for (int i = mChangesList.size() - 1; i >= 0; i--) {
+            ArrayList<ChangeInfo> changes = mChangesList.get(i);
+            endChangeAnimation(changes, item);
+            if (changes.isEmpty()) {
+                mChangesList.remove(changes);
+            }
+        }
+        for (int i = mMovesList.size() - 1; i >= 0; i--) {
+            ArrayList<MoveInfo> moves = mMovesList.get(i);
+            for (int j = moves.size() - 1; j >= 0; j--) {
+                MoveInfo moveInfo = moves.get(j);
+                if (moveInfo.holder == item) {
+                    ViewCompat.setTranslationY(view, 0);
+                    ViewCompat.setTranslationX(view, 0);
+                    dispatchMoveFinished(item);
+                    moves.remove(j);
+                    if (moves.isEmpty()) {
+                        mMovesList.remove(moves);
+                    }
+                    break;
+                }
+            }
+        }
+        for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
+            ArrayList<ViewHolder> additions = mAdditionsList.get(i);
+            if (additions.remove(item)) {
+                ViewCompat.setAlpha(view, 1);
+                dispatchAddFinished(item);
+                if (additions.isEmpty()) {
+                    mAdditionsList.remove(additions);
+                }
+            }
+        }
+
+        // animations should be ended by the cancel above.
+        if (mRemoveAnimations.remove(item) && DEBUG) {
+            throw new IllegalStateException("after animation is cancelled, item should not be in "
+                    + "mRemoveAnimations list");
+        }
+
+        if (mAddAnimations.remove(item) && DEBUG) {
+            throw new IllegalStateException("after animation is cancelled, item should not be in "
+                    + "mAddAnimations list");
+        }
+
+        if (mChangeAnimations.remove(item) && DEBUG) {
+            throw new IllegalStateException("after animation is cancelled, item should not be in "
+                    + "mChangeAnimations list");
+        }
+
+        if (mMoveAnimations.remove(item) && DEBUG) {
+            throw new IllegalStateException("after animation is cancelled, item should not be in "
+                    + "mMoveAnimations list");
+        }
+        dispatchFinishedWhenDone();
+    }
+
+    @Override
+    public boolean isRunning() {
+        return (!mPendingAdditions.isEmpty() ||
+                !mPendingChanges.isEmpty() ||
+                !mPendingMoves.isEmpty() ||
+                !mPendingRemovals.isEmpty() ||
+                !mMoveAnimations.isEmpty() ||
+                !mRemoveAnimations.isEmpty() ||
+                !mAddAnimations.isEmpty() ||
+                !mChangeAnimations.isEmpty() ||
+                !mMovesList.isEmpty() ||
+                !mAdditionsList.isEmpty() ||
+                !mChangesList.isEmpty());
+    }
+
+    /**
+     * Check the state of currently pending and running animations. If there are none
+     * pending/running, call {@link #dispatchAnimationsFinished()} to notify any
+     * listeners.
+     */
+    private void dispatchFinishedWhenDone() {
+        if (!isRunning()) {
+            dispatchAnimationsFinished();
+        }
+    }
+
+    @Override
+    public void endAnimations() {
+        int count = mPendingMoves.size();
+        for (int i = count - 1; i >= 0; i--) {
+            MoveInfo item = mPendingMoves.get(i);
+            View view = item.holder.itemView;
+            ViewCompat.setTranslationY(view, 0);
+            ViewCompat.setTranslationX(view, 0);
+            dispatchMoveFinished(item.holder);
+            mPendingMoves.remove(i);
+        }
+        count = mPendingRemovals.size();
+        for (int i = count - 1; i >= 0; i--) {
+            ViewHolder item = mPendingRemovals.get(i);
+            dispatchRemoveFinished(item);
+            mPendingRemovals.remove(i);
+        }
+        count = mPendingAdditions.size();
+        for (int i = count - 1; i >= 0; i--) {
+            ViewHolder item = mPendingAdditions.get(i);
+            View view = item.itemView;
+            ViewCompat.setAlpha(view, 1);
+            dispatchAddFinished(item);
+            mPendingAdditions.remove(i);
+        }
+        count = mPendingChanges.size();
+        for (int i = count - 1; i >= 0; i--) {
+            endChangeAnimationIfNecessary(mPendingChanges.get(i));
+        }
+        mPendingChanges.clear();
+        if (!isRunning()) {
+            return;
+        }
+
+        int listCount = mMovesList.size();
+        for (int i = listCount - 1; i >= 0; i--) {
+            ArrayList<MoveInfo> moves = mMovesList.get(i);
+            count = moves.size();
+            for (int j = count - 1; j >= 0; j--) {
+                MoveInfo moveInfo = moves.get(j);
+                ViewHolder item = moveInfo.holder;
+                View view = item.itemView;
+                ViewCompat.setTranslationY(view, 0);
+                ViewCompat.setTranslationX(view, 0);
+                dispatchMoveFinished(moveInfo.holder);
+                moves.remove(j);
+                if (moves.isEmpty()) {
+                    mMovesList.remove(moves);
+                }
+            }
+        }
+        listCount = mAdditionsList.size();
+        for (int i = listCount - 1; i >= 0; i--) {
+            ArrayList<ViewHolder> additions = mAdditionsList.get(i);
+            count = additions.size();
+            for (int j = count - 1; j >= 0; j--) {
+                ViewHolder item = additions.get(j);
+                View view = item.itemView;
+                ViewCompat.setAlpha(view, 1);
+                dispatchAddFinished(item);
+                additions.remove(j);
+                if (additions.isEmpty()) {
+                    mAdditionsList.remove(additions);
+                }
+            }
+        }
+        listCount = mChangesList.size();
+        for (int i = listCount - 1; i >= 0; i--) {
+            ArrayList<ChangeInfo> changes = mChangesList.get(i);
+            count = changes.size();
+            for (int j = count - 1; j >= 0; j--) {
+                endChangeAnimationIfNecessary(changes.get(j));
+                if (changes.isEmpty()) {
+                    mChangesList.remove(changes);
+                }
+            }
+        }
+
+        cancelAll(mRemoveAnimations);
+        cancelAll(mMoveAnimations);
+        cancelAll(mAddAnimations);
+        cancelAll(mChangeAnimations);
+
+        dispatchAnimationsFinished();
+    }
+
+    void cancelAll(List<ViewHolder> viewHolders) {
+        for (int i = viewHolders.size() - 1; i >= 0; i--) {
+            ViewCompat.animate(viewHolders.get(i).itemView).cancel();
+        }
+    }
+
+    private static class VpaListenerAdapter implements ViewPropertyAnimatorListener {
+        @Override
+        public void onAnimationStart(View view) {}
+
+        @Override
+        public void onAnimationEnd(View view) {}
+
+        @Override
+        public void onAnimationCancel(View view) {}
+    };
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
new file mode 100644
index 0000000..46fd224
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
@@ -0,0 +1,470 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific languag`e governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.Arrays;
+
+/**
+ * A {@link RecyclerView.LayoutManager} implementations that lays out items in a grid.
+ * <p>
+ * By default, each item occupies 1 span. You can change it by providing a custom
+ * {@link SpanSizeLookup} instance via {@link #setSpanSizeLookup(SpanSizeLookup)}.
+ */
+public class GridLayoutManager extends LinearLayoutManager {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = "GridLayoutManager";
+    public static final int DEFAULT_SPAN_COUNT = -1;
+    int mSpanCount = DEFAULT_SPAN_COUNT;
+    /**
+     * The size of each span
+     */
+    int mSizePerSpan;
+    /**
+     * Temporary array to keep views in layoutChunk method
+     */
+    View[] mSet;
+
+    /**
+     * The measure spec for the perpendicular orientation to {@link #getOrientation()}.
+     */
+    static int OTHER_DIM_SPEC = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+
+    SpanSizeLookup mSpanSizeLookup = new DefaultSpanSizeLookup();
+
+    // re-used variable to acquire decor insets from RecyclerView
+    final Rect mDecorInsets = new Rect();
+
+    public GridLayoutManager(Context context, int spanCount) {
+        super(context);
+        setSpanCount(spanCount);
+    }
+
+    public GridLayoutManager(Context context, int spanCount, int orientation,
+            boolean reverseLayout) {
+        super(context, orientation, reverseLayout);
+        setSpanCount(spanCount);
+    }
+
+    @Override
+    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+        super.onLayoutChildren(recycler, state);
+        if (DEBUG) {
+            validateChildOrder();
+        }
+    }
+
+    @Override
+    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
+        return new LayoutParams(c, attrs);
+    }
+
+    @Override
+    public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+        if (lp instanceof ViewGroup.MarginLayoutParams) {
+            return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
+        } else {
+            return new LayoutParams(lp);
+        }
+    }
+
+    @Override
+    public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
+        return lp instanceof LayoutParams;
+    }
+
+    /**
+     * Sets the source to get the number of spans occupied by each item in the adapter.
+     *
+     * @param spanSizeLookup {@link SpanSizeLookup} instance to be used to query number of spans
+     *                                             occupied by each item
+     */
+    public void setSpanSizeLookup(SpanSizeLookup spanSizeLookup) {
+        mSpanSizeLookup = spanSizeLookup;
+    }
+
+    /**
+     * Returns the current {@link SpanSizeLookup} used by the GridLayoutManager.
+     *
+     * @return The current {@link SpanSizeLookup} used by the GridLayoutManager.
+     */
+    public SpanSizeLookup getSpanSizeLookup() {
+        return mSpanSizeLookup;
+    }
+
+    private void updateMeasurements() {
+        int totalSpace;
+        if (getOrientation() == VERTICAL) {
+            totalSpace = getWidth() - getPaddingRight() - getPaddingLeft();
+        } else {
+            totalSpace = getHeight() - getPaddingBottom() - getPaddingTop();
+        }
+        mSizePerSpan = totalSpace / mSpanCount;
+    }
+
+    @Override
+    void onAnchorReady(LinearLayoutManager.AnchorInfo anchorInfo) {
+        super.onAnchorReady(anchorInfo);
+        updateMeasurements();
+        int span = mSpanSizeLookup.getSpanIndex(anchorInfo.mPosition, mSpanCount);
+        while (span > 0 && anchorInfo.mPosition > 0) {
+            anchorInfo.mPosition --;
+            span -= mSpanSizeLookup.getSpanSize(anchorInfo.mPosition);
+        }
+        if (mSet == null || mSet.length != mSpanCount) {
+            mSet = new View[mSpanCount];
+        }
+    }
+
+    @Override
+    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
+            LayoutState layoutState, LayoutChunkResult result) {
+        int count = 0;
+        int remainingSpan = mSpanCount;
+        while (count < mSpanCount && layoutState.hasMore(state) && remainingSpan > 0) {
+            int pos = layoutState.mCurrentPosition;
+            final int spanSize = mSpanSizeLookup.getSpanSize(pos);
+            if (spanSize > mSpanCount) {
+                throw new IllegalArgumentException("Item at position " + pos + " requires " +
+                        spanSize + " spans but GridLayoutManager has only " + mSpanCount
+                        + " spans.");
+            }
+            remainingSpan -= spanSize;
+            if (remainingSpan < 0) {
+                break; // item did not fit into this row or column
+            }
+            View view = layoutState.next(recycler);
+            if (view == null) {
+                break;
+            }
+            mSet[count] = view;
+            count ++;
+        }
+
+        if (count == 0) {
+            result.mFinished = true;
+            return;
+        }
+
+        int maxSize = 0;
+        final boolean layingOutInPrimaryDirection = mShouldReverseLayout ==
+                (layoutState.mLayoutDirection == LayoutState.LAYOUT_START);
+        // we should assign spans before item decor offsets are calculated
+        assignSpans(count, layingOutInPrimaryDirection);
+        for (int i = 0; i < count; i ++) {
+            View view = mSet[i];
+            if (layingOutInPrimaryDirection) {
+                addView(view);
+            } else {
+                addView(view, 0);
+            }
+            int spanSize = mSpanSizeLookup.getSpanSize(getPosition(view));
+            final int spec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan * spanSize,
+                    View.MeasureSpec.EXACTLY);
+            if (mOrientation == VERTICAL) {
+                measureChildWithDecorationsAndMargin(view, spec, OTHER_DIM_SPEC);
+            } else {
+                measureChildWithDecorationsAndMargin(view, OTHER_DIM_SPEC, spec);
+            }
+            final int size = mOrientationHelper.getDecoratedMeasurement(view);
+            if (size > maxSize) {
+                maxSize = size;
+            }
+        }
+        result.mConsumed = maxSize;
+
+        int left = 0, right = 0, top = 0, bottom = 0;
+        if (mOrientation == VERTICAL) {
+            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
+                bottom = layoutState.mOffset;
+                top = bottom - maxSize;
+            } else {
+                top = layoutState.mOffset;
+                bottom = top + maxSize;
+            }
+        } else {
+            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
+                right = layoutState.mOffset;
+                left = right - maxSize;
+            } else {
+                left = layoutState.mOffset;
+                right = left + maxSize;
+            }
+        }
+        for (int i = 0; i < count; i ++) {
+            View view = mSet[i];
+            LayoutParams params = (LayoutParams) view.getLayoutParams();
+            if (mOrientation == VERTICAL) {
+                left = getPaddingLeft() + mSizePerSpan * params.mSpanIndex;
+                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
+            } else {
+                top = getPaddingTop() + mSizePerSpan * params.mSpanIndex;
+                bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
+            }
+            // We calculate everything with View's bounding box (which includes decor and margins)
+            // To calculate correct layout position, we subtract margins.
+            layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
+                    right - params.rightMargin, bottom - params.bottomMargin);
+            if (DEBUG) {
+                Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
+                        + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
+                        + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin)
+                        + ", span:" + params.mSpanIndex + ", spanSize:" + params.mSpanSize);
+            }
+            // Consume the available space if the view is not removed OR changed
+            if (params.isItemRemoved() || params.isItemChanged()) {
+                result.mIgnoreConsumed = true;
+            }
+            result.mFocusable |= view.isFocusable();
+        }
+        Arrays.fill(mSet, null);
+    }
+
+    private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec) {
+        calculateItemDecorationsForChild(child, mDecorInsets);
+        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
+        widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + mDecorInsets.left,
+                lp.rightMargin + mDecorInsets.right);
+        heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + mDecorInsets.top,
+                lp.bottomMargin + mDecorInsets.bottom);
+        child.measure(widthSpec, heightSpec);
+    }
+
+    private int updateSpecWithExtra(int spec, int startInset, int endInset) {
+        if (startInset == 0 && endInset == 0) {
+            return spec;
+        }
+        final int mode = View.MeasureSpec.getMode(spec);
+        if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) {
+            return View.MeasureSpec.makeMeasureSpec(
+                    View.MeasureSpec.getSize(spec) - startInset - endInset, mode);
+        }
+        return spec;
+    }
+
+    private void assignSpans(int count, boolean layingOutInPrimaryDirection) {
+        int span, spanDiff, start, end, diff;
+        // make sure we traverse from min position to max position
+        if (layingOutInPrimaryDirection) {
+            start = 0;
+            end = count;
+            diff = 1;
+        } else {
+            start = count - 1;
+            end = -1;
+            diff = -1;
+        }
+        if (mOrientation == VERTICAL && isLayoutRTL()) { // start from last span
+            span = mSpanCount - 1;
+            spanDiff = -1;
+        } else {
+            span = 0;
+            spanDiff = 1;
+        }
+        for (int i = start; i != end; i += diff) {
+            View view = mSet[i];
+            LayoutParams params = (LayoutParams) view.getLayoutParams();
+            params.mSpanSize = mSpanSizeLookup.getSpanSize(getPosition(view));
+            if (spanDiff == -1 && params.mSpanSize > 1) {
+                params.mSpanIndex = span - (params.mSpanSize - 1);
+            } else {
+                params.mSpanIndex = span;
+            }
+            span += spanDiff * params.mSpanSize;
+        }
+    }
+
+    /**
+     * Returns the number of spans laid out by this grid.
+     *
+     * @return The number of spans
+     * @see #setSpanCount(int)
+     */
+    public int getSpanCount() {
+        return mSpanCount;
+    }
+
+    /**
+     * Sets the number of spans to be laid out.
+     * <p>
+     * If {@link #getOrientation()} is {@link #VERTICAL}, this is the number of columns.
+     * If {@link #getOrientation()} is {@link #HORIZONTAL}, this is the number of rows.
+     *
+     * @param spanCount The total number of spans in the grid
+     * @see #getSpanCount()
+     */
+    public void setSpanCount(int spanCount) {
+        if (spanCount == mSpanCount) {
+            return;
+        }
+        if (spanCount < 1) {
+            throw new IllegalArgumentException("Span count should be at least 1. Provided "
+                    + spanCount);
+        }
+        mSpanCount = spanCount;
+    }
+
+    /**
+     * A helper class to provide the number of spans each item occupies.
+     * <p>
+     * Default implementation sets each item to occupy exactly 1 span.
+     *
+     * @see GridLayoutManager#setSpanSizeLookup(SpanSizeLookup)
+     */
+    public static abstract class SpanSizeLookup {
+        /**
+         * Returns the number of span occupied by the item at <code>position</code>.
+         *
+         * @param position The adapter position of the item
+         * @return The number of spans occupied by the item at the provided position
+         */
+        abstract public int getSpanSize(int position);
+
+        /**
+         * Returns the final span index of the provided position.
+         * <p>
+         * Default implementation traverses all items before the current position to decide which
+         * span offset this item should be positioned at. You can override this method if you have
+         * a faster way to calculate it based on your data set.
+         * <p>
+         * Note that span offsets always start with 0 and is not affected by RTL.
+         *
+         * @param position The position of the item
+         * @param spanCount The total number of spans in the grid
+         * @return The final span position of the item. Should be between 0 (inclusive) and
+         * <code>spanCount</code>(exclusive)
+         */
+        public int getSpanIndex(int position, int spanCount) {
+            int positionSpanSize = getSpanSize(position);
+            if (positionSpanSize == spanCount) {
+                return 0; // quick return for full-span items
+            }
+            int span = 0;
+            for (int i = 0; i < position; i++) {
+                int size = getSpanSize(i);
+                span += size;
+                if (span == spanCount) {
+                    span = 0;
+                } else if (span > spanCount) {
+                    // did not fit, moving to next row / column
+                    span = size;
+                }
+            }
+            if (span + positionSpanSize <= spanCount) {
+                return span;
+            }
+            return 0;
+        }
+    }
+
+    @Override
+    public boolean supportsPredictiveItemAnimations() {
+        return false;
+    }
+
+    /**
+     * Default implementation for {@link SpanSizeLookup}. Each item occupies 1 span.
+     */
+    public static final class DefaultSpanSizeLookup extends SpanSizeLookup {
+        @Override
+        public int getSpanSize(int position) {
+            return 1;
+        }
+
+        @Override
+        public int getSpanIndex(int position, int spanCount) {
+            return position % spanCount;
+        }
+    }
+
+    /**
+     * LayoutParams used by GridLayoutManager.
+     */
+    public static class LayoutParams extends RecyclerView.LayoutParams {
+
+        /**
+         * Span Id for Views that are not laid out yet.
+         */
+        public static final int INVALID_SPAN_ID = -1;
+
+        private int mSpanIndex = INVALID_SPAN_ID;
+
+        private int mSpanSize = 0;
+
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+        }
+
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        public LayoutParams(ViewGroup.MarginLayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(ViewGroup.LayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(RecyclerView.LayoutParams source) {
+            super(source);
+        }
+
+        /**
+         * Returns the current span index of this View. If the View is not laid out yet, the return
+         * value is <code>undefined</code>.
+         * <p>
+         * Note that span index may change by whether the RecyclerView is RTL or not. For
+         * example, if the number of spans is 3 and layout is RTL, the rightmost item will have
+         * span index of 2. If the layout changes back to LTR, span index for this view will be 0.
+         * If the item was occupying 2 spans, span indices would be 1 and 0 respectively.
+         * <p>
+         * If the View occupies multiple spans, span with the minimum index is returned.
+         *
+         * @return The span index of the View.
+         */
+        public int getSpanIndex() {
+            return mSpanIndex;
+        }
+
+        /**
+         * Returns the number of spans occupied by this View. If the View not laid out yet, the
+         * return value is <code>undefined</code>.
+         *
+         * @return The number of spans occupied by this View.
+         */
+        public int getSpanSize() {
+            return mSpanSize;
+        }
+    }
+
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/LayoutState.java b/v7/recyclerview/src/android/support/v7/widget/LayoutState.java
new file mode 100644
index 0000000..e62a80a
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/widget/LayoutState.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific languag`e governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+import android.view.View;
+
+/**
+ * Helper class that keeps temporary state while {LayoutManager} is filling out the empty
+ * space.
+ */
+class LayoutState {
+
+    final static String TAG = "LayoutState";
+
+    final static int LAYOUT_START = -1;
+
+    final static int LAYOUT_END = 1;
+
+    final static int INVALID_LAYOUT = Integer.MIN_VALUE;
+
+    final static int ITEM_DIRECTION_HEAD = -1;
+
+    final static int ITEM_DIRECTION_TAIL = 1;
+
+    final static int SCOLLING_OFFSET_NaN = Integer.MIN_VALUE;
+
+    /**
+     * Number of pixels that we should fill, in the layout direction.
+     */
+    int mAvailable;
+
+    /**
+     * Current position on the adapter to get the next item.
+     */
+    int mCurrentPosition;
+
+    /**
+     * Defines the direction in which the data adapter is traversed.
+     * Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL}
+     */
+    int mItemDirection;
+
+    /**
+     * Defines the direction in which the layout is filled.
+     * Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
+     */
+    int mLayoutDirection;
+
+    /**
+     * Used if you want to pre-layout items that are not yet visible.
+     * The difference with {@link #mAvailable} is that, when recycling, distance rendered for
+     * {@link #mExtra} is not considered not to recycle visible children.
+     */
+    int mExtra = 0;
+
+    /**
+     * @return true if there are more items in the data adapter
+     */
+    boolean hasMore(RecyclerView.State state) {
+        return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();
+    }
+
+    /**
+     * Gets the view for the next element that we should render.
+     * Also updates current item index to the next item, based on {@link #mItemDirection}
+     *
+     * @return The next element that we should render.
+     */
+    View next(RecyclerView.Recycler recycler) {
+        final View view = recycler.getViewForPosition(mCurrentPosition);
+        mCurrentPosition += mItemDirection;
+        return view;
+    }
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
new file mode 100644
index 0000000..dcb8279
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
@@ -0,0 +1,1967 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific languag`e governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.graphics.PointF;
+import android.support.v4.view.ViewCompat;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+
+import java.util.List;
+
+/**
+ * A {@link android.support.v7.widget.RecyclerView.LayoutManager} implementation which provides
+ * similar functionality to {@link android.widget.ListView}.
+ */
+public class LinearLayoutManager extends RecyclerView.LayoutManager {
+
+    private static final String TAG = "LinearLayoutManager";
+
+    private static final boolean DEBUG = false;
+
+    public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;
+
+    public static final int VERTICAL = OrientationHelper.VERTICAL;
+
+    public static final int INVALID_OFFSET = Integer.MIN_VALUE;
+
+
+    /**
+     * While trying to find next view to focus, LinearLayoutManager will not try to scroll more
+     * than
+     * this factor times the total space of the list. If layout is vertical, total space is the
+     * height minus padding, if layout is horizontal, total space is the width minus padding.
+     */
+    private static final float MAX_SCROLL_FACTOR = 0.33f;
+
+
+    /**
+     * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}
+     */
+    int mOrientation;
+
+    /**
+     * Helper class that keeps temporary layout state.
+     * It does not keep state after layout is complete but we still keep a reference to re-use
+     * the same object.
+     */
+    private LayoutState mLayoutState;
+
+    /**
+     * Many calculations are made depending on orientation. To keep it clean, this interface
+     * helps {@link LinearLayoutManager} make those decisions.
+     * Based on {@link #mOrientation}, an implementation is lazily created in
+     * {@link #ensureLayoutState} method.
+     */
+    OrientationHelper mOrientationHelper;
+
+    /**
+     * We need to track this so that we can ignore current position when it changes.
+     */
+    private boolean mLastStackFromEnd;
+
+
+    /**
+     * Defines if layout should be calculated from end to start.
+     *
+     * @see #mShouldReverseLayout
+     */
+    private boolean mReverseLayout = false;
+
+    /**
+     * This keeps the final value for how LayoutManager should start laying out views.
+     * It is calculated by checking {@link #getReverseLayout()} and View's layout direction.
+     * {@link #onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)} is run.
+     */
+    boolean mShouldReverseLayout = false;
+
+    /**
+     * Works the same way as {@link android.widget.AbsListView#setStackFromBottom(boolean)} and
+     * it supports both orientations.
+     * see {@link android.widget.AbsListView#setStackFromBottom(boolean)}
+     */
+    private boolean mStackFromEnd = false;
+
+    /**
+     * Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}.
+     * see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}
+     */
+    private boolean mSmoothScrollbarEnabled = true;
+
+    /**
+     * When LayoutManager needs to scroll to a position, it sets this variable and requests a
+     * layout which will check this variable and re-layout accordingly.
+     */
+    int mPendingScrollPosition = NO_POSITION;
+
+    /**
+     * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is
+     * called.
+     */
+    int mPendingScrollPositionOffset = INVALID_OFFSET;
+
+    private boolean mRecycleChildrenOnDetach;
+
+    SavedState mPendingSavedState = null;
+
+    /**
+    *  Re-used variable to keep anchor information on re-layout.
+    *  Anchor position and coordinate defines the reference point for LLM while doing a layout.
+    * */
+    final AnchorInfo mAnchorInfo;
+
+    /**
+     * Creates a vertical LinearLayoutManager
+     *
+     * @param context Current context, will be used to access resources.
+     */
+    public LinearLayoutManager(Context context) {
+        this(context, VERTICAL, false);
+    }
+
+    /**
+     * @param context       Current context, will be used to access resources.
+     * @param orientation   Layout orientation. Should be {@link #HORIZONTAL} or {@link
+     *                      #VERTICAL}.
+     * @param reverseLayout When set to true, layouts from end to start.
+     */
+    public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
+        mAnchorInfo = new AnchorInfo();
+        setOrientation(orientation);
+        setReverseLayout(reverseLayout);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+        return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+    }
+
+    /**
+     * Returns whether LinearLayoutManager will recycle its children when it is detached from
+     * RecyclerView.
+     *
+     * @return true if LinearLayoutManager will recycle its children when it is detached from
+     * RecyclerView.
+     */
+    public boolean getRecycleChildrenOnDetach() {
+        return mRecycleChildrenOnDetach;
+    }
+
+    /**
+     * Set whether LinearLayoutManager will recycle its children when it is detached from
+     * RecyclerView.
+     * <p>
+     * If you are using a {@link RecyclerView.RecycledViewPool}, it might be a good idea to set
+     * this flag to <code>true</code> so that views will be avilable to other RecyclerViews
+     * immediately.
+     * <p>
+     * Note that, setting this flag will result in a performance drop if RecyclerView
+     * is restored.
+     *
+     * @param recycleChildrenOnDetach Whether children should be recycled in detach or not.
+     */
+    public void setRecycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
+        mRecycleChildrenOnDetach = recycleChildrenOnDetach;
+    }
+
+    @Override
+    public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
+        super.onDetachedFromWindow(view, recycler);
+        if (mRecycleChildrenOnDetach) {
+            removeAndRecycleAllViews(recycler);
+            recycler.clear();
+        }
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        if (mPendingSavedState != null) {
+            return new SavedState(mPendingSavedState);
+        }
+        SavedState state = new SavedState();
+        if (getChildCount() > 0) {
+            boolean didLayoutFromEnd = mLastStackFromEnd ^ mShouldReverseLayout;
+            state.mAnchorLayoutFromEnd = didLayoutFromEnd;
+            if (didLayoutFromEnd) {
+                final View refChild = getChildClosestToEnd();
+                state.mAnchorOffset = mOrientationHelper.getEndAfterPadding() -
+                        mOrientationHelper.getDecoratedEnd(refChild);
+                state.mAnchorPosition = getPosition(refChild);
+            } else {
+                final View refChild = getChildClosestToStart();
+                state.mAnchorPosition = getPosition(refChild);
+                state.mAnchorOffset = mOrientationHelper.getDecoratedStart(refChild) -
+                        mOrientationHelper.getStartAfterPadding();
+            }
+        } else {
+            state.invalidateAnchor();
+        }
+        return state;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        if (state instanceof SavedState) {
+            mPendingSavedState = (SavedState) state;
+            requestLayout();
+            if (DEBUG) {
+                Log.d(TAG, "loaded saved state");
+            }
+        } else if (DEBUG) {
+            Log.d(TAG, "invalid saved state class");
+        }
+    }
+
+    /**
+     * @return true if {@link #getOrientation()} is {@link #HORIZONTAL}
+     */
+    @Override
+    public boolean canScrollHorizontally() {
+        return mOrientation == HORIZONTAL;
+    }
+
+    /**
+     * @return true if {@link #getOrientation()} is {@link #VERTICAL}
+     */
+    @Override
+    public boolean canScrollVertically() {
+        return mOrientation == VERTICAL;
+    }
+
+    /**
+     * Compatibility support for {@link android.widget.AbsListView#setStackFromBottom(boolean)}
+     */
+    public void setStackFromEnd(boolean stackFromEnd) {
+        assertNotInLayoutOrScroll(null);
+        if (mStackFromEnd == stackFromEnd) {
+            return;
+        }
+        mStackFromEnd = stackFromEnd;
+        requestLayout();
+    }
+
+    public boolean getStackFromEnd() {
+        return mStackFromEnd;
+    }
+
+    /**
+     * Returns the current orientaion of the layout.
+     *
+     * @return Current orientation.
+     * @see #mOrientation
+     * @see #setOrientation(int)
+     */
+    public int getOrientation() {
+        return mOrientation;
+    }
+
+    /**
+     * Sets the orientation of the layout. {@link android.support.v7.widget.LinearLayoutManager}
+     * will do its best to keep scroll position.
+     *
+     * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
+     */
+    public void setOrientation(int orientation) {
+        if (orientation != HORIZONTAL && orientation != VERTICAL) {
+            throw new IllegalArgumentException("invalid orientation:" + orientation);
+        }
+        assertNotInLayoutOrScroll(null);
+        if (orientation == mOrientation) {
+            return;
+        }
+        mOrientation = orientation;
+        mOrientationHelper = null;
+        requestLayout();
+    }
+
+    /**
+     * Calculates the view layout order. (e.g. from end to start or start to end)
+     * RTL layout support is applied automatically. So if layout is RTL and
+     * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
+     */
+    private void resolveShouldLayoutReverse() {
+        // A == B is the same result, but we rather keep it readable
+        if (mOrientation == VERTICAL || !isLayoutRTL()) {
+            mShouldReverseLayout = mReverseLayout;
+        } else {
+            mShouldReverseLayout = !mReverseLayout;
+        }
+    }
+
+    /**
+     * Returns if views are laid out from the opposite direction of the layout.
+     *
+     * @return If layout is reversed or not.
+     * @see {@link #setReverseLayout(boolean)}
+     */
+    public boolean getReverseLayout() {
+        return mReverseLayout;
+    }
+
+    /**
+     * Used to reverse item traversal and layout order.
+     * This behaves similar to the layout change for RTL views. When set to true, first item is
+     * laid out at the end of the UI, second item is laid out before it etc.
+     *
+     * For horizontal layouts, it depends on the layout direction.
+     * When set to true, If {@link android.support.v7.widget.RecyclerView} is LTR, than it will
+     * layout from RTL, if {@link android.support.v7.widget.RecyclerView}} is RTL, it will layout
+     * from LTR.
+     *
+     * If you are looking for the exact same behavior of
+     * {@link android.widget.AbsListView#setStackFromBottom(boolean)}, use
+     * {@link #setStackFromEnd(boolean)}
+     */
+    public void setReverseLayout(boolean reverseLayout) {
+        assertNotInLayoutOrScroll(null);
+        if (reverseLayout == mReverseLayout) {
+            return;
+        }
+        mReverseLayout = reverseLayout;
+        requestLayout();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public View findViewByPosition(int position) {
+        final int childCount = getChildCount();
+        if (childCount == 0) {
+            return null;
+        }
+        final int firstChild = getPosition(getChildAt(0));
+        final int viewPosition = position - firstChild;
+        if (viewPosition >= 0 && viewPosition < childCount) {
+            return getChildAt(viewPosition);
+        }
+        return null;
+    }
+
+    /**
+     * <p>Returns the amount of extra space that should be laid out by LinearLayoutManager.
+     * By default, {@link android.support.v7.widget.LinearLayoutManager} lays out 1 extra page of
+     * items while smooth scrolling and 0 otherwise. You can override this method to implement your
+     * custom layout pre-cache logic.</p>
+     * <p>Laying out invisible elements will eventually come with performance cost. On the other
+     * hand, in places like smooth scrolling to an unknown location, this extra content helps
+     * LayoutManager to calculate a much smoother scrolling; which improves user experience.</p>
+     * <p>You can also use this if you are trying to pre-layout your upcoming views.</p>
+     *
+     * @return The extra space that should be laid out (in pixels).
+     */
+    protected int getExtraLayoutSpace(RecyclerView.State state) {
+        if (state.hasTargetScrollPosition()) {
+            return mOrientationHelper.getTotalSpace();
+        } else {
+            return 0;
+        }
+    }
+
+    @Override
+    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
+            int position) {
+        LinearSmoothScroller linearSmoothScroller =
+                new LinearSmoothScroller(recyclerView.getContext()) {
+                    @Override
+                    public PointF computeScrollVectorForPosition(int targetPosition) {
+                        return LinearLayoutManager.this
+                                .computeScrollVectorForPosition(targetPosition);
+                    }
+                };
+        linearSmoothScroller.setTargetPosition(position);
+        startSmoothScroll(linearSmoothScroller);
+    }
+
+    public PointF computeScrollVectorForPosition(int targetPosition) {
+        if (getChildCount() == 0) {
+            return null;
+        }
+        final int firstChildPos = getPosition(getChildAt(0));
+        final int direction = targetPosition < firstChildPos != mShouldReverseLayout ? -1 : 1;
+        if (mOrientation == HORIZONTAL) {
+            return new PointF(direction, 0);
+        } else {
+            return new PointF(0, direction);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+        // layout algorithm:
+        // 1) by checking children and other variables, find an anchor coordinate and an anchor
+        //  item position.
+        // 2) fill towards start, stacking from bottom
+        // 3) fill towards end, stacking from top
+        // 4) scroll to fulfill requirements like stack from bottom.
+        // create layout state
+        if (DEBUG) {
+            Log.d(TAG, "is pre layout:" + state.isPreLayout());
+        }
+        if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
+            mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
+        }
+
+        ensureLayoutState();
+        mLayoutState.mRecycle = false;
+        // resolve layout direction
+        resolveShouldLayoutReverse();
+
+        mAnchorInfo.reset();
+        mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
+        // calculate anchor position and coordinate
+        updateAnchorInfoForLayout(state, mAnchorInfo);
+        if (DEBUG) {
+            Log.d(TAG, "Anchor info:" + mAnchorInfo);
+        }
+
+        // LLM may decide to layout items for "extra" pixels to account for scrolling target,
+        // caching or predictive animations.
+        int extraForStart;
+        int extraForEnd;
+        final int extra = getExtraLayoutSpace(state);
+        boolean before = state.getTargetScrollPosition() < mAnchorInfo.mPosition;
+        if (before == mShouldReverseLayout) {
+            extraForEnd = extra;
+            extraForStart = 0;
+        } else {
+            extraForStart = extra;
+            extraForEnd = 0;
+        }
+        extraForStart += mOrientationHelper.getStartAfterPadding();
+        extraForEnd += mOrientationHelper.getEndPadding();
+        if (state.isPreLayout() && mPendingScrollPosition != NO_POSITION &&
+                mPendingScrollPositionOffset != INVALID_OFFSET) {
+            // if the child is visible and we are going to move it around, we should layout
+            // extra items in the opposite direction to make sure new items animate nicely
+            // instead of just fading in
+            final View existing = findViewByPosition(mPendingScrollPosition);
+            if (existing != null) {
+                final int current;
+                final int upcomingOffset;
+                if (mShouldReverseLayout) {
+                    current = mOrientationHelper.getEndAfterPadding() -
+                            mOrientationHelper.getDecoratedEnd(existing);
+                    upcomingOffset = current - mPendingScrollPositionOffset;
+                } else {
+                    current = mOrientationHelper.getDecoratedStart(existing)
+                            - mOrientationHelper.getStartAfterPadding();
+                    upcomingOffset = mPendingScrollPositionOffset - current;
+                }
+                if (upcomingOffset > 0) {
+                    extraForStart += upcomingOffset;
+                } else {
+                    extraForEnd -= upcomingOffset;
+                }
+            }
+        }
+        int startOffset;
+        int endOffset;
+        onAnchorReady(mAnchorInfo);
+        detachAndScrapAttachedViews(recycler);
+        mLayoutState.mIsPreLayout = state.isPreLayout();
+        if (mAnchorInfo.mLayoutFromEnd) {
+            // fill towards start
+            updateLayoutStateToFillStart(mAnchorInfo);
+            mLayoutState.mExtra = extraForStart;
+            fill(recycler, mLayoutState, state, false);
+            startOffset = mLayoutState.mOffset;
+            if (mLayoutState.mAvailable > 0) {
+                extraForEnd += mLayoutState.mAvailable;
+            }
+            // fill towards end
+            updateLayoutStateToFillEnd(mAnchorInfo);
+            mLayoutState.mExtra = extraForEnd;
+            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
+            fill(recycler, mLayoutState, state, false);
+            endOffset = mLayoutState.mOffset;
+        } else {
+            // fill towards end
+            updateLayoutStateToFillEnd(mAnchorInfo);
+            mLayoutState.mExtra = extraForEnd;
+            fill(recycler, mLayoutState, state, false);
+            endOffset = mLayoutState.mOffset;
+            if (mLayoutState.mAvailable > 0) {
+                extraForStart += mLayoutState.mAvailable;
+            }
+            // fill towards start
+            updateLayoutStateToFillStart(mAnchorInfo);
+            mLayoutState.mExtra = extraForStart;
+            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
+            fill(recycler, mLayoutState, state, false);
+            startOffset = mLayoutState.mOffset;
+        }
+
+        // changes may cause gaps on the UI, try to fix them.
+        // TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have
+        // changed
+        if (getChildCount() > 0) {
+            // because layout from end may be changed by scroll to position
+            // we re-calculate it.
+            // find which side we should check for gaps.
+            if (mShouldReverseLayout ^ mStackFromEnd) {
+                int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);
+                startOffset += fixOffset;
+                endOffset += fixOffset;
+                fixOffset = fixLayoutStartGap(startOffset, recycler, state, false);
+                startOffset += fixOffset;
+                endOffset += fixOffset;
+            } else {
+                int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);
+                startOffset += fixOffset;
+                endOffset += fixOffset;
+                fixOffset = fixLayoutEndGap(endOffset, recycler, state, false);
+                startOffset += fixOffset;
+                endOffset += fixOffset;
+            }
+        }
+        layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
+        if (!state.isPreLayout()) {
+            mPendingScrollPosition = NO_POSITION;
+            mPendingScrollPositionOffset = INVALID_OFFSET;
+            mOrientationHelper.onLayoutComplete();
+        }
+        mLastStackFromEnd = mStackFromEnd;
+        mPendingSavedState = null; // we don't need this anymore
+        if (DEBUG) {
+            validateChildOrder();
+        }
+    }
+
+    /**
+     * Method called when Anchor position is decided. Extending class can setup accordingly or
+     * even update anchor info if necessary.
+     *
+     * @param anchorInfo Simple data structure to keep anchor point information for the next layout
+     *                   pass
+     */
+    void onAnchorReady(AnchorInfo anchorInfo) {
+    }
+
+    /**
+     * If necessary, layouts new items for predictive animations
+     */
+    private void layoutForPredictiveAnimations(RecyclerView.Recycler recycler,
+            RecyclerView.State state, int startOffset,  int endOffset) {
+        // If there are scrap children that we did not layout, we need to find where they did go
+        // and layout them accordingly so that animations can work as expected.
+        // This case may happen if new views are added or an existing view expands and pushes
+        // another view out of bounds.
+        if (!state.willRunPredictiveAnimations() ||  getChildCount() == 0 || state.isPreLayout()
+                || !supportsPredictiveItemAnimations()) {
+            return;
+        }
+
+        // to make the logic simpler, we calculate the size of children and call fill.
+        int scrapExtraStart = 0, scrapExtraEnd = 0;
+        final List<RecyclerView.ViewHolder> scrapList = recycler.getScrapList();
+        final int scrapSize = scrapList.size();
+        final int firstChildPos = getPosition(getChildAt(0));
+        for (int i = 0; i < scrapSize; i++) {
+            RecyclerView.ViewHolder scrap = scrapList.get(i);
+            final int position = scrap.getPosition();
+            final int direction = position < firstChildPos != mShouldReverseLayout
+                    ? LayoutState.LAYOUT_START : LayoutState.LAYOUT_END;
+            if (direction == LayoutState.LAYOUT_START) {
+                scrapExtraStart += mOrientationHelper.getDecoratedMeasurement(scrap.itemView);
+            } else {
+                scrapExtraEnd += mOrientationHelper.getDecoratedMeasurement(scrap.itemView);
+            }
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "for unused scrap, decided to add " + scrapExtraStart
+                    + " towards start and " + scrapExtraEnd + " towards end");
+        }
+        mLayoutState.mScrapList = scrapList;
+        if (scrapExtraStart > 0) {
+            View anchor = getChildClosestToStart();
+            updateLayoutStateToFillStart(getPosition(anchor), startOffset);
+            mLayoutState.mExtra = scrapExtraStart;
+            mLayoutState.mAvailable = 0;
+            mLayoutState.mCurrentPosition += mShouldReverseLayout ? 1 : -1;
+            fill(recycler, mLayoutState, state, false);
+        }
+
+        if (scrapExtraEnd > 0) {
+            View anchor = getChildClosestToEnd();
+            updateLayoutStateToFillEnd(getPosition(anchor), endOffset);
+            mLayoutState.mExtra = scrapExtraEnd;
+            mLayoutState.mAvailable = 0;
+            mLayoutState.mCurrentPosition += mShouldReverseLayout ? -1 : 1;
+            fill(recycler, mLayoutState, state, false);
+        }
+        mLayoutState.mScrapList = null;
+    }
+
+    private void updateAnchorInfoForLayout(RecyclerView.State state, AnchorInfo anchorInfo) {
+        if (updateAnchorFromPendingData(state, anchorInfo)) {
+            if (DEBUG) {
+                Log.d(TAG, "updated anchor info from pending information");
+            }
+            return;
+        }
+
+        if (updateAnchorFromChildren(state, anchorInfo)) {
+            if (DEBUG) {
+                Log.d(TAG, "updated anchor info from existing children");
+            }
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "deciding anchor info for fresh state");
+        }
+        anchorInfo.assignCoordinateFromPadding();
+        anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
+    }
+
+    /**
+     * Finds an anchor child from existing Views. Most of the time, this is the view closest to
+     * start or end that has a valid position (e.g. not removed).
+     * <p>
+     * If a child has focus, it is given priority.
+     */
+    private boolean updateAnchorFromChildren(RecyclerView.State state, AnchorInfo anchorInfo) {
+        if (getChildCount() == 0) {
+            return false;
+        }
+        View focused = findItemWhichHasFocus();
+        if (focused != null && anchorInfo.assignFromViewIfValid(focused, state)) {
+            if (DEBUG) {
+                Log.d(TAG, "decided anchor child from focused view");
+            }
+            return true;
+        }
+
+        if (mLastStackFromEnd != mStackFromEnd) {
+            return false;
+        }
+
+        View referenceChild = anchorInfo.mLayoutFromEnd ? findReferenceChildClosestToEnd(state)
+                : findReferenceChildClosestToStart(state);
+        if (referenceChild != null) {
+            anchorInfo.assignFromView(referenceChild);
+            // If all visible views are removed in 1 pass, reference child might be out of bounds.
+            // If that is the case, offset it back to 0 so that we use these pre-layout children.
+            if (!state.isPreLayout() && supportsPredictiveItemAnimations()) {
+                // validate this child is at least partially visible. if not, offset it to start
+                final boolean notVisible =
+                        mOrientationHelper.getDecoratedStart(referenceChild) >= mOrientationHelper
+                                .getEndAfterPadding()
+                                || mOrientationHelper.getDecoratedStart(referenceChild)
+                                < mOrientationHelper.getStartAfterPadding();
+                if (notVisible) {
+                    anchorInfo.mCoordinate = mShouldReverseLayout
+                            ? mOrientationHelper.getEndAfterPadding()
+                            : mOrientationHelper.getStartAfterPadding();
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * If there is a pending scroll position or saved states, updates the anchor info from that
+     * data and returns true
+     */
+    private boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) {
+        if (state.isPreLayout() || mPendingScrollPosition == NO_POSITION) {
+            return false;
+        }
+        // validate scroll position
+        if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) {
+            mPendingScrollPosition = NO_POSITION;
+            mPendingScrollPositionOffset = INVALID_OFFSET;
+            if (DEBUG) {
+                Log.e(TAG, "ignoring invalid scroll position " + mPendingScrollPosition);
+            }
+            return false;
+        }
+
+        // if child is visible, try to make it a reference child and ensure it is fully visible.
+        // if child is not visible, align it depending on its virtual position.
+        anchorInfo.mPosition = mPendingScrollPosition;
+        if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
+            // Anchor offset depends on how that child was laid out. Here, we update it
+            // according to our current view bounds
+            anchorInfo.mLayoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
+            if (anchorInfo.mLayoutFromEnd) {
+                anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding() -
+                        mPendingSavedState.mAnchorOffset;
+            } else {
+                anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding() +
+                        mPendingSavedState.mAnchorOffset;
+            }
+            return true;
+        }
+
+        if (mPendingScrollPositionOffset == INVALID_OFFSET) {
+            View child = findViewByPosition(mPendingScrollPosition);
+            if (child != null) {
+                final int childSize = mOrientationHelper.getDecoratedMeasurement(child);
+                if (childSize > mOrientationHelper.getTotalSpace()) {
+                    // item does not fit. fix depending on layout direction
+                    anchorInfo.assignCoordinateFromPadding();
+                    return true;
+                }
+                final int startGap = mOrientationHelper.getDecoratedStart(child)
+                        - mOrientationHelper.getStartAfterPadding();
+                if (startGap < 0) {
+                    anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding();
+                    anchorInfo.mLayoutFromEnd = false;
+                    return true;
+                }
+                final int endGap = mOrientationHelper.getEndAfterPadding() -
+                        mOrientationHelper.getDecoratedEnd(child);
+                if (endGap < 0) {
+                    anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding();
+                    anchorInfo.mLayoutFromEnd = true;
+                    return true;
+                }
+                anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd
+                        ? (mOrientationHelper.getDecoratedEnd(child) + mOrientationHelper
+                                .getTotalSpaceChange())
+                        : mOrientationHelper.getDecoratedStart(child);
+            } else { // item is not visible.
+                if (getChildCount() > 0) {
+                    // get position of any child, does not matter
+                    int pos = getPosition(getChildAt(0));
+                    anchorInfo.mLayoutFromEnd = mPendingScrollPosition < pos
+                            == mShouldReverseLayout;
+                }
+                anchorInfo.assignCoordinateFromPadding();
+            }
+            return true;
+        }
+        // override layout from end values for consistency
+        anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
+        if (mShouldReverseLayout) {
+            anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding() -
+                    mPendingScrollPositionOffset;
+        } else {
+            anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding() +
+                    mPendingScrollPositionOffset;
+        }
+        return true;
+    }
+
+    /**
+     * @return The final offset amount for children
+     */
+    private int fixLayoutEndGap(int endOffset, RecyclerView.Recycler recycler,
+            RecyclerView.State state, boolean canOffsetChildren) {
+        int gap = mOrientationHelper.getEndAfterPadding() - endOffset;
+        int fixOffset = 0;
+        if (gap > 0) {
+            fixOffset = -scrollBy(-gap, recycler, state);
+        } else {
+            return 0; // nothing to fix
+        }
+        // move offset according to scroll amount
+        endOffset += fixOffset;
+        if (canOffsetChildren) {
+            // re-calculate gap, see if we could fix it
+            gap = mOrientationHelper.getEndAfterPadding() - endOffset;
+            if (gap > 0) {
+                mOrientationHelper.offsetChildren(gap);
+                return gap + fixOffset;
+            }
+        }
+        return fixOffset;
+    }
+
+    /**
+     * @return The final offset amount for children
+     */
+    private int fixLayoutStartGap(int startOffset, RecyclerView.Recycler recycler,
+            RecyclerView.State state, boolean canOffsetChildren) {
+        int gap = startOffset - mOrientationHelper.getStartAfterPadding();
+        int fixOffset = 0;
+        if (gap > 0) {
+            // check if we should fix this gap.
+            fixOffset = -scrollBy(gap, recycler, state);
+        } else {
+            return 0; // nothing to fix
+        }
+        startOffset += fixOffset;
+        if (canOffsetChildren) {
+            // re-calculate gap, see if we could fix it
+            gap = startOffset - mOrientationHelper.getStartAfterPadding();
+            if (gap > 0) {
+                mOrientationHelper.offsetChildren(-gap);
+                return fixOffset - gap;
+            }
+        }
+        return fixOffset;
+    }
+
+    private void updateLayoutStateToFillEnd(AnchorInfo anchorInfo) {
+        updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate);
+    }
+
+    private void updateLayoutStateToFillEnd(int itemPosition, int offset) {
+        mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset;
+        mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
+                LayoutState.ITEM_DIRECTION_TAIL;
+        mLayoutState.mCurrentPosition = itemPosition;
+        mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END;
+        mLayoutState.mOffset = offset;
+        mLayoutState.mScrollingOffset = LayoutState.SCOLLING_OFFSET_NaN;
+    }
+
+    private void updateLayoutStateToFillStart(AnchorInfo anchorInfo) {
+        updateLayoutStateToFillStart(anchorInfo.mPosition, anchorInfo.mCoordinate);
+    }
+
+    private void updateLayoutStateToFillStart(int itemPosition, int offset) {
+        mLayoutState.mAvailable = offset - mOrientationHelper.getStartAfterPadding();
+        mLayoutState.mCurrentPosition = itemPosition;
+        mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL :
+                LayoutState.ITEM_DIRECTION_HEAD;
+        mLayoutState.mLayoutDirection = LayoutState.LAYOUT_START;
+        mLayoutState.mOffset = offset;
+        mLayoutState.mScrollingOffset = LayoutState.SCOLLING_OFFSET_NaN;
+
+    }
+
+    protected boolean isLayoutRTL() {
+        return getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
+    }
+
+    void ensureLayoutState() {
+        if (mLayoutState == null) {
+            mLayoutState = new LayoutState();
+        }
+        if (mOrientationHelper == null) {
+            mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation);
+        }
+    }
+
+    /**
+     * <p>Scroll the RecyclerView to make the position visible.</p>
+     *
+     * <p>RecyclerView will scroll the minimum amount that is necessary to make the
+     * target position visible. If you are looking for a similar behavior to
+     * {@link android.widget.ListView#setSelection(int)} or
+     * {@link android.widget.ListView#setSelectionFromTop(int, int)}, use
+     * {@link #scrollToPositionWithOffset(int, int)}.</p>
+     *
+     * <p>Note that scroll position change will not be reflected until the next layout call.</p>
+     *
+     * @param position Scroll to this adapter position
+     * @see #scrollToPositionWithOffset(int, int)
+     */
+    @Override
+    public void scrollToPosition(int position) {
+        mPendingScrollPosition = position;
+        mPendingScrollPositionOffset = INVALID_OFFSET;
+        if (mPendingSavedState != null) {
+            mPendingSavedState.invalidateAnchor();
+        }
+        requestLayout();
+    }
+
+    /**
+     * Scroll to the specified adapter position with the given offset from resolved layout
+     * start. Resolved layout start depends on {@link #getReverseLayout()},
+     * {@link ViewCompat#getLayoutDirection(android.view.View)} and {@link #getStackFromEnd()}.
+     * <p>
+     * For example, if layout is {@link #VERTICAL} and {@link #getStackFromEnd()} is true, calling
+     * <code>scrollToPositionWithOffset(10, 20)</code> will layout such that
+     * <code>item[10]</code>'s bottom is 20 pixels above the RecyclerView's bottom.
+     * <p>
+     * Note that scroll position change will not be reflected until the next layout call.
+     *
+     * <p>
+     * If you are just trying to make a position visible, use {@link #scrollToPosition(int)}.
+     *
+     * @param position Index (starting at 0) of the reference item.
+     * @param offset   The distance (in pixels) between the start edge of the item view and
+     *                 start edge of the RecyclerView.
+     * @see #setReverseLayout(boolean)
+     * @see #scrollToPosition(int)
+     */
+    public void scrollToPositionWithOffset(int position, int offset) {
+        mPendingScrollPosition = position;
+        mPendingScrollPositionOffset = offset;
+        if (mPendingSavedState != null) {
+            mPendingSavedState.invalidateAnchor();
+        }
+        requestLayout();
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
+            RecyclerView.State state) {
+        if (mOrientation == VERTICAL) {
+            return 0;
+        }
+        return scrollBy(dx, recycler, state);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+            RecyclerView.State state) {
+        if (mOrientation == HORIZONTAL) {
+            return 0;
+        }
+        return scrollBy(dy, recycler, state);
+    }
+
+    @Override
+    public int computeHorizontalScrollOffset(RecyclerView.State state) {
+        return computeScrollOffset(state);
+    }
+
+    @Override
+    public int computeVerticalScrollOffset(RecyclerView.State state) {
+        return computeScrollOffset(state);
+    }
+
+    @Override
+    public int computeHorizontalScrollExtent(RecyclerView.State state) {
+        return computeScrollExtent(state);
+    }
+
+    @Override
+    public int computeVerticalScrollExtent(RecyclerView.State state) {
+        return computeScrollExtent(state);
+    }
+
+    @Override
+    public int computeHorizontalScrollRange(RecyclerView.State state) {
+        return computeScrollRange(state);
+    }
+
+    @Override
+    public int computeVerticalScrollRange(RecyclerView.State state) {
+        return computeScrollRange(state);
+    }
+
+    private int computeScrollOffset(RecyclerView.State state) {
+        if (getChildCount() == 0) {
+            return 0;
+        }
+        return ScrollbarHelper.computeScrollOffset(state, mOrientationHelper,
+                getChildClosestToStart(), getChildClosestToEnd(), this,
+                mSmoothScrollbarEnabled, mShouldReverseLayout);
+    }
+
+    private int computeScrollExtent(RecyclerView.State state) {
+        if (getChildCount() == 0) {
+            return 0;
+        }
+        return ScrollbarHelper.computeScrollExtent(state, mOrientationHelper,
+                getChildClosestToStart(), getChildClosestToEnd(), this,
+                mSmoothScrollbarEnabled);
+    }
+
+    private int computeScrollRange(RecyclerView.State state) {
+        if (getChildCount() == 0) {
+            return 0;
+        }
+        return ScrollbarHelper.computeScrollRange(state, mOrientationHelper,
+                getChildClosestToStart(), getChildClosestToEnd(), this,
+                mSmoothScrollbarEnabled);
+    }
+
+    /**
+     * When smooth scrollbar is enabled, the position and size of the scrollbar thumb is computed
+     * based on the number of visible pixels in the visible items. This however assumes that all
+     * list items have similar or equal widths or heights (depending on list orientation).
+     * If you use a list in which items have different dimensions, the scrollbar will change
+     * appearance as the user scrolls through the list. To avoid this issue,  you need to disable
+     * this property.
+     *
+     * When smooth scrollbar is disabled, the position and size of the scrollbar thumb is based
+     * solely on the number of items in the adapter and the position of the visible items inside
+     * the adapter. This provides a stable scrollbar as the user navigates through a list of items
+     * with varying widths / heights.
+     *
+     * @param enabled Whether or not to enable smooth scrollbar.
+     *
+     * @see #setSmoothScrollbarEnabled(boolean)
+     */
+    public void setSmoothScrollbarEnabled(boolean enabled) {
+        mSmoothScrollbarEnabled = enabled;
+    }
+
+    /**
+     * Returns the current state of the smooth scrollbar feature. It is enabled by default.
+     *
+     * @return True if smooth scrollbar is enabled, false otherwise.
+     *
+     * @see #setSmoothScrollbarEnabled(boolean)
+     */
+    public boolean isSmoothScrollbarEnabled() {
+        return mSmoothScrollbarEnabled;
+    }
+
+    private void updateLayoutState(int layoutDirection, int requiredSpace,
+            boolean canUseExistingSpace, RecyclerView.State state) {
+        mLayoutState.mExtra = getExtraLayoutSpace(state);
+        mLayoutState.mLayoutDirection = layoutDirection;
+        int fastScrollSpace;
+        if (layoutDirection == LayoutState.LAYOUT_END) {
+            mLayoutState.mExtra += mOrientationHelper.getEndPadding();
+            // get the first child in the direction we are going
+            final View child = getChildClosestToEnd();
+            // the direction in which we are traversing children
+            mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
+                    : LayoutState.ITEM_DIRECTION_TAIL;
+            mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
+            mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
+            // calculate how much we can scroll without adding new children (independent of layout)
+            fastScrollSpace = mOrientationHelper.getDecoratedEnd(child)
+                    - mOrientationHelper.getEndAfterPadding();
+
+        } else {
+            final View child = getChildClosestToStart();
+            mLayoutState.mExtra += mOrientationHelper.getStartAfterPadding();
+            mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
+                    : LayoutState.ITEM_DIRECTION_HEAD;
+            mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
+            mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(child);
+            fastScrollSpace = -mOrientationHelper.getDecoratedStart(child)
+                    + mOrientationHelper.getStartAfterPadding();
+        }
+        mLayoutState.mAvailable = requiredSpace;
+        if (canUseExistingSpace) {
+            mLayoutState.mAvailable -= fastScrollSpace;
+        }
+        mLayoutState.mScrollingOffset = fastScrollSpace;
+    }
+
+    private int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
+        if (getChildCount() == 0 || dy == 0) {
+            return 0;
+        }
+        mLayoutState.mRecycle = true;
+        ensureLayoutState();
+        final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
+        final int absDy = Math.abs(dy);
+        updateLayoutState(layoutDirection, absDy, true, state);
+        final int freeScroll = mLayoutState.mScrollingOffset;
+        final int consumed = freeScroll + fill(recycler, mLayoutState, state, false);
+        if (consumed < 0) {
+            if (DEBUG) {
+                Log.d(TAG, "Don't have any more elements to scroll");
+            }
+            return 0;
+        }
+        final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
+        mOrientationHelper.offsetChildren(-scrolled);
+        if (DEBUG) {
+            Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
+        }
+        return scrolled;
+    }
+
+    @Override
+    public void assertNotInLayoutOrScroll(String message) {
+        if (mPendingSavedState == null) {
+            super.assertNotInLayoutOrScroll(message);
+        }
+    }
+
+    /**
+     * Recycles children between given indices.
+     *
+     * @param startIndex inclusive
+     * @param endIndex   exclusive
+     */
+    private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
+        if (startIndex == endIndex) {
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "Recycling " + Math.abs(startIndex - endIndex) + " items");
+        }
+        if (endIndex > startIndex) {
+            for (int i = endIndex - 1; i >= startIndex; i--) {
+                removeAndRecycleViewAt(i, recycler);
+            }
+        } else {
+            for (int i = startIndex; i > endIndex; i--) {
+                removeAndRecycleViewAt(i, recycler);
+            }
+        }
+    }
+
+    /**
+     * Recycles views that went out of bounds after scrolling towards the end of the layout.
+     *
+     * @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView}
+     * @param dt       This can be used to add additional padding to the visible area. This is used
+     *                 to
+     *                 detect children that will go out of bounds after scrolling, without actually
+     *                 moving them.
+     */
+    private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) {
+        if (dt < 0) {
+            if (DEBUG) {
+                Log.d(TAG, "Called recycle from start with a negative value. This might happen"
+                        + " during layout changes but may be sign of a bug");
+            }
+            return;
+        }
+        // ignore padding, ViewGroup may not clip children.
+        final int limit = dt;
+        final int childCount = getChildCount();
+        if (mShouldReverseLayout) {
+            for (int i = childCount - 1; i >= 0; i--) {
+                View child = getChildAt(i);
+                if (mOrientationHelper.getDecoratedEnd(child) > limit) {// stop here
+                    recycleChildren(recycler, childCount - 1, i);
+                    return;
+                }
+            }
+        } else {
+            for (int i = 0; i < childCount; i++) {
+                View child = getChildAt(i);
+                if (mOrientationHelper.getDecoratedEnd(child) > limit) {// stop here
+                    recycleChildren(recycler, 0, i);
+                    return;
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Recycles views that went out of bounds after scrolling towards the start of the layout.
+     *
+     * @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView}
+     * @param dt       This can be used to add additional padding to the visible area. This is used
+     *                 to detect children that will go out of bounds after scrolling, without
+     *                 actually moving them.
+     */
+    private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) {
+        final int childCount = getChildCount();
+        if (dt < 0) {
+            if (DEBUG) {
+                Log.d(TAG, "Called recycle from end with a negative value. This might happen"
+                        + " during layout changes but may be sign of a bug");
+            }
+            return;
+        }
+        final int limit = mOrientationHelper.getEnd() - dt;
+        if (mShouldReverseLayout) {
+            for (int i = 0; i < childCount; i++) {
+                View child = getChildAt(i);
+                if (mOrientationHelper.getDecoratedStart(child) < limit) {// stop here
+                    recycleChildren(recycler, 0, i);
+                    return;
+                }
+            }
+        } else {
+            for (int i = childCount - 1; i >= 0; i--) {
+                View child = getChildAt(i);
+                if (mOrientationHelper.getDecoratedStart(child) < limit) {// stop here
+                    recycleChildren(recycler, childCount - 1, i);
+                    return;
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Helper method to call appropriate recycle method depending on current layout direction
+     *
+     * @param recycler    Current recycler that is attached to RecyclerView
+     * @param layoutState Current layout state. Right now, this object does not change but
+     *                    we may consider moving it out of this view so passing around as a
+     *                    parameter for now, rather than accessing {@link #mLayoutState}
+     * @see #recycleViewsFromStart(android.support.v7.widget.RecyclerView.Recycler, int)
+     * @see #recycleViewsFromEnd(android.support.v7.widget.RecyclerView.Recycler, int)
+     * @see android.support.v7.widget.LinearLayoutManager.LayoutState#mLayoutDirection
+     */
+    private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
+        if (!layoutState.mRecycle) {
+            return;
+        }
+        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
+            recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
+        } else {
+            recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
+        }
+    }
+
+    /**
+     * The magic functions :). Fills the given layout, defined by the layoutState. This is fairly
+     * independent from the rest of the {@link android.support.v7.widget.LinearLayoutManager}
+     * and with little change, can be made publicly available as a helper class.
+     *
+     * @param recycler        Current recycler that is attached to RecyclerView
+     * @param layoutState     Configuration on how we should fill out the available space.
+     * @param state           Context passed by the RecyclerView to control scroll steps.
+     * @param stopOnFocusable If true, filling stops in the first focusable new child
+     * @return Number of pixels that it added. Useful for scoll functions.
+     */
+    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
+            RecyclerView.State state, boolean stopOnFocusable) {
+        // max offset we should set is mFastScroll + available
+        final int start = layoutState.mAvailable;
+        if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {
+            // TODO ugly bug fix. should not happen
+            if (layoutState.mAvailable < 0) {
+                layoutState.mScrollingOffset += layoutState.mAvailable;
+            }
+            recycleByLayoutState(recycler, layoutState);
+        }
+        int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
+        LayoutChunkResult layoutChunkResult = new LayoutChunkResult();
+        while (remainingSpace > 0 && layoutState.hasMore(state)) {
+            layoutChunkResult.resetInternal();
+            layoutChunk(recycler, state, layoutState, layoutChunkResult);
+            if (layoutChunkResult.mFinished) {
+                break;
+            }
+            layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
+            /**
+             * Consume the available space if:
+             * * layoutChunk did not request to be ignored
+             * * OR we are laying out scrap children
+             * * OR we are not doing pre-layout
+             */
+            if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
+                    || !state.isPreLayout()) {
+                layoutState.mAvailable -= layoutChunkResult.mConsumed;
+                // we keep a separate remaining space because mAvailable is important for recycling
+                remainingSpace -= layoutChunkResult.mConsumed;
+            }
+
+            if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {
+                layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
+                if (layoutState.mAvailable < 0) {
+                    layoutState.mScrollingOffset += layoutState.mAvailable;
+                }
+                recycleByLayoutState(recycler, layoutState);
+            }
+            if (stopOnFocusable && layoutChunkResult.mFocusable) {
+                break;
+            }
+        }
+        if (DEBUG) {
+            validateChildOrder();
+        }
+        return start - layoutState.mAvailable;
+    }
+
+    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
+            LayoutState layoutState, LayoutChunkResult result) {
+        View view = layoutState.next(recycler);
+        if (view == null) {
+            if (DEBUG && layoutState.mScrapList == null) {
+                throw new RuntimeException("received null view when unexpected");
+            }
+            // if we are laying out views in scrap, this may return null which means there is
+            // no more items to layout.
+            result.mFinished = true;
+            return;
+        }
+        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
+        if (layoutState.mScrapList == null) {
+            if (mShouldReverseLayout == (layoutState.mLayoutDirection
+                    == LayoutState.LAYOUT_START)) {
+                addView(view);
+            } else {
+                addView(view, 0);
+            }
+        } else {
+            if (mShouldReverseLayout == (layoutState.mLayoutDirection
+                    == LayoutState.LAYOUT_START)) {
+                addDisappearingView(view);
+            } else {
+                addDisappearingView(view, 0);
+            }
+        }
+        measureChildWithMargins(view, 0, 0);
+        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
+        int left, top, right, bottom;
+        if (mOrientation == VERTICAL) {
+            if (isLayoutRTL()) {
+                right = getWidth() - getPaddingRight();
+                left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
+            } else {
+                left = getPaddingLeft();
+                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
+            }
+            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
+                bottom = layoutState.mOffset;
+                top = layoutState.mOffset - result.mConsumed;
+            } else {
+                top = layoutState.mOffset;
+                bottom = layoutState.mOffset + result.mConsumed;
+            }
+        } else {
+            top = getPaddingTop();
+            bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
+
+            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
+                right = layoutState.mOffset;
+                left = layoutState.mOffset - result.mConsumed;
+            } else {
+                left = layoutState.mOffset;
+                right = layoutState.mOffset + result.mConsumed;
+            }
+        }
+        // We calculate everything with View's bounding box (which includes decor and margins)
+        // To calculate correct layout position, we subtract margins.
+        layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
+                right - params.rightMargin, bottom - params.bottomMargin);
+        if (DEBUG) {
+            Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
+                    + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
+                    + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
+        }
+        // Consume the available space if the view is not removed OR changed
+        if (params.isItemRemoved() || params.isItemChanged()) {
+            result.mIgnoreConsumed = true;
+        }
+        result.mFocusable = view.isFocusable();
+    }
+
+    /**
+     * Converts a focusDirection to orientation.
+     *
+     * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+     *                       {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
+     *                       {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
+     *                       or 0 for not applicable
+     * @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction
+     * is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise.
+     */
+    private int convertFocusDirectionToLayoutDirection(int focusDirection) {
+        switch (focusDirection) {
+            case View.FOCUS_BACKWARD:
+                return LayoutState.LAYOUT_START;
+            case View.FOCUS_FORWARD:
+                return LayoutState.LAYOUT_END;
+            case View.FOCUS_UP:
+                return mOrientation == VERTICAL ? LayoutState.LAYOUT_START
+                        : LayoutState.INVALID_LAYOUT;
+            case View.FOCUS_DOWN:
+                return mOrientation == VERTICAL ? LayoutState.LAYOUT_END
+                        : LayoutState.INVALID_LAYOUT;
+            case View.FOCUS_LEFT:
+                return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START
+                        : LayoutState.INVALID_LAYOUT;
+            case View.FOCUS_RIGHT:
+                return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END
+                        : LayoutState.INVALID_LAYOUT;
+            default:
+                if (DEBUG) {
+                    Log.d(TAG, "Unknown focus request:" + focusDirection);
+                }
+                return LayoutState.INVALID_LAYOUT;
+        }
+
+    }
+
+    /**
+     * Convenience method to find the child closes to start. Caller should check it has enough
+     * children.
+     *
+     * @return The child closes to start of the layout from user's perspective.
+     */
+    private View getChildClosestToStart() {
+        return getChildAt(mShouldReverseLayout ? getChildCount() - 1 : 0);
+    }
+
+    /**
+     * Convenience method to find the child closes to end. Caller should check it has enough
+     * children.
+     *
+     * @return The child closes to end of the layout from user's perspective.
+     */
+    private View getChildClosestToEnd() {
+        return getChildAt(mShouldReverseLayout ? 0 : getChildCount() - 1);
+    }
+
+
+    /**
+     * Among the children that are suitable to be considered as an anchor child, returns the one
+     * closest to the end of the layout.
+     * <p>
+     * Due to ambiguous adapter updates or children being removed, some children's positions may be
+     * invalid. This method is a best effort to find a position within adapter bounds if possible.
+     * <p>
+     * It also prioritizes children that are within the visible bounds.
+     * @return A View that can be used an an anchor View.
+     */
+    private View findReferenceChildClosestToEnd(RecyclerView.State state) {
+        return mShouldReverseLayout ? findFirstReferenceChild(state.getItemCount()) :
+                findLastReferenceChild(state.getItemCount());
+    }
+
+    /**
+     * Among the children that are suitable to be considered as an anchor child, returns the one
+     * closest to the start of the layout.
+     * <p>
+     * Due to ambiguous adapter updates or children being removed, some children's positions may be
+     * invalid. This method is a best effort to find a position within adapter bounds if possible.
+     * <p>
+     * It also prioritizes children that are within the visible bounds.
+     *
+     * @return A View that can be used an an anchor View.
+     */
+    private View findReferenceChildClosestToStart(RecyclerView.State state) {
+        return mShouldReverseLayout ? findLastReferenceChild(state.getItemCount()) :
+                findFirstReferenceChild(state.getItemCount());
+    }
+
+    private View findFirstReferenceChild(int itemCount) {
+        return findReferenceChild(0, getChildCount(), itemCount);
+    }
+
+    private View findLastReferenceChild(int itemCount) {
+        return findReferenceChild(getChildCount() - 1, -1, itemCount);
+    }
+
+    private View findReferenceChild(int start, int end, int itemCount) {
+        View invalidMatch = null;
+        View outOfBoundsMatch = null;
+        final int boundsStart = mOrientationHelper.getStartAfterPadding();
+        final int boundsEnd = mOrientationHelper.getEndAfterPadding();
+        final int diff = end > start ? 1 : -1;
+        for (int i = start; i != end; i += diff) {
+            final View view = getChildAt(i);
+            final int position = getPosition(view);
+            if (position >= 0 && position < itemCount) {
+                if (((RecyclerView.LayoutParams) view.getLayoutParams()).isItemRemoved()) {
+                    if (invalidMatch == null) {
+                        invalidMatch = view; // removed item, least preferred
+                    }
+                } else if (mOrientationHelper.getDecoratedStart(view) >= boundsEnd ||
+                        mOrientationHelper.getDecoratedEnd(view) < boundsStart) {
+                    if (outOfBoundsMatch == null) {
+                        outOfBoundsMatch = view; // item is not visible, less preferred
+                    }
+                } else {
+                    return view;
+                }
+            }
+        }
+        return outOfBoundsMatch != null ? outOfBoundsMatch : invalidMatch;
+    }
+
+    /**
+     * Returns the adapter position of the first visible view.
+     * <p>
+     * Note that, this value is not affected by layout orientation or item order traversal.
+     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
+     * not in the layout.
+     * <p>
+     * If RecyclerView has item decorators, they will be considered in calculations as well.
+     * <p>
+     * LinearLayoutManager may pre-cache some views that are not necessarily visible. Those views
+     * are ignored in this method.
+     *
+     * @return The adapter position of the first visible item or {@link RecyclerView#NO_POSITION} if
+     * there aren't any visible items.
+     * @see #findFirstCompletelyVisibleItemPosition()
+     * @see #findLastVisibleItemPosition()
+     */
+    public int findFirstVisibleItemPosition() {
+        final View child = findOneVisibleChild(0, getChildCount(), false);
+        return child == null ? NO_POSITION : getPosition(child);
+    }
+
+    /**
+     * Returns the adapter position of the first fully visible view.
+     * <p>
+     * Note that bounds check is only performed in the current orientation. That means, if
+     * LinearLayoutManager is horizontal, it will only check the view's left and right edges.
+     *
+     * @return The adapter position of the first fully visible item or
+     * {@link RecyclerView#NO_POSITION} if there aren't any visible items.
+     * @see #findFirstVisibleItemPosition()
+     * @see #findLastCompletelyVisibleItemPosition()
+     */
+    public int findFirstCompletelyVisibleItemPosition() {
+        final View child = findOneVisibleChild(0, getChildCount(), true);
+        return child == null ? NO_POSITION : getPosition(child);
+    }
+
+    /**
+     * Returns the adapter position of the last visible view.
+     * <p>
+     * Note that, this value is not affected by layout orientation or item order traversal.
+     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
+     * not in the layout.
+     * <p>
+     * If RecyclerView has item decorators, they will be considered in calculations as well.
+     * <p>
+     * LinearLayoutManager may pre-cache some views that are not necessarily visible. Those views
+     * are ignored in this method.
+     *
+     * @return The adapter position of the last visible view or {@link RecyclerView#NO_POSITION} if
+     * there aren't any visible items.
+     * @see #findLastCompletelyVisibleItemPosition()
+     * @see #findFirstVisibleItemPosition()
+     */
+    public int findLastVisibleItemPosition() {
+        final View child = findOneVisibleChild(getChildCount() - 1, -1, false);
+        return child == null ? NO_POSITION : getPosition(child);
+    }
+
+    /**
+     * Returns the adapter position of the last fully visible view.
+     * <p>
+     * Note that bounds check is only performed in the current orientation. That means, if
+     * LinearLayoutManager is horizontal, it will only check the view's left and right edges.
+     *
+     * @return The adapter position of the last fully visible view or
+     * {@link RecyclerView#NO_POSITION} if there aren't any visible items.
+     * @see #findLastVisibleItemPosition()
+     * @see #findFirstCompletelyVisibleItemPosition()
+     */
+    public int findLastCompletelyVisibleItemPosition() {
+        final View child = findOneVisibleChild(getChildCount() - 1, -1, true);
+        return child == null ? NO_POSITION : getPosition(child);
+    }
+
+    View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) {
+        final int start = mOrientationHelper.getStartAfterPadding();
+        final int end = mOrientationHelper.getEndAfterPadding();
+        final int next = toIndex > fromIndex ? 1 : -1;
+        for (int i = fromIndex; i != toIndex; i+=next) {
+            final View child = getChildAt(i);
+            final int childStart = mOrientationHelper.getDecoratedStart(child);
+            final int childEnd = mOrientationHelper.getDecoratedEnd(child);
+            if (childStart < end && childEnd > start) {
+                if (completelyVisible) {
+                    if (childStart >= start && childEnd <= end) {
+                        return child;
+                    }
+                } else {
+                    return child;
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public View onFocusSearchFailed(View focused, int focusDirection,
+            RecyclerView.Recycler recycler, RecyclerView.State state) {
+        resolveShouldLayoutReverse();
+        if (getChildCount() == 0) {
+            return null;
+        }
+
+        final int layoutDir = convertFocusDirectionToLayoutDirection(focusDirection);
+        if (layoutDir == LayoutState.INVALID_LAYOUT) {
+            return null;
+        }
+        final View referenceChild;
+        if (layoutDir == LayoutState.LAYOUT_START) {
+            referenceChild = findReferenceChildClosestToStart(state);
+        } else {
+            referenceChild = findReferenceChildClosestToEnd(state);
+        }
+        if (referenceChild == null) {
+            if (DEBUG) {
+                Log.d(TAG,
+                        "Cannot find a child with a valid position to be used for focus search.");
+            }
+            return null;
+        }
+        ensureLayoutState();
+        final int maxScroll = (int) (MAX_SCROLL_FACTOR * mOrientationHelper.getTotalSpace());
+        updateLayoutState(layoutDir, maxScroll, false, state);
+        mLayoutState.mScrollingOffset = LayoutState.SCOLLING_OFFSET_NaN;
+        mLayoutState.mRecycle = false;
+        fill(recycler, mLayoutState, state, true);
+        final View nextFocus;
+        if (layoutDir == LayoutState.LAYOUT_START) {
+            nextFocus = getChildClosestToStart();
+        } else {
+            nextFocus = getChildClosestToEnd();
+        }
+        if (nextFocus == referenceChild || !nextFocus.isFocusable()) {
+            return null;
+        }
+        return nextFocus;
+    }
+
+    /**
+     * Used for debugging.
+     * Logs the internal representation of children to default logger.
+     */
+    private void logChildren() {
+        Log.d(TAG, "internal representation of views on the screen");
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            Log.d(TAG, "item " + getPosition(child) + ", coord:"
+                    + mOrientationHelper.getDecoratedStart(child));
+        }
+        Log.d(TAG, "==============");
+    }
+
+    /**
+     * Used for debugging.
+     * Validates that child views are laid out in correct order. This is important because rest of
+     * the algorithm relies on this constraint.
+     *
+     * In default layout, child 0 should be closest to screen position 0 and last child should be
+     * closest to position WIDTH or HEIGHT.
+     * In reverse layout, last child should be closes to screen position 0 and first child should
+     * be closest to position WIDTH  or HEIGHT
+     */
+    void validateChildOrder() {
+        Log.d(TAG, "validating child count " + getChildCount());
+        if (getChildCount() < 1) {
+            return;
+        }
+        int lastPos = getPosition(getChildAt(0));
+        int lastScreenLoc = mOrientationHelper.getDecoratedStart(getChildAt(0));
+        if (mShouldReverseLayout) {
+            for (int i = 1; i < getChildCount(); i++) {
+                View child = getChildAt(i);
+                int pos = getPosition(child);
+                int screenLoc = mOrientationHelper.getDecoratedStart(child);
+                if (pos < lastPos) {
+                    logChildren();
+                    throw new RuntimeException("detected invalid position. loc invalid? " +
+                            (screenLoc < lastScreenLoc));
+                }
+                if (screenLoc > lastScreenLoc) {
+                    logChildren();
+                    throw new RuntimeException("detected invalid location");
+                }
+            }
+        } else {
+            for (int i = 1; i < getChildCount(); i++) {
+                View child = getChildAt(i);
+                int pos = getPosition(child);
+                int screenLoc = mOrientationHelper.getDecoratedStart(child);
+                if (pos < lastPos) {
+                    logChildren();
+                    throw new RuntimeException("detected invalid position. loc invalid? " +
+                            (screenLoc < lastScreenLoc));
+                }
+                if (screenLoc < lastScreenLoc) {
+                    logChildren();
+                    throw new RuntimeException("detected invalid location");
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean supportsPredictiveItemAnimations() {
+        return mPendingSavedState == null && mLastStackFromEnd == mStackFromEnd;
+    }
+
+    /**
+     * Helper class that keeps temporary state while {LayoutManager} is filling out the empty
+     * space.
+     */
+    static class LayoutState {
+
+        final static String TAG = "LinearLayoutManager#LayoutState";
+
+        final static int LAYOUT_START = -1;
+
+        final static int LAYOUT_END = 1;
+
+        final static int INVALID_LAYOUT = Integer.MIN_VALUE;
+
+        final static int ITEM_DIRECTION_HEAD = -1;
+
+        final static int ITEM_DIRECTION_TAIL = 1;
+
+        final static int SCOLLING_OFFSET_NaN = Integer.MIN_VALUE;
+
+        /**
+         * We may not want to recycle children in some cases (e.g. layout)
+         */
+        boolean mRecycle = true;
+
+        /**
+         * Pixel offset where layout should start
+         */
+        int mOffset;
+
+        /**
+         * Number of pixels that we should fill, in the layout direction.
+         */
+        int mAvailable;
+
+        /**
+         * Current position on the adapter to get the next item.
+         */
+        int mCurrentPosition;
+
+        /**
+         * Defines the direction in which the data adapter is traversed.
+         * Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL}
+         */
+        int mItemDirection;
+
+        /**
+         * Defines the direction in which the layout is filled.
+         * Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
+         */
+        int mLayoutDirection;
+
+        /**
+         * Used when LayoutState is constructed in a scrolling state.
+         * It should be set the amount of scrolling we can make without creating a new view.
+         * Settings this is required for efficient view recycling.
+         */
+        int mScrollingOffset;
+
+        /**
+         * Used if you want to pre-layout items that are not yet visible.
+         * The difference with {@link #mAvailable} is that, when recycling, distance laid out for
+         * {@link #mExtra} is not considered to avoid recycling visible children.
+         */
+        int mExtra = 0;
+
+        /**
+         * Equal to {@link RecyclerView.State#isPreLayout()}. When consuming scrap, if this value
+         * is set to true, we skip removed views since they should not be laid out in post layout
+         * step.
+         */
+        boolean mIsPreLayout = false;
+
+        /**
+         * When LLM needs to layout particular views, it sets this list in which case, LayoutState
+         * will only return views from this list and return null if it cannot find an item.
+         */
+        List<RecyclerView.ViewHolder> mScrapList = null;
+
+        /**
+         * @return true if there are more items in the data adapter
+         */
+        boolean hasMore(RecyclerView.State state) {
+            return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();
+        }
+
+        /**
+         * Gets the view for the next element that we should layout.
+         * Also updates current item index to the next item, based on {@link #mItemDirection}
+         *
+         * @return The next element that we should layout.
+         */
+        View next(RecyclerView.Recycler recycler) {
+            if (mScrapList != null) {
+                return nextFromLimitedList();
+            }
+            final View view = recycler.getViewForPosition(mCurrentPosition);
+            mCurrentPosition += mItemDirection;
+            return view;
+        }
+
+        /**
+         * Returns next item from limited list.
+         * <p>
+         * Upon finding a valid VH, sets current item position to VH.itemPosition + mItemDirection
+         *
+         * @return View if an item in the current position or direction exists if not null.
+         */
+        private View nextFromLimitedList() {
+            int size = mScrapList.size();
+            RecyclerView.ViewHolder closest = null;
+            int closestDistance = Integer.MAX_VALUE;
+            for (int i = 0; i < size; i++) {
+                RecyclerView.ViewHolder viewHolder = mScrapList.get(i);
+                if (!mIsPreLayout && viewHolder.isRemoved()) {
+                    continue;
+                }
+                final int distance = (viewHolder.getPosition() - mCurrentPosition) * mItemDirection;
+                if (distance < 0) {
+                    continue; // item is not in current direction
+                }
+                if (distance < closestDistance) {
+                    closest = viewHolder;
+                    closestDistance = distance;
+                    if (distance == 0) {
+                        break;
+                    }
+                }
+            }
+            if (DEBUG) {
+                Log.d(TAG, "layout from scrap. found view:?" + (closest != null));
+            }
+            if (closest != null) {
+                mCurrentPosition = closest.getPosition() + mItemDirection;
+                return closest.itemView;
+            }
+            return null;
+        }
+
+        void log() {
+            Log.d(TAG, "avail:" + mAvailable + ", ind:" + mCurrentPosition + ", dir:" +
+                    mItemDirection + ", offset:" + mOffset + ", layoutDir:" + mLayoutDirection);
+        }
+    }
+
+    static class SavedState implements Parcelable {
+
+        int mAnchorPosition;
+
+        int mAnchorOffset;
+
+        boolean mAnchorLayoutFromEnd;
+
+        public SavedState() {
+
+        }
+
+        SavedState(Parcel in) {
+            mAnchorPosition = in.readInt();
+            mAnchorOffset = in.readInt();
+            mAnchorLayoutFromEnd = in.readInt() == 1;
+        }
+
+        public SavedState(SavedState other) {
+            mAnchorPosition = other.mAnchorPosition;
+            mAnchorOffset = other.mAnchorOffset;
+            mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd;
+        }
+
+        boolean hasValidAnchor() {
+            return mAnchorPosition >= 0;
+        }
+
+        void invalidateAnchor() {
+            mAnchorPosition = NO_POSITION;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mAnchorPosition);
+            dest.writeInt(mAnchorOffset);
+            dest.writeInt(mAnchorLayoutFromEnd ? 1 : 0);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            @Override
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            @Override
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    /**
+     * Simple data class to keep Anchor information
+     */
+    class AnchorInfo {
+        int mPosition;
+        int mCoordinate;
+        boolean mLayoutFromEnd;
+        void reset() {
+            mPosition = NO_POSITION;
+            mCoordinate = INVALID_OFFSET;
+            mLayoutFromEnd = false;
+        }
+
+        /**
+         * assigns anchor coordinate from the RecyclerView's padding depending on current
+         * layoutFromEnd value
+         */
+        void assignCoordinateFromPadding() {
+            mCoordinate = mLayoutFromEnd
+                    ? mOrientationHelper.getEndAfterPadding()
+                    : mOrientationHelper.getStartAfterPadding();
+        }
+
+        @Override
+        public String toString() {
+            return "AnchorInfo{" +
+                    "mPosition=" + mPosition +
+                    ", mCoordinate=" + mCoordinate +
+                    ", mLayoutFromEnd=" + mLayoutFromEnd +
+                    '}';
+        }
+
+        /**
+         * Assign anchor position information from the provided view if it is valid as a reference
+         * child.
+         */
+        public boolean assignFromViewIfValid(View child, RecyclerView.State state) {
+            RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
+            if (!lp.isItemRemoved() && lp.getViewPosition() >= 0
+                    && lp.getViewPosition() < state.getItemCount()) {
+                assignFromView(child);
+                return true;
+            }
+            return false;
+        }
+
+        public void assignFromView(View child) {
+            if (mLayoutFromEnd) {
+                mCoordinate = mOrientationHelper.getDecoratedEnd(child) +
+                        mOrientationHelper.getTotalSpaceChange();
+            } else {
+                mCoordinate = mOrientationHelper.getDecoratedStart(child);
+            }
+
+            mPosition = getPosition(child);
+        }
+    }
+
+    protected static class LayoutChunkResult {
+        public int mConsumed;
+        public boolean mFinished;
+        public boolean mIgnoreConsumed;
+        public boolean mFocusable;
+
+        void resetInternal() {
+            mConsumed = 0;
+            mFinished = false;
+            mIgnoreConsumed = false;
+            mFocusable = false;
+        }
+    }
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearSmoothScroller.java b/v7/recyclerview/src/android/support/v7/widget/LinearSmoothScroller.java
new file mode 100644
index 0000000..ed4c950
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearSmoothScroller.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+
+/**
+ * {@link RecyclerView.SmoothScroller} implementation which uses
+ * {@link android.view.animation.LinearInterpolator} until the target position becames a child of
+ * the RecyclerView and then uses
+ * {@link android.view.animation.DecelerateInterpolator} to slowly approach to target position.
+ */
+abstract public class LinearSmoothScroller extends RecyclerView.SmoothScroller {
+
+    private static final String TAG = "LinearSmoothScroller";
+
+    private static final boolean DEBUG = false;
+
+    private static final float MILLISECONDS_PER_INCH = 25f;
+
+    private static final int TARGET_SEEK_SCROLL_DISTANCE_PX = 10000;
+
+    /**
+     * Align child view's left or top with parent view's left or top
+     *
+     * @see #calculateDtToFit(int, int, int, int, int)
+     * @see #calculateDxToMakeVisible(android.view.View, int)
+     * @see #calculateDyToMakeVisible(android.view.View, int)
+     */
+    public static final int SNAP_TO_START = -1;
+
+    /**
+     * Align child view's right or bottom with parent view's right or bottom
+     *
+     * @see #calculateDtToFit(int, int, int, int, int)
+     * @see #calculateDxToMakeVisible(android.view.View, int)
+     * @see #calculateDyToMakeVisible(android.view.View, int)
+     */
+    public static final int SNAP_TO_END = 1;
+
+    /**
+     * <p>Decides if the child should be snapped from start or end, depending on where it
+     * currently is in relation to its parent.</p>
+     * <p>For instance, if the view is virtually on the left of RecyclerView, using
+     * {@code SNAP_TO_ANY} is the same as using {@code SNAP_TO_START}</p>
+     *
+     * @see #calculateDtToFit(int, int, int, int, int)
+     * @see #calculateDxToMakeVisible(android.view.View, int)
+     * @see #calculateDyToMakeVisible(android.view.View, int)
+     */
+    public static final int SNAP_TO_ANY = 0;
+
+    // Trigger a scroll to a further distance than TARGET_SEEK_SCROLL_DISTANCE_PX so that if target
+    // view is not laid out until interim target position is reached, we can detect the case before
+    // scrolling slows down and reschedule another interim target scroll
+    private static final float TARGET_SEEK_EXTRA_SCROLL_RATIO = 1.2f;
+
+    protected final LinearInterpolator mLinearInterpolator = new LinearInterpolator();
+
+    protected final DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
+
+    protected PointF mTargetVector;
+
+    private final float MILLISECONDS_PER_PX;
+
+    // Temporary variables to keep track of the interim scroll target. These values do not
+    // point to a real item position, rather point to an estimated location pixels.
+    protected int mInterimTargetDx = 0, mInterimTargetDy = 0;
+
+    public LinearSmoothScroller(Context context) {
+        MILLISECONDS_PER_PX = calculateSpeedPerPixel(context.getResources().getDisplayMetrics());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onStart() {
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
+        final int dx = calculateDxToMakeVisible(targetView, getHorizontalSnapPreference());
+        final int dy = calculateDyToMakeVisible(targetView, getVerticalSnapPreference());
+        final int distance = (int) Math.sqrt(dx * dx + dy * dy);
+        final int time = calculateTimeForDeceleration(distance);
+        if (time > 0) {
+            action.update(-dx, -dy, time, mDecelerateInterpolator);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onSeekTargetStep(int dx, int dy, RecyclerView.State state, Action action) {
+        if (getChildCount() == 0) {
+            stop();
+            return;
+        }
+        if (DEBUG && mTargetVector != null
+                && ((mTargetVector.x * dx < 0 || mTargetVector.y * dy < 0))) {
+            throw new IllegalStateException("Scroll happened in the opposite direction"
+                    + " of the target. Some calculations are wrong");
+        }
+        mInterimTargetDx = clampApplyScroll(mInterimTargetDx, dx);
+        mInterimTargetDy = clampApplyScroll(mInterimTargetDy, dy);
+
+        if (mInterimTargetDx == 0 && mInterimTargetDy == 0) {
+            updateActionForInterimTarget(action);
+        } // everything is valid, keep going
+
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void onStop() {
+        mInterimTargetDx = mInterimTargetDy = 0;
+        mTargetVector = null;
+    }
+
+    /**
+     * Calculates the scroll speed.
+     *
+     * @param displayMetrics DisplayMetrics to be used for real dimension calculations
+     * @return The time (in ms) it should take for each pixel. For instance, if returned value is
+     * 2 ms, it means scrolling 1000 pixels with LinearInterpolation should take 2 seconds.
+     */
+    protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
+        return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
+    }
+
+    /**
+     * <p>Calculates the time for deceleration so that transition from LinearInterpolator to
+     * DecelerateInterpolator looks smooth.</p>
+     *
+     * @param dx Distance to scroll
+     * @return Time for DecelerateInterpolator to smoothly traverse the distance when transitioning
+     * from LinearInterpolation
+     */
+    protected int calculateTimeForDeceleration(int dx) {
+        // we want to cover same area with the linear interpolator for the first 10% of the
+        // interpolation. After that, deceleration will take control.
+        // area under curve (1-(1-x)^2) can be calculated as (1 - x/3) * x * x
+        // which gives 0.100028 when x = .3356
+        // this is why we divide linear scrolling time with .3356
+        return  (int) Math.ceil(calculateTimeForScrolling(dx) / .3356);
+    }
+
+    /**
+     * Calculates the time it should take to scroll the given distance (in pixels)
+     *
+     * @param dx Distance in pixels that we want to scroll
+     * @return Time in milliseconds
+     * @see #calculateSpeedPerPixel(android.util.DisplayMetrics)
+     */
+    protected int calculateTimeForScrolling(int dx) {
+        // In a case where dx is very small, rounding may return 0 although dx > 0.
+        // To avoid that issue, ceil the result so that if dx > 0, we'll always return positive
+        // time.
+        return (int) Math.ceil(Math.abs(dx) * MILLISECONDS_PER_PX);
+    }
+
+    /**
+     * When scrolling towards a child view, this method defines whether we should align the left
+     * or the right edge of the child with the parent RecyclerView.
+     *
+     * @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
+     * @see #SNAP_TO_START
+     * @see #SNAP_TO_END
+     * @see #SNAP_TO_ANY
+     */
+    protected int getHorizontalSnapPreference() {
+        return mTargetVector == null || mTargetVector.x == 0 ? SNAP_TO_ANY :
+                mTargetVector.x > 0 ? SNAP_TO_END : SNAP_TO_START;
+    }
+
+    /**
+     * When scrolling towards a child view, this method defines whether we should align the top
+     * or the bottom edge of the child with the parent RecyclerView.
+     *
+     * @return SNAP_TO_START, SNAP_TO_END or SNAP_TO_ANY; depending on the current target vector
+     * @see #SNAP_TO_START
+     * @see #SNAP_TO_END
+     * @see #SNAP_TO_ANY
+     */
+    protected int getVerticalSnapPreference() {
+        return mTargetVector == null || mTargetVector.y == 0 ? SNAP_TO_ANY :
+                mTargetVector.y > 0 ? SNAP_TO_END : SNAP_TO_START;
+    }
+
+    /**
+     * When the target scroll position is not a child of the RecyclerView, this method calculates
+     * a direction vector towards that child and triggers a smooth scroll.
+     *
+     * @see #computeScrollVectorForPosition(int)
+     */
+    protected void updateActionForInterimTarget(Action action) {
+        // find an interim target position
+        PointF scrollVector = computeScrollVectorForPosition(getTargetPosition());
+        if (scrollVector == null || (scrollVector.x == 0 && scrollVector.y == 0)) {
+            Log.e(TAG, "To support smooth scrolling, you should override \n"
+                    + "LayoutManager#computeScrollVectorForPosition.\n"
+                    + "Falling back to instant scroll");
+            final int target = getTargetPosition();
+            stop();
+            instantScrollToPosition(target);
+            return;
+        }
+        normalize(scrollVector);
+        mTargetVector = scrollVector;
+
+        mInterimTargetDx = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.x);
+        mInterimTargetDy = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.y);
+        final int time = calculateTimeForScrolling(TARGET_SEEK_SCROLL_DISTANCE_PX);
+        // To avoid UI hiccups, trigger a smooth scroll to a distance little further than the
+        // interim target. Since we track the distance travelled in onSeekTargetStep callback, it
+        // won't actually scroll more than what we need.
+        action.update((int) (mInterimTargetDx * TARGET_SEEK_EXTRA_SCROLL_RATIO)
+                , (int) (mInterimTargetDy * TARGET_SEEK_EXTRA_SCROLL_RATIO)
+                , (int) (time * TARGET_SEEK_EXTRA_SCROLL_RATIO), mLinearInterpolator);
+    }
+
+    private int clampApplyScroll(int tmpDt, int dt) {
+        final int before = tmpDt;
+        tmpDt -= dt;
+        if (before * tmpDt <= 0) { // changed sign, reached 0 or was 0, reset
+            return 0;
+        }
+        return tmpDt;
+    }
+
+    /**
+     * Helper method for {@link #calculateDxToMakeVisible(android.view.View, int)} and
+     * {@link #calculateDyToMakeVisible(android.view.View, int)}
+     */
+    public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int
+            snapPreference) {
+        switch (snapPreference) {
+            case SNAP_TO_START:
+                return boxStart - viewStart;
+            case SNAP_TO_END:
+                return boxEnd - viewEnd;
+            case SNAP_TO_ANY:
+                final int dtStart = boxStart - viewStart;
+                if (dtStart > 0) {
+                    return dtStart;
+                }
+                final int dtEnd = boxEnd - viewEnd;
+                if (dtEnd < 0) {
+                    return dtEnd;
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("snap preference should be one of the"
+                        + " constants defined in SmoothScroller, starting with SNAP_");
+        }
+        return 0;
+    }
+
+    /**
+     * Calculates the vertical scroll amount necessary to make the given view fully visible
+     * inside the RecyclerView.
+     *
+     * @param view           The view which we want to make fully visible
+     * @param snapPreference The edge which the view should snap to when entering the visible
+     *                       area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or
+     *                       {@link #SNAP_TO_END}.
+     * @return The vertical scroll amount necessary to make the view visible with the given
+     * snap preference.
+     */
+    public int calculateDyToMakeVisible(View view, int snapPreference) {
+        final RecyclerView.LayoutManager layoutManager = getLayoutManager();
+        if (!layoutManager.canScrollVertically()) {
+            return 0;
+        }
+        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                view.getLayoutParams();
+        final int top = layoutManager.getDecoratedTop(view) - params.topMargin;
+        final int bottom = layoutManager.getDecoratedBottom(view) + params.bottomMargin;
+        final int start = layoutManager.getPaddingTop();
+        final int end = layoutManager.getHeight() - layoutManager.getPaddingBottom();
+        return calculateDtToFit(top, bottom, start, end, snapPreference);
+    }
+
+    /**
+     * Calculates the horizontal scroll amount necessary to make the given view fully visible
+     * inside the RecyclerView.
+     *
+     * @param view           The view which we want to make fully visible
+     * @param snapPreference The edge which the view should snap to when entering the visible
+     *                       area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or
+     *                       {@link #SNAP_TO_END}
+     * @return The vertical scroll amount necessary to make the view visible with the given
+     * snap preference.
+     */
+    public int calculateDxToMakeVisible(View view, int snapPreference) {
+        final RecyclerView.LayoutManager layoutManager = getLayoutManager();
+        if (!layoutManager.canScrollHorizontally()) {
+            return 0;
+        }
+        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                view.getLayoutParams();
+        final int left = layoutManager.getDecoratedLeft(view) - params.leftMargin;
+        final int right = layoutManager.getDecoratedRight(view) + params.rightMargin;
+        final int start = layoutManager.getPaddingLeft();
+        final int end = layoutManager.getWidth() - layoutManager.getPaddingRight();
+        return calculateDtToFit(left, right, start, end, snapPreference);
+    }
+
+    abstract public PointF computeScrollVectorForPosition(int targetPosition);
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/OrientationHelper.java b/v7/recyclerview/src/android/support/v7/widget/OrientationHelper.java
new file mode 100644
index 0000000..8ca9851
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/widget/OrientationHelper.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.view.View;
+import android.widget.LinearLayout;
+
+/**
+ * Helper class for LayoutManagers to abstract measurements depending on the View's orientation.
+ * <p>
+ * It is developed to easily support vertical and horizontal orientations in a LayoutManager but
+ * can also be used to abstract calls around view bounds and child measurements with margins and
+ * decorations.
+ *
+ * @see #createHorizontalHelper(RecyclerView.LayoutManager)
+ * @see #createVerticalHelper(RecyclerView.LayoutManager)
+ */
+public abstract class OrientationHelper {
+
+    private static final int INVALID_SIZE = Integer.MIN_VALUE;
+
+    protected final RecyclerView.LayoutManager mLayoutManager;
+
+    public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
+
+    public static final int VERTICAL = LinearLayout.VERTICAL;
+
+    private int mLastTotalSpace = INVALID_SIZE;
+
+    private OrientationHelper(RecyclerView.LayoutManager layoutManager) {
+        mLayoutManager = layoutManager;
+    }
+
+    /**
+     * Call this method after onLayout method is complete if state is NOT pre-layout.
+     * This method records information like layout bounds that might be useful in the next layout
+     * calculations.
+     */
+    public void onLayoutComplete() {
+        mLastTotalSpace = getTotalSpace();
+    }
+
+    /**
+     * Returns the layout space change between the previous layout pass and current layout pass.
+     * <p>
+     * Make sure you call {@link #onLayoutComplete()} at the end of your LayoutManager's
+     * {@link RecyclerView.LayoutManager#onLayoutChildren(RecyclerView.Recycler,
+     * RecyclerView.State)} method.
+     *
+     * @return The difference between the current total space and previous layout's total space.
+     * @see #onLayoutComplete()
+     */
+    public int getTotalSpaceChange() {
+        return INVALID_SIZE == mLastTotalSpace ? 0 : getTotalSpace() - mLastTotalSpace;
+    }
+
+    /**
+     * Returns the start of the view including its decoration and margin.
+     * <p>
+     * For example, for the horizontal helper, if a View's left is at pixel 20, has 2px left
+     * decoration and 3px left margin, returned value will be 15px.
+     *
+     * @param view The view element to check
+     * @return The first pixel of the element
+     * @see #getDecoratedEnd(android.view.View)
+     */
+    public abstract int getDecoratedStart(View view);
+
+    /**
+     * Returns the end of the view including its decoration and margin.
+     * <p>
+     * For example, for the horizontal helper, if a View's right is at pixel 200, has 2px right
+     * decoration and 3px right margin, returned value will be 205.
+     *
+     * @param view The view element to check
+     * @return The last pixel of the element
+     * @see #getDecoratedStart(android.view.View)
+     */
+    public abstract int getDecoratedEnd(View view);
+
+    /**
+     * Returns the space occupied by this View in the current orientation including decorations and
+     * margins.
+     *
+     * @param view The view element to check
+     * @return Total space occupied by this view
+     * @see #getDecoratedMeasurementInOther(View)
+     */
+    public abstract int getDecoratedMeasurement(View view);
+
+    /**
+     * Returns the space occupied by this View in the perpendicular orientation including
+     * decorations and margins.
+     *
+     * @param view The view element to check
+     * @return Total space occupied by this view in the perpendicular orientation to current one
+     * @see #getDecoratedMeasurement(View)
+     */
+    public abstract int getDecoratedMeasurementInOther(View view);
+
+    /**
+     * Returns the start position of the layout after the start padding is added.
+     *
+     * @return The very first pixel we can draw.
+     */
+    public abstract int getStartAfterPadding();
+
+    /**
+     * Returns the end position of the layout after the end padding is removed.
+     *
+     * @return The end boundary for this layout.
+     */
+    public abstract int getEndAfterPadding();
+
+    /**
+     * Returns the end position of the layout without taking padding into account.
+     *
+     * @return The end boundary for this layout without considering padding.
+     */
+    public abstract int getEnd();
+
+    /**
+     * Offsets all children's positions by the given amount.
+     *
+     * @param amount Value to add to each child's layout parameters
+     */
+    public abstract void offsetChildren(int amount);
+
+    /**
+     * Returns the total space to layout. This number is the difference between
+     * {@link #getEndAfterPadding()} and {@link #getStartAfterPadding()}.
+     *
+     * @return Total space to layout children
+     */
+    public abstract int getTotalSpace();
+
+    /**
+     * Offsets the child in this orientation.
+     *
+     * @param view   View to offset
+     * @param offset offset amount
+     */
+    public abstract void offsetChild(View view, int offset);
+
+    /**
+     * Returns the padding at the end of the layout. For horizontal helper, this is the right
+     * padding and for vertical helper, this is the bottom padding. This method does not check
+     * whether the layout is RTL or not.
+     *
+     * @return The padding at the end of the layout.
+     */
+    public abstract int getEndPadding();
+
+    /**
+     * Creates an OrientationHelper for the given LayoutManager and orientation.
+     *
+     * @param layoutManager LayoutManager to attach to
+     * @param orientation   Desired orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}
+     * @return A new OrientationHelper
+     */
+    public static OrientationHelper createOrientationHelper(
+            RecyclerView.LayoutManager layoutManager, int orientation) {
+        switch (orientation) {
+            case HORIZONTAL:
+                return createHorizontalHelper(layoutManager);
+            case VERTICAL:
+                return createVerticalHelper(layoutManager);
+        }
+        throw new IllegalArgumentException("invalid orientation");
+    }
+
+    /**
+     * Creates a horizontal OrientationHelper for the given LayoutManager.
+     *
+     * @param layoutManager The LayoutManager to attach to.
+     * @return A new OrientationHelper
+     */
+    public static OrientationHelper createHorizontalHelper(
+            RecyclerView.LayoutManager layoutManager) {
+        return new OrientationHelper(layoutManager) {
+            @Override
+            public int getEndAfterPadding() {
+                return mLayoutManager.getWidth() - mLayoutManager.getPaddingRight();
+            }
+
+            @Override
+            public int getEnd() {
+                return mLayoutManager.getWidth();
+            }
+
+            @Override
+            public void offsetChildren(int amount) {
+                mLayoutManager.offsetChildrenHorizontal(amount);
+            }
+
+            @Override
+            public int getStartAfterPadding() {
+                return mLayoutManager.getPaddingLeft();
+            }
+
+            @Override
+            public int getDecoratedMeasurement(View view) {
+                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                        view.getLayoutParams();
+                return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
+                        + params.rightMargin;
+            }
+
+            @Override
+            public int getDecoratedMeasurementInOther(View view) {
+                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                        view.getLayoutParams();
+                return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
+                        + params.bottomMargin;
+            }
+
+            @Override
+            public int getDecoratedEnd(View view) {
+                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                        view.getLayoutParams();
+                return mLayoutManager.getDecoratedRight(view) + params.rightMargin;
+            }
+
+            @Override
+            public int getDecoratedStart(View view) {
+                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                        view.getLayoutParams();
+                return mLayoutManager.getDecoratedLeft(view) - params.leftMargin;
+            }
+
+            @Override
+            public int getTotalSpace() {
+                return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft()
+                        - mLayoutManager.getPaddingRight();
+            }
+
+            @Override
+            public void offsetChild(View view, int offset) {
+                view.offsetLeftAndRight(offset);
+            }
+
+            @Override
+            public int getEndPadding() {
+                return mLayoutManager.getPaddingRight();
+            }
+        };
+    }
+
+    /**
+     * Creates a vertical OrientationHelper for the given LayoutManager.
+     *
+     * @param layoutManager The LayoutManager to attach to.
+     * @return A new OrientationHelper
+     */
+    public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) {
+        return new OrientationHelper(layoutManager) {
+            @Override
+            public int getEndAfterPadding() {
+                return mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom();
+            }
+
+            @Override
+            public int getEnd() {
+                return mLayoutManager.getHeight();
+            }
+
+            @Override
+            public void offsetChildren(int amount) {
+                mLayoutManager.offsetChildrenVertical(amount);
+            }
+
+            @Override
+            public int getStartAfterPadding() {
+                return mLayoutManager.getPaddingTop();
+            }
+
+            @Override
+            public int getDecoratedMeasurement(View view) {
+                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                        view.getLayoutParams();
+                return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin
+                        + params.bottomMargin;
+            }
+
+            @Override
+            public int getDecoratedMeasurementInOther(View view) {
+                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                        view.getLayoutParams();
+                return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin
+                        + params.rightMargin;
+            }
+
+            @Override
+            public int getDecoratedEnd(View view) {
+                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                        view.getLayoutParams();
+                return mLayoutManager.getDecoratedBottom(view) + params.bottomMargin;
+            }
+
+            @Override
+            public int getDecoratedStart(View view) {
+                final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
+                        view.getLayoutParams();
+                return mLayoutManager.getDecoratedTop(view) - params.topMargin;
+            }
+
+            @Override
+            public int getTotalSpace() {
+                return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop()
+                        - mLayoutManager.getPaddingBottom();
+            }
+
+            @Override
+            public void offsetChild(View view, int offset) {
+                view.offsetTopAndBottom(offset);
+            }
+
+            @Override
+            public int getEndPadding() {
+                return mLayoutManager.getPaddingBottom();
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/v7/recyclerview/src/android/support/v7/widget/PositionMap.java b/v7/recyclerview/src/android/support/v7/widget/PositionMap.java
new file mode 100644
index 0000000..3777937
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/widget/PositionMap.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2013 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.widget;
+
+import java.util.ArrayList;
+
+/**
+ * Like a SparseArray, but with the ability to offset key ranges for bulk insertions/deletions.
+ */
+class PositionMap<E> implements Cloneable {
+    private static final Object DELETED = new Object();
+    private boolean mGarbage = false;
+
+    private int[] mKeys;
+    private Object[] mValues;
+    private int mSize;
+
+    /**
+     * Creates a new SparseArray containing no mappings.
+     */
+    public PositionMap() {
+        this(10);
+    }
+
+    /**
+     * Creates a new PositionMap containing no mappings that will not
+     * require any additional memory allocation to store the specified
+     * number of mappings.  If you supply an initial capacity of 0, the
+     * sparse array will be initialized with a light-weight representation
+     * not requiring any additional array allocations.
+     */
+    public PositionMap(int initialCapacity) {
+        if (initialCapacity == 0) {
+            mKeys = ContainerHelpers.EMPTY_INTS;
+            mValues = ContainerHelpers.EMPTY_OBJECTS;
+        } else {
+            initialCapacity = idealIntArraySize(initialCapacity);
+            mKeys = new int[initialCapacity];
+            mValues = new Object[initialCapacity];
+        }
+        mSize = 0;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public PositionMap<E> clone() {
+        PositionMap<E> clone = null;
+        try {
+            clone = (PositionMap<E>) super.clone();
+            clone.mKeys = mKeys.clone();
+            clone.mValues = mValues.clone();
+        } catch (CloneNotSupportedException cnse) {
+            /* ignore */
+        }
+        return clone;
+    }
+
+    /**
+     * Gets the Object mapped from the specified key, or <code>null</code>
+     * if no such mapping has been made.
+     */
+    public E get(int key) {
+        return get(key, null);
+    }
+
+    /**
+     * Gets the Object mapped from the specified key, or the specified Object
+     * if no such mapping has been made.
+     */
+    @SuppressWarnings("unchecked")
+    public E get(int key, E valueIfKeyNotFound) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i < 0 || mValues[i] == DELETED) {
+            return valueIfKeyNotFound;
+        } else {
+            return (E) mValues[i];
+        }
+    }
+
+    /**
+     * Removes the mapping from the specified key, if there was any.
+     */
+    public void delete(int key) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i >= 0) {
+            if (mValues[i] != DELETED) {
+                mValues[i] = DELETED;
+                mGarbage = true;
+            }
+        }
+    }
+
+    /**
+     * Alias for {@link #delete(int)}.
+     */
+    public void remove(int key) {
+        delete(key);
+    }
+
+    /**
+     * Removes the mapping at the specified index.
+     */
+    public void removeAt(int index) {
+        if (mValues[index] != DELETED) {
+            mValues[index] = DELETED;
+            mGarbage = true;
+        }
+    }
+
+    /**
+     * Remove a range of mappings as a batch.
+     *
+     * @param index Index to begin at
+     * @param size Number of mappings to remove
+     */
+    public void removeAtRange(int index, int size) {
+        final int end = Math.min(mSize, index + size);
+        for (int i = index; i < end; i++) {
+            removeAt(i);
+        }
+    }
+
+    public void insertKeyRange(int keyStart, int count) {
+
+    }
+
+    public void removeKeyRange(ArrayList<E> removedItems, int keyStart, int count) {
+
+    }
+
+    private void gc() {
+        // Log.e("SparseArray", "gc start with " + mSize);
+
+        int n = mSize;
+        int o = 0;
+        int[] keys = mKeys;
+        Object[] values = mValues;
+
+        for (int i = 0; i < n; i++) {
+            Object val = values[i];
+
+            if (val != DELETED) {
+                if (i != o) {
+                    keys[o] = keys[i];
+                    values[o] = val;
+                    values[i] = null;
+                }
+
+                o++;
+            }
+        }
+
+        mGarbage = false;
+        mSize = o;
+
+        // Log.e("SparseArray", "gc end with " + mSize);
+    }
+
+    /**
+     * Adds a mapping from the specified key to the specified value,
+     * replacing the previous mapping from the specified key if there
+     * was one.
+     */
+    public void put(int key, E value) {
+        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
+
+        if (i >= 0) {
+            mValues[i] = value;
+        } else {
+            i = ~i;
+
+            if (i < mSize && mValues[i] == DELETED) {
+                mKeys[i] = key;
+                mValues[i] = value;
+                return;
+            }
+
+            if (mGarbage && mSize >= mKeys.length) {
+                gc();
+
+                // Search again because indices may have changed.
+                i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
+            }
+
+            if (mSize >= mKeys.length) {
+                int n = idealIntArraySize(mSize + 1);
+
+                int[] nkeys = new int[n];
+                Object[] nvalues = new Object[n];
+
+                // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
+                System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+                System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+                mKeys = nkeys;
+                mValues = nvalues;
+            }
+
+            if (mSize - i != 0) {
+                // Log.e("SparseArray", "move " + (mSize - i));
+                System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
+                System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
+            }
+
+            mKeys[i] = key;
+            mValues[i] = value;
+            mSize++;
+        }
+    }
+
+    /**
+     * Returns the number of key-value mappings that this SparseArray
+     * currently stores.
+     */
+    public int size() {
+        if (mGarbage) {
+            gc();
+        }
+
+        return mSize;
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the key from the <code>index</code>th key-value mapping that this
+     * SparseArray stores.
+     */
+    public int keyAt(int index) {
+        if (mGarbage) {
+            gc();
+        }
+
+        return mKeys[index];
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, returns
+     * the value from the <code>index</code>th key-value mapping that this
+     * SparseArray stores.
+     */
+    @SuppressWarnings("unchecked")
+    public E valueAt(int index) {
+        if (mGarbage) {
+            gc();
+        }
+
+        return (E) mValues[index];
+    }
+
+    /**
+     * Given an index in the range <code>0...size()-1</code>, sets a new
+     * value for the <code>index</code>th key-value mapping that this
+     * SparseArray stores.
+     */
+    public void setValueAt(int index, E value) {
+        if (mGarbage) {
+            gc();
+        }
+
+        mValues[index] = value;
+    }
+
+    /**
+     * Returns the index for which {@link #keyAt} would return the
+     * specified key, or a negative number if the specified
+     * key is not mapped.
+     */
+    public int indexOfKey(int key) {
+        if (mGarbage) {
+            gc();
+        }
+
+        return ContainerHelpers.binarySearch(mKeys, mSize, key);
+    }
+
+    /**
+     * Returns an index for which {@link #valueAt} would return the
+     * specified key, or a negative number if no keys map to the
+     * specified value.
+     * <p>Beware that this is a linear search, unlike lookups by key,
+     * and that multiple keys can map to the same value and this will
+     * find only one of them.
+     * <p>Note also that unlike most collections' {@code indexOf} methods,
+     * this method compares values using {@code ==} rather than {@code equals}.
+     */
+    public int indexOfValue(E value) {
+        if (mGarbage) {
+            gc();
+        }
+
+        for (int i = 0; i < mSize; i++)
+            if (mValues[i] == value)
+                return i;
+
+        return -1;
+    }
+
+    /**
+     * Removes all key-value mappings from this SparseArray.
+     */
+    public void clear() {
+        int n = mSize;
+        Object[] values = mValues;
+
+        for (int i = 0; i < n; i++) {
+            values[i] = null;
+        }
+
+        mSize = 0;
+        mGarbage = false;
+    }
+
+    /**
+     * Puts a key/value pair into the array, optimizing for the case where
+     * the key is greater than all existing keys in the array.
+     */
+    public void append(int key, E value) {
+        if (mSize != 0 && key <= mKeys[mSize - 1]) {
+            put(key, value);
+            return;
+        }
+
+        if (mGarbage && mSize >= mKeys.length) {
+            gc();
+        }
+
+        int pos = mSize;
+        if (pos >= mKeys.length) {
+            int n = idealIntArraySize(pos + 1);
+
+            int[] nkeys = new int[n];
+            Object[] nvalues = new Object[n];
+
+            // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
+            System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+            System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+            mKeys = nkeys;
+            mValues = nvalues;
+        }
+
+        mKeys[pos] = key;
+        mValues[pos] = value;
+        mSize = pos + 1;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>This implementation composes a string by iterating over its mappings. If
+     * this map contains itself as a value, the string "(this Map)"
+     * will appear in its place.
+     */
+    @Override
+    public String toString() {
+        if (size() <= 0) {
+            return "{}";
+        }
+
+        StringBuilder buffer = new StringBuilder(mSize * 28);
+        buffer.append('{');
+        for (int i=0; i<mSize; i++) {
+            if (i > 0) {
+                buffer.append(", ");
+            }
+            int key = keyAt(i);
+            buffer.append(key);
+            buffer.append('=');
+            Object value = valueAt(i);
+            if (value != this) {
+                buffer.append(value);
+            } else {
+                buffer.append("(this Map)");
+            }
+        }
+        buffer.append('}');
+        return buffer.toString();
+    }
+
+    static int idealByteArraySize(int need) {
+        for (int i = 4; i < 32; i++)
+            if (need <= (1 << i) - 12)
+                return (1 << i) - 12;
+
+        return need;
+    }
+
+    static int idealBooleanArraySize(int need) {
+        return idealByteArraySize(need);
+    }
+
+    static int idealShortArraySize(int need) {
+        return idealByteArraySize(need * 2) / 2;
+    }
+
+    static int idealCharArraySize(int need) {
+        return idealByteArraySize(need * 2) / 2;
+    }
+
+    static int idealIntArraySize(int need) {
+        return idealByteArraySize(need * 4) / 4;
+    }
+
+    static int idealFloatArraySize(int need) {
+        return idealByteArraySize(need * 4) / 4;
+    }
+
+    static int idealObjectArraySize(int need) {
+        return idealByteArraySize(need * 4) / 4;
+    }
+
+    static int idealLongArraySize(int need) {
+        return idealByteArraySize(need * 8) / 8;
+    }
+
+    static class ContainerHelpers {
+        static final boolean[] EMPTY_BOOLEANS = new boolean[0];
+        static final int[] EMPTY_INTS = new int[0];
+        static final long[] EMPTY_LONGS = new long[0];
+        static final Object[] EMPTY_OBJECTS = new Object[0];
+
+        // This is Arrays.binarySearch(), but doesn't do any argument validation.
+        static int binarySearch(int[] array, int size, int value) {
+            int lo = 0;
+            int hi = size - 1;
+
+            while (lo <= hi) {
+                final int mid = (lo + hi) >>> 1;
+                final int midVal = array[mid];
+
+                if (midVal < value) {
+                    lo = mid + 1;
+                } else if (midVal > value) {
+                    hi = mid - 1;
+                } else {
+                    return mid;  // value found
+                }
+            }
+            return ~lo;  // value not present
+        }
+    }
+
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
new file mode 100644
index 0000000..f9cced1
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -0,0 +1,7851 @@
+/*
+ * Copyright (C) 2013 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.widget;
+
+import android.content.Context;
+import android.database.Observable;
+import android.graphics.Canvas;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.Nullable;
+import android.support.v4.util.ArrayMap;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.VelocityTrackerCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.EdgeEffectCompat;
+import android.support.v4.widget.ScrollerCompat;
+import static android.support.v7.widget.AdapterHelper.UpdateOp;
+import static android.support.v7.widget.AdapterHelper.Callback;
+
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.FocusFinder;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.animation.Interpolator;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A flexible view for providing a limited window into a large data set.
+ *
+ * <h3>Glossary of terms:</h3>
+ *
+ * <ul>
+ *     <li><em>Adapter:</em> A subclass of {@link Adapter} responsible for providing views
+ *     that represent items in a data set.</li>
+ *     <li><em>Position:</em> The position of a data item within an <em>Adapter</em>.</li>
+ *     <li><em>Index:</em> The index of an attached child view as used in a call to
+ *     {@link ViewGroup#getChildAt}. Contrast with <em>Position.</em></li>
+ *     <li><em>Binding:</em> The process of preparing a child view to display data corresponding
+ *     to a <em>position</em> within the adapter.</li>
+ *     <li><em>Recycle (view):</em> A view previously used to display data for a specific adapter
+ *     position may be placed in a cache for later reuse to display the same type of data again
+ *     later. This can drastically improve performance by skipping initial layout inflation
+ *     or construction.</li>
+ *     <li><em>Scrap (view):</em> A child view that has entered into a temporarily detached
+ *     state during layout. Scrap views may be reused without becoming fully detached
+ *     from the parent RecyclerView, either unmodified if no rebinding is required or modified
+ *     by the adapter if the view was considered <em>dirty</em>.</li>
+ *     <li><em>Dirty (view):</em> A child view that must be rebound by the adapter before
+ *     being displayed.</li>
+ * </ul>
+ */
+public class RecyclerView extends ViewGroup {
+    private static final String TAG = "RecyclerView";
+
+    private static final boolean DEBUG = false;
+
+    /**
+     * On Kitkat, there is a bug which prevents DisplayList from being invalidated if a View is two
+     * levels deep(wrt to ViewHolder.itemView). DisplayList can be invalidated by setting
+     * View's visibility to INVISIBLE when View is detached. On Kitkat, Recycler recursively
+     * traverses itemView and invalidates display list for each ViewGroup that matches this
+     * criteria.
+     */
+    private static final boolean FORCE_INVALIDATE_DISPLAY_LIST = Build.VERSION.SDK_INT == 19 ||
+            Build.VERSION.SDK_INT == 20;
+
+    private static final boolean DISPATCH_TEMP_DETACH = false;
+    public static final int HORIZONTAL = 0;
+    public static final int VERTICAL = 1;
+
+    public static final int NO_POSITION = -1;
+    public static final long NO_ID = -1;
+    public static final int INVALID_TYPE = -1;
+
+    private static final int MAX_SCROLL_DURATION = 2000;
+
+    private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
+
+    final Recycler mRecycler = new Recycler();
+
+    private SavedState mPendingSavedState;
+
+    AdapterHelper mAdapterHelper;
+
+    ChildHelper mChildHelper;
+
+    // we use this like a set
+    final List<View> mDisappearingViewsInLayoutPass = new ArrayList<View>();
+
+    /**
+     * Prior to L, there is no way to query this variable which is why we override the setter and
+     * track it here.
+     */
+    private boolean mClipToPadding;
+
+    /**
+     * Note: this Runnable is only ever posted if:
+     * 1) We've been through first layout
+     * 2) We know we have a fixed size (mHasFixedSize)
+     * 3) We're attached
+     */
+    private final Runnable mUpdateChildViewsRunnable = new Runnable() {
+        public void run() {
+            if (!mAdapterHelper.hasPendingUpdates()) {
+                return;
+            }
+            if (!mFirstLayoutComplete) {
+                // a layout request will happen, we should not do layout here.
+                return;
+            }
+            if (mDataSetHasChangedAfterLayout) {
+                dispatchLayout();
+            } else {
+                eatRequestLayout();
+                mAdapterHelper.preProcess();
+                if (!mLayoutRequestEaten) {
+                    // We run this after pre-processing is complete so that ViewHolders have their
+                    // final adapter positions. No need to run it if a layout is already requested.
+                    rebindUpdatedViewHolders();
+                }
+                resumeRequestLayout(true);
+            }
+        }
+    };
+
+    private final Rect mTempRect = new Rect();
+    private Adapter mAdapter;
+    private LayoutManager mLayout;
+    private RecyclerListener mRecyclerListener;
+    private final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<ItemDecoration>();
+    private final ArrayList<OnItemTouchListener> mOnItemTouchListeners =
+            new ArrayList<OnItemTouchListener>();
+    private OnItemTouchListener mActiveOnItemTouchListener;
+    private boolean mIsAttached;
+    private boolean mHasFixedSize;
+    private boolean mFirstLayoutComplete;
+    private boolean mEatRequestLayout;
+    private boolean mLayoutRequestEaten;
+    private boolean mAdapterUpdateDuringMeasure;
+    private final boolean mPostUpdatesOnAnimation;
+
+    /**
+     * Set to true when an adapter data set changed notification is received.
+     * In that case, we cannot run any animations since we don't know what happened.
+     */
+    private boolean mDataSetHasChangedAfterLayout = false;
+
+    /**
+     * This variable is set to true during a dispatchLayout and/or scroll.
+     * Some methods should not be called during these periods (e.g. adapter data change).
+     * Doing so will create hard to find bugs so we better check it and throw an exception.
+     *
+     * @see #assertInLayoutOrScroll(String)
+     * @see #assertNotInLayoutOrScroll(String)
+     */
+    private boolean mRunningLayoutOrScroll = false;
+
+    private EdgeEffectCompat mLeftGlow, mTopGlow, mRightGlow, mBottomGlow;
+
+    ItemAnimator mItemAnimator = new DefaultItemAnimator();
+
+    private static final int INVALID_POINTER = -1;
+
+    /**
+     * The RecyclerView is not currently scrolling.
+     * @see #getScrollState()
+     */
+    public static final int SCROLL_STATE_IDLE = 0;
+
+    /**
+     * The RecyclerView is currently being dragged by outside input such as user touch input.
+     * @see #getScrollState()
+     */
+    public static final int SCROLL_STATE_DRAGGING = 1;
+
+    /**
+     * The RecyclerView is currently animating to a final position while not under
+     * outside control.
+     * @see #getScrollState()
+     */
+    public static final int SCROLL_STATE_SETTLING = 2;
+
+    // Touch/scrolling handling
+
+    private int mScrollState = SCROLL_STATE_IDLE;
+    private int mScrollPointerId = INVALID_POINTER;
+    private VelocityTracker mVelocityTracker;
+    private int mInitialTouchX;
+    private int mInitialTouchY;
+    private int mLastTouchX;
+    private int mLastTouchY;
+    private final int mTouchSlop;
+    private final int mMinFlingVelocity;
+    private final int mMaxFlingVelocity;
+
+    private final ViewFlinger mViewFlinger = new ViewFlinger();
+
+    final State mState = new State();
+
+    private OnScrollListener mScrollListener;
+
+    // For use in item animations
+    boolean mItemsAddedOrRemoved = false;
+    boolean mItemsChanged = false;
+    private ItemAnimator.ItemAnimatorListener mItemAnimatorListener =
+            new ItemAnimatorRestoreListener();
+    private boolean mPostedAnimatorRunner = false;
+    private Runnable mItemAnimatorRunner = new Runnable() {
+        @Override
+        public void run() {
+            if (mItemAnimator != null) {
+                mItemAnimator.runPendingAnimations();
+            }
+            mPostedAnimatorRunner = false;
+        }
+    };
+
+    private static final Interpolator sQuinticInterpolator = new Interpolator() {
+        public float getInterpolation(float t) {
+            t -= 1.0f;
+            return t * t * t * t * t + 1.0f;
+        }
+    };
+
+    public RecyclerView(Context context) {
+        this(context, null);
+    }
+
+    public RecyclerView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public RecyclerView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        final int version = Build.VERSION.SDK_INT;
+        mPostUpdatesOnAnimation = version >= 16;
+
+        final ViewConfiguration vc = ViewConfiguration.get(context);
+        mTouchSlop = vc.getScaledTouchSlop();
+        mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
+        mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
+        setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER);
+
+        mItemAnimator.setListener(mItemAnimatorListener);
+        initAdapterManager();
+        initChildrenHelper();
+    }
+
+    private void initChildrenHelper() {
+        mChildHelper = new ChildHelper(new ChildHelper.Callback() {
+            @Override
+            public int getChildCount() {
+                return RecyclerView.this.getChildCount();
+            }
+
+            @Override
+            public void addView(View child, int index) {
+                RecyclerView.this.addView(child, index);
+            }
+
+            @Override
+            public int indexOfChild(View view) {
+                return RecyclerView.this.indexOfChild(view);
+            }
+
+            @Override
+            public void removeViewAt(int index) {
+                RecyclerView.this.removeViewAt(index);
+            }
+
+            @Override
+            public View getChildAt(int offset) {
+                return RecyclerView.this.getChildAt(offset);
+            }
+
+            @Override
+            public void removeAllViews() {
+                RecyclerView.this.removeAllViews();
+            }
+
+            @Override
+            public ViewHolder getChildViewHolder(View view) {
+                return getChildViewHolderInt(view);
+            }
+
+            @Override
+            public void attachViewToParent(View child, int index,
+                    ViewGroup.LayoutParams layoutParams) {
+                RecyclerView.this.attachViewToParent(child, index, layoutParams);
+            }
+
+            @Override
+            public void detachViewFromParent(int offset) {
+                RecyclerView.this.detachViewFromParent(offset);
+            }
+        });
+    }
+
+    void initAdapterManager() {
+        mAdapterHelper = new AdapterHelper(new Callback() {
+            @Override
+            public ViewHolder findViewHolder(int position) {
+                return findViewHolderForPosition(position, true);
+            }
+
+            @Override
+            public void offsetPositionsForRemovingInvisible(int start, int count) {
+                offsetPositionRecordsForRemove(start, count, true);
+                mItemsAddedOrRemoved = true;
+                mState.mDeletedInvisibleItemCountSincePreviousLayout += count;
+            }
+
+            @Override
+            public void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount) {
+                offsetPositionRecordsForRemove(positionStart, itemCount, false);
+                mItemsAddedOrRemoved = true;
+            }
+
+            @Override
+            public void markViewHoldersUpdated(int positionStart, int itemCount) {
+                viewRangeUpdate(positionStart, itemCount);
+                mItemsChanged = true;
+            }
+
+            @Override
+            public void onDispatchFirstPass(UpdateOp op) {
+                dispatchUpdate(op);
+            }
+
+            void dispatchUpdate(UpdateOp op) {
+                switch (op.cmd) {
+                    case UpdateOp.ADD:
+                        mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount);
+                        break;
+                    case UpdateOp.REMOVE:
+                        mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount);
+                        break;
+                    case UpdateOp.UPDATE:
+                        mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount);
+                        break;
+                    case UpdateOp.MOVE:
+                        mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1);
+                        break;
+                }
+            }
+
+            @Override
+            public void onDispatchSecondPass(UpdateOp op) {
+                dispatchUpdate(op);
+            }
+
+            @Override
+            public void offsetPositionsForAdd(int positionStart, int itemCount) {
+                offsetPositionRecordsForInsert(positionStart, itemCount);
+                mItemsAddedOrRemoved = true;
+            }
+
+            @Override
+            public void offsetPositionsForMove(int from, int to) {
+                offsetPositionRecordsForMove(from, to);
+                // should we create mItemsMoved ?
+                mItemsAddedOrRemoved = true;
+            }
+        });
+    }
+
+    /**
+     * RecyclerView can perform several optimizations if it can know in advance that changes in
+     * adapter content cannot change the size of the RecyclerView itself.
+     * If your use of RecyclerView falls into this category, set this to true.
+     *
+     * @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView.
+     */
+    public void setHasFixedSize(boolean hasFixedSize) {
+        mHasFixedSize = hasFixedSize;
+    }
+
+    /**
+     * @return true if the app has specified that changes in adapter content cannot change
+     * the size of the RecyclerView itself.
+     */
+    public boolean hasFixedSize() {
+        return mHasFixedSize;
+    }
+
+    @Override
+    public void setClipToPadding(boolean clipToPadding) {
+        if (clipToPadding != mClipToPadding) {
+            invalidateGlows();
+        }
+        mClipToPadding = clipToPadding;
+        super.setClipToPadding(clipToPadding);
+        if (mFirstLayoutComplete) {
+            requestLayout();
+        }
+    }
+
+    /**
+     * Swaps the current adapter with the provided one. It is similar to
+     * {@link #setAdapter(Adapter)} but assumes existing adapter and the new adapter uses the same
+     * {@link ViewHolder} and does not clear the RecycledViewPool.
+     * <p>
+     * Note that it still calls onAdapterChanged callbacks.
+     *
+     * @param adapter The new adapter to set, or null to set no adapter.
+     * @param removeAndRecycleExistingViews If set to true, RecyclerView will recycle all existing
+     *                                      Views. If adapters have stable ids and/or you want to
+     *                                      animate the disappearing views, you may prefer to set
+     *                                      this to false.
+     * @see #setAdapter(Adapter)
+     */
+    public void swapAdapter(Adapter adapter, boolean removeAndRecycleExistingViews) {
+        setAdapterInternal(adapter, true, removeAndRecycleExistingViews);
+        mDataSetHasChangedAfterLayout = true;
+        requestLayout();
+    }
+    /**
+     * Set a new adapter to provide child views on demand.
+     * <p>
+     * When adapter is changed, all existing views are recycled back to the pool. If the pool has
+     * only one adapter, it will be cleared.
+     *
+     * @param adapter The new adapter to set, or null to set no adapter.
+     * @see #swapAdapter(Adapter, boolean)
+     */
+    public void setAdapter(Adapter adapter) {
+        setAdapterInternal(adapter, false, true);
+        requestLayout();
+    }
+
+    /**
+     * Replaces the current adapter with the new one and triggers listeners.
+     * @param adapter The new adapter
+     * @param compatibleWithPrevious If true, the new adapter is using the same View Holders and
+     *                               item types with the current adapter (helps us avoid cache
+     *                               invalidation).
+     * @param removeAndRecycleViews  If true, we'll remove and recycle all existing views. If
+     *                               compatibleWithPrevious is false, this parameter is ignored.
+     */
+    private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
+            boolean removeAndRecycleViews) {
+        if (mAdapter != null) {
+            mAdapter.unregisterAdapterDataObserver(mObserver);
+        }
+        if (!compatibleWithPrevious || removeAndRecycleViews) {
+            // end all running animations
+            if (mItemAnimator != null) {
+                mItemAnimator.endAnimations();
+            }
+            // Since animations are ended, mLayout.children should be equal to
+            // recyclerView.children. This may not be true if item animator's end does not work as
+            // expected. (e.g. not release children instantly). It is safer to use mLayout's child
+            // count.
+            if (mLayout != null) {
+                mLayout.removeAndRecycleAllViews(mRecycler);
+                mLayout.removeAndRecycleScrapInt(mRecycler, true);
+            }
+        }
+        mAdapterHelper.reset();
+        final Adapter oldAdapter = mAdapter;
+        mAdapter = adapter;
+        if (adapter != null) {
+            adapter.registerAdapterDataObserver(mObserver);
+        }
+        if (mLayout != null) {
+            mLayout.onAdapterChanged(oldAdapter, mAdapter);
+        }
+        mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
+        mState.mStructureChanged = true;
+        markKnownViewsInvalid();
+    }
+
+    /**
+     * Retrieves the previously set adapter or null if no adapter is set.
+     *
+     * @return The previously set adapter
+     * @see #setAdapter(Adapter)
+     */
+    public Adapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Register a listener that will be notified whenever a child view is recycled.
+     *
+     * <p>This listener will be called when a LayoutManager or the RecyclerView decides
+     * that a child view is no longer needed. If an application associates expensive
+     * or heavyweight data with item views, this may be a good place to release
+     * or free those resources.</p>
+     *
+     * @param listener Listener to register, or null to clear
+     */
+    public void setRecyclerListener(RecyclerListener listener) {
+        mRecyclerListener = listener;
+    }
+
+    /**
+     * Set the {@link LayoutManager} that this RecyclerView will use.
+     *
+     * <p>In contrast to other adapter-backed views such as {@link android.widget.ListView}
+     * or {@link android.widget.GridView}, RecyclerView allows client code to provide custom
+     * layout arrangements for child views. These arrangements are controlled by the
+     * {@link LayoutManager}. A LayoutManager must be provided for RecyclerView to function.</p>
+     *
+     * <p>Several default strategies are provided for common uses such as lists and grids.</p>
+     *
+     * @param layout LayoutManager to use
+     */
+    public void setLayoutManager(LayoutManager layout) {
+        if (layout == mLayout) {
+            return;
+        }
+        // TODO We should do this switch a dispachLayout pass and animate children. There is a good
+        // chance that LayoutManagers will re-use views.
+        if (mLayout != null) {
+            if (mIsAttached) {
+                mLayout.onDetachedFromWindow(this, mRecycler);
+            }
+            mLayout.setRecyclerView(null);
+        }
+        mRecycler.clear();
+        mChildHelper.removeAllViewsUnfiltered();
+        mLayout = layout;
+        if (layout != null) {
+            if (layout.mRecyclerView != null) {
+                throw new IllegalArgumentException("LayoutManager " + layout +
+                        " is already attached to a RecyclerView: " + layout.mRecyclerView);
+            }
+            mLayout.setRecyclerView(this);
+            if (mIsAttached) {
+                mLayout.onAttachedToWindow(this);
+            }
+        }
+        requestLayout();
+    }
+
+    @Override
+    protected Parcelable onSaveInstanceState() {
+        SavedState state = new SavedState(super.onSaveInstanceState());
+        if (mPendingSavedState != null) {
+            state.copyFrom(mPendingSavedState);
+        } else if (mLayout != null) {
+            state.mLayoutState = mLayout.onSaveInstanceState();
+        } else {
+            state.mLayoutState = null;
+        }
+
+        return state;
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Parcelable state) {
+        mPendingSavedState = (SavedState) state;
+        super.onRestoreInstanceState(mPendingSavedState.getSuperState());
+        if (mLayout != null && mPendingSavedState.mLayoutState != null) {
+            mLayout.onRestoreInstanceState(mPendingSavedState.mLayoutState);
+        }
+    }
+
+    /**
+     * Adds a view to the animatingViews list.
+     * mAnimatingViews holds the child views that are currently being kept around
+     * purely for the purpose of being animated out of view. They are drawn as a regular
+     * part of the child list of the RecyclerView, but they are invisible to the LayoutManager
+     * as they are managed separately from the regular child views.
+     * @param view The view to be removed
+     */
+    private void addAnimatingView(View view) {
+        final boolean alreadyParented = view.getParent() == this;
+        mRecycler.unscrapView(getChildViewHolder(view));
+        if (!alreadyParented) {
+            mChildHelper.addView(view, true);
+        } else {
+            mChildHelper.hide(view);
+        }
+    }
+
+    /**
+     * Removes a view from the animatingViews list.
+     * @param view The view to be removed
+     * @see #addAnimatingView(View)
+     */
+    private void removeAnimatingView(View view) {
+        eatRequestLayout();
+        if (mChildHelper.removeViewIfHidden(view)) {
+            final ViewHolder viewHolder = getChildViewHolderInt(view);
+            mRecycler.unscrapView(viewHolder);
+            mRecycler.recycleViewHolderInternal(viewHolder);
+            if (DEBUG) {
+                Log.d(TAG, "after removing animated view: " + view + ", " + this);
+            }
+        }
+        resumeRequestLayout(false);
+    }
+
+    /**
+     * Return the {@link LayoutManager} currently responsible for
+     * layout policy for this RecyclerView.
+     *
+     * @return The currently bound LayoutManager
+     */
+    public LayoutManager getLayoutManager() {
+        return mLayout;
+    }
+
+    /**
+     * Retrieve this RecyclerView's {@link RecycledViewPool}. This method will never return null;
+     * if no pool is set for this view a new one will be created. See
+     * {@link #setRecycledViewPool(RecycledViewPool) setRecycledViewPool} for more information.
+     *
+     * @return The pool used to store recycled item views for reuse.
+     * @see #setRecycledViewPool(RecycledViewPool)
+     */
+    public RecycledViewPool getRecycledViewPool() {
+        return mRecycler.getRecycledViewPool();
+    }
+
+    /**
+     * Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views.
+     * This can be useful if you have multiple RecyclerViews with adapters that use the same
+     * view types, for example if you have several data sets with the same kinds of item views
+     * displayed by a {@link android.support.v4.view.ViewPager ViewPager}.
+     *
+     * @param pool Pool to set. If this parameter is null a new pool will be created and used.
+     */
+    public void setRecycledViewPool(RecycledViewPool pool) {
+        mRecycler.setRecycledViewPool(pool);
+    }
+
+    /**
+     * Sets a new {@link ViewCacheExtension} to be used by the Recycler.
+     *
+     * @param extension ViewCacheExtension to be used or null if you want to clear the existing one.
+     *
+     * @see {@link ViewCacheExtension#getViewForPositionAndType(Recycler, int, int)}
+     */
+    public void setViewCacheExtension(ViewCacheExtension extension) {
+        mRecycler.setViewCacheExtension(extension);
+    }
+
+    /**
+     * Set the number of offscreen views to retain before adding them to the potentially shared
+     * {@link #getRecycledViewPool() recycled view pool}.
+     *
+     * <p>The offscreen view cache stays aware of changes in the attached adapter, allowing
+     * a LayoutManager to reuse those views unmodified without needing to return to the adapter
+     * to rebind them.</p>
+     *
+     * @param size Number of views to cache offscreen before returning them to the general
+     *             recycled view pool
+     */
+    public void setItemViewCacheSize(int size) {
+        mRecycler.setViewCacheSize(size);
+    }
+
+    /**
+     * Return the current scrolling state of the RecyclerView.
+     *
+     * @return {@link #SCROLL_STATE_IDLE}, {@link #SCROLL_STATE_DRAGGING} or
+     * {@link #SCROLL_STATE_SETTLING}
+     */
+    public int getScrollState() {
+        return mScrollState;
+    }
+
+    private void setScrollState(int state) {
+        if (state == mScrollState) {
+            return;
+        }
+        mScrollState = state;
+        if (state != SCROLL_STATE_SETTLING) {
+            stopScroll();
+        }
+        if (mScrollListener != null) {
+            mScrollListener.onScrollStateChanged(this, state);
+        }
+        mLayout.onScrollStateChanged(state);
+    }
+
+    /**
+     * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
+     * affect both measurement and drawing of individual item views.
+     *
+     * <p>Item decorations are ordered. Decorations placed earlier in the list will
+     * be run/queried/drawn first for their effects on item views. Padding added to views
+     * will be nested; a padding added by an earlier decoration will mean further
+     * item decorations in the list will be asked to draw/pad within the previous decoration's
+     * given area.</p>
+     *
+     * @param decor Decoration to add
+     * @param index Position in the decoration chain to insert this decoration at. If this value
+     *              is negative the decoration will be added at the end.
+     */
+    public void addItemDecoration(ItemDecoration decor, int index) {
+        if (mLayout != null) {
+            mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll  or"
+                    + " layout");
+        }
+        if (mItemDecorations.isEmpty()) {
+            setWillNotDraw(false);
+        }
+        if (index < 0) {
+            mItemDecorations.add(decor);
+        } else {
+            mItemDecorations.add(index, decor);
+        }
+        markItemDecorInsetsDirty();
+        requestLayout();
+    }
+
+    /**
+     * Add an {@link ItemDecoration} to this RecyclerView. Item decorations can
+     * affect both measurement and drawing of individual item views.
+     *
+     * <p>Item decorations are ordered. Decorations placed earlier in the list will
+     * be run/queried/drawn first for their effects on item views. Padding added to views
+     * will be nested; a padding added by an earlier decoration will mean further
+     * item decorations in the list will be asked to draw/pad within the previous decoration's
+     * given area.</p>
+     *
+     * @param decor Decoration to add
+     */
+    public void addItemDecoration(ItemDecoration decor) {
+        addItemDecoration(decor, -1);
+    }
+
+    /**
+     * Remove an {@link ItemDecoration} from this RecyclerView.
+     *
+     * <p>The given decoration will no longer impact the measurement and drawing of
+     * item views.</p>
+     *
+     * @param decor Decoration to remove
+     * @see #addItemDecoration(ItemDecoration)
+     */
+    public void removeItemDecoration(ItemDecoration decor) {
+        if (mLayout != null) {
+            mLayout.assertNotInLayoutOrScroll("Cannot remove item decoration during a scroll  or"
+                    + " layout");
+        }
+        mItemDecorations.remove(decor);
+        if (mItemDecorations.isEmpty()) {
+            setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER);
+        }
+        markItemDecorInsetsDirty();
+        requestLayout();
+    }
+
+    /**
+     * Set a listener that will be notified of any changes in scroll state or position.
+     *
+     * @param listener Listener to set or null to clear
+     */
+    public void setOnScrollListener(OnScrollListener listener) {
+        mScrollListener = listener;
+    }
+
+    /**
+     * Convenience method to scroll to a certain position.
+     *
+     * RecyclerView does not implement scrolling logic, rather forwards the call to
+     * {@link android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int)}
+     * @param position Scroll to this adapter position
+     * @see android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int)
+     */
+    public void scrollToPosition(int position) {
+        stopScroll();
+        mLayout.scrollToPosition(position);
+        awakenScrollBars();
+    }
+
+    /**
+     * Starts a smooth scroll to an adapter position.
+     * <p>
+     * To support smooth scrolling, you must override
+     * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} and create a
+     * {@link SmoothScroller}.
+     * <p>
+     * {@link LayoutManager} is responsible for creating the actual scroll action. If you want to
+     * provide a custom smooth scroll logic, override
+     * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} in your
+     * LayoutManager.
+     *
+     * @param position The adapter position to scroll to
+     * @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int)
+     */
+    public void smoothScrollToPosition(int position) {
+        mLayout.smoothScrollToPosition(this, mState, position);
+    }
+
+    @Override
+    public void scrollTo(int x, int y) {
+        throw new UnsupportedOperationException(
+                "RecyclerView does not support scrolling to an absolute position.");
+    }
+
+    @Override
+    public void scrollBy(int x, int y) {
+        if (mLayout == null) {
+            throw new IllegalStateException("Cannot scroll without a LayoutManager set. " +
+                    "Call setLayoutManager with a non-null argument.");
+        }
+        final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
+        final boolean canScrollVertical = mLayout.canScrollVertically();
+        if (canScrollHorizontal || canScrollVertical) {
+            scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0);
+        }
+    }
+
+    /**
+     * Helper method reflect data changes to the state.
+     * <p>
+     * Adapter changes during a scroll may trigger a crash because scroll assumes no data change
+     * but data actually changed.
+     * <p>
+     * This method consumes all deferred changes to avoid that case.
+     */
+    private void consumePendingUpdateOperations() {
+        if (mAdapterHelper.hasPendingUpdates()) {
+            mUpdateChildViewsRunnable.run();
+        }
+    }
+
+    /**
+     * Does not perform bounds checking. Used by internal methods that have already validated input.
+     */
+    void scrollByInternal(int x, int y) {
+        int overscrollX = 0, overscrollY = 0;
+        int hresult = 0, vresult = 0;
+        consumePendingUpdateOperations();
+        if (mAdapter != null) {
+            eatRequestLayout();
+            mRunningLayoutOrScroll = true;
+            if (x != 0) {
+                hresult = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
+                overscrollX = x - hresult;
+            }
+            if (y != 0) {
+                vresult = mLayout.scrollVerticallyBy(y, mRecycler, mState);
+                overscrollY = y - vresult;
+            }
+            if (supportsChangeAnimations()) {
+                // Fix up shadow views used by changing animations
+                int count = mChildHelper.getChildCount();
+                for (int i = 0; i < count; i++) {
+                    View view = mChildHelper.getChildAt(i);
+                    ViewHolder holder = getChildViewHolder(view);
+                    if (holder != null && holder.mShadowingHolder != null) {
+                        ViewHolder shadowingHolder = holder.mShadowingHolder;
+                        View shadowingView = shadowingHolder != null ? shadowingHolder.itemView : null;
+                        if (shadowingView != null) {
+                            int left = view.getLeft();
+                            int top = view.getTop();
+                            if (left != shadowingView.getLeft() || top != shadowingView.getTop()) {
+                                shadowingView.layout(left, top,
+                                        left + shadowingView.getWidth(),
+                                        top + shadowingView.getHeight());
+                            }
+                        }
+                    }
+                }
+            }
+            mRunningLayoutOrScroll = false;
+            resumeRequestLayout(false);
+        }
+        if (!mItemDecorations.isEmpty()) {
+            invalidate();
+        }
+        if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) {
+            considerReleasingGlowsOnScroll(x, y);
+            pullGlows(overscrollX, overscrollY);
+        }
+        if (mScrollListener != null && (hresult != 0 || vresult != 0)) {
+            mScrollListener.onScrolled(this, hresult, vresult);
+        }
+        if (!awakenScrollBars()) {
+            invalidate();
+        }
+    }
+
+    /**
+     * <p>Compute the horizontal offset of the horizontal scrollbar's thumb within the horizontal
+     * range. This value is used to compute the length of the thumb within the scrollbar's track.
+     * </p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the units used by
+     * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollExtent()}.</p>
+     *
+     * <p>Default implementation returns 0.</p>
+     *
+     * <p>If you want to support scroll bars, override
+     * {@link RecyclerView.LayoutManager#computeHorizontalScrollOffset(RecyclerView.State)} in your
+     * LayoutManager. </p>
+     *
+     * @return The horizontal offset of the scrollbar's thumb
+     * @see android.support.v7.widget.RecyclerView.LayoutManager#computeHorizontalScrollOffset
+     * (RecyclerView.Adapter)
+     */
+    @Override
+    protected int computeHorizontalScrollOffset() {
+        return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mState)
+                : 0;
+    }
+
+    /**
+     * <p>Compute the horizontal extent of the horizontal scrollbar's thumb within the
+     * horizontal range. This value is used to compute the length of the thumb within the
+     * scrollbar's track.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the units used by
+     * {@link #computeHorizontalScrollRange()} and {@link #computeHorizontalScrollOffset()}.</p>
+     *
+     * <p>Default implementation returns 0.</p>
+     *
+     * <p>If you want to support scroll bars, override
+     * {@link RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)} in your
+     * LayoutManager.</p>
+     *
+     * @return The horizontal extent of the scrollbar's thumb
+     * @see RecyclerView.LayoutManager#computeHorizontalScrollExtent(RecyclerView.State)
+     */
+    @Override
+    protected int computeHorizontalScrollExtent() {
+        return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mState) : 0;
+    }
+
+    /**
+     * <p>Compute the horizontal range that the horizontal scrollbar represents.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the units used by
+     * {@link #computeHorizontalScrollExtent()} and {@link #computeHorizontalScrollOffset()}.</p>
+     *
+     * <p>Default implementation returns 0.</p>
+     *
+     * <p>If you want to support scroll bars, override
+     * {@link RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)} in your
+     * LayoutManager.</p>
+     *
+     * @return The total horizontal range represented by the vertical scrollbar
+     * @see RecyclerView.LayoutManager#computeHorizontalScrollRange(RecyclerView.State)
+     */
+    @Override
+    protected int computeHorizontalScrollRange() {
+        return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mState) : 0;
+    }
+
+    /**
+     * <p>Compute the vertical offset of the vertical scrollbar's thumb within the vertical range.
+     * This value is used to compute the length of the thumb within the scrollbar's track. </p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the units used by
+     * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollExtent()}.</p>
+     *
+     * <p>Default implementation returns 0.</p>
+     *
+     * <p>If you want to support scroll bars, override
+     * {@link RecyclerView.LayoutManager#computeVerticalScrollOffset(RecyclerView.State)} in your
+     * LayoutManager.</p>
+     *
+     * @return The vertical offset of the scrollbar's thumb
+     * @see android.support.v7.widget.RecyclerView.LayoutManager#computeVerticalScrollOffset
+     * (RecyclerView.Adapter)
+     */
+    @Override
+    protected int computeVerticalScrollOffset() {
+        return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0;
+    }
+
+    /**
+     * <p>Compute the vertical extent of the vertical scrollbar's thumb within the vertical range.
+     * This value is used to compute the length of the thumb within the scrollbar's track.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the units used by
+     * {@link #computeVerticalScrollRange()} and {@link #computeVerticalScrollOffset()}.</p>
+     *
+     * <p>Default implementation returns 0.</p>
+     *
+     * <p>If you want to support scroll bars, override
+     * {@link RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)} in your
+     * LayoutManager.</p>
+     *
+     * @return The vertical extent of the scrollbar's thumb
+     * @see RecyclerView.LayoutManager#computeVerticalScrollExtent(RecyclerView.State)
+     */
+    @Override
+    protected int computeVerticalScrollExtent() {
+        return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mState) : 0;
+    }
+
+    /**
+     * <p>Compute the vertical range that the vertical scrollbar represents.</p>
+     *
+     * <p>The range is expressed in arbitrary units that must be the same as the units used by
+     * {@link #computeVerticalScrollExtent()} and {@link #computeVerticalScrollOffset()}.</p>
+     *
+     * <p>Default implementation returns 0.</p>
+     *
+     * <p>If you want to support scroll bars, override
+     * {@link RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)} in your
+     * LayoutManager.</p>
+     *
+     * @return The total vertical range represented by the vertical scrollbar
+     * @see RecyclerView.LayoutManager#computeVerticalScrollRange(RecyclerView.State)
+     */
+    @Override
+    protected int computeVerticalScrollRange() {
+        return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mState) : 0;
+    }
+
+
+    void eatRequestLayout() {
+        if (!mEatRequestLayout) {
+            mEatRequestLayout = true;
+            mLayoutRequestEaten = false;
+        }
+    }
+
+    void resumeRequestLayout(boolean performLayoutChildren) {
+        if (mEatRequestLayout) {
+            if (performLayoutChildren && mLayoutRequestEaten &&
+                    mLayout != null && mAdapter != null) {
+                dispatchLayout();
+            }
+            mEatRequestLayout = false;
+            mLayoutRequestEaten = false;
+        }
+    }
+
+    /**
+     * Animate a scroll by the given amount of pixels along either axis.
+     *
+     * @param dx Pixels to scroll horizontally
+     * @param dy Pixels to scroll vertically
+     */
+    public void smoothScrollBy(int dx, int dy) {
+        if (dx != 0 || dy != 0) {
+            mViewFlinger.smoothScrollBy(dx, dy);
+        }
+    }
+
+    /**
+     * Begin a standard fling with an initial velocity along each axis in pixels per second.
+     * If the velocity given is below the system-defined minimum this method will return false
+     * and no fling will occur.
+     *
+     * @param velocityX Initial horizontal velocity in pixels per second
+     * @param velocityY Initial vertical velocity in pixels per second
+     * @return true if the fling was started, false if the velocity was too low to fling
+     */
+    public boolean fling(int velocityX, int velocityY) {
+        if (Math.abs(velocityX) < mMinFlingVelocity) {
+            velocityX = 0;
+        }
+        if (Math.abs(velocityY) < mMinFlingVelocity) {
+            velocityY = 0;
+        }
+        velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
+        velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
+        if (velocityX != 0 || velocityY != 0) {
+            mViewFlinger.fling(velocityX, velocityY);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Stop any current scroll in progress, such as one started by
+     * {@link #smoothScrollBy(int, int)}, {@link #fling(int, int)} or a touch-initiated fling.
+     */
+    public void stopScroll() {
+        mViewFlinger.stop();
+        mLayout.stopSmoothScroller();
+    }
+
+    /**
+     * Apply a pull to relevant overscroll glow effects
+     */
+    private void pullGlows(int overscrollX, int overscrollY) {
+        if (overscrollX < 0) {
+            ensureLeftGlow();
+            mLeftGlow.onPull(-overscrollX / (float) getWidth());
+        } else if (overscrollX > 0) {
+            ensureRightGlow();
+            mRightGlow.onPull(overscrollX / (float) getWidth());
+        }
+
+        if (overscrollY < 0) {
+            ensureTopGlow();
+            mTopGlow.onPull(-overscrollY / (float) getHeight());
+        } else if (overscrollY > 0) {
+            ensureBottomGlow();
+            mBottomGlow.onPull(overscrollY / (float) getHeight());
+        }
+
+        if (overscrollX != 0 || overscrollY != 0) {
+            ViewCompat.postInvalidateOnAnimation(this);
+        }
+    }
+
+    private void releaseGlows() {
+        boolean needsInvalidate = false;
+        if (mLeftGlow != null) needsInvalidate = mLeftGlow.onRelease();
+        if (mTopGlow != null) needsInvalidate |= mTopGlow.onRelease();
+        if (mRightGlow != null) needsInvalidate |= mRightGlow.onRelease();
+        if (mBottomGlow != null) needsInvalidate |= mBottomGlow.onRelease();
+        if (needsInvalidate) {
+            ViewCompat.postInvalidateOnAnimation(this);
+        }
+    }
+
+    private void considerReleasingGlowsOnScroll(int dx, int dy) {
+        boolean needsInvalidate = false;
+        if (mLeftGlow != null && !mLeftGlow.isFinished() && dx > 0) {
+            needsInvalidate = mLeftGlow.onRelease();
+        }
+        if (mRightGlow != null && !mRightGlow.isFinished() && dx < 0) {
+            needsInvalidate |= mRightGlow.onRelease();
+        }
+        if (mTopGlow != null && !mTopGlow.isFinished() && dy > 0) {
+            needsInvalidate |= mTopGlow.onRelease();
+        }
+        if (mBottomGlow != null && !mBottomGlow.isFinished() && dy < 0) {
+            needsInvalidate |= mBottomGlow.onRelease();
+        }
+        if (needsInvalidate) {
+            ViewCompat.postInvalidateOnAnimation(this);
+        }
+    }
+
+    void absorbGlows(int velocityX, int velocityY) {
+        if (velocityX < 0) {
+            ensureLeftGlow();
+            mLeftGlow.onAbsorb(-velocityX);
+        } else if (velocityX > 0) {
+            ensureRightGlow();
+            mRightGlow.onAbsorb(velocityX);
+        }
+
+        if (velocityY < 0) {
+            ensureTopGlow();
+            mTopGlow.onAbsorb(-velocityY);
+        } else if (velocityY > 0) {
+            ensureBottomGlow();
+            mBottomGlow.onAbsorb(velocityY);
+        }
+
+        if (velocityX != 0 || velocityY != 0) {
+            ViewCompat.postInvalidateOnAnimation(this);
+        }
+    }
+
+    void ensureLeftGlow() {
+        if (mLeftGlow != null) {
+            return;
+        }
+        mLeftGlow = new EdgeEffectCompat(getContext());
+        if (mClipToPadding) {
+            mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
+                    getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
+        } else {
+            mLeftGlow.setSize(getMeasuredHeight(), getMeasuredWidth());
+        }
+    }
+
+    void ensureRightGlow() {
+        if (mRightGlow != null) {
+            return;
+        }
+        mRightGlow = new EdgeEffectCompat(getContext());
+        if (mClipToPadding) {
+            mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
+                    getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
+        } else {
+            mRightGlow.setSize(getMeasuredHeight(), getMeasuredWidth());
+        }
+    }
+
+    void ensureTopGlow() {
+        if (mTopGlow != null) {
+            return;
+        }
+        mTopGlow = new EdgeEffectCompat(getContext());
+        if (mClipToPadding) {
+            mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
+                    getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
+        } else {
+            mTopGlow.setSize(getMeasuredWidth(), getMeasuredHeight());
+        }
+
+    }
+
+    void ensureBottomGlow() {
+        if (mBottomGlow != null) {
+            return;
+        }
+        mBottomGlow = new EdgeEffectCompat(getContext());
+        if (mClipToPadding) {
+            mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
+                    getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
+        } else {
+            mBottomGlow.setSize(getMeasuredWidth(), getMeasuredHeight());
+        }
+    }
+
+    void invalidateGlows() {
+        mLeftGlow = mRightGlow = mTopGlow = mBottomGlow = null;
+    }
+
+    // Focus handling
+
+    @Override
+    public View focusSearch(View focused, int direction) {
+        View result = mLayout.onInterceptFocusSearch(focused, direction);
+        if (result != null) {
+            return result;
+        }
+        final FocusFinder ff = FocusFinder.getInstance();
+        result = ff.findNextFocus(this, focused, direction);
+        if (result == null && mAdapter != null) {
+            eatRequestLayout();
+            result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
+            resumeRequestLayout(false);
+        }
+        return result != null ? result : super.focusSearch(focused, direction);
+    }
+
+    @Override
+    public void requestChildFocus(View child, View focused) {
+        if (!mLayout.onRequestChildFocus(this, mState, child, focused) && focused != null) {
+            mTempRect.set(0, 0, focused.getWidth(), focused.getHeight());
+            offsetDescendantRectToMyCoords(focused, mTempRect);
+            offsetRectIntoDescendantCoords(child, mTempRect);
+            requestChildRectangleOnScreen(child, mTempRect, !mFirstLayoutComplete);
+        }
+        super.requestChildFocus(child, focused);
+    }
+
+    @Override
+    public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) {
+        return mLayout.requestChildRectangleOnScreen(this, child, rect, immediate);
+    }
+
+    @Override
+    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+        if (!mLayout.onAddFocusables(this, views, direction, focusableMode)) {
+            super.addFocusables(views, direction, focusableMode);
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mIsAttached = true;
+        mFirstLayoutComplete = false;
+        if (mLayout != null) {
+            mLayout.onAttachedToWindow(this);
+        }
+        mPostedAnimatorRunner = false;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mFirstLayoutComplete = false;
+
+        stopScroll();
+        mIsAttached = false;
+        if (mLayout != null) {
+            mLayout.onDetachedFromWindow(this, mRecycler);
+        }
+        removeCallbacks(mItemAnimatorRunner);
+    }
+
+    /**
+     * Checks if RecyclerView is in the middle of a layout or scroll and throws an
+     * {@link IllegalStateException} if it <b>is not</b>.
+     *
+     * @param message The message for the exception. Can be null.
+     * @see #assertNotInLayoutOrScroll(String)
+     */
+    void assertInLayoutOrScroll(String message) {
+        if (!mRunningLayoutOrScroll) {
+            if (message == null) {
+                throw new IllegalStateException("Cannot call this method unless RecyclerView is "
+                        + "computing a layout or scrolling");
+            }
+            throw new IllegalStateException(message);
+
+        }
+    }
+
+    /**
+     * Checks if RecyclerView is in the middle of a layout or scroll and throws an
+     * {@link IllegalStateException} if it <b>is</b>.
+     *
+     * @param message The message for the exception. Can be null.
+     * @see #assertInLayoutOrScroll(String)
+     */
+    void assertNotInLayoutOrScroll(String message) {
+        if (mRunningLayoutOrScroll) {
+            if (message == null) {
+                throw new IllegalStateException("Cannot call this method while RecyclerView is "
+                        + "computing a layout or scrolling");
+            }
+            throw new IllegalStateException(message);
+        }
+    }
+
+    /**
+     * Add an {@link OnItemTouchListener} to intercept touch events before they are dispatched
+     * to child views or this view's standard scrolling behavior.
+     *
+     * <p>Client code may use listeners to implement item manipulation behavior. Once a listener
+     * returns true from
+     * {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} its
+     * {@link OnItemTouchListener#onTouchEvent(RecyclerView, MotionEvent)} method will be called
+     * for each incoming MotionEvent until the end of the gesture.</p>
+     *
+     * @param listener Listener to add
+     */
+    public void addOnItemTouchListener(OnItemTouchListener listener) {
+        mOnItemTouchListeners.add(listener);
+    }
+
+    /**
+     * Remove an {@link OnItemTouchListener}. It will no longer be able to intercept touch events.
+     *
+     * @param listener Listener to remove
+     */
+    public void removeOnItemTouchListener(OnItemTouchListener listener) {
+        mOnItemTouchListeners.remove(listener);
+        if (mActiveOnItemTouchListener == listener) {
+            mActiveOnItemTouchListener = null;
+        }
+    }
+
+    private boolean dispatchOnItemTouchIntercept(MotionEvent e) {
+        final int action = e.getAction();
+        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) {
+            mActiveOnItemTouchListener = null;
+        }
+
+        final int listenerCount = mOnItemTouchListeners.size();
+        for (int i = 0; i < listenerCount; i++) {
+            final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
+            if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
+                mActiveOnItemTouchListener = listener;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean dispatchOnItemTouch(MotionEvent e) {
+        final int action = e.getAction();
+        if (mActiveOnItemTouchListener != null) {
+            if (action == MotionEvent.ACTION_DOWN) {
+                // Stale state from a previous gesture, we're starting a new one. Clear it.
+                mActiveOnItemTouchListener = null;
+            } else {
+                mActiveOnItemTouchListener.onTouchEvent(this, e);
+                if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+                    // Clean up for the next gesture.
+                    mActiveOnItemTouchListener = null;
+                }
+                return true;
+            }
+        }
+
+        // Listeners will have already received the ACTION_DOWN via dispatchOnItemTouchIntercept
+        // as called from onInterceptTouchEvent; skip it.
+        if (action != MotionEvent.ACTION_DOWN) {
+            final int listenerCount = mOnItemTouchListeners.size();
+            for (int i = 0; i < listenerCount; i++) {
+                final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
+                if (listener.onInterceptTouchEvent(this, e)) {
+                    mActiveOnItemTouchListener = listener;
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent e) {
+        if (dispatchOnItemTouchIntercept(e)) {
+            cancelTouch();
+            return true;
+        }
+
+        final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
+        final boolean canScrollVertically = mLayout.canScrollVertically();
+
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(e);
+
+        final int action = MotionEventCompat.getActionMasked(e);
+        final int actionIndex = MotionEventCompat.getActionIndex(e);
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mScrollPointerId = MotionEventCompat.getPointerId(e, 0);
+                mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
+                mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
+
+                if (mScrollState == SCROLL_STATE_SETTLING) {
+                    getParent().requestDisallowInterceptTouchEvent(true);
+                    setScrollState(SCROLL_STATE_DRAGGING);
+                }
+                break;
+
+            case MotionEventCompat.ACTION_POINTER_DOWN:
+                mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex);
+                mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f);
+                mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f);
+                break;
+
+            case MotionEvent.ACTION_MOVE: {
+                final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
+                if (index < 0) {
+                    Log.e(TAG, "Error processing scroll; pointer index for id " +
+                            mScrollPointerId + " not found. Did any MotionEvents get skipped?");
+                    return false;
+                }
+
+                final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
+                final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
+                if (mScrollState != SCROLL_STATE_DRAGGING) {
+                    final int dx = x - mInitialTouchX;
+                    final int dy = y - mInitialTouchY;
+                    boolean startScroll = false;
+                    if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
+                        mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
+                        startScroll = true;
+                    }
+                    if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
+                        mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
+                        startScroll = true;
+                    }
+                    if (startScroll) {
+                        getParent().requestDisallowInterceptTouchEvent(true);
+                        setScrollState(SCROLL_STATE_DRAGGING);
+                    }
+                }
+            } break;
+
+            case MotionEventCompat.ACTION_POINTER_UP: {
+                onPointerUp(e);
+            } break;
+
+            case MotionEvent.ACTION_UP: {
+                mVelocityTracker.clear();
+            } break;
+
+            case MotionEvent.ACTION_CANCEL: {
+                cancelTouch();
+            }
+        }
+        return mScrollState == SCROLL_STATE_DRAGGING;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent e) {
+        if (dispatchOnItemTouch(e)) {
+            cancelTouch();
+            return true;
+        }
+
+        final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
+        final boolean canScrollVertically = mLayout.canScrollVertically();
+
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(e);
+
+        final int action = MotionEventCompat.getActionMasked(e);
+        final int actionIndex = MotionEventCompat.getActionIndex(e);
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN: {
+                mScrollPointerId = MotionEventCompat.getPointerId(e, 0);
+                mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
+                mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
+            } break;
+
+            case MotionEventCompat.ACTION_POINTER_DOWN: {
+                mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex);
+                mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f);
+                mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f);
+            } break;
+
+            case MotionEvent.ACTION_MOVE: {
+                final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId);
+                if (index < 0) {
+                    Log.e(TAG, "Error processing scroll; pointer index for id " +
+                            mScrollPointerId + " not found. Did any MotionEvents get skipped?");
+                    return false;
+                }
+
+                final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f);
+                final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f);
+                if (mScrollState != SCROLL_STATE_DRAGGING) {
+                    final int dx = x - mInitialTouchX;
+                    final int dy = y - mInitialTouchY;
+                    boolean startScroll = false;
+                    if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
+                        mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1);
+                        startScroll = true;
+                    }
+                    if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
+                        mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1);
+                        startScroll = true;
+                    }
+                    if (startScroll) {
+                        getParent().requestDisallowInterceptTouchEvent(true);
+                        setScrollState(SCROLL_STATE_DRAGGING);
+                    }
+                }
+                if (mScrollState == SCROLL_STATE_DRAGGING) {
+                    final int dx = x - mLastTouchX;
+                    final int dy = y - mLastTouchY;
+                    scrollByInternal(canScrollHorizontally ? -dx : 0,
+                            canScrollVertically ? -dy : 0);
+                }
+                mLastTouchX = x;
+                mLastTouchY = y;
+            } break;
+
+            case MotionEventCompat.ACTION_POINTER_UP: {
+                onPointerUp(e);
+            } break;
+
+            case MotionEvent.ACTION_UP: {
+                mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
+                final float xvel = canScrollHorizontally ?
+                        -VelocityTrackerCompat.getXVelocity(mVelocityTracker, mScrollPointerId) : 0;
+                final float yvel = canScrollVertically ?
+                        -VelocityTrackerCompat.getYVelocity(mVelocityTracker, mScrollPointerId) : 0;
+                if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
+                    setScrollState(SCROLL_STATE_IDLE);
+                }
+                mVelocityTracker.clear();
+                releaseGlows();
+            } break;
+
+            case MotionEvent.ACTION_CANCEL: {
+                cancelTouch();
+            } break;
+        }
+
+        return true;
+    }
+
+    private void cancelTouch() {
+        if (mVelocityTracker != null) {
+            mVelocityTracker.clear();
+        }
+        releaseGlows();
+        setScrollState(SCROLL_STATE_IDLE);
+    }
+
+    private void onPointerUp(MotionEvent e) {
+        final int actionIndex = MotionEventCompat.getActionIndex(e);
+        if (MotionEventCompat.getPointerId(e, actionIndex) == mScrollPointerId) {
+            // Pick a new pointer to pick up the slack.
+            final int newIndex = actionIndex == 0 ? 1 : 0;
+            mScrollPointerId = MotionEventCompat.getPointerId(e, newIndex);
+            mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, newIndex) + 0.5f);
+            mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, newIndex) + 0.5f);
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthSpec, int heightSpec) {
+        if (mAdapterUpdateDuringMeasure) {
+            eatRequestLayout();
+            processAdapterUpdatesAndSetAnimationFlags();
+
+            if (mState.mRunPredictiveAnimations) {
+                // TODO: try to provide a better approach.
+                // When RV decides to run predictive animations, we need to measure in pre-layout
+                // state so that pre-layout pass results in correct layout.
+                // On the other hand, this will prevent the layout manager from resizing properly.
+                mState.mInPreLayout = true;
+            } else {
+                // consume remaining updates to provide a consistent state with the layout pass.
+                mAdapterHelper.consumeUpdatesInOnePass();
+                mState.mInPreLayout = false;
+            }
+            mAdapterUpdateDuringMeasure = false;
+            resumeRequestLayout(false);
+        }
+
+        if (mAdapter != null) {
+            mState.mItemCount = mAdapter.getItemCount();
+        } else {
+            mState.mItemCount = 0;
+        }
+
+        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
+        mState.mInPreLayout = false; // clear
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        if (w != oldw || h != oldh) {
+            invalidateGlows();
+        }
+    }
+
+    /**
+     * Sets the {@link ItemAnimator} that will handle animations involving changes
+     * to the items in this RecyclerView. By default, RecyclerView instantiates and
+     * uses an instance of {@link DefaultItemAnimator}. Whether item animations are
+     * enabled for the RecyclerView depends on the ItemAnimator and whether
+     * the LayoutManager {@link LayoutManager#supportsPredictiveItemAnimations()
+     * supports item animations}.
+     *
+     * @param animator The ItemAnimator being set. If null, no animations will occur
+     * when changes occur to the items in this RecyclerView.
+     */
+    public void setItemAnimator(ItemAnimator animator) {
+        if (mItemAnimator != null) {
+            mItemAnimator.endAnimations();
+            mItemAnimator.setListener(null);
+        }
+        mItemAnimator = animator;
+        if (mItemAnimator != null) {
+            mItemAnimator.setListener(mItemAnimatorListener);
+        }
+    }
+
+    /**
+     * Gets the current ItemAnimator for this RecyclerView. A null return value
+     * indicates that there is no animator and that item changes will happen without
+     * any animations. By default, RecyclerView instantiates and
+     * uses an instance of {@link DefaultItemAnimator}.
+     *
+     * @return ItemAnimator The current ItemAnimator. If null, no animations will occur
+     * when changes occur to the items in this RecyclerView.
+     */
+    public ItemAnimator getItemAnimator() {
+        return mItemAnimator;
+    }
+
+    private boolean supportsChangeAnimations() {
+        return mItemAnimator != null && mItemAnimator.getSupportsChangeAnimations();
+    }
+
+    /**
+     * Post a runnable to the next frame to run pending item animations. Only the first such
+     * request will be posted, governed by the mPostedAnimatorRunner flag.
+     */
+    private void postAnimationRunner() {
+        if (!mPostedAnimatorRunner && mIsAttached) {
+            ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
+            mPostedAnimatorRunner = true;
+        }
+    }
+
+    private boolean predictiveItemAnimationsEnabled() {
+        return (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations());
+    }
+
+    /**
+     * Consumes adapter updates and calculates which type of animations we want to run.
+     * Called in onMeasure and dispatchLayout.
+     * <p>
+     * This method may process only the pre-layout state of updates or all of them.
+     */
+    private void processAdapterUpdatesAndSetAnimationFlags() {
+        if (mDataSetHasChangedAfterLayout) {
+            // Processing these items have no value since data set changed unexpectedly.
+            // Instead, we just reset it.
+            mAdapterHelper.reset();
+            markKnownViewsInvalid();
+            mLayout.onItemsChanged(this);
+        }
+        // simple animations are a subset of advanced animations (which will cause a
+        // pre-layout step)
+        // If layout supports predictive animations, pre-process to decide if we want to run them
+        if (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations()) {
+            mAdapterHelper.preProcess();
+        } else {
+            mAdapterHelper.consumeUpdatesInOnePass();
+        }
+        boolean animationTypeSupported = (mItemsAddedOrRemoved && !mItemsChanged) ||
+                (mItemsAddedOrRemoved || (mItemsChanged && supportsChangeAnimations()));
+        mState.mRunSimpleAnimations = mFirstLayoutComplete && mItemAnimator != null &&
+                (mDataSetHasChangedAfterLayout || animationTypeSupported ||
+                        mLayout.mRequestedSimpleAnimations) &&
+                (!mDataSetHasChangedAfterLayout || mAdapter.hasStableIds());
+        mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations &&
+                animationTypeSupported && !mDataSetHasChangedAfterLayout &&
+                predictiveItemAnimationsEnabled();
+    }
+
+    /**
+     * Wrapper around layoutChildren() that handles animating changes caused by layout.
+     * Animations work on the assumption that there are five different kinds of items
+     * in play:
+     * PERSISTENT: items are visible before and after layout
+     * REMOVED: items were visible before layout and were removed by the app
+     * ADDED: items did not exist before layout and were added by the app
+     * DISAPPEARING: items exist in the data set before/after, but changed from
+     * visible to non-visible in the process of layout (they were moved off
+     * screen as a side-effect of other changes)
+     * APPEARING: items exist in the data set before/after, but changed from
+     * non-visible to visible in the process of layout (they were moved on
+     * screen as a side-effect of other changes)
+     * The overall approach figures out what items exist before/after layout and
+     * infers one of the five above states for each of the items. Then the animations
+     * are set up accordingly:
+     * PERSISTENT views are moved ({@link ItemAnimator#animateMove(ViewHolder, int, int, int, int)})
+     * REMOVED views are removed ({@link ItemAnimator#animateRemove(ViewHolder)})
+     * ADDED views are added ({@link ItemAnimator#animateAdd(ViewHolder)})
+     * DISAPPEARING views are moved off screen
+     * APPEARING views are moved on screen
+     */
+    void dispatchLayout() {
+        if (mAdapter == null) {
+            Log.e(TAG, "No adapter attached; skipping layout");
+            return;
+        }
+        mDisappearingViewsInLayoutPass.clear();
+        eatRequestLayout();
+        mRunningLayoutOrScroll = true;
+
+        processAdapterUpdatesAndSetAnimationFlags();
+
+        mState.mOldChangedHolders = mState.mRunSimpleAnimations && mItemsChanged
+                && supportsChangeAnimations() ? new ArrayMap<Long, ViewHolder>() : null;
+        mItemsAddedOrRemoved = mItemsChanged = false;
+        ArrayMap<View, Rect> appearingViewInitialBounds = null;
+        mState.mInPreLayout = mState.mRunPredictiveAnimations;
+        mState.mItemCount = mAdapter.getItemCount();
+
+        if (mState.mRunSimpleAnimations) {
+            // Step 0: Find out where all non-removed items are, pre-layout
+            mState.mPreLayoutHolderMap.clear();
+            mState.mPostLayoutHolderMap.clear();
+            int count = mChildHelper.getChildCount();
+            for (int i = 0; i < count; ++i) {
+                final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
+                if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
+                    continue;
+                }
+                final View view = holder.itemView;
+                mState.mPreLayoutHolderMap.put(holder, new ItemHolderInfo(holder,
+                        view.getLeft(), view.getTop(), view.getRight(), view.getBottom()));
+            }
+        }
+        if (mState.mRunPredictiveAnimations) {
+            // Step 1: run prelayout: This will use the old positions of items. The layout manager
+            // is expected to layout everything, even removed items (though not to add removed
+            // items back to the container). This gives the pre-layout position of APPEARING views
+            // which come into existence as part of the real layout.
+
+            // Save old positions so that LayoutManager can run its mapping logic.
+            saveOldPositions();
+            // processAdapterUpdatesAndSetAnimationFlags already run pre-layout animations.
+            if (mState.mOldChangedHolders != null) {
+                int count = mChildHelper.getChildCount();
+                for (int i = 0; i < count; ++i) {
+                    final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
+                    if (holder.isChanged() && !holder.isRemoved() && !holder.shouldIgnore()) {
+                        long key = getChangedHolderKey(holder);
+                        mState.mOldChangedHolders.put(key, holder);
+                        mState.mPreLayoutHolderMap.remove(holder);
+                    }
+                }
+            }
+
+            final boolean didStructureChange = mState.mStructureChanged;
+            mState.mStructureChanged = false;
+            // temporarily disable flag because we are asking for previous layout
+            mLayout.onLayoutChildren(mRecycler, mState);
+            mState.mStructureChanged = didStructureChange;
+
+            appearingViewInitialBounds = new ArrayMap<View, Rect>();
+            for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
+                boolean found = false;
+                View child = mChildHelper.getChildAt(i);
+                if (getChildViewHolderInt(child).shouldIgnore()) {
+                    continue;
+                }
+                for (int j = 0; j < mState.mPreLayoutHolderMap.size(); ++j) {
+                    ViewHolder holder = mState.mPreLayoutHolderMap.keyAt(j);
+                    if (holder.itemView == child) {
+                        found = true;
+                        continue;
+                    }
+                }
+                if (!found) {
+                    appearingViewInitialBounds.put(child, new Rect(child.getLeft(), child.getTop(),
+                            child.getRight(), child.getBottom()));
+                }
+            }
+            // we don't process disappearing list because they may re-appear in post layout pass.
+            clearOldPositions();
+            mAdapterHelper.consumePostponedUpdates();
+        } else {
+            clearOldPositions();
+            // in case pre layout did run but we decided not to run predictive animations.
+            mAdapterHelper.consumeUpdatesInOnePass();
+            if (mState.mOldChangedHolders != null) {
+                int count = mChildHelper.getChildCount();
+                for (int i = 0; i < count; ++i) {
+                    final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
+                    if (holder.isChanged() && !holder.isRemoved() && !holder.shouldIgnore()) {
+                        long key = getChangedHolderKey(holder);
+                        mState.mOldChangedHolders.put(key, holder);
+                        mState.mPreLayoutHolderMap.remove(holder);
+                    }
+                }
+            }
+        }
+        mState.mItemCount = mAdapter.getItemCount();
+        mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
+
+        // Step 2: Run layout
+        mState.mInPreLayout = false;
+        mLayout.onLayoutChildren(mRecycler, mState);
+
+        mState.mStructureChanged = false;
+        mPendingSavedState = null;
+
+        // onLayoutChildren may have caused client code to disable item animations; re-check
+        mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
+
+        if (mState.mRunSimpleAnimations) {
+            // Step 3: Find out where things are now, post-layout
+            ArrayMap<Long, ViewHolder> newChangedHolders = mState.mOldChangedHolders != null ?
+                    new ArrayMap<Long, ViewHolder>() : null;
+            int count = mChildHelper.getChildCount();
+            for (int i = 0; i < count; ++i) {
+                ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
+                if (holder.shouldIgnore()) {
+                    continue;
+                }
+                final View view = holder.itemView;
+                long key = getChangedHolderKey(holder);
+                if (newChangedHolders != null && mState.mOldChangedHolders.get(key) != null) {
+                    newChangedHolders.put(key, holder);
+                } else {
+                    mState.mPostLayoutHolderMap.put(holder, new ItemHolderInfo(holder,
+                            view.getLeft(), view.getTop(), view.getRight(), view.getBottom()));
+                }
+            }
+            processDisappearingList();
+            // Step 4: Animate DISAPPEARING and REMOVED items
+            int preLayoutCount = mState.mPreLayoutHolderMap.size();
+            for (int i = preLayoutCount - 1; i >= 0; i--) {
+                ViewHolder itemHolder = mState.mPreLayoutHolderMap.keyAt(i);
+                if (!mState.mPostLayoutHolderMap.containsKey(itemHolder)) {
+                    ItemHolderInfo disappearingItem = mState.mPreLayoutHolderMap.valueAt(i);
+                    mState.mPreLayoutHolderMap.removeAt(i);
+
+                    View disappearingItemView = disappearingItem.holder.itemView;
+                    removeDetachedView(disappearingItemView, false);
+                    mRecycler.unscrapView(disappearingItem.holder);
+
+                    animateDisappearance(disappearingItem);
+                }
+            }
+            // Step 5: Animate APPEARING and ADDED items
+            int postLayoutCount = mState.mPostLayoutHolderMap.size();
+            if (postLayoutCount > 0) {
+                for (int i = postLayoutCount - 1; i >= 0; i--) {
+                    ViewHolder itemHolder = mState.mPostLayoutHolderMap.keyAt(i);
+                    ItemHolderInfo info = mState.mPostLayoutHolderMap.valueAt(i);
+                    if ((mState.mPreLayoutHolderMap.isEmpty() ||
+                            !mState.mPreLayoutHolderMap.containsKey(itemHolder))) {
+                        mState.mPostLayoutHolderMap.removeAt(i);
+                        Rect initialBounds = (appearingViewInitialBounds != null) ?
+                                appearingViewInitialBounds.get(itemHolder.itemView) : null;
+                        animateAppearance(itemHolder, initialBounds,
+                                info.left, info.top);
+                    }
+                }
+            }
+            // Step 6: Animate PERSISTENT items
+            count = mState.mPostLayoutHolderMap.size();
+            for (int i = 0; i < count; ++i) {
+                ViewHolder postHolder = mState.mPostLayoutHolderMap.keyAt(i);
+                ItemHolderInfo postInfo = mState.mPostLayoutHolderMap.valueAt(i);
+                ItemHolderInfo preInfo = mState.mPreLayoutHolderMap.get(postHolder);
+                if (preInfo != null && postInfo != null) {
+                    if (preInfo.left != postInfo.left || preInfo.top != postInfo.top) {
+                        postHolder.setIsRecyclable(false);
+                        if (DEBUG) {
+                            Log.d(TAG, "PERSISTENT: " + postHolder +
+                                    " with view " + postHolder.itemView);
+                        }
+                        if (mItemAnimator.animateMove(postHolder,
+                                preInfo.left, preInfo.top, postInfo.left, postInfo.top)) {
+                            postAnimationRunner();
+                        }
+                    }
+                }
+            }
+            // Step 7: Animate CHANGING items
+            count = mState.mOldChangedHolders != null ? mState.mOldChangedHolders.size() : 0;
+            // traverse reverse in case view gets recycled while we are traversing the list.
+            for (int i = count - 1; i >= 0; i--) {
+                long key = mState.mOldChangedHolders.keyAt(i);
+                ViewHolder oldHolder = mState.mOldChangedHolders.get(key);
+                View oldView = oldHolder.itemView;
+                if (oldHolder.shouldIgnore()) {
+                    continue;
+                }
+                // We probably don't need this check anymore since these views are removed from
+                // the list if they are recycled.
+                if (mRecycler.mChangedScrap != null &&
+                        mRecycler.mChangedScrap.contains(oldHolder)) {
+                    animateChange(oldHolder, newChangedHolders.get(key));
+                } else if (DEBUG) {
+                    Log.e(TAG, "cannot find old changed holder in changed scrap :/" + oldHolder);
+                }
+            }
+        }
+        resumeRequestLayout(false);
+        mLayout.removeAndRecycleScrapInt(mRecycler, !mState.mRunPredictiveAnimations);
+        mState.mPreviousLayoutItemCount = mState.mItemCount;
+        mDataSetHasChangedAfterLayout = false;
+        mState.mRunSimpleAnimations = false;
+        mState.mRunPredictiveAnimations = false;
+        mRunningLayoutOrScroll = false;
+        mLayout.mRequestedSimpleAnimations = false;
+        if (mRecycler.mChangedScrap != null) {
+            mRecycler.mChangedScrap.clear();
+        }
+        mState.mOldChangedHolders = null;
+    }
+
+    /**
+     * Returns a unique key to be used while handling change animations.
+     * It might be child's position or stable id depending on the adapter type.
+     */
+    long getChangedHolderKey(ViewHolder holder) {
+        return mAdapter.hasStableIds() ? holder.getItemId() : holder.mPosition;
+    }
+
+    /**
+     * A LayoutManager may want to layout a view just to animate disappearance.
+     * This method handles those views and triggers remove animation on them.
+     */
+    private void processDisappearingList() {
+        final int count = mDisappearingViewsInLayoutPass.size();
+        for (int i = 0; i < count; i ++) {
+            View view = mDisappearingViewsInLayoutPass.get(i);
+            ViewHolder vh = getChildViewHolderInt(view);
+            final ItemHolderInfo info = mState.mPreLayoutHolderMap.remove(vh);
+            if (!mState.isPreLayout()) {
+                mState.mPostLayoutHolderMap.remove(vh);
+            }
+            if (info != null) {
+                animateDisappearance(info);
+            } else {
+                // let it disappear from the position it becomes visible
+                animateDisappearance(new ItemHolderInfo(vh, view.getLeft(), view.getTop(),
+                        view.getRight(), view.getBottom()));
+            }
+        }
+        mDisappearingViewsInLayoutPass.clear();
+    }
+
+    private void animateAppearance(ViewHolder itemHolder, Rect beforeBounds, int afterLeft,
+            int afterTop) {
+        View newItemView = itemHolder.itemView;
+        if (beforeBounds != null &&
+                (beforeBounds.left != afterLeft || beforeBounds.top != afterTop)) {
+            // slide items in if before/after locations differ
+            itemHolder.setIsRecyclable(false);
+            if (DEBUG) {
+                Log.d(TAG, "APPEARING: " + itemHolder + " with view " + newItemView);
+            }
+            if (mItemAnimator.animateMove(itemHolder,
+                    beforeBounds.left, beforeBounds.top,
+                    afterLeft, afterTop)) {
+                postAnimationRunner();
+            }
+        } else {
+            if (DEBUG) {
+                Log.d(TAG, "ADDED: " + itemHolder + " with view " + newItemView);
+            }
+            itemHolder.setIsRecyclable(false);
+            if (mItemAnimator.animateAdd(itemHolder)) {
+                postAnimationRunner();
+            }
+        }
+    }
+
+    private void animateDisappearance(ItemHolderInfo disappearingItem) {
+        View disappearingItemView = disappearingItem.holder.itemView;
+        addAnimatingView(disappearingItemView);
+        int oldLeft = disappearingItem.left;
+        int oldTop = disappearingItem.top;
+        int newLeft = disappearingItemView.getLeft();
+        int newTop = disappearingItemView.getTop();
+        if (oldLeft != newLeft || oldTop != newTop) {
+            disappearingItem.holder.setIsRecyclable(false);
+            disappearingItemView.layout(newLeft, newTop,
+                    newLeft + disappearingItemView.getWidth(),
+                    newTop + disappearingItemView.getHeight());
+            if (DEBUG) {
+                Log.d(TAG, "DISAPPEARING: " + disappearingItem.holder +
+                        " with view " + disappearingItemView);
+            }
+            if (mItemAnimator.animateMove(disappearingItem.holder, oldLeft, oldTop,
+                    newLeft, newTop)) {
+                postAnimationRunner();
+            }
+        } else {
+            if (DEBUG) {
+                Log.d(TAG, "REMOVED: " + disappearingItem.holder +
+                        " with view " + disappearingItemView);
+            }
+            disappearingItem.holder.setIsRecyclable(false);
+            if (mItemAnimator.animateRemove(disappearingItem.holder)) {
+                postAnimationRunner();
+            }
+        }
+    }
+
+    private void animateChange(ViewHolder oldHolder, ViewHolder newHolder) {
+        oldHolder.setIsRecyclable(false);
+        removeDetachedView(oldHolder.itemView, false);
+        addAnimatingView(oldHolder.itemView);
+        oldHolder.mShadowedHolder = newHolder;
+        mRecycler.unscrapView(oldHolder);
+        if (DEBUG) {
+            Log.d(TAG, "CHANGED: " + oldHolder + " with view " + oldHolder.itemView);
+        }
+        final int fromLeft = oldHolder.itemView.getLeft();
+        final int fromTop = oldHolder.itemView.getTop();
+        final int toLeft, toTop;
+        if (newHolder == null || newHolder.shouldIgnore()) {
+            toLeft = fromLeft;
+            toTop = fromTop;
+        } else {
+            toLeft = newHolder.itemView.getLeft();
+            toTop = newHolder.itemView.getTop();
+            newHolder.setIsRecyclable(false);
+            newHolder.mShadowingHolder = oldHolder;
+        }
+        if(mItemAnimator.animateChange(oldHolder, newHolder,
+                fromLeft, fromTop, toLeft, toTop)) {
+            postAnimationRunner();
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        eatRequestLayout();
+        dispatchLayout();
+        resumeRequestLayout(false);
+        mFirstLayoutComplete = true;
+    }
+
+    @Override
+    public void requestLayout() {
+        if (!mEatRequestLayout) {
+            super.requestLayout();
+        } else {
+            mLayoutRequestEaten = true;
+        }
+    }
+
+    void markItemDecorInsetsDirty() {
+        final int childCount = mChildHelper.getUnfilteredChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = mChildHelper.getUnfilteredChildAt(i);
+            ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
+        }
+        mRecycler.markItemDecorInsetsDirty();
+    }
+
+    @Override
+    public void draw(Canvas c) {
+        super.draw(c);
+
+        final int count = mItemDecorations.size();
+        for (int i = 0; i < count; i++) {
+            mItemDecorations.get(i).onDrawOver(c, this, mState);
+        }
+        // TODO If padding is not 0 and chilChildrenToPadding is false, to draw glows properly, we
+        // need find children closest to edges. Not sure if it is worth the effort.
+        boolean needsInvalidate = false;
+        if (mLeftGlow != null && !mLeftGlow.isFinished()) {
+            final int restore = c.save();
+            final int padding = mClipToPadding ? getPaddingBottom() : 0;
+            c.rotate(270);
+            c.translate(-getHeight() + padding, 0);
+            needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c);
+            c.restoreToCount(restore);
+        }
+        if (mTopGlow != null && !mTopGlow.isFinished()) {
+            final int restore = c.save();
+            if (mClipToPadding) {
+                c.translate(getPaddingLeft(), getPaddingTop());
+            }
+            needsInvalidate |= mTopGlow != null && mTopGlow.draw(c);
+            c.restoreToCount(restore);
+        }
+        if (mRightGlow != null && !mRightGlow.isFinished()) {
+            final int restore = c.save();
+            final int width = getWidth();
+            final int padding = mClipToPadding ? getPaddingTop() : 0;
+            c.rotate(90);
+            c.translate(-padding, -width);
+            needsInvalidate |= mRightGlow != null && mRightGlow.draw(c);
+            c.restoreToCount(restore);
+        }
+        if (mBottomGlow != null && !mBottomGlow.isFinished()) {
+            final int restore = c.save();
+            c.rotate(180);
+            if (mClipToPadding) {
+                c.translate(-getWidth() + getPaddingRight(), -getHeight() + getPaddingBottom());
+            } else {
+                c.translate(-getWidth(), -getHeight());
+            }
+            needsInvalidate |= mBottomGlow != null && mBottomGlow.draw(c);
+            c.restoreToCount(restore);
+        }
+
+        // If some views are animating, ItemDecorators are likely to move/change with them.
+        // Invalidate RecyclerView to re-draw decorators. This is still efficient because children's
+        // display lists are not invalidated.
+        if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0 &&
+                mItemAnimator.isRunning()) {
+            needsInvalidate = true;
+        }
+
+        if (needsInvalidate) {
+            ViewCompat.postInvalidateOnAnimation(this);
+        }
+    }
+
+    @Override
+    public void onDraw(Canvas c) {
+        super.onDraw(c);
+
+        final int count = mItemDecorations.size();
+        for (int i = 0; i < count; i++) {
+            mItemDecorations.get(i).onDraw(c, this, mState);
+        }
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof LayoutParams && mLayout.checkLayoutParams((LayoutParams) p);
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+        if (mLayout == null) {
+            throw new IllegalStateException("RecyclerView has no LayoutManager");
+        }
+        return mLayout.generateDefaultLayoutParams();
+    }
+
+    @Override
+    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+        if (mLayout == null) {
+            throw new IllegalStateException("RecyclerView has no LayoutManager");
+        }
+        return mLayout.generateLayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+        if (mLayout == null) {
+            throw new IllegalStateException("RecyclerView has no LayoutManager");
+        }
+        return mLayout.generateLayoutParams(p);
+    }
+
+    void saveOldPositions() {
+        final int childCount = mChildHelper.getUnfilteredChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+            if (DEBUG && holder.mPosition == -1 && !holder.isRemoved()) {
+                throw new IllegalStateException("view holder cannot have position -1 unless it"
+                        + " is removed");
+            }
+            if (!holder.shouldIgnore()) {
+                holder.saveOldPosition();
+            }
+        }
+    }
+
+    void clearOldPositions() {
+        final int childCount = mChildHelper.getUnfilteredChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+            if (!holder.shouldIgnore()) {
+                holder.clearOldPosition();
+            }
+        }
+        mRecycler.clearOldPositions();
+    }
+
+    void offsetPositionRecordsForMove(int from, int to) {
+        final int childCount = mChildHelper.getUnfilteredChildCount();
+        final int start, end, inBetweenOffset;
+        if (from < to) {
+            start = from;
+            end = to;
+            inBetweenOffset = -1;
+        } else {
+            start = to;
+            end = from;
+            inBetweenOffset = 1;
+        }
+
+        for (int i = 0; i < childCount; i++) {
+            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+            if (holder == null || holder.mPosition < start || holder.mPosition > end) {
+                continue;
+            }
+            if (DEBUG) {
+                Log.d(TAG, "offsetPositionRecordsForMove attached child " + i + " holder " +
+                        holder);
+            }
+            if (holder.mPosition == from) {
+                holder.offsetPosition(to - from, false);
+            } else {
+                holder.offsetPosition(inBetweenOffset, false);
+            }
+
+            mState.mStructureChanged = true;
+        }
+        mRecycler.offsetPositionRecordsForMove(from, to);
+        requestLayout();
+    }
+
+    void offsetPositionRecordsForInsert(int positionStart, int itemCount) {
+        final int childCount = mChildHelper.getUnfilteredChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+            if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) {
+                if (DEBUG) {
+                    Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder " +
+                            holder + " now at position " + (holder.mPosition + itemCount));
+                }
+                holder.offsetPosition(itemCount, false);
+                mState.mStructureChanged = true;
+            }
+        }
+        mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount);
+        requestLayout();
+    }
+
+    void offsetPositionRecordsForRemove(int positionStart, int itemCount,
+            boolean applyToPreLayout) {
+        final int positionEnd = positionStart + itemCount;
+        final int childCount = mChildHelper.getUnfilteredChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+            if (holder != null && !holder.shouldIgnore()) {
+                if (holder.mPosition >= positionEnd) {
+                    if (DEBUG) {
+                        Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i +
+                                " holder " + holder + " now at position " +
+                                (holder.mPosition - itemCount));
+                    }
+                    holder.offsetPosition(-itemCount, applyToPreLayout);
+                    mState.mStructureChanged = true;
+                } else if (holder.mPosition >= positionStart) {
+                    if (DEBUG) {
+                        Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i +
+                                " holder " + holder + " now REMOVED");
+                    }
+                    holder.addFlags(ViewHolder.FLAG_REMOVED);
+                    mState.mStructureChanged = true;
+                    holder.offsetPosition(-itemCount, applyToPreLayout);
+                }
+            }
+        }
+        mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount, applyToPreLayout);
+        requestLayout();
+    }
+
+    /**
+     * Rebind existing views for the given range, or create as needed.
+     *
+     * @param positionStart Adapter position to start at
+     * @param itemCount Number of views that must explicitly be rebound
+     */
+    void viewRangeUpdate(int positionStart, int itemCount) {
+        final int childCount = mChildHelper.getUnfilteredChildCount();
+        final int positionEnd = positionStart + itemCount;
+
+        for (int i = 0; i < childCount; i++) {
+            final View child = mChildHelper.getUnfilteredChildAt(i);
+            final ViewHolder holder = getChildViewHolderInt(child);
+            if (holder == null || holder.shouldIgnore()) {
+                continue;
+            }
+            if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) {
+                // We re-bind these view holders after pre-processing is complete so that
+                // ViewHolders have their final positions assigned.
+                holder.addFlags(ViewHolder.FLAG_UPDATE);
+                if (supportsChangeAnimations()) {
+                    holder.addFlags(ViewHolder.FLAG_CHANGED);
+                }
+                // lp cannot be null since we get ViewHolder from it.
+                ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
+            }
+        }
+        mRecycler.viewRangeUpdate(positionStart, itemCount);
+    }
+
+    void rebindUpdatedViewHolders() {
+        final int childCount = mChildHelper.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
+            // validate type is correct
+            if (holder == null || holder.shouldIgnore()) {
+                continue;
+            }
+            if (holder.isRemoved() || holder.isInvalid()) {
+                requestLayout();
+            } else if (holder.needsUpdate()) {
+                final int type = mAdapter.getItemViewType(holder.mPosition);
+                if (holder.getItemViewType() == type) {
+                    // Binding an attached view will request a layout if needed.
+                    if (!holder.isChanged() || !supportsChangeAnimations()) {
+                        mAdapter.bindViewHolder(holder, holder.mPosition);
+                    } else {
+                        // Don't rebind changed holders if change animations are enabled.
+                        // We want the old contents for the animation and will get a new
+                        // holder for the new contents.
+                        requestLayout();
+                    }
+                } else {
+                    // binding to a new view will need re-layout anyways. We can as well trigger
+                    // it here so that it happens during layout
+                    holder.addFlags(ViewHolder.FLAG_INVALID);
+                    requestLayout();
+                }
+            }
+        }
+    }
+
+    /**
+     * Mark all known views as invalid. Used in response to a, "the whole world might have changed"
+     * data change event.
+     */
+    void markKnownViewsInvalid() {
+        final int childCount = mChildHelper.getUnfilteredChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+            if (holder != null && !holder.shouldIgnore()) {
+                holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
+            }
+        }
+        markItemDecorInsetsDirty();
+        mRecycler.markKnownViewsInvalid();
+    }
+
+    /**
+     * Invalidates all ItemDecorations. If RecyclerView has item decorations, calling this method
+     * will trigger a {@link #requestLayout()} call.
+     */
+    public void invalidateItemDecorations() {
+        if (mItemDecorations.size() == 0) {
+            return;
+        }
+        if (mLayout != null) {
+            mLayout.assertNotInLayoutOrScroll("Cannot invalidate item decorations during a scroll"
+                    + " or layout");
+        }
+        markItemDecorInsetsDirty();
+        requestLayout();
+    }
+
+    /**
+     * Retrieve the {@link ViewHolder} for the given child view.
+     *
+     * @param child Child of this RecyclerView to query for its ViewHolder
+     * @return The child view's ViewHolder
+     */
+    public ViewHolder getChildViewHolder(View child) {
+        final ViewParent parent = child.getParent();
+        if (parent != null && parent != this) {
+            throw new IllegalArgumentException("View " + child + " is not a direct child of " +
+                    this);
+        }
+        return getChildViewHolderInt(child);
+    }
+
+    static ViewHolder getChildViewHolderInt(View child) {
+        if (child == null) {
+            return null;
+        }
+        return ((LayoutParams) child.getLayoutParams()).mViewHolder;
+    }
+
+    /**
+     * Return the adapter position that the given child view corresponds to.
+     *
+     * @param child Child View to query
+     * @return Adapter position corresponding to the given view or {@link #NO_POSITION}
+     */
+    public int getChildPosition(View child) {
+        final ViewHolder holder = getChildViewHolderInt(child);
+        return holder != null ? holder.getPosition() : NO_POSITION;
+    }
+
+    /**
+     * Return the stable item id that the given child view corresponds to.
+     *
+     * @param child Child View to query
+     * @return Item id corresponding to the given view or {@link #NO_ID}
+     */
+    public long getChildItemId(View child) {
+        if (mAdapter == null || !mAdapter.hasStableIds()) {
+            return NO_ID;
+        }
+        final ViewHolder holder = getChildViewHolderInt(child);
+        return holder != null ? holder.getItemId() : NO_ID;
+    }
+
+    /**
+     * Return the ViewHolder for the item in the given position of the data set.
+     *
+     * @param position The position of the item in the data set of the adapter
+     * @return The ViewHolder at <code>position</code>
+     */
+    public ViewHolder findViewHolderForPosition(int position) {
+        return findViewHolderForPosition(position, false);
+    }
+
+    ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) {
+        final int childCount = mChildHelper.getUnfilteredChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+            if (holder != null && !holder.isRemoved()) {
+                if (checkNewPosition) {
+                    if (holder.mPosition == position) {
+                        return holder;
+                    }
+                } else if (holder.getPosition() == position) {
+                    return holder;
+                }
+            }
+        }
+        // This method should not query cached views. It creates a problem during adapter updates
+        // when we are dealing with already laid out views. Also, for the public method, it is more
+        // reasonable to return null if position is not laid out.
+        return null;
+    }
+
+    /**
+     * Return the ViewHolder for the item with the given id. The RecyclerView must
+     * use an Adapter with {@link Adapter#setHasStableIds(boolean) stableIds} to
+     * return a non-null value.
+     *
+     * @param id The id for the requested item
+     * @return The ViewHolder with the given <code>id</code>, of null if there
+     * is no such item.
+     */
+    public ViewHolder findViewHolderForItemId(long id) {
+        final int childCount = mChildHelper.getUnfilteredChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
+            if (holder != null && holder.getItemId() == id) {
+                return holder;
+            }
+        }
+        // this method should not query cached views. They are not children so they
+        // should not be returned in this public method
+        return null;
+    }
+
+    /**
+     * Find the topmost view under the given point.
+     *
+     * @param x Horizontal position in pixels to search
+     * @param y Vertical position in pixels to search
+     * @return The child view under (x, y) or null if no matching child is found
+     */
+    public View findChildViewUnder(float x, float y) {
+        final int count = mChildHelper.getChildCount();
+        for (int i = count - 1; i >= 0; i--) {
+            final View child = mChildHelper.getChildAt(i);
+            final float translationX = ViewCompat.getTranslationX(child);
+            final float translationY = ViewCompat.getTranslationY(child);
+            if (x >= child.getLeft() + translationX &&
+                    x <= child.getRight() + translationX &&
+                    y >= child.getTop() + translationY &&
+                    y <= child.getBottom() + translationY) {
+                return child;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Offset the bounds of all child views by <code>dy</code> pixels.
+     * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}.
+     *
+     * @param dy Vertical pixel offset to apply to the bounds of all child views
+     */
+    public void offsetChildrenVertical(int dy) {
+        final int childCount = mChildHelper.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            mChildHelper.getChildAt(i).offsetTopAndBottom(dy);
+        }
+    }
+
+    /**
+     * Called when an item view is attached to this RecyclerView.
+     *
+     * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications
+     * of child views as they become attached. This will be called before a
+     * {@link LayoutManager} measures or lays out the view and is a good time to perform these
+     * changes.</p>
+     *
+     * @param child Child view that is now attached to this RecyclerView and its associated window
+     */
+    public void onChildAttachedToWindow(View child) {
+    }
+
+    /**
+     * Called when an item view is detached from this RecyclerView.
+     *
+     * <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications
+     * of child views as they become detached. This will be called as a
+     * {@link LayoutManager} fully detaches the child view from the parent and its window.</p>
+     *
+     * @param child Child view that is now detached from this RecyclerView and its associated window
+     */
+    public void onChildDetachedFromWindow(View child) {
+    }
+
+    /**
+     * Offset the bounds of all child views by <code>dx</code> pixels.
+     * Useful for implementing simple scrolling in {@link LayoutManager LayoutManagers}.
+     *
+     * @param dx Horizontal pixel offset to apply to the bounds of all child views
+     */
+    public void offsetChildrenHorizontal(int dx) {
+        final int childCount = mChildHelper.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            mChildHelper.getChildAt(i).offsetLeftAndRight(dx);
+        }
+    }
+
+    Rect getItemDecorInsetsForChild(View child) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        if (!lp.mInsetsDirty) {
+            return lp.mDecorInsets;
+        }
+
+        final Rect insets = lp.mDecorInsets;
+        insets.set(0, 0, 0, 0);
+        final int decorCount = mItemDecorations.size();
+        for (int i = 0; i < decorCount; i++) {
+            mTempRect.set(0, 0, 0, 0);
+            mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
+            insets.left += mTempRect.left;
+            insets.top += mTempRect.top;
+            insets.right += mTempRect.right;
+            insets.bottom += mTempRect.bottom;
+        }
+        lp.mInsetsDirty = false;
+        return insets;
+    }
+
+    private class ViewFlinger implements Runnable {
+        private int mLastFlingX;
+        private int mLastFlingY;
+        private ScrollerCompat mScroller;
+        private Interpolator mInterpolator = sQuinticInterpolator;
+
+
+        // When set to true, postOnAnimation callbacks are delayed until the run method completes
+        private boolean mEatRunOnAnimationRequest = false;
+
+        // Tracks if postAnimationCallback should be re-attached when it is done
+        private boolean mReSchedulePostAnimationCallback = false;
+
+        public ViewFlinger() {
+            mScroller = ScrollerCompat.create(getContext(), sQuinticInterpolator);
+        }
+
+        @Override
+        public void run() {
+            disableRunOnAnimationRequests();
+            consumePendingUpdateOperations();
+            // keep a local reference so that if it is changed during onAnimation method, it won't
+            // cause unexpected behaviors
+            final ScrollerCompat scroller = mScroller;
+            final SmoothScroller smoothScroller = mLayout.mSmoothScroller;
+            if (scroller.computeScrollOffset()) {
+                final int x = scroller.getCurrX();
+                final int y = scroller.getCurrY();
+                final int dx = x - mLastFlingX;
+                final int dy = y - mLastFlingY;
+                int hresult = 0;
+                int vresult = 0;
+                mLastFlingX = x;
+                mLastFlingY = y;
+                int overscrollX = 0, overscrollY = 0;
+                if (mAdapter != null) {
+                    eatRequestLayout();
+                    mRunningLayoutOrScroll = true;
+                    if (dx != 0) {
+                        hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
+                        overscrollX = dx - hresult;
+                    }
+                    if (dy != 0) {
+                        vresult = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
+                        overscrollY = dy - vresult;
+                    }
+                    if (supportsChangeAnimations()) {
+                        // Fix up shadow views used by changing animations
+                        int count = mChildHelper.getChildCount();
+                        for (int i = 0; i < count; i++) {
+                            View view = mChildHelper.getChildAt(i);
+                            ViewHolder holder = getChildViewHolder(view);
+                            if (holder != null && holder.mShadowingHolder != null) {
+                                View shadowingView = holder.mShadowingHolder != null ?
+                                        holder.mShadowingHolder.itemView : null;
+                                if (shadowingView != null) {
+                                    int left = view.getLeft();
+                                    int top = view.getTop();
+                                    if (left != shadowingView.getLeft() ||
+                                            top != shadowingView.getTop()) {
+                                        shadowingView.layout(left, top,
+                                                left + shadowingView.getWidth(),
+                                                top + shadowingView.getHeight());
+                                    }
+                                }
+                            }
+                        }
+                    }
+
+                    if (smoothScroller != null && !smoothScroller.isPendingInitialRun() &&
+                            smoothScroller.isRunning()) {
+                        final int adapterSize = mState.getItemCount();
+                        if (adapterSize == 0) {
+                            smoothScroller.stop();
+                        } else if (smoothScroller.getTargetPosition() >= adapterSize) {
+                            smoothScroller.setTargetPosition(adapterSize - 1);
+                            smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY);
+                        } else {
+                            smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY);
+                        }
+                    }
+                    mRunningLayoutOrScroll = false;
+                    resumeRequestLayout(false);
+                }
+                final boolean fullyConsumedScroll = dx == hresult && dy == vresult;
+                if (!mItemDecorations.isEmpty()) {
+                    invalidate();
+                }
+                if (ViewCompat.getOverScrollMode(RecyclerView.this) !=
+                        ViewCompat.OVER_SCROLL_NEVER) {
+                    considerReleasingGlowsOnScroll(dx, dy);
+                }
+                if (overscrollX != 0 || overscrollY != 0) {
+                    final int vel = (int) scroller.getCurrVelocity();
+
+                    int velX = 0;
+                    if (overscrollX != x) {
+                        velX = overscrollX < 0 ? -vel : overscrollX > 0 ? vel : 0;
+                    }
+
+                    int velY = 0;
+                    if (overscrollY != y) {
+                        velY = overscrollY < 0 ? -vel : overscrollY > 0 ? vel : 0;
+                    }
+
+                    if (ViewCompat.getOverScrollMode(RecyclerView.this) !=
+                            ViewCompat.OVER_SCROLL_NEVER) {
+                        absorbGlows(velX, velY);
+                    }
+                    if ((velX != 0 || overscrollX == x || scroller.getFinalX() == 0) &&
+                            (velY != 0 || overscrollY == y || scroller.getFinalY() == 0)) {
+                        scroller.abortAnimation();
+                    }
+                }
+                if (mScrollListener != null && (hresult != 0 || vresult != 0)) {
+                    mScrollListener.onScrolled(RecyclerView.this, hresult, vresult);
+                }
+
+                if (!awakenScrollBars()) {
+                    invalidate();
+                }
+
+                if (scroller.isFinished() || !fullyConsumedScroll) {
+                    setScrollState(SCROLL_STATE_IDLE); // setting state to idle will stop this.
+                } else {
+                    postOnAnimation();
+                }
+            }
+            // call this after the onAnimation is complete not to have inconsistent callbacks etc.
+            if (smoothScroller != null && smoothScroller.isPendingInitialRun()) {
+                smoothScroller.onAnimation(0, 0);
+            }
+            enableRunOnAnimationRequests();
+        }
+
+        private void disableRunOnAnimationRequests() {
+            mReSchedulePostAnimationCallback = false;
+            mEatRunOnAnimationRequest = true;
+        }
+
+        private void enableRunOnAnimationRequests() {
+            mEatRunOnAnimationRequest = false;
+            if (mReSchedulePostAnimationCallback) {
+                postOnAnimation();
+            }
+        }
+
+        void postOnAnimation() {
+            if (mEatRunOnAnimationRequest) {
+                mReSchedulePostAnimationCallback = true;
+            } else {
+                ViewCompat.postOnAnimation(RecyclerView.this, this);
+            }
+        }
+
+        public void fling(int velocityX, int velocityY) {
+            setScrollState(SCROLL_STATE_SETTLING);
+            mLastFlingX = mLastFlingY = 0;
+            mScroller.fling(0, 0, velocityX, velocityY,
+                    Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
+            postOnAnimation();
+        }
+
+        public void smoothScrollBy(int dx, int dy) {
+            smoothScrollBy(dx, dy, 0, 0);
+        }
+
+        public void smoothScrollBy(int dx, int dy, int vx, int vy) {
+            smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, vx, vy));
+        }
+
+        private float distanceInfluenceForSnapDuration(float f) {
+            f -= 0.5f; // center the values about 0.
+            f *= 0.3f * Math.PI / 2.0f;
+            return (float) Math.sin(f);
+        }
+
+        private int computeScrollDuration(int dx, int dy, int vx, int vy) {
+            final int absDx = Math.abs(dx);
+            final int absDy = Math.abs(dy);
+            final boolean horizontal = absDx > absDy;
+            final int velocity = (int) Math.sqrt(vx * vx + vy * vy);
+            final int delta = (int) Math.sqrt(dx * dx + dy * dy);
+            final int containerSize = horizontal ? getWidth() : getHeight();
+            final int halfContainerSize = containerSize / 2;
+            final float distanceRatio = Math.min(1.f, 1.f * delta / containerSize);
+            final float distance = halfContainerSize + halfContainerSize *
+                    distanceInfluenceForSnapDuration(distanceRatio);
+
+            final int duration;
+            if (velocity > 0) {
+                duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
+            } else {
+                float absDelta = (float) (horizontal ? absDx : absDy);
+                duration = (int) (((absDelta / containerSize) + 1) * 300);
+            }
+            return Math.min(duration, MAX_SCROLL_DURATION);
+        }
+
+        public void smoothScrollBy(int dx, int dy, int duration) {
+            smoothScrollBy(dx, dy, duration, sQuinticInterpolator);
+        }
+
+        public void smoothScrollBy(int dx, int dy, int duration, Interpolator interpolator) {
+            if (mInterpolator != interpolator) {
+                mInterpolator = interpolator;
+                mScroller = ScrollerCompat.create(getContext(), interpolator);
+            }
+            setScrollState(SCROLL_STATE_SETTLING);
+            mLastFlingX = mLastFlingY = 0;
+            mScroller.startScroll(0, 0, dx, dy, duration);
+            postOnAnimation();
+        }
+
+        public void stop() {
+            removeCallbacks(this);
+            mScroller.abortAnimation();
+        }
+
+    }
+
+    private class RecyclerViewDataObserver extends AdapterDataObserver {
+        @Override
+        public void onChanged() {
+            assertNotInLayoutOrScroll(null);
+            if (mAdapter.hasStableIds()) {
+                // TODO Determine what actually changed.
+                // This is more important to implement now since this callback will disable all
+                // animations because we cannot rely on positions.
+                mState.mStructureChanged = true;
+                mDataSetHasChangedAfterLayout = true;
+            } else {
+                mState.mStructureChanged = true;
+                mDataSetHasChangedAfterLayout = true;
+            }
+            if (!mAdapterHelper.hasPendingUpdates()) {
+                requestLayout();
+            }
+        }
+
+        @Override
+        public void onItemRangeChanged(int positionStart, int itemCount) {
+            assertNotInLayoutOrScroll(null);
+            if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount)) {
+                triggerUpdateProcessor();
+            }
+        }
+
+        @Override
+        public void onItemRangeInserted(int positionStart, int itemCount) {
+            assertNotInLayoutOrScroll(null);
+            if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
+                triggerUpdateProcessor();
+            }
+        }
+
+        @Override
+        public void onItemRangeRemoved(int positionStart, int itemCount) {
+            assertNotInLayoutOrScroll(null);
+            if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
+                triggerUpdateProcessor();
+            }
+        }
+
+        @Override
+        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+            assertNotInLayoutOrScroll(null);
+            if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
+                triggerUpdateProcessor();
+            }
+        }
+
+        void triggerUpdateProcessor() {
+            if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) {
+                ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
+            } else {
+                mAdapterUpdateDuringMeasure = true;
+                requestLayout();
+            }
+        }
+    }
+
+    /**
+     * RecycledViewPool lets you share Views between multiple RecyclerViews.
+     * <p>
+     * If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
+     * and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}.
+     * <p>
+     * RecyclerView automatically creates a pool for itself if you don't provide one.
+     *
+     */
+    public static class RecycledViewPool {
+        private SparseArray<ArrayList<ViewHolder>> mScrap =
+                new SparseArray<ArrayList<ViewHolder>>();
+        private SparseIntArray mMaxScrap = new SparseIntArray();
+        private int mAttachCount = 0;
+
+        private static final int DEFAULT_MAX_SCRAP = 5;
+
+        public void clear() {
+            mScrap.clear();
+        }
+
+        public void setMaxRecycledViews(int viewType, int max) {
+            mMaxScrap.put(viewType, max);
+            final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
+            if (scrapHeap != null) {
+                while (scrapHeap.size() > max) {
+                    scrapHeap.remove(scrapHeap.size() - 1);
+                }
+            }
+        }
+
+        public ViewHolder getRecycledView(int viewType) {
+            final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
+            if (scrapHeap != null && !scrapHeap.isEmpty()) {
+                final int index = scrapHeap.size() - 1;
+                final ViewHolder scrap = scrapHeap.get(index);
+                scrapHeap.remove(index);
+                return scrap;
+            }
+            return null;
+        }
+
+        int size() {
+            int count = 0;
+            for (int i = 0; i < mScrap.size(); i ++) {
+                ArrayList<ViewHolder> viewHolders = mScrap.valueAt(i);
+                if (viewHolders != null) {
+                    count += viewHolders.size();
+                }
+            }
+            return count;
+        }
+
+        public void putRecycledView(ViewHolder scrap) {
+            final int viewType = scrap.getItemViewType();
+            final ArrayList scrapHeap = getScrapHeapForType(viewType);
+            if (mMaxScrap.get(viewType) <= scrapHeap.size()) {
+                return;
+            }
+            scrap.resetInternal();
+            scrapHeap.add(scrap);
+        }
+
+        void attach(Adapter adapter) {
+            mAttachCount++;
+        }
+
+        void detach() {
+            mAttachCount--;
+        }
+
+
+        /**
+         * Detaches the old adapter and attaches the new one.
+         * <p>
+         * RecycledViewPool will clear its cache if it has only one adapter attached and the new
+         * adapter uses a different ViewHolder than the oldAdapter.
+         *
+         * @param oldAdapter The previous adapter instance. Will be detached.
+         * @param newAdapter The new adapter instance. Will be attached.
+         * @param compatibleWithPrevious True if both oldAdapter and newAdapter are using the same
+         *                               ViewHolder and view types.
+         */
+        void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
+                boolean compatibleWithPrevious) {
+            if (oldAdapter != null) {
+                detach();
+            }
+            if (!compatibleWithPrevious && mAttachCount == 0) {
+                clear();
+            }
+            if (newAdapter != null) {
+                attach(newAdapter);
+            }
+        }
+
+        private ArrayList<ViewHolder> getScrapHeapForType(int viewType) {
+            ArrayList<ViewHolder> scrap = mScrap.get(viewType);
+            if (scrap == null) {
+                scrap = new ArrayList<ViewHolder>();
+                mScrap.put(viewType, scrap);
+                if (mMaxScrap.indexOfKey(viewType) < 0) {
+                    mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
+                }
+            }
+            return scrap;
+        }
+    }
+
+    /**
+     * A Recycler is responsible for managing scrapped or detached item views for reuse.
+     *
+     * <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but
+     * that has been marked for removal or reuse.</p>
+     *
+     * <p>Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for
+     * an adapter's data set representing the data at a given position or item ID.
+     * If the view to be reused is considered "dirty" the adapter will be asked to rebind it.
+     * If not, the view can be quickly reused by the LayoutManager with no further work.
+     * Clean views that have not {@link android.view.View#isLayoutRequested() requested layout}
+     * may be repositioned by a LayoutManager without remeasurement.</p>
+     */
+    public final class Recycler {
+        private final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<ViewHolder>();
+        private ArrayList<ViewHolder> mChangedScrap = null;
+
+        final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
+
+        private final List<ViewHolder>
+                mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
+
+        private int mViewCacheMax = DEFAULT_CACHE_SIZE;
+
+        private RecycledViewPool mRecyclerPool;
+
+        private ViewCacheExtension mViewCacheExtension;
+
+        private static final int DEFAULT_CACHE_SIZE = 2;
+
+        /**
+         * Clear scrap views out of this recycler. Detached views contained within a
+         * recycled view pool will remain.
+         */
+        public void clear() {
+            mAttachedScrap.clear();
+            recycleAndClearCachedViews();
+        }
+
+        /**
+         * Set the maximum number of detached, valid views we should retain for later use.
+         *
+         * @param viewCount Number of views to keep before sending views to the shared pool
+         */
+        public void setViewCacheSize(int viewCount) {
+            mViewCacheMax = viewCount;
+            // first, try the views that can be recycled
+            for (int i = mCachedViews.size() - 1; i >= 0 && mCachedViews.size() > viewCount; i--) {
+                tryToRecycleCachedViewAt(i);
+            }
+            // if we could not recycle enough of them, remove some.
+            while (mCachedViews.size() > viewCount) {
+                mCachedViews.remove(mCachedViews.size() - 1);
+            }
+        }
+
+        /**
+         * Returns an unmodifiable list of ViewHolders that are currently in the scrap list.
+         *
+         * @return List of ViewHolders in the scrap list.
+         */
+        public List<ViewHolder> getScrapList() {
+            return mUnmodifiableAttachedScrap;
+        }
+
+        /**
+         * Helper method for getViewForPosition.
+         * <p>
+         * Checks whether a given view holder can be used for the provided position.
+         *
+         * @param holder ViewHolder
+         * @return true if ViewHolder matches the provided position, false otherwise
+         */
+        boolean validateViewHolderForOffsetPosition(ViewHolder holder) {
+            // if it is a removed holder, nothing to verify since we cannot ask adapter anymore
+            // if it is not removed, verify the type and id.
+            if (holder.isRemoved()) {
+                return true;
+            }
+            if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) {
+                throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder "
+                        + "adapter position" + holder);
+            }
+            if (!mState.isPreLayout()) {
+                // don't check type if it is pre-layout.
+                final int type = mAdapter.getItemViewType(holder.mPosition);
+                if (type != holder.getItemViewType()) {
+                    return false;
+                }
+            }
+            if (mAdapter.hasStableIds()) {
+                return holder.getItemId() == mAdapter.getItemId(holder.mPosition);
+            }
+            return true;
+        }
+
+        /**
+         * Binds the given View to the position. The View can be a View previously retrieved via
+         * {@link #getViewForPosition(int)} or created by
+         * {@link Adapter#onCreateViewHolder(ViewGroup, int)}.
+         * <p>
+         * Generally, a LayoutManager should acquire its views via {@link #getViewForPosition(int)}
+         * and let the RecyclerView handle caching. This is a helper method for LayoutManager who
+         * wants to handle its own recycling logic.
+         * <p>
+         * Note that, {@link #getViewForPosition(int)} already binds the View to the position so
+         * you don't need to call this method unless you want to bind this View to another position.
+         *
+         * @param view The view to update.
+         * @param position The position of the item to bind to this View.
+         */
+        public void bindViewToPosition(View view, int position) {
+            ViewHolder holder = getChildViewHolderInt(view);
+            if (holder == null) {
+                throw new IllegalArgumentException("The view does not have a ViewHolder. You cannot"
+                        + " pass arbitrary views to this method, they should be created by the "
+                        + "Adapter");
+            }
+            final int offsetPosition = mAdapterHelper.findPositionOffset(position);
+            if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
+                throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+                        + "position " + position + "(offset:" + offsetPosition + ")."
+                        + "state:" + mState.getItemCount());
+            }
+            mAdapter.bindViewHolder(holder, offsetPosition);
+            if (mState.isPreLayout()) {
+                holder.mPreLayoutPosition = position;
+            }
+
+            ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
+            if (lp == null) {
+                lp = generateDefaultLayoutParams();
+                holder.itemView.setLayoutParams(lp);
+            } else if (!checkLayoutParams(lp)) {
+                lp = generateLayoutParams(lp);
+                holder.itemView.setLayoutParams(lp);
+            }
+            ((LayoutParams) lp).mInsetsDirty = true;
+            ((LayoutParams) lp).mViewHolder = holder;
+        }
+
+        /**
+         * RecyclerView provides artificial position range (item count) in pre-layout state and
+         * automatically maps these positions to {@link Adapter} positions when
+         * {@link #getViewForPosition(int)} or {@link #bindViewToPosition(View, int)} is called.
+         * <p>
+         * Usually, LayoutManager does not need to worry about this. However, in some cases, your
+         * LayoutManager may need to call some custom component with item positions in which
+         * case you need the actual adapter position instead of the pre layout position. You
+         * can use this method to convert a pre-layout position to adapter (post layout) position.
+         * <p>
+         * Note that if the provided position belongs to a deleted ViewHolder, this method will
+         * return -1.
+         * <p>
+         * Calling this method in post-layout state returns the same value back.
+         *
+         * @param position The pre-layout position to convert. Must be greater or equal to 0 and
+         *                 less than {@link State#getItemCount()}.
+         */
+        public int convertPreLayoutPositionToPostLayout(int position) {
+            if (position < 0 || position >= mState.getItemCount()) {
+                throw new IndexOutOfBoundsException("invalid position " + position + ". State "
+                        + "item count is " + mState.getItemCount());
+            }
+            if (!mState.isPreLayout()) {
+                return position;
+            }
+            return mAdapterHelper.findPositionOffset(position);
+        }
+
+        /**
+         * Obtain a view initialized for the given position.
+         *
+         * This method should be used by {@link LayoutManager} implementations to obtain
+         * views to represent data from an {@link Adapter}.
+         * <p>
+         * The Recycler may reuse a scrap or detached view from a shared pool if one is
+         * available for the correct view type. If the adapter has not indicated that the
+         * data at the given position has changed, the Recycler will attempt to hand back
+         * a scrap view that was previously initialized for that data without rebinding.
+         *
+         * @param position Position to obtain a view for
+         * @return A view representing the data at <code>position</code> from <code>adapter</code>
+         */
+        public View getViewForPosition(int position) {
+            return getViewForPosition(position, false);
+        }
+
+        View getViewForPosition(int position, boolean dryRun) {
+            if (position < 0 || position >= mState.getItemCount()) {
+                throw new IndexOutOfBoundsException("Invalid item position " + position
+                        + "(" + position + "). Item count:" + mState.getItemCount());
+            }
+            ViewHolder holder = null;
+            // 0) If there is a changed scrap, try to find from there
+            if (mState.isPreLayout()) {
+                holder = getChangedScrapViewForPosition(position);
+            }
+            // 1) Find from scrap by position
+            if (holder == null) {
+                holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
+                if (holder != null) {
+                    if (!validateViewHolderForOffsetPosition(holder)) {
+                        // recycle this scrap
+                        if (!dryRun) {
+                            // we would like to recycle this but need to make sure it is not used by
+                            // animation logic etc.
+                            holder.addFlags(ViewHolder.FLAG_INVALID);
+                            if (holder.isScrap()) {
+                                removeDetachedView(holder.itemView, false);
+                                holder.unScrap();
+                            } else if (holder.wasReturnedFromScrap()) {
+                                holder.clearReturnedFromScrapFlag();
+                            }
+                            recycleViewHolderInternal(holder);
+                        }
+                        holder = null;
+                    }
+                }
+            }
+            if (holder == null) {
+                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
+                if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
+                    throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+                            + "position " + position + "(offset:" + offsetPosition + ")."
+                            + "state:" + mState.getItemCount());
+                }
+
+                final int type = mAdapter.getItemViewType(offsetPosition);
+                // 2) Find from scrap via stable ids, if exists
+                if (mAdapter.hasStableIds()) {
+                    holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
+                    if (holder != null) {
+                        // update position
+                        holder.mPosition = offsetPosition;
+                    }
+                }
+                if (holder == null && mViewCacheExtension != null) {
+                    // We are NOT sending the offsetPosition because LayoutManager does not
+                    // know it.
+                    final View view = mViewCacheExtension
+                            .getViewForPositionAndType(this, position, type);
+                    if (view != null) {
+                        holder = getChildViewHolder(view);
+                        if (holder == null) {
+                            throw new IllegalArgumentException("getViewForPositionAndType returned"
+                                    + " a view which does not have a ViewHolder");
+                        } else if (holder.shouldIgnore()) {
+                            throw new IllegalArgumentException("getViewForPositionAndType returned"
+                                    + " a view that is ignored. You must call stopIgnoring before"
+                                    + " returning this view.");
+                        }
+                    }
+                }
+                if (holder == null) { // fallback to recycler
+                    // try recycler.
+                    // Head to the shared pool.
+                    if (DEBUG) {
+                        Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
+                                + "pool");
+                    }
+                    holder = getRecycledViewPool()
+                            .getRecycledView(mAdapter.getItemViewType(offsetPosition));
+                    if (holder != null) {
+                        holder.resetInternal();
+                        if (FORCE_INVALIDATE_DISPLAY_LIST) {
+                            invalidateDisplayListInt(holder);
+                        }
+                    }
+                }
+                if (holder == null) {
+                    holder = mAdapter.createViewHolder(RecyclerView.this,
+                            mAdapter.getItemViewType(offsetPosition));
+                    if (DEBUG) {
+                        Log.d(TAG, "getViewForPosition created new ViewHolder");
+                    }
+                }
+            }
+
+            if (mState.isPreLayout() && holder.isBound()) {
+                // do not update unless we absolutely have to.
+                holder.mPreLayoutPosition = position;
+            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
+                if (DEBUG && holder.isRemoved()) {
+                    throw new IllegalStateException("Removed holder should be bound and it should"
+                            + " come here only in pre-layout. Holder: " + holder);
+                }
+                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
+                mAdapter.bindViewHolder(holder, offsetPosition);
+                if (mState.isPreLayout()) {
+                    holder.mPreLayoutPosition = position;
+                }
+            }
+
+            ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
+            if (lp == null) {
+                lp = generateDefaultLayoutParams();
+                holder.itemView.setLayoutParams(lp);
+            } else if (!checkLayoutParams(lp)) {
+                lp = generateLayoutParams(lp);
+                holder.itemView.setLayoutParams(lp);
+            }
+            ((LayoutParams) lp).mViewHolder = holder;
+
+            return holder.itemView;
+        }
+
+        private void invalidateDisplayListInt(ViewHolder holder) {
+            if (holder.itemView instanceof ViewGroup) {
+                invalidateDisplayListInt((ViewGroup) holder.itemView, false);
+            }
+        }
+
+        private void invalidateDisplayListInt(ViewGroup viewGroup, boolean invalidateThis) {
+            for (int i = viewGroup.getChildCount() - 1; i >= 0; i--) {
+                final View view = viewGroup.getChildAt(i);
+                if (view instanceof ViewGroup) {
+                    invalidateDisplayListInt((ViewGroup) view, true);
+                }
+            }
+            if (!invalidateThis) {
+                return;
+            }
+            // we need to force it to become invisible
+            if (viewGroup.getVisibility() == View.INVISIBLE) {
+                viewGroup.setVisibility(View.VISIBLE);
+                viewGroup.setVisibility(View.INVISIBLE);
+            } else {
+                final int visibility = viewGroup.getVisibility();
+                viewGroup.setVisibility(View.INVISIBLE);
+                viewGroup.setVisibility(visibility);
+            }
+        }
+
+        /**
+         * Recycle a detached view. The specified view will be added to a pool of views
+         * for later rebinding and reuse.
+         *
+         * <p>A view must be fully detached before it may be recycled. If the View is scrapped,
+         * it will be removed from scrap list.</p>
+         *
+         * @param view Removed view for recycling
+         * @see LayoutManager#removeAndRecycleView(View, Recycler)
+         */
+        public void recycleView(View view) {
+            // This public recycle method tries to make view recycle-able since layout manager
+            // intended to recycle this view (e.g. even if it is in scrap or change cache)
+            ViewHolder holder = getChildViewHolderInt(view);
+            if (holder.isScrap()) {
+                holder.unScrap();
+            } else if (holder.wasReturnedFromScrap()){
+                holder.clearReturnedFromScrapFlag();
+            }
+            recycleViewHolderInternal(holder);
+        }
+
+        /**
+         * Internally, use this method instead of {@link #recycleView(android.view.View)} to
+         * catch potential bugs.
+         * @param view
+         */
+        void recycleViewInternal(View view) {
+            recycleViewHolderInternal(getChildViewHolderInt(view));
+        }
+
+        void recycleAndClearCachedViews() {
+            final int count = mCachedViews.size();
+            for (int i = count - 1; i >= 0; i--) {
+                tryToRecycleCachedViewAt(i);
+            }
+            mCachedViews.clear();
+        }
+
+        /**
+         * Tries to recyle a cached view and removes the view from the list if and only if it
+         * is recycled.
+         *
+         * @param cachedViewIndex The index of the view in cached views list
+         * @return True if item is recycled
+         */
+        boolean tryToRecycleCachedViewAt(int cachedViewIndex) {
+            if (DEBUG) {
+                Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
+            }
+            ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
+            if (DEBUG) {
+                Log.d(TAG, "CachedViewHolder to be recycled(if recycleable): " + viewHolder);
+            }
+            if (viewHolder.isRecyclable()) {
+                getRecycledViewPool().putRecycledView(viewHolder);
+                dispatchViewRecycled(viewHolder);
+                mCachedViews.remove(cachedViewIndex);
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * internal implementation checks if view is scrapped or attached and throws an exception
+         * if so.
+         * Public version un-scraps before calling recycle.
+         */
+        void recycleViewHolderInternal(ViewHolder holder) {
+            if (holder.isScrap() || holder.itemView.getParent() != null) {
+                throw new IllegalArgumentException(
+                        "Scrapped or attached views may not be recycled. isScrap:"
+                                + holder.isScrap() + " isAttached:"
+                                + (holder.itemView.getParent() != null));
+            }
+
+            if (holder.shouldIgnore()) {
+                throw new IllegalArgumentException("Trying to recycle an ignored view holder. You"
+                        + " should first call stopIgnoringView(view) before calling recycle.");
+            }
+            if (holder.isRecyclable()) {
+                boolean cached = false;
+                if (!holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved()) &&
+                        !holder.isChanged()) {
+                    // Retire oldest cached views first
+                    if (mCachedViews.size() == mViewCacheMax && !mCachedViews.isEmpty()) {
+                        for (int i = 0; i < mCachedViews.size(); i++) {
+                            if (tryToRecycleCachedViewAt(i)) {
+                                break;
+                            }
+                        }
+                    }
+                    if (mCachedViews.size() < mViewCacheMax) {
+                        mCachedViews.add(holder);
+                        cached = true;
+                    }
+                }
+                if (!cached) {
+                    getRecycledViewPool().putRecycledView(holder);
+                    dispatchViewRecycled(holder);
+                }
+            } else if (DEBUG) {
+                Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
+                        + "re-visit here. We are stil removing it from animation lists");
+            }
+            // even if the holder is not removed, we still call this method so that it is removed
+            // from view holder lists.
+            mState.onViewRecycled(holder);
+        }
+
+        /**
+         * Used as a fast path for unscrapping and recycling a view during a bulk operation.
+         * The caller must call {@link #clearScrap()} when it's done to update the recycler's
+         * internal bookkeeping.
+         */
+        void quickRecycleScrapView(View view) {
+            final ViewHolder holder = getChildViewHolderInt(view);
+            holder.mScrapContainer = null;
+            holder.clearReturnedFromScrapFlag();
+            recycleViewHolderInternal(holder);
+        }
+
+        /**
+         * Mark an attached view as scrap.
+         *
+         * <p>"Scrap" views are still attached to their parent RecyclerView but are eligible
+         * for rebinding and reuse. Requests for a view for a given position may return a
+         * reused or rebound scrap view instance.</p>
+         *
+         * @param view View to scrap
+         */
+        void scrapView(View view) {
+            final ViewHolder holder = getChildViewHolderInt(view);
+            holder.setScrapContainer(this);
+            if (!holder.isChanged() || !supportsChangeAnimations()) {
+                if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
+                    throw new IllegalArgumentException("Called scrap view with an invalid view."
+                            + " Invalid views cannot be reused from scrap, they should rebound from"
+                            + " recycler pool.");
+                }
+                mAttachedScrap.add(holder);
+            } else {
+                if (mChangedScrap == null) {
+                    mChangedScrap = new ArrayList<ViewHolder>();
+                }
+                mChangedScrap.add(holder);
+            }
+        }
+
+        /**
+         * Remove a previously scrapped view from the pool of eligible scrap.
+         *
+         * <p>This view will no longer be eligible for reuse until re-scrapped or
+         * until it is explicitly removed and recycled.</p>
+         */
+        void unscrapView(ViewHolder holder) {
+            if (!holder.isChanged() || !supportsChangeAnimations() || mChangedScrap == null) {
+                mAttachedScrap.remove(holder);
+            } else {
+                mChangedScrap.remove(holder);
+            }
+            holder.mScrapContainer = null;
+            holder.clearReturnedFromScrapFlag();
+        }
+
+        int getScrapCount() {
+            return mAttachedScrap.size();
+        }
+
+        View getScrapViewAt(int index) {
+            return mAttachedScrap.get(index).itemView;
+        }
+
+        void clearScrap() {
+            mAttachedScrap.clear();
+        }
+
+        ViewHolder getChangedScrapViewForPosition(int position) {
+            // If pre-layout, check the changed scrap for an exact match.
+            final int changedScrapSize;
+            if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
+                return null;
+            }
+            // find by position
+            for (int i = 0; i < changedScrapSize; i++) {
+                final ViewHolder holder = mChangedScrap.get(i);
+                if (!holder.wasReturnedFromScrap() && holder.getPosition() == position) {
+                    holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
+                    return holder;
+                }
+            }
+            // find by id
+            if (mAdapter.hasStableIds()) {
+                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
+                if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
+                    final long id = mAdapter.getItemId(offsetPosition);
+                    for (int i = 0; i < changedScrapSize; i++) {
+                        final ViewHolder holder = mChangedScrap.get(i);
+                        if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
+                            holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
+                            return holder;
+                        }
+                    }
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Returns a scrap view for the position. If type is not INVALID_TYPE, it also checks if
+         * ViewHolder's type matches the provided type.
+         *
+         * @param position Item position
+         * @param type View type
+         * @param dryRun  Does a dry run, finds the ViewHolder but does not remove
+         * @return a ViewHolder that can be re-used for this position.
+         */
+        ViewHolder getScrapViewForPosition(int position, int type, boolean dryRun) {
+            final int scrapCount = mAttachedScrap.size();
+
+            // Try first for an exact, non-invalid match from scrap.
+            for (int i = 0; i < scrapCount; i++) {
+                final ViewHolder holder = mAttachedScrap.get(i);
+                if (!holder.wasReturnedFromScrap() && holder.getPosition() == position
+                        && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
+                    if (type != INVALID_TYPE && holder.getItemViewType() != type) {
+                        Log.e(TAG, "Scrap view for position " + position + " isn't dirty but has" +
+                                " wrong view type! (found " + holder.getItemViewType() +
+                                " but expected " + type + ")");
+                        break;
+                    }
+                    holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
+                    return holder;
+                }
+            }
+
+            if (!dryRun) {
+                View view = mChildHelper.findHiddenNonRemovedView(position, type);
+                if (view != null) {
+                    // ending the animation should cause it to get recycled before we reuse it
+                    mItemAnimator.endAnimation(getChildViewHolder(view));
+                }
+            }
+
+            // Search in our first-level recycled view cache.
+            final int cacheSize = mCachedViews.size();
+            for (int i = 0; i < cacheSize; i++) {
+                final ViewHolder holder = mCachedViews.get(i);
+                // invalid view holders may be in cache if adapter has stable ids as they can be
+                // retrieved via getScrapViewForId
+                if (!holder.isInvalid() && holder.getPosition() == position) {
+                    if (!dryRun) {
+                        mCachedViews.remove(i);
+                    }
+                    if (DEBUG) {
+                        Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type +
+                                ") found match in cache: " + holder);
+                    }
+                    return holder;
+                }
+            }
+            return null;
+        }
+
+        ViewHolder getScrapViewForId(long id, int type, boolean dryRun) {
+            // Look in our attached views first
+            final int count = mAttachedScrap.size();
+            for (int i = count - 1; i >= 0; i--) {
+                final ViewHolder holder = mAttachedScrap.get(i);
+                if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
+                    if (type == holder.getItemViewType()) {
+                        holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
+                        if (holder.isRemoved()) {
+                            // this might be valid in two cases:
+                            // > item is removed but we are in pre-layout pass
+                            // >> do nothing. return as is. make sure we don't rebind
+                            // > item is removed then added to another position and we are in
+                            // post layout.
+                            // >> remove removed and invalid flags, add update flag to rebind
+                            // because item was invisible to us and we don't know what happened in
+                            // between.
+                            if (!mState.isPreLayout()) {
+                                holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE |
+                                        ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
+                            }
+                        }
+                        return holder;
+                    } else if (!dryRun) {
+                        // Recycle this scrap. Type mismatch.
+                        mAttachedScrap.remove(i);
+                        removeDetachedView(holder.itemView, false);
+                        quickRecycleScrapView(holder.itemView);
+                    }
+                }
+            }
+
+            // Search the first-level cache
+            final int cacheSize = mCachedViews.size();
+            for (int i = cacheSize - 1; i >= 0; i--) {
+                final ViewHolder holder = mCachedViews.get(i);
+                if (holder.getItemId() == id) {
+                    if (type == holder.getItemViewType()) {
+                        if (!dryRun) {
+                            mCachedViews.remove(i);
+                        }
+                        return holder;
+                    } else if (!dryRun) {
+                        tryToRecycleCachedViewAt(i);
+                    }
+                }
+            }
+            return null;
+        }
+
+        void dispatchViewRecycled(ViewHolder holder) {
+            if (mRecyclerListener != null) {
+                mRecyclerListener.onViewRecycled(holder);
+            }
+            if (mAdapter != null) {
+                mAdapter.onViewRecycled(holder);
+            }
+            if (mState != null) {
+                mState.onViewRecycled(holder);
+            }
+            if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder);
+        }
+
+        void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
+                boolean compatibleWithPrevious) {
+            clear();
+            getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, compatibleWithPrevious);
+        }
+
+        void offsetPositionRecordsForMove(int from, int to) {
+            final int start, end, inBetweenOffset;
+            if (from < to) {
+                start = from;
+                end = to;
+                inBetweenOffset = -1;
+            } else {
+                start = to;
+                end = from;
+                inBetweenOffset = 1;
+            }
+            final int cachedCount = mCachedViews.size();
+            for (int i = 0; i < cachedCount; i++) {
+                final ViewHolder holder = mCachedViews.get(i);
+                if (holder == null || holder.mPosition < start || holder.mPosition > end) {
+                    continue;
+                }
+                if (holder.mPosition == from) {
+                    holder.offsetPosition(to - from, false);
+                } else {
+                    holder.offsetPosition(inBetweenOffset, false);
+                }
+                if (DEBUG) {
+                    Log.d(TAG, "offsetPositionRecordsForMove cached child " + i + " holder " +
+                            holder);
+                }
+            }
+        }
+
+        void offsetPositionRecordsForInsert(int insertedAt, int count) {
+            final int cachedCount = mCachedViews.size();
+            for (int i = 0; i < cachedCount; i++) {
+                final ViewHolder holder = mCachedViews.get(i);
+                if (holder != null && holder.getPosition() >= insertedAt) {
+                    if (DEBUG) {
+                        Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder " +
+                                holder + " now at position " + (holder.mPosition + count));
+                    }
+                    holder.offsetPosition(count, true);
+                }
+            }
+        }
+
+        /**
+         * @param removedFrom Remove start index
+         * @param count Remove count
+         * @param applyToPreLayout If true, changes will affect ViewHolder's pre-layout position, if
+         *                         false, they'll be applied before the second layout pass
+         */
+        void offsetPositionRecordsForRemove(int removedFrom, int count, boolean applyToPreLayout) {
+            final int removedEnd = removedFrom + count;
+            final int cachedCount = mCachedViews.size();
+            for (int i = cachedCount - 1; i >= 0; i--) {
+                final ViewHolder holder = mCachedViews.get(i);
+                if (holder != null) {
+                    if (holder.getPosition() >= removedEnd) {
+                        if (DEBUG) {
+                            Log.d(TAG, "offsetPositionRecordsForRemove cached " + i +
+                                    " holder " + holder + " now at position " +
+                                    (holder.mPosition - count));
+                        }
+                        holder.offsetPosition(-count, applyToPreLayout);
+                    } else if (holder.getPosition() >= removedFrom) {
+                        // Item for this view was removed. Dump it from the cache.
+                        if (!tryToRecycleCachedViewAt(i)) {
+                            // if we cannot recycle it, at least invalidate so that we won't return
+                            // it by position.
+                            holder.addFlags(ViewHolder.FLAG_INVALID);
+                            if (DEBUG) {
+                                Log.d(TAG, "offsetPositionRecordsForRemove cached " + i +
+                                        " holder " + holder + " now flagged as invalid because it "
+                                        + "could not be recycled");
+                            }
+                        } else if (DEBUG) {
+                            Log.d(TAG, "offsetPositionRecordsForRemove cached " + i +
+                                    " holder " + holder + " now placed in pool");
+                        }
+                    }
+                }
+            }
+        }
+
+        void setViewCacheExtension(ViewCacheExtension extension) {
+            mViewCacheExtension = extension;
+        }
+
+        void setRecycledViewPool(RecycledViewPool pool) {
+            if (mRecyclerPool != null) {
+                mRecyclerPool.detach();
+            }
+            mRecyclerPool = pool;
+            if (pool != null) {
+                mRecyclerPool.attach(getAdapter());
+            }
+        }
+
+        RecycledViewPool getRecycledViewPool() {
+            if (mRecyclerPool == null) {
+                mRecyclerPool = new RecycledViewPool();
+            }
+            return mRecyclerPool;
+        }
+
+        void viewRangeUpdate(int positionStart, int itemCount) {
+            final int positionEnd = positionStart + itemCount;
+            final int cachedCount = mCachedViews.size();
+            for (int i = 0; i < cachedCount; i++) {
+                final ViewHolder holder = mCachedViews.get(i);
+                if (holder == null) {
+                    continue;
+                }
+
+                final int pos = holder.getPosition();
+                if (pos >= positionStart && pos < positionEnd) {
+                    holder.addFlags(ViewHolder.FLAG_UPDATE);
+                    // cached views should not be flagged as changed because this will cause them
+                    // to animate when they are returned from cache.
+                }
+            }
+        }
+
+        void markKnownViewsInvalid() {
+            if (mAdapter != null && mAdapter.hasStableIds()) {
+                final int cachedCount = mCachedViews.size();
+                for (int i = 0; i < cachedCount; i++) {
+                    final ViewHolder holder = mCachedViews.get(i);
+                    if (holder != null) {
+                        holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
+                    }
+                }
+            } else {
+                // we cannot re-use cached views in this case. Recycle the ones we can and flag
+                // the remaining as invalid so that they can be recycled later on (when their
+                // animations end.)
+                for (int i = mCachedViews.size() - 1; i >= 0; i--) {
+                    if (!tryToRecycleCachedViewAt(i)) {
+                        final ViewHolder holder = mCachedViews.get(i);
+                        holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
+                    }
+                }
+            }
+
+        }
+
+        void clearOldPositions() {
+            final int cachedCount = mCachedViews.size();
+            for (int i = 0; i < cachedCount; i++) {
+                final ViewHolder holder = mCachedViews.get(i);
+                holder.clearOldPosition();
+            }
+        }
+
+        void markItemDecorInsetsDirty() {
+            final int cachedCount = mCachedViews.size();
+            for (int i = 0; i < cachedCount; i++) {
+                final ViewHolder holder = mCachedViews.get(i);
+                LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams();
+                if (layoutParams != null) {
+                    layoutParams.mInsetsDirty = true;
+                }
+            }
+        }
+    }
+
+    /**
+     * ViewCacheExtension is a helper class to provide an additional layer of view caching that can
+     * ben controlled by the developer.
+     * <p>
+     * When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and
+     * first level cache to find a matching View. If it cannot find a suitable View, Recycler will
+     * call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking
+     * {@link RecycledViewPool}.
+     * <p>
+     * Note that, Recycler never sends Views to this method to be cached. It is developers
+     * responsibility to decide whether they want to keep their Views in this custom cache or let
+     * the default recycling policy handle it.
+     */
+    public abstract static class ViewCacheExtension {
+
+        /**
+         * Returns a View that can be binded to the given Adapter position.
+         * <p>
+         * This method should <b>not</b> create a new View. Instead, it is expected to return
+         * an already created View that can be re-used for the given type and position.
+         * If the View is marked as ignored, it should first call
+         * {@link LayoutManager#stopIgnoringView(View)} before returning the View.
+         * <p>
+         * RecyclerView will re-bind the returned View to the position if necessary.
+         *
+         * @param recycler The Recycler that can be used to bind the View
+         * @param position The adapter position
+         * @param type     The type of the View, defined by adapter
+         * @return A View that is bound to the given position or NULL if there is no View to re-use
+         * @see LayoutManager#ignoreView(View)
+         */
+        abstract public View getViewForPositionAndType(Recycler recycler, int position, int type);
+    }
+
+    /**
+     * Base class for an Adapter
+     *
+     * <p>Adapters provide a binding from an app-specific data set to views that are displayed
+     * within a {@link RecyclerView}.</p>
+     */
+    public static abstract class Adapter<VH extends ViewHolder> {
+        private final AdapterDataObservable mObservable = new AdapterDataObservable();
+        private boolean mHasStableIds = false;
+
+        public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
+        public abstract void onBindViewHolder(VH holder, int position);
+
+        public final VH createViewHolder(ViewGroup parent, int viewType) {
+            final VH holder = onCreateViewHolder(parent, viewType);
+            holder.mItemViewType = viewType;
+            return holder;
+        }
+
+        public final void bindViewHolder(VH holder, int position) {
+            holder.mPosition = position;
+            if (hasStableIds()) {
+                holder.mItemId = getItemId(position);
+            }
+            onBindViewHolder(holder, position);
+            holder.setFlags(ViewHolder.FLAG_BOUND,
+                    ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
+        }
+
+        /**
+         * Return the view type of the item at <code>position</code> for the purposes
+         * of view recycling.
+         *
+         * <p>The default implementation of this method returns 0, making the assumption of
+         * a single view type for the adapter. Unlike ListView adapters, types need not
+         * be contiguous. Consider using id resources to uniquely identify item view types.
+         *
+         * @param position position to query
+         * @return integer value identifying the type of the view needed to represent the item at
+         *                 <code>position</code>. Type codes need not be contiguous.
+         */
+        public int getItemViewType(int position) {
+            return 0;
+        }
+
+        public void setHasStableIds(boolean hasStableIds) {
+            if (hasObservers()) {
+                throw new IllegalStateException("Cannot change whether this adapter has " +
+                        "stable IDs while the adapter has registered observers.");
+            }
+            mHasStableIds = hasStableIds;
+        }
+
+        /**
+         * Return the stable ID for the item at <code>position</code>. If {@link #hasStableIds()}
+         * would return false this method should return {@link #NO_ID}. The default implementation
+         * of this method returns {@link #NO_ID}.
+         *
+         * @param position Adapter position to query
+         * @return the stable ID of the item at position
+         */
+        public long getItemId(int position) {
+            return NO_ID;
+        }
+
+        public abstract int getItemCount();
+
+        /**
+         * Returns true if this adapter publishes a unique <code>long</code> value that can
+         * act as a key for the item at a given position in the data set. If that item is relocated
+         * in the data set, the ID returned for that item should be the same.
+         *
+         * @return true if this adapter's items have stable IDs
+         */
+        public final boolean hasStableIds() {
+            return mHasStableIds;
+        }
+
+        /**
+         * Called when a view created by this adapter has been recycled.
+         *
+         * <p>A view is recycled when a {@link LayoutManager} decides that it no longer
+         * needs to be attached to its parent {@link RecyclerView}. This can be because it has
+         * fallen out of visibility or a set of cached views represented by views still
+         * attached to the parent RecyclerView. If an item view has large or expensive data
+         * bound to it such as large bitmaps, this may be a good place to release those
+         * resources.</p>
+         *
+         * @param holder The ViewHolder for the view being recycled
+         */
+        public void onViewRecycled(VH holder) {
+        }
+
+        /**
+         * Called when a view created by this adapter has been attached to a window.
+         *
+         * <p>This can be used as a reasonable signal that the view is about to be seen
+         * by the user. If the adapter previously freed any resources in
+         * {@link #onViewDetachedFromWindow(RecyclerView.ViewHolder) onViewDetachedFromWindow}
+         * those resources should be restored here.</p>
+         *
+         * @param holder Holder of the view being attached
+         */
+        public void onViewAttachedToWindow(VH holder) {
+        }
+
+        /**
+         * Called when a view created by this adapter has been detached from its window.
+         *
+         * <p>Becoming detached from the window is not necessarily a permanent condition;
+         * the consumer of an Adapter's views may choose to cache views offscreen while they
+         * are not visible, attaching an detaching them as appropriate.</p>
+         *
+         * @param holder Holder of the view being detached
+         */
+        public void onViewDetachedFromWindow(VH holder) {
+        }
+
+        /**
+         * Returns true if one or more observers are attached to this adapter.
+         * @return true if this adapter has observers
+         */
+        public final boolean hasObservers() {
+            return mObservable.hasObservers();
+        }
+
+        /**
+         * Register a new observer to listen for data changes.
+         *
+         * <p>The adapter may publish a variety of events describing specific changes.
+         * Not all adapters may support all change types and some may fall back to a generic
+         * {@link android.support.v7.widget.RecyclerView.AdapterDataObserver#onChanged()
+         * "something changed"} event if more specific data is not available.</p>
+         *
+         * <p>Components registering observers with an adapter are responsible for
+         * {@link #unregisterAdapterDataObserver(android.support.v7.widget.RecyclerView.AdapterDataObserver)
+         * unregistering} those observers when finished.</p>
+         *
+         * @param observer Observer to register
+         *
+         * @see #unregisterAdapterDataObserver(android.support.v7.widget.RecyclerView.AdapterDataObserver)
+         */
+        public void registerAdapterDataObserver(AdapterDataObserver observer) {
+            mObservable.registerObserver(observer);
+        }
+
+        /**
+         * Unregister an observer currently listening for data changes.
+         *
+         * <p>The unregistered observer will no longer receive events about changes
+         * to the adapter.</p>
+         *
+         * @param observer Observer to unregister
+         *
+         * @see #registerAdapterDataObserver(android.support.v7.widget.RecyclerView.AdapterDataObserver)
+         */
+        public void unregisterAdapterDataObserver(AdapterDataObserver observer) {
+            mObservable.unregisterObserver(observer);
+        }
+
+        /**
+         * Notify any registered observers that the data set has changed.
+         *
+         * <p>There are two different classes of data change events, item changes and structural
+         * changes. Item changes are when a single item has its data updated but no positional
+         * changes have occurred. Structural changes are when items are inserted, removed or moved
+         * within the data set.</p>
+         *
+         * <p>This event does not specify what about the data set has changed, forcing
+         * any observers to assume that all existing items and structure may no longer be valid.
+         * LayoutManagers will be forced to fully rebind and relayout all visible views.</p>
+         *
+         * <p><code>RecyclerView</code> will attempt to synthesize visible structural change events
+         * for adapters that report that they have {@link #hasStableIds() stable IDs} when
+         * this method is used. This can help for the purposes of animation and visual
+         * object persistence but individual item views will still need to be rebound
+         * and relaid out.</p>
+         *
+         * <p>If you are writing an adapter it will always be more efficient to use the more
+         * specific change events if you can. Rely on <code>notifyDataSetChanged()</code>
+         * as a last resort.</p>
+         *
+         * @see #notifyItemChanged(int)
+         * @see #notifyItemInserted(int)
+         * @see #notifyItemRemoved(int)
+         * @see #notifyItemRangeChanged(int, int)
+         * @see #notifyItemRangeInserted(int, int)
+         * @see #notifyItemRangeRemoved(int, int)
+         */
+        public final void notifyDataSetChanged() {
+            mObservable.notifyChanged();
+        }
+
+        /**
+         * Notify any registered observers that the item at <code>position</code> has changed.
+         *
+         * <p>This is an item change event, not a structural change event. It indicates that any
+         * reflection of the data at <code>position</code> is out of date and should be updated.
+         * The item at <code>position</code> retains the same identity.</p>
+         *
+         * @param position Position of the item that has changed
+         *
+         * @see #notifyItemRangeChanged(int, int)
+         */
+        public final void notifyItemChanged(int position) {
+            mObservable.notifyItemRangeChanged(position, 1);
+        }
+
+        /**
+         * Notify any registered observers that the <code>itemCount</code> items starting at
+         * position <code>positionStart</code> have changed.
+         *
+         * <p>This is an item change event, not a structural change event. It indicates that
+         * any reflection of the data in the given position range is out of date and should
+         * be updated. The items in the given range retain the same identity.</p>
+         *
+         * @param positionStart Position of the first item that has changed
+         * @param itemCount Number of items that have changed
+         *
+         * @see #notifyItemChanged(int)
+         */
+        public final void notifyItemRangeChanged(int positionStart, int itemCount) {
+            mObservable.notifyItemRangeChanged(positionStart, itemCount);
+        }
+
+        /**
+         * Notify any registered observers that the item reflected at <code>position</code>
+         * has been newly inserted. The item previously at <code>position</code> is now at
+         * position <code>position + 1</code>.
+         *
+         * <p>This is a structural change event. Representations of other existing items in the
+         * data set are still considered up to date and will not be rebound, though their
+         * positions may be altered.</p>
+         *
+         * @param position Position of the newly inserted item in the data set
+         *
+         * @see #notifyItemRangeInserted(int, int)
+         */
+        public final void notifyItemInserted(int position) {
+            mObservable.notifyItemRangeInserted(position, 1);
+        }
+
+        /**
+         * Notify any registered observers that the item reflected at <code>fromPosition</code>
+         * has been moved to <code>toPosition</code>.
+         *
+         * <p>This is a structural change event. Representations of other existing items in the
+         * data set are still considered up to date and will not be rebound, though their
+         * positions may be altered.</p>
+         *
+         * @param fromPosition Previous position of the item.
+         * @param toPosition New position of the item.
+         */
+        public final void notifyItemMoved(int fromPosition, int toPosition) {
+            mObservable.notifyItemMoved(fromPosition, toPosition);
+        }
+
+        /**
+         * Notify any registered observers that the currently reflected <code>itemCount</code>
+         * items starting at <code>positionStart</code> have been newly inserted. The items
+         * previously located at <code>positionStart</code> and beyond can now be found starting
+         * at position <code>positionStart + itemCount</code>.
+         *
+         * <p>This is a structural change event. Representations of other existing items in the
+         * data set are still considered up to date and will not be rebound, though their positions
+         * may be altered.</p>
+         *
+         * @param positionStart Position of the first item that was inserted
+         * @param itemCount Number of items inserted
+         *
+         * @see #notifyItemInserted(int)
+         */
+        public final void notifyItemRangeInserted(int positionStart, int itemCount) {
+            mObservable.notifyItemRangeInserted(positionStart, itemCount);
+        }
+
+        /**
+         * Notify any registered observers that the item previously located at <code>position</code>
+         * has been removed from the data set. The items previously located at and after
+         * <code>position</code> may now be found at <code>oldPosition - 1</code>.
+         *
+         * <p>This is a structural change event. Representations of other existing items in the
+         * data set are still considered up to date and will not be rebound, though their positions
+         * may be altered.</p>
+         *
+         * @param position Position of the item that has now been removed
+         *
+         * @see #notifyItemRangeRemoved(int, int)
+         */
+        public final void notifyItemRemoved(int position) {
+            mObservable.notifyItemRangeRemoved(position, 1);
+        }
+
+        /**
+         * Notify any registered observers that the <code>itemCount</code> items previously
+         * located at <code>positionStart</code> have been removed from the data set. The items
+         * previously located at and after <code>positionStart + itemCount</code> may now be found
+         * at <code>oldPosition - itemCount</code>.
+         *
+         * <p>This is a structural change event. Representations of other existing items in the data
+         * set are still considered up to date and will not be rebound, though their positions
+         * may be altered.</p>
+         *
+         * @param positionStart Previous position of the first item that was removed
+         * @param itemCount Number of items removed from the data set
+         */
+        public final void notifyItemRangeRemoved(int positionStart, int itemCount) {
+            mObservable.notifyItemRangeRemoved(positionStart, itemCount);
+        }
+    }
+
+    /**
+     * A <code>LayoutManager</code> is responsible for measuring and positioning item views
+     * within a <code>RecyclerView</code> as well as determining the policy for when to recycle
+     * item views that are no longer visible to the user. By changing the <code>LayoutManager</code>
+     * a <code>RecyclerView</code> can be used to implement a standard vertically scrolling list,
+     * a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock
+     * layout managers are provided for general use.
+     */
+    public static abstract class LayoutManager {
+        ChildHelper mChildHelper;
+        RecyclerView mRecyclerView;
+
+        @Nullable
+        SmoothScroller mSmoothScroller;
+
+        private boolean mRequestedSimpleAnimations = false;
+
+        void setRecyclerView(RecyclerView recyclerView) {
+            if (recyclerView == null) {
+                mRecyclerView = null;
+                mChildHelper = null;
+            } else {
+                mRecyclerView = recyclerView;
+                mChildHelper = recyclerView.mChildHelper;
+            }
+
+        }
+
+        /**
+         * Calls {@code RecyclerView#requestLayout} on the underlying RecyclerView
+         */
+        public void requestLayout() {
+            if(mRecyclerView != null) {
+                mRecyclerView.requestLayout();
+            }
+        }
+
+        /**
+         * Checks if RecyclerView is in the middle of a layout or scroll and throws an
+         * {@link IllegalStateException} if it <b>is not</b>.
+         *
+         * @param message The message for the exception. Can be null.
+         * @see #assertNotInLayoutOrScroll(String)
+         */
+        public void assertInLayoutOrScroll(String message) {
+            if (mRecyclerView != null) {
+                mRecyclerView.assertInLayoutOrScroll(message);
+            }
+        }
+
+        /**
+         * Checks if RecyclerView is in the middle of a layout or scroll and throws an
+         * {@link IllegalStateException} if it <b>is</b>.
+         *
+         * @param message The message for the exception. Can be null.
+         * @see #assertInLayoutOrScroll(String)
+         */
+        public void assertNotInLayoutOrScroll(String message) {
+            if (mRecyclerView != null) {
+                mRecyclerView.assertNotInLayoutOrScroll(message);
+            }
+        }
+
+        /**
+         * Returns whether this LayoutManager supports automatic item animations.
+         * A LayoutManager wishing to support item animations should obey certain
+         * rules as outlined in {@link #onLayoutChildren(Recycler, State)}.
+         * The default return value is <code>false</code>, so subclasses of LayoutManager
+         * will not get predictive item animations by default.
+         *
+         * <p>Whether item animations are enabled in a RecyclerView is determined both
+         * by the return value from this method and the
+         * {@link RecyclerView#setItemAnimator(ItemAnimator) ItemAnimator} set on the
+         * RecyclerView itself. If the RecyclerView has a non-null ItemAnimator but this
+         * method returns false, then simple item animations will be enabled, in which
+         * views that are moving onto or off of the screen are simply faded in/out. If
+         * the RecyclerView has a non-null ItemAnimator and this method returns true,
+         * then there will be two calls to {@link #onLayoutChildren(Recycler, State)} to
+         * setup up the information needed to more intelligently predict where appearing
+         * and disappearing views should be animated from/to.</p>
+         *
+         * @return true if predictive item animations should be enabled, false otherwise
+         */
+        public boolean supportsPredictiveItemAnimations() {
+            return false;
+        }
+
+        /**
+         * Called when this LayoutManager is both attached to a RecyclerView and that RecyclerView
+         * is attached to a window.
+         *
+         * <p>Subclass implementations should always call through to the superclass implementation.
+         * </p>
+         *
+         * @param view The RecyclerView this LayoutManager is bound to
+         */
+        public void onAttachedToWindow(RecyclerView view) {
+        }
+
+        /**
+         * @deprecated
+         * override {@link #onDetachedFromWindow(RecyclerView, Recycler)}
+         */
+        @Deprecated
+        public void onDetachedFromWindow(RecyclerView view) {
+
+        }
+
+        /**
+         * Called when this LayoutManager is detached from its parent RecyclerView or when
+         * its parent RecyclerView is detached from its window.
+         *
+         * <p>Subclass implementations should always call through to the superclass implementation.
+         * </p>
+         *
+         * @param view The RecyclerView this LayoutManager is bound to
+         * @param recycler The recycler to use if you prefer to recycle your children instead of
+         *                 keeping them around.
+         */
+        public void onDetachedFromWindow(RecyclerView view, Recycler recycler) {
+            onDetachedFromWindow(view);
+        }
+
+        /**
+         * Check if the RecyclerView is configured to clip child views to its padding.
+         *
+         * @return true if this RecyclerView clips children to its padding, false otherwise
+         */
+        public boolean getClipToPadding() {
+            return mRecyclerView != null && mRecyclerView.mClipToPadding;
+        }
+
+        /**
+         * Lay out all relevant child views from the given adapter.
+         *
+         * The LayoutManager is in charge of the behavior of item animations. By default,
+         * RecyclerView has a non-null {@link #getItemAnimator() ItemAnimator}, and simple
+         * item animations are enabled. This means that add/remove operations on the
+         * adapter will result in animations to add new or appearing items, removed or
+         * disappearing items, and moved items. If a LayoutManager returns false from
+         * {@link #supportsPredictiveItemAnimations()}, which is the default, and runs a
+         * normal layout operation during {@link #onLayoutChildren(Recycler, State)}, the
+         * RecyclerView will have enough information to run those animations in a simple
+         * way. For example, the default ItemAnimator, {@link DefaultItemAnimator}, will
+         * simple fade views in and out, whether they are actuall added/removed or whether
+         * they are moved on or off the screen due to other add/remove operations.
+         *
+         * <p>A LayoutManager wanting a better item animation experience, where items can be
+         * animated onto and off of the screen according to where the items exist when they
+         * are not on screen, then the LayoutManager should return true from
+         * {@link #supportsPredictiveItemAnimations()} and add additional logic to
+         * {@link #onLayoutChildren(Recycler, State)}. Supporting predictive animations
+         * means that {@link #onLayoutChildren(Recycler, State)} will be called twice;
+         * once as a "pre" layout step to determine where items would have been prior to
+         * a real layout, and again to do the "real" layout. In the pre-layout phase,
+         * items will remember their pre-layout positions to allow them to be laid out
+         * appropriately. Also, {@link LayoutParams#isItemRemoved() removed} items will
+         * be returned from the scrap to help determine correct placement of other items.
+         * These removed items should not be added to the child list, but should be used
+         * to help calculate correct positioning of other views, including views that
+         * were not previously onscreen (referred to as APPEARING views), but whose
+         * pre-layout offscreen position can be determined given the extra
+         * information about the pre-layout removed views.</p>
+         *
+         * <p>The second layout pass is the real layout in which only non-removed views
+         * will be used. The only additional requirement during this pass is, if
+         * {@link #supportsPredictiveItemAnimations()} returns true, to note which
+         * views exist in the child list prior to layout and which are not there after
+         * layout (referred to as DISAPPEARING views), and to position/layout those views
+         * appropriately, without regard to the actual bounds of the RecyclerView. This allows
+         * the animation system to know the location to which to animate these disappearing
+         * views.</p>
+         *
+         * <p>The default LayoutManager implementations for RecyclerView handle all of these
+         * requirements for animations already. Clients of RecyclerView can either use one
+         * of these layout managers directly or look at their implementations of
+         * onLayoutChildren() to see how they account for the APPEARING and
+         * DISAPPEARING views.</p>
+         *
+         * @param recycler         Recycler to use for fetching potentially cached views for a
+         *                         position
+         * @param state            Transient state of RecyclerView
+         */
+        public void onLayoutChildren(Recycler recycler, State state) {
+            Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
+        }
+
+        /**
+         * Create a default <code>LayoutParams</code> object for a child of the RecyclerView.
+         *
+         * <p>LayoutManagers will often want to use a custom <code>LayoutParams</code> type
+         * to store extra information specific to the layout. Client code should subclass
+         * {@link RecyclerView.LayoutParams} for this purpose.</p>
+         *
+         * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type
+         * you must also override
+         * {@link #checkLayoutParams(LayoutParams)},
+         * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and
+         * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p>
+         *
+         * @return A new LayoutParams for a child view
+         */
+        public abstract LayoutParams generateDefaultLayoutParams();
+
+        /**
+         * Determines the validity of the supplied LayoutParams object.
+         *
+         * <p>This should check to make sure that the object is of the correct type
+         * and all values are within acceptable ranges. The default implementation
+         * returns <code>true</code> for non-null params.</p>
+         *
+         * @param lp LayoutParams object to check
+         * @return true if this LayoutParams object is valid, false otherwise
+         */
+        public boolean checkLayoutParams(LayoutParams lp) {
+            return lp != null;
+        }
+
+        /**
+         * Create a LayoutParams object suitable for this LayoutManager, copying relevant
+         * values from the supplied LayoutParams object if possible.
+         *
+         * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type
+         * you must also override
+         * {@link #checkLayoutParams(LayoutParams)},
+         * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and
+         * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p>
+         *
+         * @param lp Source LayoutParams object to copy values from
+         * @return a new LayoutParams object
+         */
+        public LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+            if (lp instanceof LayoutParams) {
+                return new LayoutParams((LayoutParams) lp);
+            } else if (lp instanceof MarginLayoutParams) {
+                return new LayoutParams((MarginLayoutParams) lp);
+            } else {
+                return new LayoutParams(lp);
+            }
+        }
+
+        /**
+         * Create a LayoutParams object suitable for this LayoutManager from
+         * an inflated layout resource.
+         *
+         * <p><em>Important:</em> if you use your own custom <code>LayoutParams</code> type
+         * you must also override
+         * {@link #checkLayoutParams(LayoutParams)},
+         * {@link #generateLayoutParams(android.view.ViewGroup.LayoutParams)} and
+         * {@link #generateLayoutParams(android.content.Context, android.util.AttributeSet)}.</p>
+         *
+         * @param c Context for obtaining styled attributes
+         * @param attrs AttributeSet describing the supplied arguments
+         * @return a new LayoutParams object
+         */
+        public LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
+            return new LayoutParams(c, attrs);
+        }
+
+        /**
+         * Scroll horizontally by dx pixels in screen coordinates and return the distance traveled.
+         * The default implementation does nothing and returns 0.
+         *
+         * @param dx            distance to scroll by in pixels. X increases as scroll position
+         *                      approaches the right.
+         * @param recycler      Recycler to use for fetching potentially cached views for a
+         *                      position
+         * @param state         Transient state of RecyclerView
+         * @return The actual distance scrolled. The return value will be negative if dx was
+         * negative and scrolling proceeeded in that direction.
+         * <code>Math.abs(result)</code> may be less than dx if a boundary was reached.
+         */
+        public int scrollHorizontallyBy(int dx, Recycler recycler, State state) {
+            return 0;
+        }
+
+        /**
+         * Scroll vertically by dy pixels in screen coordinates and return the distance traveled.
+         * The default implementation does nothing and returns 0.
+         *
+         * @param dy            distance to scroll in pixels. Y increases as scroll position
+         *                      approaches the bottom.
+         * @param recycler      Recycler to use for fetching potentially cached views for a
+         *                      position
+         * @param state         Transient state of RecyclerView
+         * @return The actual distance scrolled. The return value will be negative if dy was
+         * negative and scrolling proceeeded in that direction.
+         * <code>Math.abs(result)</code> may be less than dy if a boundary was reached.
+         */
+        public int scrollVerticallyBy(int dy, Recycler recycler, State state) {
+            return 0;
+        }
+
+        /**
+         * Query if horizontal scrolling is currently supported. The default implementation
+         * returns false.
+         *
+         * @return True if this LayoutManager can scroll the current contents horizontally
+         */
+        public boolean canScrollHorizontally() {
+            return false;
+        }
+
+        /**
+         * Query if vertical scrolling is currently supported. The default implementation
+         * returns false.
+         *
+         * @return True if this LayoutManager can scroll the current contents vertically
+         */
+        public boolean canScrollVertically() {
+            return false;
+        }
+
+        /**
+         * Scroll to the specified adapter position.
+         *
+         * Actual position of the item on the screen depends on the LayoutManager implementation.
+         * @param position Scroll to this adapter position.
+         */
+        public void scrollToPosition(int position) {
+            if (DEBUG) {
+                Log.e(TAG, "You MUST implement scrollToPosition. It will soon become abstract");
+            }
+        }
+
+        /**
+         * <p>Smooth scroll to the specified adapter position.</p>
+         * <p>To support smooth scrolling, override this method, create your {@link SmoothScroller}
+         * instance and call {@link #startSmoothScroll(SmoothScroller)}.
+         * </p>
+         * @param recyclerView The RecyclerView to which this layout manager is attached
+         * @param state    Current State of RecyclerView
+         * @param position Scroll to this adapter position.
+         */
+        public void smoothScrollToPosition(RecyclerView recyclerView, State state,
+                int position) {
+            Log.e(TAG, "You must override smoothScrollToPosition to support smooth scrolling");
+        }
+
+        /**
+         * <p>Starts a smooth scroll using the provided SmoothScroller.</p>
+         * <p>Calling this method will cancel any previous smooth scroll request.</p>
+         * @param smoothScroller Unstance which defines how smooth scroll should be animated
+         */
+        public void startSmoothScroll(SmoothScroller smoothScroller) {
+            if (mSmoothScroller != null && smoothScroller != mSmoothScroller
+                    && mSmoothScroller.isRunning()) {
+                mSmoothScroller.stop();
+            }
+            mSmoothScroller = smoothScroller;
+            mSmoothScroller.start(mRecyclerView, this);
+        }
+
+        /**
+         * @return true if RecycylerView is currently in the state of smooth scrolling.
+         */
+        public boolean isSmoothScrolling() {
+            return mSmoothScroller != null && mSmoothScroller.isRunning();
+        }
+
+
+        /**
+         * Returns the resolved layout direction for this RecyclerView.
+         *
+         * @return {@link android.support.v4.view.ViewCompat#LAYOUT_DIRECTION_RTL} if the layout
+         * direction is RTL or returns
+         * {@link android.support.v4.view.ViewCompat#LAYOUT_DIRECTION_LTR} if the layout direction
+         * is not RTL.
+         */
+        public int getLayoutDirection() {
+            return ViewCompat.getLayoutDirection(mRecyclerView);
+        }
+
+        /**
+         * Ends all animations on the view created by the {@link ItemAnimator}.
+         *
+         * @param view The View for which the animations should be ended.
+         * @see RecyclerView.ItemAnimator#endAnimations()
+         */
+        public void endAnimation(View view) {
+            if (mRecyclerView.mItemAnimator != null) {
+                mRecyclerView.mItemAnimator.endAnimation(getChildViewHolderInt(view));
+            }
+        }
+
+        /**
+         * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view
+         * to the layout that is known to be going away, either because it has been
+         * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the
+         * visible portion of the container but is being laid out in order to inform RecyclerView
+         * in how to animate the item out of view.
+         * <p>
+         * Views added via this method are going to be invisible to LayoutManager after the
+         * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)}
+         * or won't be included in {@link #getChildCount()} method.
+         *
+         * @param child View to add and then remove with animation.
+         */
+        public void addDisappearingView(View child) {
+            addDisappearingView(child, -1);
+        }
+
+        /**
+         * To be called only during {@link #onLayoutChildren(Recycler, State)} to add a view
+         * to the layout that is known to be going away, either because it has been
+         * {@link Adapter#notifyItemRemoved(int) removed} or because it is actually not in the
+         * visible portion of the container but is being laid out in order to inform RecyclerView
+         * in how to animate the item out of view.
+         * <p>
+         * Views added via this method are going to be invisible to LayoutManager after the
+         * dispatchLayout pass is complete. They cannot be retrieved via {@link #getChildAt(int)}
+         * or won't be included in {@link #getChildCount()} method.
+         *
+         * @param child View to add and then remove with animation.
+         * @param index Index of the view.
+         */
+        public void addDisappearingView(View child, int index) {
+            addViewInt(child, index, true);
+        }
+
+        /**
+         * Add a view to the currently attached RecyclerView if needed. LayoutManagers should
+         * use this method to add views obtained from a {@link Recycler} using
+         * {@link Recycler#getViewForPosition(int)}.
+         *
+         * @param child View to add
+         */
+        public void addView(View child) {
+            addView(child, -1);
+        }
+
+        /**
+         * Add a view to the currently attached RecyclerView if needed. LayoutManagers should
+         * use this method to add views obtained from a {@link Recycler} using
+         * {@link Recycler#getViewForPosition(int)}.
+         *
+         * @param child View to add
+         * @param index Index to add child at
+         */
+        public void addView(View child, int index) {
+            addViewInt(child, index, false);
+        }
+
+        private void addViewInt(View child, int index, boolean disappearing) {
+            final ViewHolder holder = getChildViewHolderInt(child);
+            if (disappearing || holder.isRemoved()) {
+                // these views will be hidden at the end of the layout pass.
+                mRecyclerView.addToDisappearingList(child);
+            } else {
+                // This may look like unnecessary but may happen if layout manager supports
+                // predictive layouts and adapter removed then re-added the same item.
+                // In this case, added version will be visible in the post layout (because add is
+                // deferred) but RV will still bind it to the same View.
+                // So if a View re-appears in post layout pass, remove it from disappearing list.
+                mRecyclerView.removeFromDisappearingList(child);
+            }
+
+            if (holder.wasReturnedFromScrap() || holder.isScrap()) {
+                if (holder.isScrap()) {
+                    holder.unScrap();
+                } else {
+                    holder.clearReturnedFromScrapFlag();
+                }
+                mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false);
+                if (DISPATCH_TEMP_DETACH) {
+                    ViewCompat.dispatchFinishTemporaryDetach(child);
+                }
+            } else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child
+                // ensure in correct position
+                int currentIndex = mChildHelper.indexOfChild(child);
+                if (index == -1) {
+                    index = mChildHelper.getChildCount();
+                }
+                if (currentIndex == -1) {
+                    throw new IllegalStateException("Added View has RecyclerView as parent but"
+                            + " view is not a real child. Unfiltered index:"
+                            + mRecyclerView.indexOfChild(child));
+                }
+                if (currentIndex != index) {
+                    mRecyclerView.mLayout.moveView(currentIndex, index);
+                }
+            } else {
+                mChildHelper.addView(child, index, false);
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                lp.mInsetsDirty = true;
+                final Adapter adapter = mRecyclerView.getAdapter();
+                if (adapter != null) {
+                    adapter.onViewAttachedToWindow(getChildViewHolderInt(child));
+                }
+                mRecyclerView.onChildAttachedToWindow(child);
+                if (mSmoothScroller != null && mSmoothScroller.isRunning()) {
+                    mSmoothScroller.onChildAttachedToWindow(child);
+                }
+            }
+        }
+
+        /**
+         * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should
+         * use this method to completely remove a child view that is no longer needed.
+         * LayoutManagers should strongly consider recycling removed views using
+         * {@link Recycler#recycleView(android.view.View)}.
+         *
+         * @param child View to remove
+         */
+        public void removeView(View child) {
+            final Adapter adapter = mRecyclerView.getAdapter();
+            if (adapter != null) {
+                adapter.onViewDetachedFromWindow(getChildViewHolderInt(child));
+            }
+            mRecyclerView.onChildDetachedFromWindow(child);
+            mChildHelper.removeView(child);
+        }
+
+        /**
+         * Remove a view from the currently attached RecyclerView if needed. LayoutManagers should
+         * use this method to completely remove a child view that is no longer needed.
+         * LayoutManagers should strongly consider recycling removed views using
+         * {@link Recycler#recycleView(android.view.View)}.
+         *
+         * @param index Index of the child view to remove
+         */
+        public void removeViewAt(int index) {
+            final View child = getChildAt(index);
+            if (child != null) {
+                final Adapter adapter = mRecyclerView.getAdapter();
+                if (adapter != null) {
+                    adapter.onViewDetachedFromWindow(getChildViewHolderInt(child));
+                }
+                mRecyclerView.onChildDetachedFromWindow(child);
+                mChildHelper.removeViewAt(index);
+            }
+        }
+
+        /**
+         * Remove all views from the currently attached RecyclerView. This will not recycle
+         * any of the affected views; the LayoutManager is responsible for doing so if desired.
+         */
+        public void removeAllViews() {
+            final Adapter adapter = mRecyclerView.getAdapter();
+            // Only remove non-animating views
+            final int childCount = getChildCount();
+
+            for (int i = 0; i < childCount; i++) {
+                final View child = getChildAt(i);
+                if (adapter != null) {
+                    adapter.onViewDetachedFromWindow(getChildViewHolderInt(child));
+                }
+                mRecyclerView.onChildDetachedFromWindow(child);
+            }
+
+            for (int i = childCount - 1; i >= 0; i--) {
+                mChildHelper.removeViewAt(i);
+            }
+        }
+
+        /**
+         * Returns the adapter position of the item represented by the given View.
+         *
+         * @param view The view to query
+         * @return The adapter position of the item which is rendered by this View.
+         */
+        public int getPosition(View view) {
+            return ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewPosition();
+        }
+
+        /**
+         * Returns the View type defined by the adapter.
+         *
+         * @param view The view to query
+         * @return The type of the view assigned by the adapter.
+         */
+        public int getItemViewType(View view) {
+            return getChildViewHolderInt(view).getItemViewType();
+        }
+
+        /**
+         * <p>
+         * Finds the view which represents the given adapter position.
+         * <p>
+         * This method traverses each child since it has no information about child order.
+         * Override this method to improve performance if your LayoutManager keeps data about
+         * child views.
+         * <p>
+         * If a view is ignored via {@link #ignoreView(View)}, it is also ignored by this method.
+         *
+         * @param position Position of the item in adapter
+         * @return The child view that represents the given position or null if the position is not
+         * visible
+         */
+        public View findViewByPosition(int position) {
+            final int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                View child = getChildAt(i);
+                ViewHolder vh = getChildViewHolderInt(child);
+                if (vh == null) {
+                    continue;
+                }
+                if (vh.getPosition() == position && !vh.shouldIgnore() &&
+                        (mRecyclerView.mState.isPreLayout() || !vh.isRemoved())) {
+                    return child;
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Temporarily detach a child view.
+         *
+         * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange
+         * views currently attached to the RecyclerView. Generally LayoutManager implementations
+         * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}
+         * so that the detached view may be rebound and reused.</p>
+         *
+         * <p>If a LayoutManager uses this method to detach a view, it <em>must</em>
+         * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach}
+         * or {@link #removeDetachedView(android.view.View) fully remove} the detached view
+         * before the LayoutManager entry point method called by RecyclerView returns.</p>
+         *
+         * @param child Child to detach
+         */
+        public void detachView(View child) {
+            if (DISPATCH_TEMP_DETACH) {
+                ViewCompat.dispatchStartTemporaryDetach(child);
+            }
+            mRecyclerView.detachViewFromParent(child);
+        }
+
+        /**
+         * Temporarily detach a child view.
+         *
+         * <p>LayoutManagers may want to perform a lightweight detach operation to rearrange
+         * views currently attached to the RecyclerView. Generally LayoutManager implementations
+         * will want to use {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}
+         * so that the detached view may be rebound and reused.</p>
+         *
+         * <p>If a LayoutManager uses this method to detach a view, it <em>must</em>
+         * {@link #attachView(android.view.View, int, RecyclerView.LayoutParams) reattach}
+         * or {@link #removeDetachedView(android.view.View) fully remove} the detached view
+         * before the LayoutManager entry point method called by RecyclerView returns.</p>
+         *
+         * @param index Index of the child to detach
+         */
+        public void detachViewAt(int index) {
+            if (DISPATCH_TEMP_DETACH) {
+                ViewCompat.dispatchStartTemporaryDetach(getChildAt(index));
+            }
+            mChildHelper.detachViewFromParent(index);
+        }
+
+        /**
+         * Reattach a previously {@link #detachView(android.view.View) detached} view.
+         * This method should not be used to reattach views that were previously
+         * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}  scrapped}.
+         *
+         * @param child Child to reattach
+         * @param index Intended child index for child
+         * @param lp LayoutParams for child
+         */
+        public void attachView(View child, int index, LayoutParams lp) {
+            ViewHolder vh = getChildViewHolderInt(child);
+            if (vh.isRemoved()) {
+                mRecyclerView.addToDisappearingList(child);
+            } else {
+                mRecyclerView.removeFromDisappearingList(child);
+            }
+            mChildHelper.attachViewToParent(child, index, lp, vh.isRemoved());
+            if (DISPATCH_TEMP_DETACH)  {
+                ViewCompat.dispatchFinishTemporaryDetach(child);
+            }
+        }
+
+        /**
+         * Reattach a previously {@link #detachView(android.view.View) detached} view.
+         * This method should not be used to reattach views that were previously
+         * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}  scrapped}.
+         *
+         * @param child Child to reattach
+         * @param index Intended child index for child
+         */
+        public void attachView(View child, int index) {
+            attachView(child, index, (LayoutParams) child.getLayoutParams());
+        }
+
+        /**
+         * Reattach a previously {@link #detachView(android.view.View) detached} view.
+         * This method should not be used to reattach views that were previously
+         * {@link #detachAndScrapView(android.view.View, RecyclerView.Recycler)}  scrapped}.
+         *
+         * @param child Child to reattach
+         */
+        public void attachView(View child) {
+            attachView(child, -1);
+        }
+
+        /**
+         * Finish removing a view that was previously temporarily
+         * {@link #detachView(android.view.View) detached}.
+         *
+         * @param child Detached child to remove
+         */
+        public void removeDetachedView(View child) {
+            mRecyclerView.removeDetachedView(child, false);
+        }
+
+        /**
+         * Moves a View from one position to another.
+         *
+         * @param fromIndex The View's initial index
+         * @param toIndex The View's target index
+         */
+        public void moveView(int fromIndex, int toIndex) {
+            View view = getChildAt(fromIndex);
+            if (view == null) {
+                throw new IllegalArgumentException("Cannot move a child from non-existing index:"
+                        + fromIndex);
+            }
+            detachViewAt(fromIndex);
+            attachView(view, toIndex);
+        }
+
+        /**
+         * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap.
+         *
+         * <p>Scrapping a view allows it to be rebound and reused to show updated or
+         * different data.</p>
+         *
+         * @param child Child to detach and scrap
+         * @param recycler Recycler to deposit the new scrap view into
+         */
+        public void detachAndScrapView(View child, Recycler recycler) {
+            int index = mChildHelper.indexOfChild(child);
+            scrapOrRecycleView(recycler, index, child);
+        }
+
+        /**
+         * Detach a child view and add it to a {@link Recycler Recycler's} scrap heap.
+         *
+         * <p>Scrapping a view allows it to be rebound and reused to show updated or
+         * different data.</p>
+         *
+         * @param index Index of child to detach and scrap
+         * @param recycler Recycler to deposit the new scrap view into
+         */
+        public void detachAndScrapViewAt(int index, Recycler recycler) {
+            final View child = getChildAt(index);
+            scrapOrRecycleView(recycler, index, child);
+        }
+
+        /**
+         * Remove a child view and recycle it using the given Recycler.
+         *
+         * @param child Child to remove and recycle
+         * @param recycler Recycler to use to recycle child
+         */
+        public void removeAndRecycleView(View child, Recycler recycler) {
+            removeView(child);
+            recycler.recycleView(child);
+        }
+
+        /**
+         * Remove a child view and recycle it using the given Recycler.
+         *
+         * @param index Index of child to remove and recycle
+         * @param recycler Recycler to use to recycle child
+         */
+        public void removeAndRecycleViewAt(int index, Recycler recycler) {
+            final View view = getChildAt(index);
+            removeViewAt(index);
+            recycler.recycleView(view);
+        }
+
+        /**
+         * Return the current number of child views attached to the parent RecyclerView.
+         * This does not include child views that were temporarily detached and/or scrapped.
+         *
+         * @return Number of attached children
+         */
+        public int getChildCount() {
+            return mChildHelper != null ? mChildHelper.getChildCount() : 0;
+        }
+
+        /**
+         * Return the child view at the given index
+         * @param index Index of child to return
+         * @return Child view at index
+         */
+        public View getChildAt(int index) {
+            return mChildHelper != null ? mChildHelper.getChildAt(index) : null;
+        }
+
+        /**
+         * Return the width of the parent RecyclerView
+         *
+         * @return Width in pixels
+         */
+        public int getWidth() {
+            return mRecyclerView != null ? mRecyclerView.getWidth() : 0;
+        }
+
+        /**
+         * Return the height of the parent RecyclerView
+         *
+         * @return Height in pixels
+         */
+        public int getHeight() {
+            return mRecyclerView != null ? mRecyclerView.getHeight() : 0;
+        }
+
+        /**
+         * Return the left padding of the parent RecyclerView
+         *
+         * @return Padding in pixels
+         */
+        public int getPaddingLeft() {
+            return mRecyclerView != null ? mRecyclerView.getPaddingLeft() : 0;
+        }
+
+        /**
+         * Return the top padding of the parent RecyclerView
+         *
+         * @return Padding in pixels
+         */
+        public int getPaddingTop() {
+            return mRecyclerView != null ? mRecyclerView.getPaddingTop() : 0;
+        }
+
+        /**
+         * Return the right padding of the parent RecyclerView
+         *
+         * @return Padding in pixels
+         */
+        public int getPaddingRight() {
+            return mRecyclerView != null ? mRecyclerView.getPaddingRight() : 0;
+        }
+
+        /**
+         * Return the bottom padding of the parent RecyclerView
+         *
+         * @return Padding in pixels
+         */
+        public int getPaddingBottom() {
+            return mRecyclerView != null ? mRecyclerView.getPaddingBottom() : 0;
+        }
+
+        /**
+         * Return the start padding of the parent RecyclerView
+         *
+         * @return Padding in pixels
+         */
+        public int getPaddingStart() {
+            return mRecyclerView != null ? ViewCompat.getPaddingStart(mRecyclerView) : 0;
+        }
+
+        /**
+         * Return the end padding of the parent RecyclerView
+         *
+         * @return Padding in pixels
+         */
+        public int getPaddingEnd() {
+            return mRecyclerView != null ? ViewCompat.getPaddingEnd(mRecyclerView) : 0;
+        }
+
+        /**
+         * Returns true if the RecyclerView this LayoutManager is bound to has focus.
+         *
+         * @return True if the RecyclerView has focus, false otherwise.
+         * @see View#isFocused()
+         */
+        public boolean isFocused() {
+            return mRecyclerView != null && mRecyclerView.isFocused();
+        }
+
+        /**
+         * Returns true if the RecyclerView this LayoutManager is bound to has or contains focus.
+         *
+         * @return true if the RecyclerView has or contains focus
+         * @see View#hasFocus()
+         */
+        public boolean hasFocus() {
+            return mRecyclerView != null && mRecyclerView.hasFocus();
+        }
+
+        /**
+         * Returns the item View which has or contains focus.
+         *
+         * @return A direct child of RecyclerView which has focus or contains the focused child.
+         */
+        public View findItemWhichHasFocus() {
+            if (mRecyclerView == null) {
+                return null;
+            }
+            View focused = mRecyclerView.findFocus();
+            if (focused != null && focused.getParent() == mRecyclerView) {
+                return focused;
+            }
+            return null;
+        }
+
+        /**
+         * Returns the number of items in the adapter bound to the parent RecyclerView.
+         * <p>
+         * Note that this number is not necessarily equal to {@link State#getItemCount()}. In
+         * methods where State is available, you should use {@link State#getItemCount()} instead.
+         * For more details, check the documentation for {@link State#getItemCount()}.
+         *
+         * @return The number of items in the bound adapter
+         * @see State#getItemCount()
+         */
+        public int getItemCount() {
+            final Adapter a = mRecyclerView != null ? mRecyclerView.getAdapter() : null;
+            return a != null ? a.getItemCount() : 0;
+        }
+
+        /**
+         * Offset all child views attached to the parent RecyclerView by dx pixels along
+         * the horizontal axis.
+         *
+         * @param dx Pixels to offset by
+         */
+        public void offsetChildrenHorizontal(int dx) {
+            if (mRecyclerView != null) {
+                mRecyclerView.offsetChildrenHorizontal(dx);
+            }
+        }
+
+        /**
+         * Offset all child views attached to the parent RecyclerView by dy pixels along
+         * the vertical axis.
+         *
+         * @param dy Pixels to offset by
+         */
+        public void offsetChildrenVertical(int dy) {
+            if (mRecyclerView != null) {
+                mRecyclerView.offsetChildrenVertical(dy);
+            }
+        }
+
+        /**
+         * Flags a view so that it will not be scrapped or recycled.
+         * <p>
+         * Scope of ignoring a child is strictly restricted to position tracking, scrapping and
+         * recyling. Methods like {@link #removeAndRecycleAllViews(Recycler)} will ignore the child
+         * whereas {@link #removeAllViews()} or {@link #offsetChildrenHorizontal(int)} will not
+         * ignore the child.
+         * <p>
+         * Before this child can be recycled again, you have to call
+         * {@link #stopIgnoringView(View)}.
+         * <p>
+         * You can call this method only if your LayoutManger is in onLayout or onScroll callback.
+         *
+         * @param view View to ignore.
+         * @see #stopIgnoringView(View)
+         */
+        public void ignoreView(View view) {
+            if (view.getParent() != mRecyclerView || mRecyclerView.indexOfChild(view) == -1) {
+                // checking this because calling this method on a recycled or detached view may
+                // cause loss of state.
+                throw new IllegalArgumentException("View should be fully attached to be ignored");
+            }
+            final ViewHolder vh = getChildViewHolderInt(view);
+            vh.addFlags(ViewHolder.FLAG_IGNORE);
+            mRecyclerView.mState.onViewIgnored(vh);
+        }
+
+        /**
+         * View can be scrapped and recycled again.
+         * <p>
+         * Note that calling this method removes all information in the view holder.
+         * <p>
+         * You can call this method only if your LayoutManger is in onLayout or onScroll callback.
+         *
+         * @param view View to ignore.
+         */
+        public void stopIgnoringView(View view) {
+            final ViewHolder vh = getChildViewHolderInt(view);
+            vh.stopIgnoring();
+            vh.resetInternal();
+            vh.addFlags(ViewHolder.FLAG_INVALID);
+        }
+
+        /**
+         * Temporarily detach and scrap all currently attached child views. Views will be scrapped
+         * into the given Recycler. The Recycler may prefer to reuse scrap views before
+         * other views that were previously recycled.
+         *
+         * @param recycler Recycler to scrap views into
+         */
+        public void detachAndScrapAttachedViews(Recycler recycler) {
+            final int childCount = getChildCount();
+            for (int i = childCount - 1; i >= 0; i--) {
+                final View v = getChildAt(i);
+                scrapOrRecycleView(recycler, i, v);
+            }
+        }
+
+        private void scrapOrRecycleView(Recycler recycler, int index, View view) {
+            final ViewHolder viewHolder = getChildViewHolderInt(view);
+            if (viewHolder.shouldIgnore()) {
+                if (DEBUG) {
+                    Log.d(TAG, "ignoring view " + viewHolder);
+                }
+                return;
+            }
+            if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !viewHolder.isChanged() &&
+                    !mRecyclerView.mAdapter.hasStableIds()) {
+                removeViewAt(index);
+                recycler.recycleViewHolderInternal(viewHolder);
+            } else {
+                detachViewAt(index);
+                recycler.scrapView(view);
+            }
+        }
+
+        /**
+         * Recycles the scrapped views.
+         * <p>
+         * When a view is detached and removed, it does not trigger a ViewGroup invalidate. This is
+         * the expected behavior if scrapped views are used for animations. Otherwise, we need to
+         * call remove and invalidate RecyclerView to ensure UI update.
+         *
+         * @param recycler Recycler
+         * @param remove   Whether scrapped views should be removed from ViewGroup or not. This
+         *                 method will invalidate RecyclerView if it removes any scrapped child.
+         */
+        void removeAndRecycleScrapInt(Recycler recycler, boolean remove) {
+            final int scrapCount = recycler.getScrapCount();
+            for (int i = 0; i < scrapCount; i++) {
+                final View scrap = recycler.getScrapViewAt(i);
+                if (getChildViewHolderInt(scrap).shouldIgnore()) {
+                    continue;
+                }
+                if (remove) {
+                    mRecyclerView.removeDetachedView(scrap, false);
+                }
+                recycler.quickRecycleScrapView(scrap);
+            }
+            recycler.clearScrap();
+            if (remove && scrapCount > 0) {
+                mRecyclerView.invalidate();
+            }
+        }
+
+
+        /**
+         * Measure a child view using standard measurement policy, taking the padding
+         * of the parent RecyclerView and any added item decorations into account.
+         *
+         * <p>If the RecyclerView can be scrolled in either dimension the caller may
+         * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p>
+         *
+         * @param child Child view to measure
+         * @param widthUsed Width in pixels currently consumed by other views, if relevant
+         * @param heightUsed Height in pixels currently consumed by other views, if relevant
+         */
+        public void measureChild(View child, int widthUsed, int heightUsed) {
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+            final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
+            widthUsed += insets.left + insets.right;
+            heightUsed += insets.top + insets.bottom;
+
+            final int widthSpec = getChildMeasureSpec(getWidth(),
+                    getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
+                    canScrollHorizontally());
+            final int heightSpec = getChildMeasureSpec(getHeight(),
+                    getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
+                    canScrollVertically());
+            child.measure(widthSpec, heightSpec);
+        }
+
+        /**
+         * Measure a child view using standard measurement policy, taking the padding
+         * of the parent RecyclerView, any added item decorations and the child margins
+         * into account.
+         *
+         * <p>If the RecyclerView can be scrolled in either dimension the caller may
+         * pass 0 as the widthUsed or heightUsed parameters as they will be irrelevant.</p>
+         *
+         * @param child Child view to measure
+         * @param widthUsed Width in pixels currently consumed by other views, if relevant
+         * @param heightUsed Height in pixels currently consumed by other views, if relevant
+         */
+        public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+            final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
+            widthUsed += insets.left + insets.right;
+            heightUsed += insets.top + insets.bottom;
+
+            final int widthSpec = getChildMeasureSpec(getWidth(),
+                    getPaddingLeft() + getPaddingRight() +
+                            lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
+                    canScrollHorizontally());
+            final int heightSpec = getChildMeasureSpec(getHeight(),
+                    getPaddingTop() + getPaddingBottom() +
+                            lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
+                    canScrollVertically());
+            child.measure(widthSpec, heightSpec);
+        }
+
+        /**
+         * Calculate a MeasureSpec value for measuring a child view in one dimension.
+         *
+         * @param parentSize Size of the parent view where the child will be placed
+         * @param padding Total space currently consumed by other elements of parent
+         * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT.
+         *                       Generally obtained from the child view's LayoutParams
+         * @param canScroll true if the parent RecyclerView can scroll in this dimension
+         *
+         * @return a MeasureSpec value for the child view
+         */
+        public static int getChildMeasureSpec(int parentSize, int padding, int childDimension,
+                boolean canScroll) {
+            int size = Math.max(0, parentSize - padding);
+            int resultSize = 0;
+            int resultMode = 0;
+
+            if (canScroll) {
+                if (childDimension >= 0) {
+                    resultSize = childDimension;
+                    resultMode = MeasureSpec.EXACTLY;
+                } else {
+                    // MATCH_PARENT can't be applied since we can scroll in this dimension, wrap
+                    // instead using UNSPECIFIED.
+                    resultSize = 0;
+                    resultMode = MeasureSpec.UNSPECIFIED;
+                }
+            } else {
+                if (childDimension >= 0) {
+                    resultSize = childDimension;
+                    resultMode = MeasureSpec.EXACTLY;
+                } else if (childDimension == LayoutParams.FILL_PARENT) {
+                    resultSize = size;
+                    resultMode = MeasureSpec.EXACTLY;
+                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+                    resultSize = size;
+                    resultMode = MeasureSpec.AT_MOST;
+                }
+            }
+            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
+        }
+
+        /**
+         * Returns the measured width of the given child, plus the additional size of
+         * any insets applied by {@link ItemDecoration ItemDecorations}.
+         *
+         * @param child Child view to query
+         * @return child's measured width plus <code>ItemDecoration</code> insets
+         *
+         * @see View#getMeasuredWidth()
+         */
+        public int getDecoratedMeasuredWidth(View child) {
+            final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+            return child.getMeasuredWidth() + insets.left + insets.right;
+        }
+
+        /**
+         * Returns the measured height of the given child, plus the additional size of
+         * any insets applied by {@link ItemDecoration ItemDecorations}.
+         *
+         * @param child Child view to query
+         * @return child's measured height plus <code>ItemDecoration</code> insets
+         *
+         * @see View#getMeasuredHeight()
+         */
+        public int getDecoratedMeasuredHeight(View child) {
+            final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+            return child.getMeasuredHeight() + insets.top + insets.bottom;
+        }
+
+        /**
+         * Lay out the given child view within the RecyclerView using coordinates that
+         * include any current {@link ItemDecoration ItemDecorations}.
+         *
+         * <p>LayoutManagers should prefer working in sizes and coordinates that include
+         * item decoration insets whenever possible. This allows the LayoutManager to effectively
+         * ignore decoration insets within measurement and layout code. See the following
+         * methods:</p>
+         * <ul>
+         *     <li>{@link #measureChild(View, int, int)}</li>
+         *     <li>{@link #measureChildWithMargins(View, int, int)}</li>
+         *     <li>{@link #getDecoratedLeft(View)}</li>
+         *     <li>{@link #getDecoratedTop(View)}</li>
+         *     <li>{@link #getDecoratedRight(View)}</li>
+         *     <li>{@link #getDecoratedBottom(View)}</li>
+         *     <li>{@link #getDecoratedMeasuredWidth(View)}</li>
+         *     <li>{@link #getDecoratedMeasuredHeight(View)}</li>
+         * </ul>
+         *
+         * @param child Child to lay out
+         * @param left Left edge, with item decoration insets included
+         * @param top Top edge, with item decoration insets included
+         * @param right Right edge, with item decoration insets included
+         * @param bottom Bottom edge, with item decoration insets included
+         *
+         * @see View#layout(int, int, int, int)
+         */
+        public void layoutDecorated(View child, int left, int top, int right, int bottom) {
+            final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+            child.layout(left + insets.left, top + insets.top, right - insets.right,
+                    bottom - insets.bottom);
+        }
+
+        /**
+         * Returns the left edge of the given child view within its parent, offset by any applied
+         * {@link ItemDecoration ItemDecorations}.
+         *
+         * @param child Child to query
+         * @return Child left edge with offsets applied
+         * @see #getLeftDecorationWidth(View)
+         */
+        public int getDecoratedLeft(View child) {
+            return child.getLeft() - getLeftDecorationWidth(child);
+        }
+
+        /**
+         * Returns the top edge of the given child view within its parent, offset by any applied
+         * {@link ItemDecoration ItemDecorations}.
+         *
+         * @param child Child to query
+         * @return Child top edge with offsets applied
+         * @see #getTopDecorationHeight(View)
+         */
+        public int getDecoratedTop(View child) {
+            return child.getTop() - getTopDecorationHeight(child);
+        }
+
+        /**
+         * Returns the right edge of the given child view within its parent, offset by any applied
+         * {@link ItemDecoration ItemDecorations}.
+         *
+         * @param child Child to query
+         * @return Child right edge with offsets applied
+         * @see #getRightDecorationWidth(View)
+         */
+        public int getDecoratedRight(View child) {
+            return child.getRight() + getRightDecorationWidth(child);
+        }
+
+        /**
+         * Returns the bottom edge of the given child view within its parent, offset by any applied
+         * {@link ItemDecoration ItemDecorations}.
+         *
+         * @param child Child to query
+         * @return Child bottom edge with offsets applied
+         * @see #getBottomDecorationHeight(View)
+         */
+        public int getDecoratedBottom(View child) {
+            return child.getBottom() + getBottomDecorationHeight(child);
+        }
+
+        /**
+         * Calculates the item decor insets applied to the given child and updates the provided
+         * Rect instance with the inset values.
+         * <ul>
+         *     <li>The Rect's left is set to the total width of left decorations.</li>
+         *     <li>The Rect's top is set to the total height of top decorations.</li>
+         *     <li>The Rect's right is set to the total width of right decorations.</li>
+         *     <li>The Rect's bottom is set to total height of bottom decorations.</li>
+         * </ul>
+         * <p>
+         * Note that item decorations are automatically calculated when one of the LayoutManager's
+         * measure child methods is called. If you need to measure the child with custom specs via
+         * {@link View#measure(int, int)}, you can use this method to get decorations.
+         *
+         * @param child The child view whose decorations should be calculated
+         * @param outRect The Rect to hold result values
+         */
+        public void calculateItemDecorationsForChild(View child, Rect outRect) {
+            if (mRecyclerView == null) {
+                outRect.set(0, 0, 0, 0);
+                return;
+            }
+            Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
+            outRect.set(insets);
+        }
+
+        /**
+         * Returns the total height of item decorations applied to child's top.
+         * <p>
+         * Note that this value is not updated until the View is measured or
+         * {@link #calculateItemDecorationsForChild(View, Rect)} is called.
+         *
+         * @param child Child to query
+         * @return The total height of item decorations applied to the child's top.
+         * @see #getDecoratedTop(View)
+         * @see #calculateItemDecorationsForChild(View, Rect)
+         */
+        public int getTopDecorationHeight(View child) {
+            return ((LayoutParams) child.getLayoutParams()).mDecorInsets.top;
+        }
+
+        /**
+         * Returns the total height of item decorations applied to child's bottom.
+         * <p>
+         * Note that this value is not updated until the View is measured or
+         * {@link #calculateItemDecorationsForChild(View, Rect)} is called.
+         *
+         * @param child Child to query
+         * @return The total height of item decorations applied to the child's bottom.
+         * @see #getDecoratedBottom(View)
+         * @see #calculateItemDecorationsForChild(View, Rect)
+         */
+        public int getBottomDecorationHeight(View child) {
+            return ((LayoutParams) child.getLayoutParams()).mDecorInsets.bottom;
+        }
+
+        /**
+         * Returns the total width of item decorations applied to child's left.
+         * <p>
+         * Note that this value is not updated until the View is measured or
+         * {@link #calculateItemDecorationsForChild(View, Rect)} is called.
+         *
+         * @param child Child to query
+         * @return The total width of item decorations applied to the child's left.
+         * @see #getDecoratedLeft(View)
+         * @see #calculateItemDecorationsForChild(View, Rect)
+         */
+        public int getLeftDecorationWidth(View child) {
+            return ((LayoutParams) child.getLayoutParams()).mDecorInsets.left;
+        }
+
+        /**
+         * Returns the total width of item decorations applied to child's right.
+         * <p>
+         * Note that this value is not updated until the View is measured or
+         * {@link #calculateItemDecorationsForChild(View, Rect)} is called.
+         *
+         * @param child Child to query
+         * @return The total width of item decorations applied to the child's right.
+         * @see #getDecoratedRight(View)
+         * @see #calculateItemDecorationsForChild(View, Rect)
+         */
+        public int getRightDecorationWidth(View child) {
+            return ((LayoutParams) child.getLayoutParams()).mDecorInsets.right;
+        }
+
+        /**
+         * Called when searching for a focusable view in the given direction has failed
+         * for the current content of the RecyclerView.
+         *
+         * <p>This is the LayoutManager's opportunity to populate views in the given direction
+         * to fulfill the request if it can. The LayoutManager should attach and return
+         * the view to be focused. The default implementation returns null.</p>
+         *
+         * @param focused   The currently focused view
+         * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+         *                  {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
+         *                  {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
+         *                  or 0 for not applicable
+         * @param recycler  The recycler to use for obtaining views for currently offscreen items
+         * @param state     Transient state of RecyclerView
+         * @return The chosen view to be focused
+         */
+        public View onFocusSearchFailed(View focused, int direction, Recycler recycler,
+                State state) {
+            return null;
+        }
+
+        /**
+         * This method gives a LayoutManager an opportunity to intercept the initial focus search
+         * before the default behavior of {@link FocusFinder} is used. If this method returns
+         * null FocusFinder will attempt to find a focusable child view. If it fails
+         * then {@link #onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)}
+         * will be called to give the LayoutManager an opportunity to add new views for items
+         * that did not have attached views representing them. The LayoutManager should not add
+         * or remove views from this method.
+         *
+         * @param focused The currently focused view
+         * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+         *                  {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
+         *                  {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
+         * @return A descendant view to focus or null to fall back to default behavior.
+         *         The default implementation returns null.
+         */
+        public View onInterceptFocusSearch(View focused, int direction) {
+            return null;
+        }
+
+        /**
+         * Called when a child of the RecyclerView wants a particular rectangle to be positioned
+         * onto the screen. See {@link ViewParent#requestChildRectangleOnScreen(android.view.View,
+         * android.graphics.Rect, boolean)} for more details.
+         *
+         * <p>The base implementation will attempt to perform a standard programmatic scroll
+         * to bring the given rect into view, within the padded area of the RecyclerView.</p>
+         *
+         * @param child The direct child making the request.
+         * @param rect  The rectangle in the child's coordinates the child
+         *              wishes to be on the screen.
+         * @param immediate True to forbid animated or delayed scrolling,
+         *                  false otherwise
+         * @return Whether the group scrolled to handle the operation
+         */
+        public boolean requestChildRectangleOnScreen(RecyclerView parent, View child, Rect rect,
+                boolean immediate) {
+            final int parentLeft = getPaddingLeft();
+            final int parentTop = getPaddingTop();
+            final int parentRight = getWidth() - getPaddingRight();
+            final int parentBottom = getHeight() - getPaddingBottom();
+            final int childLeft = child.getLeft() + rect.left;
+            final int childTop = child.getTop() + rect.top;
+            final int childRight = childLeft + rect.right;
+            final int childBottom = childTop + rect.bottom;
+
+            final int offScreenLeft = Math.min(0, childLeft - parentLeft);
+            final int offScreenTop = Math.min(0, childTop - parentTop);
+            final int offScreenRight = Math.max(0, childRight - parentRight);
+            final int offScreenBottom = Math.max(0, childBottom - parentBottom);
+
+            // Favor the "start" layout direction over the end when bringing one side or the other
+            // of a large rect into view.
+            final int dx;
+            if (ViewCompat.getLayoutDirection(parent) == ViewCompat.LAYOUT_DIRECTION_RTL) {
+                dx = offScreenRight != 0 ? offScreenRight : offScreenLeft;
+            } else {
+                dx = offScreenLeft != 0 ? offScreenLeft : offScreenRight;
+            }
+
+            // Favor bringing the top into view over the bottom
+            final int dy = offScreenTop != 0 ? offScreenTop : offScreenBottom;
+
+            if (dx != 0 || dy != 0) {
+                if (immediate) {
+                    parent.scrollBy(dx, dy);
+                } else {
+                    parent.smoothScrollBy(dx, dy);
+                }
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * @deprecated Use {@link #onRequestChildFocus(RecyclerView, State, View, View)}
+         */
+        @Deprecated
+        public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) {
+            return false;
+        }
+
+        /**
+         * Called when a descendant view of the RecyclerView requests focus.
+         *
+         * <p>A LayoutManager wishing to keep focused views aligned in a specific
+         * portion of the view may implement that behavior in an override of this method.</p>
+         *
+         * <p>If the LayoutManager executes different behavior that should override the default
+         * behavior of scrolling the focused child on screen instead of running alongside it,
+         * this method should return true.</p>
+         *
+         * @param parent  The RecyclerView hosting this LayoutManager
+         * @param state   Current state of RecyclerView
+         * @param child   Direct child of the RecyclerView containing the newly focused view
+         * @param focused The newly focused view. This may be the same view as child or it may be
+         *                null
+         * @return true if the default scroll behavior should be suppressed
+         */
+        public boolean onRequestChildFocus(RecyclerView parent, State state, View child,
+                View focused) {
+            return onRequestChildFocus(parent, child, focused);
+        }
+
+        /**
+         * Called if the RecyclerView this LayoutManager is bound to has a different adapter set.
+         * The LayoutManager may use this opportunity to clear caches and configure state such
+         * that it can relayout appropriately with the new data and potentially new view types.
+         *
+         * <p>The default implementation removes all currently attached views.</p>
+         *
+         * @param oldAdapter The previous adapter instance. Will be null if there was previously no
+         *                   adapter.
+         * @param newAdapter The new adapter instance. Might be null if
+         *                   {@link #setAdapter(RecyclerView.Adapter)} is called with {@code null}.
+         */
+        public void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter) {
+        }
+
+        /**
+         * Called to populate focusable views within the RecyclerView.
+         *
+         * <p>The LayoutManager implementation should return <code>true</code> if the default
+         * behavior of {@link ViewGroup#addFocusables(java.util.ArrayList, int)} should be
+         * suppressed.</p>
+         *
+         * <p>The default implementation returns <code>false</code> to trigger RecyclerView
+         * to fall back to the default ViewGroup behavior.</p>
+         *
+         * @param recyclerView The RecyclerView hosting this LayoutManager
+         * @param views List of output views. This method should add valid focusable views
+         *              to this list.
+         * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+         *                  {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
+         *                  {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
+         * @param focusableMode The type of focusables to be added.
+         *
+         * @return true to suppress the default behavior, false to add default focusables after
+         *         this method returns.
+         *
+         * @see #FOCUSABLES_ALL
+         * @see #FOCUSABLES_TOUCH_MODE
+         */
+        public boolean onAddFocusables(RecyclerView recyclerView, ArrayList<View> views,
+                int direction, int focusableMode) {
+            return false;
+        }
+
+        /**
+         * Called when {@link Adapter#notifyDataSetChanged()} is triggered instead of giving
+         * detailed information on what has actually changed.
+         *
+         * @param recyclerView
+         */
+        public void onItemsChanged(RecyclerView recyclerView) {
+        }
+
+        /**
+         * Called when items have been added to the adapter. The LayoutManager may choose to
+         * requestLayout if the inserted items would require refreshing the currently visible set
+         * of child views. (e.g. currently empty space would be filled by appended items, etc.)
+         *
+         * @param recyclerView
+         * @param positionStart
+         * @param itemCount
+         */
+        public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
+        }
+
+        /**
+         * Called when items have been removed from the adapter.
+         *
+         * @param recyclerView
+         * @param positionStart
+         * @param itemCount
+         */
+        public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
+        }
+
+        /**
+         * Called when items have been changed in the adapter.
+         *
+         * @param recyclerView
+         * @param positionStart
+         * @param itemCount
+         */
+        public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
+        }
+
+        /**
+         * Called when an item is moved withing the adapter.
+         * <p>
+         * Note that, an item may also change position in response to another ADD/REMOVE/MOVE
+         * operation. This callback is only called if and only if {@link Adapter#notifyItemMoved}
+         * is called.
+         *
+         * @param recyclerView
+         * @param from
+         * @param to
+         * @param itemCount
+         */
+        public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
+
+        }
+
+
+        /**
+         * <p>Override this method if you want to support scroll bars.</p>
+         *
+         * <p>Read {@link RecyclerView#computeHorizontalScrollExtent()} for details.</p>
+         *
+         * <p>Default implementation returns 0.</p>
+         *
+         * @param state Current state of RecyclerView
+         * @return The horizontal extent of the scrollbar's thumb
+         * @see RecyclerView#computeHorizontalScrollExtent()
+         */
+        public int computeHorizontalScrollExtent(State state) {
+            return 0;
+        }
+
+        /**
+         * <p>Override this method if you want to support scroll bars.</p>
+         *
+         * <p>Read {@link RecyclerView#computeHorizontalScrollOffset()} for details.</p>
+         *
+         * <p>Default implementation returns 0.</p>
+         *
+         * @param state Current State of RecyclerView where you can find total item count
+         * @return The horizontal offset of the scrollbar's thumb
+         * @see RecyclerView#computeHorizontalScrollOffset()
+         */
+        public int computeHorizontalScrollOffset(State state) {
+            return 0;
+        }
+
+        /**
+         * <p>Override this method if you want to support scroll bars.</p>
+         *
+         * <p>Read {@link RecyclerView#computeHorizontalScrollRange()} for details.</p>
+         *
+         * <p>Default implementation returns 0.</p>
+         *
+         * @param state Current State of RecyclerView where you can find total item count
+         * @return The total horizontal range represented by the vertical scrollbar
+         * @see RecyclerView#computeHorizontalScrollRange()
+         */
+        public int computeHorizontalScrollRange(State state) {
+            return 0;
+        }
+
+        /**
+         * <p>Override this method if you want to support scroll bars.</p>
+         *
+         * <p>Read {@link RecyclerView#computeVerticalScrollExtent()} for details.</p>
+         *
+         * <p>Default implementation returns 0.</p>
+         *
+         * @param state Current state of RecyclerView
+         * @return The vertical extent of the scrollbar's thumb
+         * @see RecyclerView#computeVerticalScrollExtent()
+         */
+        public int computeVerticalScrollExtent(State state) {
+            return 0;
+        }
+
+        /**
+         * <p>Override this method if you want to support scroll bars.</p>
+         *
+         * <p>Read {@link RecyclerView#computeVerticalScrollOffset()} for details.</p>
+         *
+         * <p>Default implementation returns 0.</p>
+         *
+         * @param state Current State of RecyclerView where you can find total item count
+         * @return The vertical offset of the scrollbar's thumb
+         * @see RecyclerView#computeVerticalScrollOffset()
+         */
+        public int computeVerticalScrollOffset(State state) {
+            return 0;
+        }
+
+        /**
+         * <p>Override this method if you want to support scroll bars.</p>
+         *
+         * <p>Read {@link RecyclerView#computeVerticalScrollRange()} for details.</p>
+         *
+         * <p>Default implementation returns 0.</p>
+         *
+         * @param state Current State of RecyclerView where you can find total item count
+         * @return The total vertical range represented by the vertical scrollbar
+         * @see RecyclerView#computeVerticalScrollRange()
+         */
+        public int computeVerticalScrollRange(State state) {
+            return 0;
+        }
+
+        /**
+         * Measure the attached RecyclerView. Implementations must call
+         * {@link #setMeasuredDimension(int, int)} before returning.
+         *
+         * <p>The default implementation will handle EXACTLY measurements and respect
+         * the minimum width and height properties of the host RecyclerView if measured
+         * as UNSPECIFIED. AT_MOST measurements will be treated as EXACTLY and the RecyclerView
+         * will consume all available space.</p>
+         *
+         * @param recycler Recycler
+         * @param state Transient state of RecyclerView
+         * @param widthSpec Width {@link android.view.View.MeasureSpec}
+         * @param heightSpec Height {@link android.view.View.MeasureSpec}
+         */
+        public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
+            final int widthMode = MeasureSpec.getMode(widthSpec);
+            final int heightMode = MeasureSpec.getMode(heightSpec);
+            final int widthSize = MeasureSpec.getSize(widthSpec);
+            final int heightSize = MeasureSpec.getSize(heightSpec);
+
+            int width = 0;
+            int height = 0;
+
+            switch (widthMode) {
+                case MeasureSpec.EXACTLY:
+                case MeasureSpec.AT_MOST:
+                    width = widthSize;
+                    break;
+                case MeasureSpec.UNSPECIFIED:
+                default:
+                    width = getMinimumWidth();
+                    break;
+            }
+
+            switch (heightMode) {
+                case MeasureSpec.EXACTLY:
+                case MeasureSpec.AT_MOST:
+                    height = heightSize;
+                    break;
+                case MeasureSpec.UNSPECIFIED:
+                default:
+                    height = getMinimumHeight();
+                    break;
+            }
+
+            setMeasuredDimension(width, height);
+        }
+
+        /**
+         * {@link View#setMeasuredDimension(int, int) Set the measured dimensions} of the
+         * host RecyclerView.
+         *
+         * @param widthSize Measured width
+         * @param heightSize Measured height
+         */
+        public void setMeasuredDimension(int widthSize, int heightSize) {
+            mRecyclerView.setMeasuredDimension(widthSize, heightSize);
+        }
+
+        /**
+         * @return The host RecyclerView's {@link View#getMinimumWidth()}
+         */
+        public int getMinimumWidth() {
+            return ViewCompat.getMinimumWidth(mRecyclerView);
+        }
+
+        /**
+         * @return The host RecyclerView's {@link View#getMinimumHeight()}
+         */
+        public int getMinimumHeight() {
+            return ViewCompat.getMinimumHeight(mRecyclerView);
+        }
+        /**
+         * <p>Called when the LayoutManager should save its state. This is a good time to save your
+         * scroll position, configuration and anything else that may be required to restore the same
+         * layout state if the LayoutManager is recreated.</p>
+         * <p>RecyclerView does NOT verify if the LayoutManager has changed between state save and
+         * restore. This will let you share information between your LayoutManagers but it is also
+         * your responsibility to make sure they use the same parcelable class.</p>
+         *
+         * @return Necessary information for LayoutManager to be able to restore its state
+         */
+        public Parcelable onSaveInstanceState() {
+            return null;
+        }
+
+
+        public void onRestoreInstanceState(Parcelable state) {
+
+        }
+
+        void stopSmoothScroller() {
+            if (mSmoothScroller != null) {
+                mSmoothScroller.stop();
+            }
+        }
+
+        private void onSmoothScrollerStopped(SmoothScroller smoothScroller) {
+            if (mSmoothScroller == smoothScroller) {
+                mSmoothScroller = null;
+            }
+        }
+
+        /**
+         * RecyclerView calls this method to notify LayoutManager that scroll state has changed.
+         *
+         * @param state The new scroll state for RecyclerView
+         */
+        public void onScrollStateChanged(int state) {
+        }
+
+        /**
+         * Removes all views and recycles them using the given recycler.
+         * <p>
+         * If you want to clean cached views as well, you should call {@link Recycler#clear()} too.
+         * <p>
+         * If a View is marked as "ignored", it is not removed nor recycled.
+         *
+         * @param recycler Recycler to use to recycle children
+         * @see #removeAndRecycleView(View, Recycler)
+         * @see #removeAndRecycleViewAt(int, Recycler)
+         * @see #ignoreView(View)
+         */
+        public void removeAndRecycleAllViews(Recycler recycler) {
+            for (int i = getChildCount() - 1; i >= 0; i--) {
+                final View view = getChildAt(i);
+                if (!getChildViewHolderInt(view).shouldIgnore()) {
+                    removeAndRecycleViewAt(i, recycler);
+                }
+            }
+        }
+
+        /**
+         * A LayoutManager can call this method to force RecyclerView to run simple animations in
+         * the next layout pass, even if there is not any trigger to do so. (e.g. adapter data
+         * change).
+         * <p>
+         * Note that, calling this method will not guarantee that RecyclerView will run animations
+         * at all. For example, if there is not any {@link ItemAnimator} set, RecyclerView will
+         * not run any animations but will still clear this flag after the layout is complete.
+         *
+         */
+        public void requestSimpleAnimationsInNextLayout() {
+            mRequestedSimpleAnimations = true;
+        }
+    }
+
+    private void removeFromDisappearingList(View child) {
+        mDisappearingViewsInLayoutPass.remove(child);
+    }
+
+    private void addToDisappearingList(View child) {
+        if (!mDisappearingViewsInLayoutPass.contains(child)) {
+            mDisappearingViewsInLayoutPass.add(child);
+        }
+    }
+
+    /**
+     * An ItemDecoration allows the application to add a special drawing and layout offset
+     * to specific item views from the adapter's data set. This can be useful for drawing dividers
+     * between items, highlights, visual grouping boundaries and more.
+     *
+     * <p>All ItemDecorations are drawn in the order they were added, before the item
+     * views (in {@link ItemDecoration#onDraw(Canvas, RecyclerView, RecyclerView.State) onDraw()}
+     * and after the items (in {@link ItemDecoration#onDrawOver(Canvas, RecyclerView,
+     * RecyclerView.State)}.</p>
+     */
+    public static abstract class ItemDecoration {
+        /**
+         * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
+         * Any content drawn by this method will be drawn before the item views are drawn,
+         * and will thus appear underneath the views.
+         *
+         * @param c Canvas to draw into
+         * @param parent RecyclerView this ItemDecoration is drawing into
+         * @param state The current state of RecyclerView
+         */
+        public void onDraw(Canvas c, RecyclerView parent, State state) {
+            onDraw(c, parent);
+        }
+
+        /**
+         * @deprecated
+         * Override {@link #onDraw(Canvas, RecyclerView, RecyclerView.State)}
+         */
+        @Deprecated
+        public void onDraw(Canvas c, RecyclerView parent) {
+        }
+
+        /**
+         * Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
+         * Any content drawn by this method will be drawn after the item views are drawn
+         * and will thus appear over the views.
+         *
+         * @param c Canvas to draw into
+         * @param parent RecyclerView this ItemDecoration is drawing into
+         * @param state The current state of RecyclerView.
+         */
+        public void onDrawOver(Canvas c, RecyclerView parent, State state) {
+            onDrawOver(c, parent);
+        }
+
+        /**
+         * @deprecated
+         * Override {@link #onDrawOver(Canvas, RecyclerView, RecyclerView.State)}
+         */
+        @Deprecated
+        public void onDrawOver(Canvas c, RecyclerView parent) {
+        }
+
+
+        /**
+         * @deprecated
+         * Use {@link #getItemOffsets(Rect, View, RecyclerView, State)}
+         */
+        @Deprecated
+        public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
+            outRect.set(0, 0, 0, 0);
+        }
+
+        /**
+         * Retrieve any offsets for the given item. Each field of <code>outRect</code> specifies
+         * the number of pixels that the item view should be inset by, similar to padding or margin.
+         * The default implementation sets the bounds of outRect to 0 and returns.
+         *
+         * <p>If this ItemDecoration does not affect the positioning of item views it should set
+         * all four fields of <code>outRect</code> (left, top, right, bottom) to zero
+         * before returning.</p>
+         *
+         * @param outRect Rect to receive the output.
+         * @param view    The child view to decorate
+         * @param parent  RecyclerView this ItemDecoration is decorating
+         * @param state   The current state of RecyclerView.
+         */
+        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
+            getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewPosition(),
+                    parent);
+        }
+    }
+
+    /**
+     * An OnItemTouchListener allows the application to intercept touch events in progress at the
+     * view hierarchy level of the RecyclerView before those touch events are considered for
+     * RecyclerView's own scrolling behavior.
+     *
+     * <p>This can be useful for applications that wish to implement various forms of gestural
+     * manipulation of item views within the RecyclerView. OnItemTouchListeners may intercept
+     * a touch interaction already in progress even if the RecyclerView is already handling that
+     * gesture stream itself for the purposes of scrolling.</p>
+     */
+    public interface OnItemTouchListener {
+        /**
+         * Silently observe and/or take over touch events sent to the RecyclerView
+         * before they are handled by either the RecyclerView itself or its child views.
+         *
+         * <p>The onInterceptTouchEvent methods of each attached OnItemTouchListener will be run
+         * in the order in which each listener was added, before any other touch processing
+         * by the RecyclerView itself or child views occurs.</p>
+         *
+         * @param e MotionEvent describing the touch event. All coordinates are in
+         *          the RecyclerView's coordinate system.
+         * @return true if this OnItemTouchListener wishes to begin intercepting touch events, false
+         *         to continue with the current behavior and continue observing future events in
+         *         the gesture.
+         */
+        public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e);
+
+        /**
+         * Process a touch event as part of a gesture that was claimed by returning true from
+         * a previous call to {@link #onInterceptTouchEvent}.
+         *
+         * @param e MotionEvent describing the touch event. All coordinates are in
+         *          the RecyclerView's coordinate system.
+         */
+        public void onTouchEvent(RecyclerView rv, MotionEvent e);
+    }
+
+    /**
+     * An OnScrollListener can be set on a RecyclerView to receive messages
+     * when a scrolling event has occurred on that RecyclerView.
+     *
+     * @see RecyclerView#setOnScrollListener(OnScrollListener)
+     */
+    abstract static public class OnScrollListener {
+        /**
+         * Callback method to be invoked when RecyclerView's scroll state changes.
+         *
+         * @param recyclerView The RecyclerView whose scroll state has changed.
+         * @param newState     The updated scroll state. One of {@link #SCROLL_STATE_IDLE},
+         *                     {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}.
+         */
+        public void onScrollStateChanged(RecyclerView recyclerView, int newState){}
+
+        /**
+         * Callback method to be invoked when the RecyclerView has been scrolled. This will be
+         * called after the scroll has completed.
+         *
+         * @param recyclerView The RecyclerView which scrolled.
+         * @param dx The amount of horizontal scroll.
+         * @param dy The amount of vertical scroll.
+         */
+        public void onScrolled(RecyclerView recyclerView, int dx, int dy){}
+    }
+
+    /**
+     * A RecyclerListener can be set on a RecyclerView to receive messages whenever
+     * a view is recycled.
+     *
+     * @see RecyclerView#setRecyclerListener(RecyclerListener)
+     */
+    public interface RecyclerListener {
+
+        /**
+         * This method is called whenever the view in the ViewHolder is recycled.
+         *
+         * @param holder The ViewHolder containing the view that was recycled
+         */
+        public void onViewRecycled(ViewHolder holder);
+    }
+
+    /**
+     * A ViewHolder describes an item view and metadata about its place within the RecyclerView.
+     *
+     * <p>{@link Adapter} implementations should subclass ViewHolder and add fields for caching
+     * potentially expensive {@link View#findViewById(int)} results.</p>
+     *
+     * <p>While {@link LayoutParams} belong to the {@link LayoutManager},
+     * {@link ViewHolder ViewHolders} belong to the adapter. Adapters should feel free to use
+     * their own custom ViewHolder implementations to store data that makes binding view contents
+     * easier. Implementations should assume that individual item views will hold strong references
+     * to <code>ViewHolder</code> objects and that <code>RecyclerView</code> instances may hold
+     * strong references to extra off-screen item views for caching purposes</p>
+     */
+    public static abstract class ViewHolder {
+        public final View itemView;
+        int mPosition = NO_POSITION;
+        int mOldPosition = NO_POSITION;
+        long mItemId = NO_ID;
+        int mItemViewType = INVALID_TYPE;
+        int mPreLayoutPosition = NO_POSITION;
+
+        // The item that this holder is shadowing during an item change event/animation
+        ViewHolder mShadowedHolder = null;
+        // The item that is shadowing this holder during an item change event/animation
+        ViewHolder mShadowingHolder = null;
+
+        /**
+         * This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType
+         * are all valid.
+         */
+        static final int FLAG_BOUND = 1 << 0;
+
+        /**
+         * The data this ViewHolder's view reflects is stale and needs to be rebound
+         * by the adapter. mPosition and mItemId are consistent.
+         */
+        static final int FLAG_UPDATE = 1 << 1;
+
+        /**
+         * This ViewHolder's data is invalid. The identity implied by mPosition and mItemId
+         * are not to be trusted and may no longer match the item view type.
+         * This ViewHolder must be fully rebound to different data.
+         */
+        static final int FLAG_INVALID = 1 << 2;
+
+        /**
+         * This ViewHolder points at data that represents an item previously removed from the
+         * data set. Its view may still be used for things like outgoing animations.
+         */
+        static final int FLAG_REMOVED = 1 << 3;
+
+        /**
+         * This ViewHolder should not be recycled. This flag is set via setIsRecyclable()
+         * and is intended to keep views around during animations.
+         */
+        static final int FLAG_NOT_RECYCLABLE = 1 << 4;
+
+        /**
+         * This ViewHolder is returned from scrap which means we are expecting an addView call
+         * for this itemView. When returned from scrap, ViewHolder stays in the scrap list until
+         * the end of the layout pass and then recycled by RecyclerView if it is not added back to
+         * the RecyclerView.
+         */
+        static final int FLAG_RETURNED_FROM_SCRAP = 1 << 5;
+
+        /**
+         * This ViewHolder's contents have changed. This flag is used as an indication that
+         * change animations may be used, if supported by the ItemAnimator.
+         */
+        static final int FLAG_CHANGED = 1 << 6;
+
+        /**
+         * This ViewHolder is fully managed by the LayoutManager. We do not scrap, recycle or remove
+         * it unless LayoutManager is replaced.
+         * It is still fully visible to the LayoutManager.
+         */
+        static final int FLAG_IGNORE = 1 << 7;
+
+        private int mFlags;
+
+        private int mIsRecyclableCount = 0;
+
+        // If non-null, view is currently considered scrap and may be reused for other data by the
+        // scrap container.
+        private Recycler mScrapContainer = null;
+
+        public ViewHolder(View itemView) {
+            if (itemView == null) {
+                throw new IllegalArgumentException("itemView may not be null");
+            }
+            this.itemView = itemView;
+        }
+
+        void offsetPosition(int offset, boolean applyToPreLayout) {
+            if (mOldPosition == NO_POSITION) {
+                mOldPosition = mPosition;
+            }
+            if (mPreLayoutPosition == NO_POSITION) {
+                mPreLayoutPosition = mPosition;
+            }
+            if (applyToPreLayout) {
+                mPreLayoutPosition += offset;
+            }
+            mPosition += offset;
+            if (itemView.getLayoutParams() != null) {
+                ((LayoutParams) itemView.getLayoutParams()).mInsetsDirty = true;
+            }
+        }
+
+        void clearOldPosition() {
+            mOldPosition = NO_POSITION;
+            mPreLayoutPosition = NO_POSITION;
+        }
+
+        void saveOldPosition() {
+            if (mOldPosition == NO_POSITION) {
+                mOldPosition = mPosition;
+            }
+        }
+
+        boolean shouldIgnore() {
+            return (mFlags & FLAG_IGNORE) != 0;
+        }
+
+        public final int getPosition() {
+            return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
+        }
+
+        /**
+         * When LayoutManager supports animations, RecyclerView tracks 3 positions for ViewHolders
+         * to perform animations.
+         * <p>
+         * If a ViewHolder was laid out in the previous onLayout call, old position will keep its
+         * adapter index in the previous layout.
+         *
+         * @return The previous adapter index of the Item represented by this ViewHolder or
+         * {@link #NO_POSITION} if old position does not exists or cleared (pre-layout is
+         * complete).
+         */
+        public final int getOldPosition() {
+            return mOldPosition;
+        }
+
+        /**
+         * Returns The itemId represented by this ViewHolder.
+         *
+         * @return The the item's id if adapter has stable ids, {@link RecyclerView#NO_ID}
+         * otherwise
+         */
+        public final long getItemId() {
+            return mItemId;
+        }
+
+        /**
+         * @return The view type of this ViewHolder.
+         */
+        public final int getItemViewType() {
+            return mItemViewType;
+        }
+
+        boolean isScrap() {
+            return mScrapContainer != null;
+        }
+
+        void unScrap() {
+            mScrapContainer.unscrapView(this);
+        }
+
+        boolean wasReturnedFromScrap() {
+            return (mFlags & FLAG_RETURNED_FROM_SCRAP) != 0;
+        }
+
+        void clearReturnedFromScrapFlag() {
+            mFlags = mFlags & ~FLAG_RETURNED_FROM_SCRAP;
+        }
+
+        void stopIgnoring() {
+            mFlags = mFlags & ~FLAG_IGNORE;
+        }
+
+        void setScrapContainer(Recycler recycler) {
+            mScrapContainer = recycler;
+        }
+
+        boolean isInvalid() {
+            return (mFlags & FLAG_INVALID) != 0;
+        }
+
+        boolean needsUpdate() {
+            return (mFlags & FLAG_UPDATE) != 0;
+        }
+
+        boolean isChanged() {
+            return (mFlags & FLAG_CHANGED) != 0;
+        }
+
+        boolean isBound() {
+            return (mFlags & FLAG_BOUND) != 0;
+        }
+
+        boolean isRemoved() {
+            return (mFlags & FLAG_REMOVED) != 0;
+        }
+
+        void setFlags(int flags, int mask) {
+            mFlags = (mFlags & ~mask) | (flags & mask);
+        }
+
+        void addFlags(int flags) {
+            mFlags |= flags;
+        }
+
+        void resetInternal() {
+            mFlags = 0;
+            mPosition = NO_POSITION;
+            mOldPosition = NO_POSITION;
+            mItemId = NO_ID;
+            mPreLayoutPosition = NO_POSITION;
+            mIsRecyclableCount = 0;
+            mShadowedHolder = null;
+            mShadowingHolder = null;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder("ViewHolder{" +
+                    Integer.toHexString(hashCode()) + " position=" + mPosition + " id=" + mItemId +
+                    ", oldPos=" + mOldPosition + ", pLpos:" + mPreLayoutPosition);
+            if (isScrap()) sb.append(" scrap");
+            if (isInvalid()) sb.append(" invalid");
+            if (!isBound()) sb.append(" unbound");
+            if (needsUpdate()) sb.append(" update");
+            if (isRemoved()) sb.append(" removed");
+            if (shouldIgnore()) sb.append(" ignored");
+            if (isChanged()) sb.append(" changed");
+            if (!isRecyclable()) sb.append(" not recyclable(" + mIsRecyclableCount + ")");
+            if (itemView.getParent() == null) sb.append(" no parent");
+            sb.append("}");
+            return sb.toString();
+        }
+
+        /**
+         * Informs the recycler whether this item can be recycled. Views which are not
+         * recyclable will not be reused for other items until setIsRecyclable() is
+         * later set to true. Calls to setIsRecyclable() should always be paired (one
+         * call to setIsRecyclabe(false) should always be matched with a later call to
+         * setIsRecyclable(true)). Pairs of calls may be nested, as the state is internally
+         * reference-counted.
+         *
+         * @param recyclable Whether this item is available to be recycled. Default value
+         * is true.
+         */
+        public final void setIsRecyclable(boolean recyclable) {
+            mIsRecyclableCount = recyclable ? mIsRecyclableCount - 1 : mIsRecyclableCount + 1;
+            if (mIsRecyclableCount < 0) {
+                mIsRecyclableCount = 0;
+                if (DEBUG) {
+                    throw new RuntimeException("isRecyclable decremented below 0: " +
+                            "unmatched pair of setIsRecyable() calls for " + this);
+                }
+                Log.e(VIEW_LOG_TAG, "isRecyclable decremented below 0: " +
+                        "unmatched pair of setIsRecyable() calls for " + this);
+            } else if (!recyclable && mIsRecyclableCount == 1) {
+                mFlags |= FLAG_NOT_RECYCLABLE;
+            } else if (recyclable && mIsRecyclableCount == 0) {
+                mFlags &= ~FLAG_NOT_RECYCLABLE;
+            }
+            if (DEBUG) {
+                Log.d(TAG, "setIsRecyclable val:" + recyclable + ":" + this);
+            }
+        }
+
+        /**
+         * @see {@link #setIsRecyclable(boolean)}
+         *
+         * @return true if this item is available to be recycled, false otherwise.
+         */
+        public final boolean isRecyclable() {
+            return (mFlags & FLAG_NOT_RECYCLABLE) == 0 &&
+                    !ViewCompat.hasTransientState(itemView);
+        }
+    }
+
+    /**
+     * {@link android.view.ViewGroup.MarginLayoutParams LayoutParams} subclass for children of
+     * {@link RecyclerView}. Custom {@link LayoutManager layout managers} are encouraged
+     * to create their own subclass of this <code>LayoutParams</code> class
+     * to store any additional required per-child view metadata about the layout.
+     */
+    public static class LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+        ViewHolder mViewHolder;
+        final Rect mDecorInsets = new Rect();
+        boolean mInsetsDirty = true;
+
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+        }
+
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        public LayoutParams(MarginLayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(ViewGroup.LayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(LayoutParams source) {
+            super((ViewGroup.LayoutParams) source);
+        }
+
+        /**
+         * Returns true if the view this LayoutParams is attached to needs to have its content
+         * updated from the corresponding adapter.
+         *
+         * @return true if the view should have its content updated
+         */
+        public boolean viewNeedsUpdate() {
+            return mViewHolder.needsUpdate();
+        }
+
+        /**
+         * Returns true if the view this LayoutParams is attached to is now representing
+         * potentially invalid data. A LayoutManager should scrap/recycle it.
+         *
+         * @return true if the view is invalid
+         */
+        public boolean isViewInvalid() {
+            return mViewHolder.isInvalid();
+        }
+
+        /**
+         * Returns true if the adapter data item corresponding to the view this LayoutParams
+         * is attached to has been removed from the data set. A LayoutManager may choose to
+         * treat it differently in order to animate its outgoing or disappearing state.
+         *
+         * @return true if the item the view corresponds to was removed from the data set
+         */
+        public boolean isItemRemoved() {
+            return mViewHolder.isRemoved();
+        }
+
+        /**
+         * Returns true if the adapter data item corresponding to the view this LayoutParams
+         * is attached to has been changed in the data set. A LayoutManager may choose to
+         * treat it differently in order to animate its changing state.
+         *
+         * @return true if the item the view corresponds to was changed in the data set
+         */
+        public boolean isItemChanged() {
+            return mViewHolder.isChanged();
+        }
+
+        /**
+         * Returns the position that the view this LayoutParams is attached to corresponds to.
+         *
+         * @return the adapter position this view was bound from
+         */
+        public int getViewPosition() {
+            return mViewHolder.getPosition();
+        }
+    }
+
+    /**
+     * Observer base class for watching changes to an {@link Adapter}.
+     * See {@link Adapter#registerAdapterDataObserver(AdapterDataObserver)}.
+     */
+    public static abstract class AdapterDataObserver {
+        public void onChanged() {
+            // Do nothing
+        }
+
+        public void onItemRangeChanged(int positionStart, int itemCount) {
+            // do nothing
+        }
+
+        public void onItemRangeInserted(int positionStart, int itemCount) {
+            // do nothing
+        }
+
+        public void onItemRangeRemoved(int positionStart, int itemCount) {
+            // do nothing
+        }
+
+        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+            // do nothing
+        }
+    }
+
+    /**
+     * <p>Base class for smooth scrolling. Handles basic tracking of the target view position and
+     * provides methods to trigger a programmatic scroll.</p>
+     *
+     * @see LinearSmoothScroller
+     */
+    public static abstract class SmoothScroller {
+
+        private int mTargetPosition = RecyclerView.NO_POSITION;
+
+        private RecyclerView mRecyclerView;
+
+        private LayoutManager mLayoutManager;
+
+        private boolean mPendingInitialRun;
+
+        private boolean mRunning;
+
+        private View mTargetView;
+
+        private final Action mRecyclingAction;
+
+        public SmoothScroller() {
+            mRecyclingAction = new Action(0, 0);
+        }
+
+        /**
+         * Starts a smooth scroll for the given target position.
+         * <p>In each animation step, {@link RecyclerView} will check
+         * for the target view and call either
+         * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or
+         * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)} until
+         * SmoothScroller is stopped.</p>
+         *
+         * <p>Note that if RecyclerView finds the target view, it will automatically stop the
+         * SmoothScroller. This <b>does not</b> mean that scroll will stop, it only means it will
+         * stop calling SmoothScroller in each animation step.</p>
+         */
+        void start(RecyclerView recyclerView, LayoutManager layoutManager) {
+            mRecyclerView = recyclerView;
+            mLayoutManager = layoutManager;
+            if (mTargetPosition == RecyclerView.NO_POSITION) {
+                throw new IllegalArgumentException("Invalid target position");
+            }
+            mRecyclerView.mState.mTargetPosition = mTargetPosition;
+            mRunning = true;
+            mPendingInitialRun = true;
+            mTargetView = findViewByPosition(getTargetPosition());
+            onStart();
+            mRecyclerView.mViewFlinger.postOnAnimation();
+        }
+
+        public void setTargetPosition(int targetPosition) {
+            mTargetPosition = targetPosition;
+        }
+
+        /**
+         * @return The LayoutManager to which this SmoothScroller is attached
+         */
+        public LayoutManager getLayoutManager() {
+            return mLayoutManager;
+        }
+
+        /**
+         * Stops running the SmoothScroller in each animation callback. Note that this does not
+         * cancel any existing {@link Action} updated by
+         * {@link #onTargetFound(android.view.View, RecyclerView.State, SmoothScroller.Action)} or
+         * {@link #onSeekTargetStep(int, int, RecyclerView.State, SmoothScroller.Action)}.
+         */
+        final protected void stop() {
+            if (!mRunning) {
+                return;
+            }
+            onStop();
+            mRecyclerView.mState.mTargetPosition = RecyclerView.NO_POSITION;
+            mTargetView = null;
+            mTargetPosition = RecyclerView.NO_POSITION;
+            mPendingInitialRun = false;
+            mRunning = false;
+            // trigger a cleanup
+            mLayoutManager.onSmoothScrollerStopped(this);
+            // clear references to avoid any potential leak by a custom smooth scroller
+            mLayoutManager = null;
+            mRecyclerView = null;
+        }
+
+        /**
+         * Returns true if SmoothScroller has been started but has not received the first
+         * animation
+         * callback yet.
+         *
+         * @return True if this SmoothScroller is waiting to start
+         */
+        public boolean isPendingInitialRun() {
+            return mPendingInitialRun;
+        }
+
+
+        /**
+         * @return True if SmoothScroller is currently active
+         */
+        public boolean isRunning() {
+            return mRunning;
+        }
+
+        /**
+         * Returns the adapter position of the target item
+         *
+         * @return Adapter position of the target item or
+         * {@link RecyclerView#NO_POSITION} if no target view is set.
+         */
+        public int getTargetPosition() {
+            return mTargetPosition;
+        }
+
+        private void onAnimation(int dx, int dy) {
+            if (!mRunning || mTargetPosition == RecyclerView.NO_POSITION) {
+                stop();
+            }
+            mPendingInitialRun = false;
+            if (mTargetView != null) {
+                // verify target position
+                if (getChildPosition(mTargetView) == mTargetPosition) {
+                    onTargetFound(mTargetView, mRecyclerView.mState, mRecyclingAction);
+                    mRecyclingAction.runIfNecessary(mRecyclerView);
+                    stop();
+                } else {
+                    Log.e(TAG, "Passed over target position while smooth scrolling.");
+                    mTargetView = null;
+                }
+            }
+            if (mRunning) {
+                onSeekTargetStep(dx, dy, mRecyclerView.mState, mRecyclingAction);
+                mRecyclingAction.runIfNecessary(mRecyclerView);
+            }
+        }
+
+        /**
+         * @see RecyclerView#getChildPosition(android.view.View)
+         */
+        public int getChildPosition(View view) {
+            return mRecyclerView.getChildPosition(view);
+        }
+
+        /**
+         * @see RecyclerView.LayoutManager#getChildCount()
+         */
+        public int getChildCount() {
+            return mRecyclerView.mLayout.getChildCount();
+        }
+
+        /**
+         * @see RecyclerView.LayoutManager#findViewByPosition(int)
+         */
+        public View findViewByPosition(int position) {
+            return mRecyclerView.mLayout.findViewByPosition(position);
+        }
+
+        /**
+         * @see RecyclerView#scrollToPosition(int)
+         */
+        public void instantScrollToPosition(int position) {
+            mRecyclerView.scrollToPosition(position);
+        }
+
+        protected void onChildAttachedToWindow(View child) {
+            if (getChildPosition(child) == getTargetPosition()) {
+                mTargetView = child;
+                if (DEBUG) {
+                    Log.d(TAG, "smooth scroll target view has been attached");
+                }
+            }
+        }
+
+        /**
+         * Normalizes the vector.
+         * @param scrollVector The vector that points to the target scroll position
+         */
+        protected void normalize(PointF scrollVector) {
+            final double magnitute = Math.sqrt(scrollVector.x * scrollVector.x + scrollVector.y *
+                    scrollVector.y);
+            scrollVector.x /= magnitute;
+            scrollVector.y /= magnitute;
+        }
+
+        /**
+         * Called when smooth scroll is started. This might be a good time to do setup.
+         */
+        abstract protected void onStart();
+
+        /**
+         * Called when smooth scroller is stopped. This is a good place to cleanup your state etc.
+         * @see #stop()
+         */
+        abstract protected void onStop();
+
+        /**
+         * <p>RecyclerView will call this method each time it scrolls until it can find the target
+         * position in the layout.</p>
+         * <p>SmoothScroller should check dx, dy and if scroll should be changed, update the
+         * provided {@link Action} to define the next scroll.</p>
+         *
+         * @param dx        Last scroll amount horizontally
+         * @param dy        Last scroll amount verticaully
+         * @param state     Transient state of RecyclerView
+         * @param action    If you want to trigger a new smooth scroll and cancel the previous one,
+         *                  update this object.
+         */
+        abstract protected void onSeekTargetStep(int dx, int dy, State state, Action action);
+
+        /**
+         * Called when the target position is laid out. This is the last callback SmoothScroller
+         * will receive and it should update the provided {@link Action} to define the scroll
+         * details towards the target view.
+         * @param targetView    The view element which render the target position.
+         * @param state         Transient state of RecyclerView
+         * @param action        Action instance that you should update to define final scroll action
+         *                      towards the targetView
+         * @return An {@link Action} to finalize the smooth scrolling
+         */
+        abstract protected void onTargetFound(View targetView, State state, Action action);
+
+        /**
+         * Holds information about a smooth scroll request by a {@link SmoothScroller}.
+         */
+        public static class Action {
+
+            public static final int UNDEFINED_DURATION = Integer.MIN_VALUE;
+
+            private int mDx;
+
+            private int mDy;
+
+            private int mDuration;
+
+            private Interpolator mInterpolator;
+
+            private boolean changed = false;
+
+            // we track this variable to inform custom implementer if they are updating the action
+            // in every animation callback
+            private int consecutiveUpdates = 0;
+
+            /**
+             * @param dx Pixels to scroll horizontally
+             * @param dy Pixels to scroll vertically
+             */
+            public Action(int dx, int dy) {
+                this(dx, dy, UNDEFINED_DURATION, null);
+            }
+
+            /**
+             * @param dx       Pixels to scroll horizontally
+             * @param dy       Pixels to scroll vertically
+             * @param duration Duration of the animation in milliseconds
+             */
+            public Action(int dx, int dy, int duration) {
+                this(dx, dy, duration, null);
+            }
+
+            /**
+             * @param dx           Pixels to scroll horizontally
+             * @param dy           Pixels to scroll vertically
+             * @param duration     Duration of the animation in milliseconds
+             * @param interpolator Interpolator to be used when calculating scroll position in each
+             *                     animation step
+             */
+            public Action(int dx, int dy, int duration, Interpolator interpolator) {
+                mDx = dx;
+                mDy = dy;
+                mDuration = duration;
+                mInterpolator = interpolator;
+            }
+            private void runIfNecessary(RecyclerView recyclerView) {
+                if (changed) {
+                    validate();
+                    if (mInterpolator == null) {
+                        if (mDuration == UNDEFINED_DURATION) {
+                            recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy);
+                        } else {
+                            recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration);
+                        }
+                    } else {
+                        recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration, mInterpolator);
+                    }
+                    consecutiveUpdates ++;
+                    if (consecutiveUpdates > 10) {
+                        // A new action is being set in every animation step. This looks like a bad
+                        // implementation. Inform developer.
+                        Log.e(TAG, "Smooth Scroll action is being updated too frequently. Make sure"
+                                + " you are not changing it unless necessary");
+                    }
+                    changed = false;
+                } else {
+                    consecutiveUpdates = 0;
+                }
+            }
+
+            private void validate() {
+                if (mInterpolator != null && mDuration < 1) {
+                    throw new IllegalStateException("If you provide an interpolator, you must"
+                            + " set a positive duration");
+                } else if (mDuration < 1) {
+                    throw new IllegalStateException("Scroll duration must be a positive number");
+                }
+            }
+
+            public int getDx() {
+                return mDx;
+            }
+
+            public void setDx(int dx) {
+                changed = true;
+                mDx = dx;
+            }
+
+            public int getDy() {
+                return mDy;
+            }
+
+            public void setDy(int dy) {
+                changed = true;
+                mDy = dy;
+            }
+
+            public int getDuration() {
+                return mDuration;
+            }
+
+            public void setDuration(int duration) {
+                changed = true;
+                mDuration = duration;
+            }
+
+            public Interpolator getInterpolator() {
+                return mInterpolator;
+            }
+
+            /**
+             * Sets the interpolator to calculate scroll steps
+             * @param interpolator The interpolator to use. If you specify an interpolator, you must
+             *                     also set the duration.
+             * @see #setDuration(int)
+             */
+            public void setInterpolator(Interpolator interpolator) {
+                changed = true;
+                mInterpolator = interpolator;
+            }
+
+            /**
+             * Updates the action with given parameters.
+             * @param dx Pixels to scroll horizontally
+             * @param dy Pixels to scroll vertically
+             * @param duration Duration of the animation in milliseconds
+             * @param interpolator Interpolator to be used when calculating scroll position in each
+             *                     animation step
+             */
+            public void update(int dx, int dy, int duration, Interpolator interpolator) {
+                mDx = dx;
+                mDy = dy;
+                mDuration = duration;
+                mInterpolator = interpolator;
+                changed = true;
+            }
+        }
+    }
+
+    static class AdapterDataObservable extends Observable<AdapterDataObserver> {
+        public boolean hasObservers() {
+            return !mObservers.isEmpty();
+        }
+
+        public void notifyChanged() {
+            // since onChanged() is implemented by the app, it could do anything, including
+            // removing itself from {@link mObservers} - and that could cause problems if
+            // an iterator is used on the ArrayList {@link mObservers}.
+            // to avoid such problems, just march thru the list in the reverse order.
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onChanged();
+            }
+        }
+
+        public void notifyItemRangeChanged(int positionStart, int itemCount) {
+            // since onItemRangeChanged() is implemented by the app, it could do anything, including
+            // removing itself from {@link mObservers} - and that could cause problems if
+            // an iterator is used on the ArrayList {@link mObservers}.
+            // to avoid such problems, just march thru the list in the reverse order.
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onItemRangeChanged(positionStart, itemCount);
+            }
+        }
+
+        public void notifyItemRangeInserted(int positionStart, int itemCount) {
+            // since onItemRangeInserted() is implemented by the app, it could do anything,
+            // including removing itself from {@link mObservers} - and that could cause problems if
+            // an iterator is used on the ArrayList {@link mObservers}.
+            // to avoid such problems, just march thru the list in the reverse order.
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
+            }
+        }
+
+        public void notifyItemRangeRemoved(int positionStart, int itemCount) {
+            // since onItemRangeRemoved() is implemented by the app, it could do anything, including
+            // removing itself from {@link mObservers} - and that could cause problems if
+            // an iterator is used on the ArrayList {@link mObservers}.
+            // to avoid such problems, just march thru the list in the reverse order.
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
+            }
+        }
+
+        public void notifyItemMoved(int fromPosition, int toPosition) {
+            for (int i = mObservers.size() - 1; i >= 0; i--) {
+                mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1);
+            }
+        }
+    }
+
+    static class SavedState extends android.view.View.BaseSavedState {
+
+        Parcelable mLayoutState;
+
+        /**
+         * called by CREATOR
+         */
+        SavedState(Parcel in) {
+            super(in);
+            mLayoutState = in.readParcelable(LayoutManager.class.getClassLoader());
+        }
+
+        /**
+         * Called by onSaveInstanceState
+         */
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeParcelable(mLayoutState, 0);
+        }
+
+        private void copyFrom(SavedState other) {
+            mLayoutState = other.mLayoutState;
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            @Override
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            @Override
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+    /**
+     * <p>Contains useful information about the current RecyclerView state like target scroll
+     * position or view focus. State object can also keep arbitrary data, identified by resource
+     * ids.</p>
+     * <p>Often times, RecyclerView components will need to pass information between each other.
+     * To provide a well defined data bus between components, RecyclerView passes the same State
+     * object to component callbacks and these components can use it to exchange data.</p>
+     * <p>If you implement custom components, you can use State's put/get/remove methods to pass
+     * data between your components without needing to manage their lifecycles.</p>
+     */
+    public static class State {
+
+        private int mTargetPosition = RecyclerView.NO_POSITION;
+        ArrayMap<ViewHolder, ItemHolderInfo> mPreLayoutHolderMap =
+                new ArrayMap<ViewHolder, ItemHolderInfo>();
+        ArrayMap<ViewHolder, ItemHolderInfo> mPostLayoutHolderMap =
+                new ArrayMap<ViewHolder, ItemHolderInfo>();
+        // nullable
+        ArrayMap<Long, ViewHolder> mOldChangedHolders = new ArrayMap<Long, ViewHolder>();
+
+        private SparseArray<Object> mData;
+
+        /**
+         * Number of items adapter has.
+         */
+        private int mItemCount = 0;
+
+        /**
+         * Number of items adapter had in the previous layout.
+         */
+        private int mPreviousLayoutItemCount = 0;
+
+        /**
+         * Number of items that were NOT laid out but has been deleted from the adapter after the
+         * previous layout.
+         */
+        private int mDeletedInvisibleItemCountSincePreviousLayout = 0;
+
+        private boolean mStructureChanged = false;
+
+        private boolean mInPreLayout = false;
+
+        private boolean mRunSimpleAnimations = false;
+
+        private boolean mRunPredictiveAnimations = false;
+
+        State reset() {
+            mTargetPosition = RecyclerView.NO_POSITION;
+            if (mData != null) {
+                mData.clear();
+            }
+            mItemCount = 0;
+            mStructureChanged = false;
+            return this;
+        }
+
+        public boolean isPreLayout() {
+            return mInPreLayout;
+        }
+
+        /**
+         * Returns whether RecyclerView will run predictive animations in this layout pass
+         * or not.
+         *
+         * @return true if RecyclerView is calculating predictive animations to be run at the end
+         *         of the layout pass.
+         */
+        public boolean willRunPredictiveAnimations() {
+            return mRunPredictiveAnimations;
+        }
+
+        /**
+         * Returns whether RecyclerView will run simple animations in this layout pass
+         * or not.
+         *
+         * @return true if RecyclerView is calculating simple animations to be run at the end of
+         *         the layout pass.
+         */
+        public boolean willRunSimpleAnimations() {
+            return mRunSimpleAnimations;
+        }
+
+        /**
+         * Removes the mapping from the specified id, if there was any.
+         * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.* to
+         *                   preserve cross functionality and avoid conflicts.
+         */
+        public void remove(int resourceId) {
+            if (mData == null) {
+                return;
+            }
+            mData.remove(resourceId);
+        }
+
+        /**
+         * Gets the Object mapped from the specified id, or <code>null</code>
+         * if no such data exists.
+         *
+         * @param resourceId Id of the resource you want to remove. It is suggested to use R.id.*
+         *                   to
+         *                   preserve cross functionality and avoid conflicts.
+         */
+        public <T> T get(int resourceId) {
+            if (mData == null) {
+                return null;
+            }
+            return (T) mData.get(resourceId);
+        }
+
+        /**
+         * Adds a mapping from the specified id to the specified value, replacing the previous
+         * mapping from the specified key if there was one.
+         *
+         * @param resourceId Id of the resource you want to add. It is suggested to use R.id.* to
+         *                   preserve cross functionality and avoid conflicts.
+         * @param data       The data you want to associate with the resourceId.
+         */
+        public void put(int resourceId, Object data) {
+            if (mData == null) {
+                mData = new SparseArray<Object>();
+            }
+            mData.put(resourceId, data);
+        }
+
+        /**
+         * If scroll is triggered to make a certain item visible, this value will return the
+         * adapter index of that item.
+         * @return Adapter index of the target item or
+         * {@link RecyclerView#NO_POSITION} if there is no target
+         * position.
+         */
+        public int getTargetScrollPosition() {
+            return mTargetPosition;
+        }
+
+        /**
+         * Returns if current scroll has a target position.
+         * @return true if scroll is being triggered to make a certain position visible
+         * @see #getTargetScrollPosition()
+         */
+        public boolean hasTargetScrollPosition() {
+            return mTargetPosition != RecyclerView.NO_POSITION;
+        }
+
+        /**
+         * @return true if the structure of the data set has changed since the last call to
+         *         onLayoutChildren, false otherwise
+         */
+        public boolean didStructureChange() {
+            return mStructureChanged;
+        }
+
+        /**
+         * Returns the total number of items that can be laid out. Note that this number is not
+         * necessarily equal to the number of items in the adapter, so you should always use this
+         * number for your position calculations and never access the adapter directly.
+         * <p>
+         * RecyclerView listens for Adapter's notify events and calculates the effects of adapter
+         * data changes on existing Views. These calculations are used to decide which animations
+         * should be run.
+         * <p>
+         * To support predictive animations, RecyclerView may rewrite or reorder Adapter changes to
+         * present the correct state to LayoutManager in pre-layout pass.
+         * <p>
+         * For example, a newly added item is not included in pre-layout item count because
+         * pre-layout reflects the contents of the adapter before the item is added. Behind the
+         * scenes, RecyclerView offsets {@link Recycler#getViewForPosition(int)} calls such that
+         * LayoutManager does not know about the new item's existence in pre-layout. The item will
+         * be available in second layout pass and will be included in the item count. Similar
+         * adjustments are made for moved and removed items as well.
+         * <p>
+         * You can get the adapter's item count via {@link LayoutManager#getItemCount()} method.
+         *
+         * @return The number of items currently available
+         * @see LayoutManager#getItemCount()
+         */
+        public int getItemCount() {
+            return mInPreLayout ?
+                    (mPreviousLayoutItemCount - mDeletedInvisibleItemCountSincePreviousLayout) :
+                    mItemCount;
+        }
+
+        public void onViewRecycled(ViewHolder holder) {
+            mPreLayoutHolderMap.remove(holder);
+            mPostLayoutHolderMap.remove(holder);
+            if (mOldChangedHolders != null) {
+                removeFrom(mOldChangedHolders, holder);
+            }
+            // holder cannot be in new list.
+        }
+
+        public void onViewIgnored(ViewHolder holder) {
+            onViewRecycled(holder);
+        }
+
+        private void removeFrom(ArrayMap<Long, ViewHolder> holderMap, ViewHolder holder) {
+            for (int i = holderMap.size() - 1; i >= 0; i --) {
+                if (holder == holderMap.valueAt(i)) {
+                    holderMap.removeAt(i);
+                    return;
+                }
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "State{" +
+                    "mTargetPosition=" + mTargetPosition +
+                    ", mPreLayoutHolderMap=" + mPreLayoutHolderMap +
+                    ", mPostLayoutHolderMap=" + mPostLayoutHolderMap +
+                    ", mData=" + mData +
+                    ", mItemCount=" + mItemCount +
+                    ", mPreviousLayoutItemCount=" + mPreviousLayoutItemCount +
+                    ", mDeletedInvisibleItemCountSincePreviousLayout="
+                    + mDeletedInvisibleItemCountSincePreviousLayout +
+                    ", mStructureChanged=" + mStructureChanged +
+                    ", mInPreLayout=" + mInPreLayout +
+                    ", mRunSimpleAnimations=" + mRunSimpleAnimations +
+                    ", mRunPredictiveAnimations=" + mRunPredictiveAnimations +
+                    '}';
+        }
+    }
+
+    /**
+     * Internal listener that manages items after animations finish. This is how items are
+     * retained (not recycled) during animations, but allowed to be recycled afterwards.
+     * It depends on the contract with the ItemAnimator to call the appropriate dispatch*Finished()
+     * method on the animator's listener when it is done animating any item.
+     */
+    private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener {
+
+        @Override
+        public void onRemoveFinished(ViewHolder item) {
+            item.setIsRecyclable(true);
+            removeAnimatingView(item.itemView);
+            removeDetachedView(item.itemView, false);
+        }
+
+        @Override
+        public void onAddFinished(ViewHolder item) {
+            item.setIsRecyclable(true);
+            if (item.isRecyclable()) {
+                removeAnimatingView(item.itemView);
+            }
+        }
+
+        @Override
+        public void onMoveFinished(ViewHolder item) {
+            item.setIsRecyclable(true);
+            if (item.isRecyclable()) {
+                removeAnimatingView(item.itemView);
+            }
+        }
+
+        @Override
+        public void onChangeFinished(ViewHolder item) {
+            item.setIsRecyclable(true);
+            /**
+             * We check both shadowed and shadowing because a ViewHolder may get both roles at the
+             * same time.
+             *
+             * Assume this flow:
+             * item X is represented by VH_1. Then itemX changes, so we create VH_2 .
+             * RV sets the following and calls item animator:
+             * VH_1.shadowed = VH_2;
+             * VH_1.mChanged = true;
+             * VH_2.shadowing =VH_1;
+             *
+             * Then, before the first change finishes, item changes again so we create VH_3.
+             * RV sets the following and calls item animator:
+             * VH_2.shadowed = VH_3
+             * VH_2.mChanged = true
+             * VH_3.shadowing = VH_2
+             *
+             * Because VH_2 already has an animation, it will be cancelled. At this point VH_2 has
+             * both shadowing and shadowed fields set. Shadowing information is obsolete now
+             * because the first animation where VH_2 is newViewHolder is not valid anymore.
+             * We ended up in this case because VH_2 played both roles. On the other hand,
+             * we DO NOT want to clear its changed flag.
+             *
+             * If second change was simply reverting first change, we would find VH_1 in
+             * {@link Recycler#getScrapViewForPosition(int, int, boolean)} and recycle it before
+             * re-using
+             */
+            if (item.mShadowedHolder != null && item.mShadowingHolder == null) { // old vh
+                item.mShadowedHolder = null;
+                item.setFlags(~ViewHolder.FLAG_CHANGED, item.mFlags);
+            }
+            // always null this because an OldViewHolder can never become NewViewHolder w/o being
+            // recycled.
+            item.mShadowingHolder = null;
+            if (item.isRecyclable()) {
+                removeAnimatingView(item.itemView);
+            }
+        }
+    };
+
+    /**
+     * This class defines the animations that take place on items as changes are made
+     * to the adapter.
+     *
+     * Subclasses of ItemAnimator can be used to implement custom animations for actions on
+     * ViewHolder items. The RecyclerView will manage retaining these items while they
+     * are being animated, but implementors must call the appropriate "Starting"
+     * ({@link #dispatchRemoveStarting(ViewHolder)}, {@link #dispatchMoveStarting(ViewHolder)},
+     * {@link #dispatchChangeStarting(ViewHolder, boolean)}, or
+     * {@link #dispatchAddStarting(ViewHolder)})
+     * and "Finished" ({@link #dispatchRemoveFinished(ViewHolder)},
+     * {@link #dispatchMoveFinished(ViewHolder)},
+     * {@link #dispatchChangeFinished(ViewHolder, boolean)},
+     * or {@link #dispatchAddFinished(ViewHolder)}) methods when each item animation is
+     * being started and ended.
+     *
+     * <p>By default, RecyclerView uses {@link DefaultItemAnimator}</p>
+     *
+     * @see #setItemAnimator(ItemAnimator)
+     */
+    public static abstract class ItemAnimator {
+
+        private ItemAnimatorListener mListener = null;
+        private ArrayList<ItemAnimatorFinishedListener> mFinishedListeners =
+                new ArrayList<ItemAnimatorFinishedListener>();
+
+        private long mAddDuration = 120;
+        private long mRemoveDuration = 120;
+        private long mMoveDuration = 250;
+        private long mChangeDuration = 250;
+
+        private boolean mSupportsChangeAnimations = false;
+
+        /**
+         * Gets the current duration for which all move animations will run.
+         *
+         * @return The current move duration
+         */
+        public long getMoveDuration() {
+            return mMoveDuration;
+        }
+
+        /**
+         * Sets the duration for which all move animations will run.
+         *
+         * @param moveDuration The move duration
+         */
+        public void setMoveDuration(long moveDuration) {
+            mMoveDuration = moveDuration;
+        }
+
+        /**
+         * Gets the current duration for which all add animations will run.
+         *
+         * @return The current add duration
+         */
+        public long getAddDuration() {
+            return mAddDuration;
+        }
+
+        /**
+         * Sets the duration for which all add animations will run.
+         *
+         * @param addDuration The add duration
+         */
+        public void setAddDuration(long addDuration) {
+            mAddDuration = addDuration;
+        }
+
+        /**
+         * Gets the current duration for which all remove animations will run.
+         *
+         * @return The current remove duration
+         */
+        public long getRemoveDuration() {
+            return mRemoveDuration;
+        }
+
+        /**
+         * Sets the duration for which all remove animations will run.
+         *
+         * @param removeDuration The remove duration
+         */
+        public void setRemoveDuration(long removeDuration) {
+            mRemoveDuration = removeDuration;
+        }
+
+        /**
+         * Gets the current duration for which all change animations will run.
+         *
+         * @return The current change duration
+         */
+        public long getChangeDuration() {
+            return mChangeDuration;
+        }
+
+        /**
+         * Sets the duration for which all change animations will run.
+         *
+         * @param changeDuration The change duration
+         */
+        public void setChangeDuration(long changeDuration) {
+            mChangeDuration = changeDuration;
+        }
+
+        /**
+         * Returns whether this ItemAnimator supports animations of change events.
+         *
+         * @return true if change animations are supported, false otherwise
+         */
+        public boolean getSupportsChangeAnimations() {
+            return mSupportsChangeAnimations;
+        }
+
+        /**
+         * Sets whether this ItemAnimator supports animations of item change events.
+         * By default, ItemAnimator only supports animations when items are added or removed.
+         * By setting this property to true, actions on the data set which change the
+         * contents of items may also be animated. What those animations are is left
+         * up to the discretion of the ItemAnimator subclass, in its
+         * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} implementation.
+         * The value of this property is false by default.
+         *
+         * @see Adapter#notifyItemChanged(int)
+         * @see Adapter#notifyItemRangeChanged(int, int)
+         *
+         * @param supportsChangeAnimations true if change animations are supported by
+         * this ItemAnimator, false otherwise. If the property is false, the ItemAnimator
+         * will not receive a call to
+         * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} when changes occur.
+         */
+        public void setSupportsChangeAnimations(boolean supportsChangeAnimations) {
+            mSupportsChangeAnimations = supportsChangeAnimations;
+        }
+
+        /**
+         * Internal only:
+         * Sets the listener that must be called when the animator is finished
+         * animating the item (or immediately if no animation happens). This is set
+         * internally and is not intended to be set by external code.
+         *
+         * @param listener The listener that must be called.
+         */
+        void setListener(ItemAnimatorListener listener) {
+            mListener = listener;
+        }
+
+        /**
+         * Called when there are pending animations waiting to be started. This state
+         * is governed by the return values from {@link #animateAdd(ViewHolder) animateAdd()},
+         * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()}, and
+         * {@link #animateRemove(ViewHolder) animateRemove()}, which inform the
+         * RecyclerView that the ItemAnimator wants to be called later to start the
+         * associated animations. runPendingAnimations() will be scheduled to be run
+         * on the next frame.
+         */
+        abstract public void runPendingAnimations();
+
+        /**
+         * Called when an item is removed from the RecyclerView. Implementors can choose
+         * whether and how to animate that change, but must always call
+         * {@link #dispatchRemoveFinished(ViewHolder)} when done, either
+         * immediately (if no animation will occur) or after the animation actually finishes.
+         * The return value indicates whether an animation has been set up and whether the
+         * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
+         * next opportunity. This mechanism allows ItemAnimator to set up individual animations
+         * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
+         * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
+         * {@link #animateRemove(ViewHolder) animateRemove()}, and
+         * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
+         * then start the animations together in the later call to {@link #runPendingAnimations()}.
+         *
+         * <p>This method may also be called for disappearing items which continue to exist in the
+         * RecyclerView, but for which the system does not have enough information to animate
+         * them out of view. In that case, the default animation for removing items is run
+         * on those items as well.</p>
+         *
+         * @param holder The item that is being removed.
+         * @return true if a later call to {@link #runPendingAnimations()} is requested,
+         * false otherwise.
+         */
+        abstract public boolean animateRemove(ViewHolder holder);
+
+        /**
+         * Called when an item is added to the RecyclerView. Implementors can choose
+         * whether and how to animate that change, but must always call
+         * {@link #dispatchAddFinished(ViewHolder)} when done, either
+         * immediately (if no animation will occur) or after the animation actually finishes.
+         * The return value indicates whether an animation has been set up and whether the
+         * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
+         * next opportunity. This mechanism allows ItemAnimator to set up individual animations
+         * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
+         * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
+         * {@link #animateRemove(ViewHolder) animateRemove()}, and
+         * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
+         * then start the animations together in the later call to {@link #runPendingAnimations()}.
+         *
+         * <p>This method may also be called for appearing items which were already in the
+         * RecyclerView, but for which the system does not have enough information to animate
+         * them into view. In that case, the default animation for adding items is run
+         * on those items as well.</p>
+         *
+         * @param holder The item that is being added.
+         * @return true if a later call to {@link #runPendingAnimations()} is requested,
+         * false otherwise.
+         */
+        abstract public boolean animateAdd(ViewHolder holder);
+
+        /**
+         * Called when an item is moved in the RecyclerView. Implementors can choose
+         * whether and how to animate that change, but must always call
+         * {@link #dispatchMoveFinished(ViewHolder)} when done, either
+         * immediately (if no animation will occur) or after the animation actually finishes.
+         * The return value indicates whether an animation has been set up and whether the
+         * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
+         * next opportunity. This mechanism allows ItemAnimator to set up individual animations
+         * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
+         * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
+         * {@link #animateRemove(ViewHolder) animateRemove()}, and
+         * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
+         * then start the animations together in the later call to {@link #runPendingAnimations()}.
+         *
+         * @param holder The item that is being moved.
+         * @return true if a later call to {@link #runPendingAnimations()} is requested,
+         * false otherwise.
+         */
+        abstract public boolean animateMove(ViewHolder holder, int fromX, int fromY,
+                int toX, int toY);
+
+        /**
+         * Called when an item is changed in the RecyclerView, as indicated by a call to
+         * {@link Adapter#notifyItemChanged(int)} or
+         * {@link Adapter#notifyItemRangeChanged(int, int)}.
+         * <p>
+         * Implementers can choose whether and how to animate changes, but must always call
+         * {@link #dispatchChangeFinished(ViewHolder, boolean)} for each non-null ViewHolder,
+         * either immediately (if no animation will occur) or after the animation actually finishes.
+         * The return value indicates whether an animation has been set up and whether the
+         * ItemAnimator's {@link #runPendingAnimations()} method should be called at the
+         * next opportunity. This mechanism allows ItemAnimator to set up individual animations
+         * as separate calls to {@link #animateAdd(ViewHolder) animateAdd()},
+         * {@link #animateMove(ViewHolder, int, int, int, int) animateMove()},
+         * {@link #animateRemove(ViewHolder) animateRemove()}, and
+         * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)} come in one by one,
+         * then start the animations together in the later call to {@link #runPendingAnimations()}.
+         *
+         * @param oldHolder The original item that changed.
+         * @param newHolder The new item that was created with the changed content. Might be null
+         * @param fromLeft  Left of the old view holder
+         * @param fromTop   Top of the old view holder
+         * @param toLeft    Left of the new view holder
+         * @param toTop     Top of the new view holder
+         * @return true if a later call to {@link #runPendingAnimations()} is requested,
+         * false otherwise.
+         */
+        abstract public boolean animateChange(ViewHolder oldHolder,
+                ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop);
+
+
+        /**
+         * Method to be called by subclasses when a remove animation is done.
+         *
+         * @param item The item which has been removed
+         */
+        public final void dispatchRemoveFinished(ViewHolder item) {
+            onRemoveFinished(item);
+            if (mListener != null) {
+                mListener.onRemoveFinished(item);
+            }
+        }
+
+        /**
+         * Method to be called by subclasses when a move animation is done.
+         *
+         * @param item The item which has been moved
+         */
+        public final void dispatchMoveFinished(ViewHolder item) {
+            onMoveFinished(item);
+            if (mListener != null) {
+                mListener.onMoveFinished(item);
+            }
+        }
+
+        /**
+         * Method to be called by subclasses when an add animation is done.
+         *
+         * @param item The item which has been added
+         */
+        public final void dispatchAddFinished(ViewHolder item) {
+            onAddFinished(item);
+            if (mListener != null) {
+                mListener.onAddFinished(item);
+            }
+        }
+
+        /**
+         * Method to be called by subclasses when a change animation is done.
+         *
+         * @see #animateChange(ViewHolder, ViewHolder, int, int, int, int)
+         * @param item The item which has been changed (this method must be called for
+         * each non-null ViewHolder passed into
+         * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}).
+         * @param oldItem true if this is the old item that was changed, false if
+         * it is the new item that replaced the old item.
+         */
+        public final void dispatchChangeFinished(ViewHolder item, boolean oldItem) {
+            onChangeFinished(item, oldItem);
+            if (mListener != null) {
+                mListener.onChangeFinished(item);
+            }
+        }
+
+        /**
+         * Method to be called by subclasses when a remove animation is being started.
+         *
+         * @param item The item being removed
+         */
+        public final void dispatchRemoveStarting(ViewHolder item) {
+            onRemoveStarting(item);
+        }
+
+        /**
+         * Method to be called by subclasses when a move animation is being started.
+         *
+         * @param item The item being moved
+         */
+        public final void dispatchMoveStarting(ViewHolder item) {
+            onMoveStarting(item);
+        }
+
+        /**
+         * Method to be called by subclasses when an add animation is being started.
+         *
+         * @param item The item being added
+         */
+        public final void dispatchAddStarting(ViewHolder item) {
+            onAddStarting(item);
+        }
+
+        /**
+         * Method to be called by subclasses when a change animation is being started.
+         *
+         * @param item The item which has been changed (this method must be called for
+         * each non-null ViewHolder passed into
+         * {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}).
+         * @param oldItem true if this is the old item that was changed, false if
+         * it is the new item that replaced the old item.
+         */
+        public final void dispatchChangeStarting(ViewHolder item, boolean oldItem) {
+            onChangeStarting(item, oldItem);
+        }
+
+        /**
+         * Method called when an animation on a view should be ended immediately.
+         * This could happen when other events, like scrolling, occur, so that
+         * animating views can be quickly put into their proper end locations.
+         * Implementations should ensure that any animations running on the item
+         * are canceled and affected properties are set to their end values.
+         * Also, appropriate dispatch methods (e.g., {@link #dispatchAddFinished(ViewHolder)}
+         * should be called since the animations are effectively done when this
+         * method is called.
+         *
+         * @param item The item for which an animation should be stopped.
+         */
+        abstract public void endAnimation(ViewHolder item);
+
+        /**
+         * Method called when all item animations should be ended immediately.
+         * This could happen when other events, like scrolling, occur, so that
+         * animating views can be quickly put into their proper end locations.
+         * Implementations should ensure that any animations running on any items
+         * are canceled and affected properties are set to their end values.
+         * Also, appropriate dispatch methods (e.g., {@link #dispatchAddFinished(ViewHolder)}
+         * should be called since the animations are effectively done when this
+         * method is called.
+         */
+        abstract public void endAnimations();
+
+        /**
+         * Method which returns whether there are any item animations currently running.
+         * This method can be used to determine whether to delay other actions until
+         * animations end.
+         *
+         * @return true if there are any item animations currently running, false otherwise.
+         */
+        abstract public boolean isRunning();
+
+        /**
+         * Like {@link #isRunning()}, this method returns whether there are any item
+         * animations currently running. Addtionally, the listener passed in will be called
+         * when there are no item animations running, either immediately (before the method
+         * returns) if no animations are currently running, or when the currently running
+         * animations are {@link #dispatchAnimationsFinished() finished}.
+         *
+         * <p>Note that the listener is transient - it is either called immediately and not
+         * stored at all, or stored only until it is called when running animations
+         * are finished sometime later.</p>
+         *
+         * @param listener A listener to be called immediately if no animations are running
+         * or later when currently-running animations have finished. A null listener is
+         * equivalent to calling {@link #isRunning()}.
+         * @return true if there are any item animations currently running, false otherwise.
+         */
+        public final boolean isRunning(ItemAnimatorFinishedListener listener) {
+            boolean running = isRunning();
+            if (listener != null) {
+                if (!running) {
+                    listener.onAnimationsFinished();
+                } else {
+                    mFinishedListeners.add(listener);
+                }
+            }
+            return running;
+        }
+
+        /**
+         * The interface to be implemented by listeners to animation events from this
+         * ItemAnimator. This is used internally and is not intended for developers to
+         * create directly.
+         */
+        interface ItemAnimatorListener {
+            void onRemoveFinished(ViewHolder item);
+            void onAddFinished(ViewHolder item);
+            void onMoveFinished(ViewHolder item);
+            void onChangeFinished(ViewHolder item);
+        }
+
+        /**
+         * This method should be called by ItemAnimator implementations to notify
+         * any listeners that all pending and active item animations are finished.
+         */
+        public final void dispatchAnimationsFinished() {
+            final int count = mFinishedListeners.size();
+            for (int i = 0; i < count; ++i) {
+                mFinishedListeners.get(i).onAnimationsFinished();
+            }
+            mFinishedListeners.clear();
+        }
+
+        /**
+         * This interface is used to inform listeners when all pending or running animations
+         * in an ItemAnimator are finished. This can be used, for example, to delay an action
+         * in a data set until currently-running animations are complete.
+         *
+         * @see #isRunning(ItemAnimatorFinishedListener)
+         */
+        public interface ItemAnimatorFinishedListener {
+            void onAnimationsFinished();
+        }
+
+        /**
+         * Called when a remove animation is being started on the given ViewHolder.
+         * The default implementation does nothing. Subclasses may wish to override
+         * this method to handle any ViewHolder-specific operations linked to animation
+         * lifecycles.
+         *
+         * @param item The ViewHolder being animated.
+         */
+        public void onRemoveStarting(ViewHolder item) {}
+
+        /**
+         * Called when a remove animation has ended on the given ViewHolder.
+         * The default implementation does nothing. Subclasses may wish to override
+         * this method to handle any ViewHolder-specific operations linked to animation
+         * lifecycles.
+         *
+         * @param item The ViewHolder being animated.
+         */
+        public void onRemoveFinished(ViewHolder item) {}
+
+        /**
+         * Called when an add animation is being started on the given ViewHolder.
+         * The default implementation does nothing. Subclasses may wish to override
+         * this method to handle any ViewHolder-specific operations linked to animation
+         * lifecycles.
+         *
+         * @param item The ViewHolder being animated.
+         */
+        public void onAddStarting(ViewHolder item) {}
+
+        /**
+         * Called when an add animation has ended on the given ViewHolder.
+         * The default implementation does nothing. Subclasses may wish to override
+         * this method to handle any ViewHolder-specific operations linked to animation
+         * lifecycles.
+         *
+         * @param item The ViewHolder being animated.
+         */
+        public void onAddFinished(ViewHolder item) {}
+
+        /**
+         * Called when a move animation is being started on the given ViewHolder.
+         * The default implementation does nothing. Subclasses may wish to override
+         * this method to handle any ViewHolder-specific operations linked to animation
+         * lifecycles.
+         *
+         * @param item The ViewHolder being animated.
+         */
+        public void onMoveStarting(ViewHolder item) {}
+
+        /**
+         * Called when a move animation has ended on the given ViewHolder.
+         * The default implementation does nothing. Subclasses may wish to override
+         * this method to handle any ViewHolder-specific operations linked to animation
+         * lifecycles.
+         *
+         * @param item The ViewHolder being animated.
+         */
+        public void onMoveFinished(ViewHolder item) {}
+
+        /**
+         * Called when a change animation is being started on the given ViewHolder.
+         * The default implementation does nothing. Subclasses may wish to override
+         * this method to handle any ViewHolder-specific operations linked to animation
+         * lifecycles.
+         *
+         * @param item The ViewHolder being animated.
+         * @param oldItem true if this is the old item that was changed, false if
+         * it is the new item that replaced the old item.
+         */
+        public void onChangeStarting(ViewHolder item, boolean oldItem) {}
+
+        /**
+         * Called when a change animation has ended on the given ViewHolder.
+         * The default implementation does nothing. Subclasses may wish to override
+         * this method to handle any ViewHolder-specific operations linked to animation
+         * lifecycles.
+         *
+         * @param item The ViewHolder being animated.
+         * @param oldItem true if this is the old item that was changed, false if
+         * it is the new item that replaced the old item.
+         */
+        public void onChangeFinished(ViewHolder item, boolean oldItem) {}
+
+    }
+
+    /**
+     * Internal data structure that holds information about an item's bounds.
+     * This information is used in calculating item animations.
+     */
+    private static class ItemHolderInfo {
+        ViewHolder holder;
+        int left, top, right, bottom;
+
+        ItemHolderInfo(ViewHolder holder, int left, int top, int right, int bottom) {
+            this.holder = holder;
+            this.left = left;
+            this.top = top;
+            this.right = right;
+            this.bottom = bottom;
+        }
+    }
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/ScrollbarHelper.java b/v7/recyclerview/src/android/support/v7/widget/ScrollbarHelper.java
new file mode 100644
index 0000000..0903f64
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/widget/ScrollbarHelper.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.view.View;
+
+/**
+ * A helper class to do scroll offset calculations.
+ */
+class ScrollbarHelper {
+
+    /**
+     * @param startChild View closest to start of the list. (top or left)
+     * @param endChild   View closest to end of the list (bottom or right)
+     */
+    static int computeScrollOffset(RecyclerView.State state, OrientationHelper orientation,
+            View startChild, View endChild, RecyclerView.LayoutManager lm,
+            boolean smoothScrollbarEnabled, boolean reverseLayout) {
+        if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null ||
+                endChild == null) {
+            return 0;
+        }
+        final int minPosition = Math.min(lm.getPosition(startChild), lm.getPosition(endChild));
+        final int maxPosition = Math.max(lm.getPosition(startChild), lm.getPosition(endChild));
+        final int itemsBefore = reverseLayout
+                ? Math.max(0, state.getItemCount() - maxPosition - 1)
+                : Math.max(0, minPosition - 1);
+        if (!smoothScrollbarEnabled) {
+            return itemsBefore;
+        }
+        final int laidOutArea = Math.abs(orientation.getDecoratedEnd(endChild) -
+                orientation.getDecoratedStart(startChild));
+        final int itemRange = Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild)) + 1;
+        final float avgSizePerRow = (float) laidOutArea / itemRange;
+
+        return Math.round(itemsBefore * avgSizePerRow + (orientation.getStartAfterPadding()
+                - orientation.getDecoratedStart(startChild)));
+    }
+
+    /**
+     * @param startChild View closest to start of the list. (top or left)
+     * @param endChild   View closest to end of the list (bottom or right)
+     */
+    static int computeScrollExtent(RecyclerView.State state, OrientationHelper orientation,
+            View startChild, View endChild, RecyclerView.LayoutManager lm,
+            boolean smoothScrollbarEnabled) {
+        if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null ||
+                endChild == null) {
+            return 0;
+        }
+        if (!smoothScrollbarEnabled) {
+            return Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild)) + 1;
+        }
+        final int extend = orientation.getDecoratedEnd(endChild)
+                - orientation.getDecoratedStart(startChild);
+        return Math.min(orientation.getTotalSpace(), extend);
+    }
+
+    /**
+     * @param startChild View closest to start of the list. (top or left)
+     * @param endChild   View closest to end of the list (bottom or right)
+     */
+    static int computeScrollRange(RecyclerView.State state, OrientationHelper orientation,
+            View startChild, View endChild, RecyclerView.LayoutManager lm,
+            boolean smoothScrollbarEnabled) {
+        if (lm.getChildCount() == 0 || state.getItemCount() == 0 || startChild == null ||
+                endChild == null) {
+            return 0;
+        }
+        if (!smoothScrollbarEnabled) {
+            return state.getItemCount();
+        }
+        // smooth scrollbar enabled. try to estimate better.
+        final int laidOutArea = orientation.getDecoratedEnd(endChild) -
+                orientation.getDecoratedStart(startChild);
+        final int laidOutRange = Math.abs(lm.getPosition(startChild) - lm.getPosition(endChild))
+                + 1;
+        // estimate a size for full list.
+        return (int) ((float) laidOutArea / laidOutRange * state.getItemCount());
+    }
+}
diff --git a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
new file mode 100644
index 0000000..014b949
--- /dev/null
+++ b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -0,0 +1,2556 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.List;
+
+import static android.support.v7.widget.LayoutState.LAYOUT_START;
+import static android.support.v7.widget.LayoutState.LAYOUT_END;
+import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_HEAD;
+import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_TAIL;
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+
+/**
+ * A LayoutManager that lays out children in a staggered grid formation.
+ * It supports horizontal & vertical layout as well as an ability to layout children in reverse.
+ * <p>
+ * Staggered grids are likely to have gaps at the edges of the layout. To avoid these gaps,
+ * StaggeredGridLayoutManager can offset spans independently or move items between spans. You can
+ * control this behavior via {@link #setGapStrategy(int)}.
+ */
+public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager {
+
+    public static final String TAG = "StaggeredGridLayoutManager";
+
+    private static final boolean DEBUG = false;
+
+    public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;
+
+    public static final int VERTICAL = OrientationHelper.VERTICAL;
+
+    /**
+     * Does not do anything to hide gaps.
+     */
+    public static final int GAP_HANDLING_NONE = 0;
+
+    @Deprecated
+    public static final int GAP_HANDLING_LAZY = 1;
+
+    /**
+     * When scroll state is changed to {@link RecyclerView#SCROLL_STATE_IDLE}, StaggeredGrid will
+     * check if there are gaps in the because of full span items. If it finds, it will re-layout
+     * and move items to correct positions with animations.
+     * <p>
+     * For example, if LayoutManager ends up with the following layout due to adapter changes:
+     * <code>
+     * AAA
+     * _BC
+     * DDD
+     * </code>
+     * It will animate to the following state:
+     * <code>
+     * AAA
+     * BC_
+     * DDD
+     * </code>
+     */
+    public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2;
+
+    private static final int INVALID_OFFSET = Integer.MIN_VALUE;
+
+    /**
+     * Number of spans
+     */
+    private int mSpanCount = -1;
+
+    private Span[] mSpans;
+
+    /**
+     * Primary orientation is the layout's orientation, secondary orientation is the orientation
+     * for spans. Having both makes code much cleaner for calculations.
+     */
+    OrientationHelper mPrimaryOrientation;
+    OrientationHelper mSecondaryOrientation;
+
+    private int mOrientation;
+
+    /**
+     * The width or height per span, depending on the orientation.
+     */
+    private int mSizePerSpan;
+
+    private LayoutState mLayoutState;
+
+    private boolean mReverseLayout = false;
+
+    /**
+     * Aggregated reverse layout value that takes RTL into account.
+     */
+    boolean mShouldReverseLayout = false;
+
+    /**
+     * Temporary variable used during fill method to check which spans needs to be filled.
+     */
+    private BitSet mRemainingSpans;
+
+    /**
+     * When LayoutManager needs to scroll to a position, it sets this variable and requests a
+     * layout which will check this variable and re-layout accordingly.
+     */
+    int mPendingScrollPosition = NO_POSITION;
+
+    /**
+     * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is
+     * called.
+     */
+    int mPendingScrollPositionOffset = INVALID_OFFSET;
+
+    /**
+     * Keeps the mapping between the adapter positions and spans. This is necessary to provide
+     * a consistent experience when user scrolls the list.
+     */
+    LazySpanLookup mLazySpanLookup = new LazySpanLookup();
+
+    /**
+     * how we handle gaps in UI.
+     */
+    private int mGapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
+
+    /**
+     * Saved state needs this information to properly layout on restore.
+     */
+    private boolean mLastLayoutFromEnd;
+
+    /**
+     * Saved state and onLayout needs this information to re-layout properly
+     */
+    private boolean mLastLayoutRTL;
+
+    /**
+     * SavedState is not handled until a layout happens. This is where we keep it until next
+     * layout.
+     */
+    private SavedState mPendingSavedState;
+
+    /**
+     * Re-used measurement specs. updated by onLayout.
+     */
+    private int mFullSizeSpec, mWidthSpec, mHeightSpec;
+
+    /**
+     * Re-used anchor info.
+     */
+    private final AnchorInfo mAnchorInfo = new AnchorInfo();
+
+    /**
+     * If a full span item is invalid / or created in reverse direction; it may create gaps in
+     * the UI. While laying out, if such case is detected, we set this flag.
+     * <p>
+     * After scrolling stops, we check this flag and if it is set, re-layout.
+     */
+    private boolean mLaidOutInvalidFullSpan = false;
+
+    /**
+     * Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}.
+     * see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}
+     */
+    private boolean mSmoothScrollbarEnabled = true;
+
+    private final Runnable checkForGapsRunnable = new Runnable() {
+        @Override
+        public void run() {
+            checkForGaps();
+        }
+    };
+
+    /**
+     * Creates a StaggeredGridLayoutManager with given parameters.
+     *
+     * @param spanCount   If orientation is vertical, spanCount is number of columns. If
+     *                    orientation is horizontal, spanCount is number of rows.
+     * @param orientation {@link #VERTICAL} or {@link #HORIZONTAL}
+     */
+    public StaggeredGridLayoutManager(int spanCount, int orientation) {
+        mOrientation = orientation;
+        setSpanCount(spanCount);
+    }
+
+    /**
+     * Checks for gaps in the UI that may be caused by adapter changes.
+     * <p>
+     * When a full span item is laid out in reverse direction, it sets a flag which we check when
+     * scroll is stopped (or re-layout happens) and re-layout after first valid item.
+     */
+    private void checkForGaps() {
+        if (getChildCount() == 0 || mGapStrategy == GAP_HANDLING_NONE) {
+            return;
+        }
+        final int minPos, maxPos;
+        if (mShouldReverseLayout) {
+            minPos = getLastChildPosition();
+            maxPos = getFirstChildPosition();
+        } else {
+            minPos = getFirstChildPosition();
+            maxPos = getLastChildPosition();
+        }
+        if (minPos == 0) {
+            View gapView = hasGapsToFix();
+            if (gapView != null) {
+                mLazySpanLookup.clear();
+                requestSimpleAnimationsInNextLayout();
+                requestLayout();
+                return;
+            }
+        }
+        if (!mLaidOutInvalidFullSpan) {
+            return;
+        }
+        int invalidGapDir = mShouldReverseLayout ? LAYOUT_START : LAYOUT_END;
+        final LazySpanLookup.FullSpanItem invalidFsi = mLazySpanLookup
+                .getFirstFullSpanItemInRange(minPos, maxPos + 1, invalidGapDir);
+        if (invalidFsi == null) {
+            mLaidOutInvalidFullSpan = false;
+            mLazySpanLookup.forceInvalidateAfter(maxPos + 1);
+            return;
+        }
+        final LazySpanLookup.FullSpanItem validFsi = mLazySpanLookup
+                .getFirstFullSpanItemInRange(minPos, invalidFsi.mPosition,
+                        invalidGapDir * -1);
+        if (validFsi == null) {
+            mLazySpanLookup.forceInvalidateAfter(invalidFsi.mPosition);
+        } else {
+            mLazySpanLookup.forceInvalidateAfter(validFsi.mPosition + 1);
+        }
+        requestSimpleAnimationsInNextLayout();
+        requestLayout();
+    }
+
+    @Override
+    public void onScrollStateChanged(int state) {
+        if (state == RecyclerView.SCROLL_STATE_IDLE) {
+            checkForGaps();
+        }
+    }
+
+    @Override
+    public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
+        for (int i = 0; i < mSpanCount; i++) {
+            mSpans[i].clear();
+        }
+    }
+
+    /**
+     * Checks for gaps if we've reached to the top of the list.
+     * <p>
+     * Intermediate gaps created by full span items are tracked via mLaidOutInvalidFullSpan field.
+     */
+    View hasGapsToFix() {
+        int startChildIndex = 0;
+        int endChildIndex = getChildCount() - 1;
+        BitSet mSpansToCheck = new BitSet(mSpanCount);
+        mSpansToCheck.set(0, mSpanCount, true);
+
+        final int firstChildIndex, childLimit;
+        final int preferredSpanDir = mOrientation == VERTICAL && isLayoutRTL() ? 1 : -1;
+
+        if (mShouldReverseLayout) {
+            firstChildIndex = endChildIndex - 1;
+            childLimit = startChildIndex - 1;
+        } else {
+            firstChildIndex = startChildIndex;
+            childLimit = endChildIndex;
+        }
+        final int nextChildDiff = firstChildIndex < childLimit ? 1 : -1;
+        for (int i = firstChildIndex; i != childLimit; i += nextChildDiff) {
+            View child = getChildAt(i);
+            LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            if (mSpansToCheck.get(lp.mSpan.mIndex)) {
+                if (checkSpanForGap(lp.mSpan)) {
+                    return child;
+                }
+                mSpansToCheck.clear(lp.mSpan.mIndex);
+            }
+            if (lp.mFullSpan) {
+                continue; // quick reject
+            }
+
+            if (i + nextChildDiff != childLimit) {
+                View nextChild = getChildAt(i + nextChildDiff);
+                boolean compareSpans = false;
+                if (mShouldReverseLayout) {
+                    // ensure child's end is below nextChild's end
+                    int myEnd = mPrimaryOrientation.getDecoratedEnd(child);
+                    int nextEnd = mPrimaryOrientation.getDecoratedEnd(nextChild);
+                    if (myEnd < nextEnd) {
+                        return child;//i should have a better position
+                    } else if (myEnd == nextEnd) {
+                        compareSpans = true;
+                    }
+                } else {
+                    int myStart = mPrimaryOrientation.getDecoratedStart(child);
+                    int nextStart = mPrimaryOrientation.getDecoratedStart(nextChild);
+                    if (myStart > nextStart) {
+                        return child;//i should have a better position
+                    } else if (myStart == nextStart) {
+                        compareSpans = true;
+                    }
+                }
+                if (compareSpans) {
+                    // equal, check span indices.
+                    LayoutParams nextLp = (LayoutParams) nextChild.getLayoutParams();
+                    if (lp.mSpan.mIndex - nextLp.mSpan.mIndex < 0 != preferredSpanDir < 0) {
+                        return child;
+                    }
+                }
+            }
+        }
+        // everything looks good
+        return null;
+    }
+
+    private boolean checkSpanForGap(Span span) {
+        if (mShouldReverseLayout) {
+            if (span.getEndLine() < mPrimaryOrientation.getEndAfterPadding()) {
+                return true;
+            }
+        } else if (span.getStartLine() > mPrimaryOrientation.getStartAfterPadding()) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Sets the number of spans for the layout. This will invalidate all of the span assignments
+     * for Views.
+     * <p>
+     * Calling this method will automatically result in a new layout request unless the spanCount
+     * parameter is equal to current span count.
+     *
+     * @param spanCount Number of spans to layout
+     */
+    public void setSpanCount(int spanCount) {
+        assertNotInLayoutOrScroll(null);
+        if (spanCount != mSpanCount) {
+            invalidateSpanAssignments();
+            mSpanCount = spanCount;
+            mRemainingSpans = new BitSet(mSpanCount);
+            mSpans = new Span[mSpanCount];
+            for (int i = 0; i < mSpanCount; i++) {
+                mSpans[i] = new Span(i);
+            }
+            requestLayout();
+        }
+    }
+
+    /**
+     * Sets the orientation of the layout. StaggeredGridLayoutManager will do its best to keep
+     * scroll position if this method is called after views are laid out.
+     *
+     * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
+     */
+    public void setOrientation(int orientation) {
+        if (orientation != HORIZONTAL && orientation != VERTICAL) {
+            throw new IllegalArgumentException("invalid orientation.");
+        }
+        assertNotInLayoutOrScroll(null);
+        if (orientation == mOrientation) {
+            return;
+        }
+        mOrientation = orientation;
+        if (mPrimaryOrientation != null && mSecondaryOrientation != null) {
+            // swap
+            OrientationHelper tmp = mPrimaryOrientation;
+            mPrimaryOrientation = mSecondaryOrientation;
+            mSecondaryOrientation = tmp;
+        }
+        requestLayout();
+    }
+
+    /**
+     * Sets whether LayoutManager should start laying out items from the end of the UI. The order
+     * items are traversed is not affected by this call.
+     * <p>
+     * For vertical layout, if it is set to <code>true</code>, first item will be at the bottom of
+     * the list.
+     * <p>
+     * For horizontal layouts, it depends on the layout direction.
+     * When set to true, If {@link RecyclerView} is LTR, than it will layout from RTL, if
+     * {@link RecyclerView}} is RTL, it will layout from LTR.
+     *
+     * @param reverseLayout Whether layout should be in reverse or not
+     */
+    public void setReverseLayout(boolean reverseLayout) {
+        assertNotInLayoutOrScroll(null);
+        if (mPendingSavedState != null && mPendingSavedState.mReverseLayout != reverseLayout) {
+            mPendingSavedState.mReverseLayout = reverseLayout;
+        }
+        mReverseLayout = reverseLayout;
+        requestLayout();
+    }
+
+    /**
+     * Returns the current gap handling strategy for StaggeredGridLayoutManager.
+     * <p>
+     * Staggered grid may have gaps in the layout due to changes in the adapter. To avoid gaps,
+     * StaggeredGridLayoutManager provides 2 options. Check {@link #GAP_HANDLING_NONE} and
+     * {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS} for details.
+     * <p>
+     * By default, StaggeredGridLayoutManager uses {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS}.
+     *
+     * @return Current gap handling strategy.
+     * @see #setGapStrategy(int)
+     * @see #GAP_HANDLING_NONE
+     * @see #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
+     */
+    public int getGapStrategy() {
+        return mGapStrategy;
+    }
+
+    /**
+     * Sets the gap handling strategy for StaggeredGridLayoutManager. If the gapStrategy parameter
+     * is different than the current strategy, calling this method will trigger a layout request.
+     *
+     * @param gapStrategy The new gap handling strategy. Should be
+     *                    {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS} or {@link
+     *                    #GAP_HANDLING_NONE}.
+     * @see #getGapStrategy()
+     */
+    public void setGapStrategy(int gapStrategy) {
+        assertNotInLayoutOrScroll(null);
+        if (gapStrategy == mGapStrategy) {
+            return;
+        }
+        if (gapStrategy != GAP_HANDLING_NONE &&
+                gapStrategy != GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS) {
+            throw new IllegalArgumentException("invalid gap strategy. Must be GAP_HANDLING_NONE "
+                    + "or GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS");
+        }
+        mGapStrategy = gapStrategy;
+        requestLayout();
+    }
+
+    @Override
+    public void assertNotInLayoutOrScroll(String message) {
+        if (mPendingSavedState == null) {
+            super.assertNotInLayoutOrScroll(message);
+        }
+    }
+
+    /**
+     * Returns the number of spans laid out by StaggeredGridLayoutManager.
+     *
+     * @return Number of spans in the layout
+     */
+    public int getSpanCount() {
+        return mSpanCount;
+    }
+
+    /**
+     * For consistency, StaggeredGridLayoutManager keeps a mapping between spans and items.
+     * <p>
+     * If you need to cancel current assignments, you can call this method which will clear all
+     * assignments and request a new layout.
+     */
+    public void invalidateSpanAssignments() {
+        mLazySpanLookup.clear();
+        requestLayout();
+    }
+
+    private void ensureOrientationHelper() {
+        if (mPrimaryOrientation == null) {
+            mPrimaryOrientation = OrientationHelper.createOrientationHelper(this, mOrientation);
+            mSecondaryOrientation = OrientationHelper
+                    .createOrientationHelper(this, 1 - mOrientation);
+            mLayoutState = new LayoutState();
+        }
+    }
+
+    /**
+     * Calculates the views' layout order. (e.g. from end to start or start to end)
+     * RTL layout support is applied automatically. So if layout is RTL and
+     * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
+     */
+    private void resolveShouldLayoutReverse() {
+        // A == B is the same result, but we rather keep it readable
+        if (mOrientation == VERTICAL || !isLayoutRTL()) {
+            mShouldReverseLayout = mReverseLayout;
+        } else {
+            mShouldReverseLayout = !mReverseLayout;
+        }
+    }
+
+    boolean isLayoutRTL() {
+        return getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
+    }
+
+    /**
+     * Returns whether views are laid out in reverse order or not.
+     * <p>
+     * Not that this value is not affected by RecyclerView's layout direction.
+     *
+     * @return True if layout is reversed, false otherwise
+     * @see #setReverseLayout(boolean)
+     */
+    public boolean getReverseLayout() {
+        return mReverseLayout;
+    }
+
+    @Override
+    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+        ensureOrientationHelper();
+        // Update adapter size.
+        mLazySpanLookup.mAdapterSize = state.getItemCount();
+
+        final AnchorInfo anchorInfo = mAnchorInfo;
+        anchorInfo.reset();
+
+        if (mPendingSavedState != null) {
+            applyPendingSavedState(anchorInfo);
+        } else {
+            resolveShouldLayoutReverse();
+            anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
+        }
+
+        updateAnchorInfoForLayout(state, anchorInfo);
+
+        if (mPendingSavedState == null) {
+            if (anchorInfo.mLayoutFromEnd != mLastLayoutFromEnd ||
+                    isLayoutRTL() != mLastLayoutRTL) {
+                mLazySpanLookup.clear();
+                anchorInfo.mInvalidateOffsets = true;
+            }
+        }
+
+        if (getChildCount() > 0 && (mPendingSavedState == null ||
+                mPendingSavedState.mSpanOffsetsSize < 1)) {
+            if (anchorInfo.mInvalidateOffsets) {
+                for (int i = 0; i < mSpanCount; i++) {
+                    // Scroll to position is set, clear.
+                    mSpans[i].clear();
+                    if (anchorInfo.mOffset != INVALID_OFFSET) {
+                        mSpans[i].setLine(anchorInfo.mOffset);
+                    }
+                }
+            } else {
+                for (int i = 0; i < mSpanCount; i++) {
+                    mSpans[i].cacheReferenceLineAndClear(mShouldReverseLayout, anchorInfo.mOffset);
+                }
+            }
+        }
+        detachAndScrapAttachedViews(recycler);
+        mLaidOutInvalidFullSpan = false;
+        updateMeasureSpecs();
+        if (anchorInfo.mLayoutFromEnd) {
+            // Layout start.
+            updateLayoutStateToFillStart(anchorInfo.mPosition, state);
+            fill(recycler, mLayoutState, state);
+            // Layout end.
+            updateLayoutStateToFillEnd(anchorInfo.mPosition, state);
+            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
+            fill(recycler, mLayoutState, state);
+        } else {
+            // Layout end.
+            updateLayoutStateToFillEnd(anchorInfo.mPosition, state);
+            fill(recycler, mLayoutState, state);
+            // Layout start.
+            updateLayoutStateToFillStart(anchorInfo.mPosition, state);
+            mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
+            fill(recycler, mLayoutState, state);
+        }
+
+        if (getChildCount() > 0) {
+            if (mShouldReverseLayout) {
+                fixEndGap(recycler, state, true);
+                fixStartGap(recycler, state, false);
+            } else {
+                fixStartGap(recycler, state, true);
+                fixEndGap(recycler, state, false);
+            }
+        }
+
+        if (!state.isPreLayout()) {
+            if (getChildCount() > 0 && mPendingScrollPosition != NO_POSITION &&
+                    mLaidOutInvalidFullSpan) {
+                ViewCompat.postOnAnimation(getChildAt(0), checkForGapsRunnable);
+            }
+            mPendingScrollPosition = NO_POSITION;
+            mPendingScrollPositionOffset = INVALID_OFFSET;
+        }
+        mLastLayoutFromEnd = anchorInfo.mLayoutFromEnd;
+        mLastLayoutRTL = isLayoutRTL();
+        mPendingSavedState = null; // we don't need this anymore
+    }
+
+    private void applyPendingSavedState(AnchorInfo anchorInfo) {
+        if (DEBUG) {
+            Log.d(TAG, "found saved state: " + mPendingSavedState);
+        }
+        if (mPendingSavedState.mSpanOffsetsSize > 0) {
+            if (mPendingSavedState.mSpanOffsetsSize == mSpanCount) {
+                for (int i = 0; i < mSpanCount; i++) {
+                    mSpans[i].clear();
+                    int line = mPendingSavedState.mSpanOffsets[i];
+                    if (line != Span.INVALID_LINE) {
+                        if (mPendingSavedState.mAnchorLayoutFromEnd) {
+                            line += mPrimaryOrientation.getEndAfterPadding();
+                        } else {
+                            line += mPrimaryOrientation.getStartAfterPadding();
+                        }
+                    }
+                    mSpans[i].setLine(line);
+                }
+            } else {
+                mPendingSavedState.invalidateSpanInfo();
+                mPendingSavedState.mAnchorPosition = mPendingSavedState.mVisibleAnchorPosition;
+            }
+        }
+        mLastLayoutRTL = mPendingSavedState.mLastLayoutRTL;
+        setReverseLayout(mPendingSavedState.mReverseLayout);
+        resolveShouldLayoutReverse();
+
+        if (mPendingSavedState.mAnchorPosition != NO_POSITION) {
+            mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
+            anchorInfo.mLayoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
+        } else {
+            anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
+        }
+        if (mPendingSavedState.mSpanLookupSize > 1) {
+            mLazySpanLookup.mData = mPendingSavedState.mSpanLookup;
+            mLazySpanLookup.mFullSpanItems = mPendingSavedState.mFullSpanItems;
+        }
+    }
+
+    void updateAnchorInfoForLayout(RecyclerView.State state, AnchorInfo anchorInfo) {
+        if (updateAnchorFromPendingData(state, anchorInfo)) {
+            return;
+        }
+        if (updateAnchorFromChildren(state, anchorInfo)) {
+            return;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "Deciding anchor info from fresh state");
+        }
+        anchorInfo.assignCoordinateFromPadding();
+        anchorInfo.mPosition = 0;
+    }
+
+    private boolean updateAnchorFromChildren(RecyclerView.State state, AnchorInfo anchorInfo) {
+        // We don't recycle views out of adapter order. This way, we can rely on the first or
+        // last child as the anchor position.
+        // Layout direction may change but we should select the child depending on the latest
+        // layout direction. Otherwise, we'll choose the wrong child.
+        anchorInfo.mPosition = mLastLayoutFromEnd
+                ? findLastReferenceChildPosition(state.getItemCount())
+                : findFirstReferenceChildPosition(state.getItemCount());
+        anchorInfo.mOffset = INVALID_OFFSET;
+        return true;
+    }
+
+    boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) {
+        // Validate scroll position if exists.
+        if (state.isPreLayout() || mPendingScrollPosition == NO_POSITION) {
+            return false;
+        }
+        // Validate it.
+        if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) {
+            mPendingScrollPosition = NO_POSITION;
+            mPendingScrollPositionOffset = INVALID_OFFSET;
+            return false;
+        }
+
+        if (mPendingSavedState == null || mPendingSavedState.mAnchorPosition == NO_POSITION
+                || mPendingSavedState.mSpanOffsetsSize < 1) {
+            // If item is visible, make it fully visible.
+            final View child = findViewByPosition(mPendingScrollPosition);
+            if (child != null) {
+                // Use regular anchor position, offset according to pending offset and target
+                // child
+                anchorInfo.mPosition = mShouldReverseLayout ? getLastChildPosition()
+                        : getFirstChildPosition();
+
+                if (mPendingScrollPositionOffset != INVALID_OFFSET) {
+                    if (anchorInfo.mLayoutFromEnd) {
+                        final int target = mPrimaryOrientation.getEndAfterPadding() -
+                                mPendingScrollPositionOffset;
+                        anchorInfo.mOffset = target - mPrimaryOrientation.getDecoratedEnd(child);
+                    } else {
+                        final int target = mPrimaryOrientation.getStartAfterPadding() +
+                                mPendingScrollPositionOffset;
+                        anchorInfo.mOffset = target - mPrimaryOrientation.getDecoratedStart(child);
+                    }
+                    return true;
+                }
+
+                // no offset provided. Decide according to the child location
+                final int childSize = mPrimaryOrientation.getDecoratedMeasurement(child);
+                if (childSize > mPrimaryOrientation.getTotalSpace()) {
+                    // Item does not fit. Fix depending on layout direction.
+                    anchorInfo.mOffset = anchorInfo.mLayoutFromEnd
+                            ? mPrimaryOrientation.getEndAfterPadding()
+                            : mPrimaryOrientation.getStartAfterPadding();
+                    return true;
+                }
+
+                final int startGap = mPrimaryOrientation.getDecoratedStart(child)
+                        - mPrimaryOrientation.getStartAfterPadding();
+                if (startGap < 0) {
+                    anchorInfo.mOffset = -startGap;
+                    return true;
+                }
+                final int endGap = mPrimaryOrientation.getEndAfterPadding() -
+                        mPrimaryOrientation.getDecoratedEnd(child);
+                if (endGap < 0) {
+                    anchorInfo.mOffset = endGap;
+                    return true;
+                }
+                // child already visible. just layout as usual
+                anchorInfo.mOffset = INVALID_OFFSET;
+            } else {
+                // Child is not visible. Set anchor coordinate depending on in which direction
+                // child will be visible.
+                anchorInfo.mPosition = mPendingScrollPosition;
+                if (mPendingScrollPositionOffset == INVALID_OFFSET) {
+                    final int position = calculateScrollDirectionForPosition(
+                            anchorInfo.mPosition);
+                    anchorInfo.mLayoutFromEnd = position == LAYOUT_END;
+                    anchorInfo.assignCoordinateFromPadding();
+                } else {
+                    anchorInfo.assignCoordinateFromPadding(mPendingScrollPositionOffset);
+                }
+                anchorInfo.mInvalidateOffsets = true;
+            }
+        } else {
+            anchorInfo.mOffset = INVALID_OFFSET;
+            anchorInfo.mPosition = mPendingScrollPosition;
+        }
+        return true;
+    }
+
+    void updateMeasureSpecs() {
+        mSizePerSpan = mSecondaryOrientation.getTotalSpace() / mSpanCount;
+        mFullSizeSpec = View.MeasureSpec.makeMeasureSpec(
+                mSecondaryOrientation.getTotalSpace(), View.MeasureSpec.EXACTLY);
+        if (mOrientation == VERTICAL) {
+            mWidthSpec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan, View.MeasureSpec.EXACTLY);
+            mHeightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+        } else {
+            mHeightSpec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan, View.MeasureSpec.EXACTLY);
+            mWidthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+        }
+    }
+
+    @Override
+    public boolean supportsPredictiveItemAnimations() {
+        return mPendingSavedState == null;
+    }
+
+    /**
+     * Returns the adapter position of the first visible view for each span.
+     * <p>
+     * Note that, this value is not affected by layout orientation or item order traversal.
+     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
+     * not in the layout.
+     * <p>
+     * If RecyclerView has item decorators, they will be considered in calculations as well.
+     * <p>
+     * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
+     * views are ignored in this method.
+     *
+     * @param into An array to put the results into. If you don't provide any, LayoutManager will
+     *             create a new one.
+     * @return The adapter position of the first visible item in each span. If a span does not have
+     * any items, {@link RecyclerView#NO_POSITION} is returned for that span.
+     * @see #findFirstCompletelyVisibleItemPositions(int[])
+     * @see #findLastVisibleItemPositions(int[])
+     */
+    public int[] findFirstVisibleItemPositions(int[] into) {
+        if (into == null) {
+            into = new int[mSpanCount];
+        } else if (into.length < mSpanCount) {
+            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
+                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
+        }
+        for (int i = 0; i < mSpanCount; i++) {
+            into[i] = mSpans[i].findFirstVisibleItemPosition();
+        }
+        return into;
+    }
+
+    /**
+     * Returns the adapter position of the first completely visible view for each span.
+     * <p>
+     * Note that, this value is not affected by layout orientation or item order traversal.
+     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
+     * not in the layout.
+     * <p>
+     * If RecyclerView has item decorators, they will be considered in calculations as well.
+     * <p>
+     * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
+     * views are ignored in this method.
+     *
+     * @param into An array to put the results into. If you don't provide any, LayoutManager will
+     *             create a new one.
+     * @return The adapter position of the first fully visible item in each span. If a span does
+     * not have any items, {@link RecyclerView#NO_POSITION} is returned for that span.
+     * @see #findFirstVisibleItemPositions(int[])
+     * @see #findLastCompletelyVisibleItemPositions(int[])
+     */
+    public int[] findFirstCompletelyVisibleItemPositions(int[] into) {
+        if (into == null) {
+            into = new int[mSpanCount];
+        } else if (into.length < mSpanCount) {
+            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
+                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
+        }
+        for (int i = 0; i < mSpanCount; i++) {
+            into[i] = mSpans[i].findFirstCompletelyVisibleItemPosition();
+        }
+        return into;
+    }
+
+    /**
+     * Returns the adapter position of the last visible view for each span.
+     * <p>
+     * Note that, this value is not affected by layout orientation or item order traversal.
+     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
+     * not in the layout.
+     * <p>
+     * If RecyclerView has item decorators, they will be considered in calculations as well.
+     * <p>
+     * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
+     * views are ignored in this method.
+     *
+     * @param into An array to put the results into. If you don't provide any, LayoutManager will
+     *             create a new one.
+     * @return The adapter position of the last visible item in each span. If a span does not have
+     * any items, {@link RecyclerView#NO_POSITION} is returned for that span.
+     * @see #findLastCompletelyVisibleItemPositions(int[])
+     * @see #findFirstVisibleItemPositions(int[])
+     */
+    public int[] findLastVisibleItemPositions(int[] into) {
+        if (into == null) {
+            into = new int[mSpanCount];
+        } else if (into.length < mSpanCount) {
+            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
+                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
+        }
+        for (int i = 0; i < mSpanCount; i++) {
+            into[i] = mSpans[i].findLastVisibleItemPosition();
+        }
+        return into;
+    }
+
+    /**
+     * Returns the adapter position of the last completely visible view for each span.
+     * <p>
+     * Note that, this value is not affected by layout orientation or item order traversal.
+     * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
+     * not in the layout.
+     * <p>
+     * If RecyclerView has item decorators, they will be considered in calculations as well.
+     * <p>
+     * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
+     * views are ignored in this method.
+     *
+     * @param into An array to put the results into. If you don't provide any, LayoutManager will
+     *             create a new one.
+     * @return The adapter position of the last fully visible item in each span. If a span does not
+     * have any items, {@link RecyclerView#NO_POSITION} is returned for that span.
+     * @see #findFirstCompletelyVisibleItemPositions(int[])
+     * @see #findLastVisibleItemPositions(int[])
+     */
+    public int[] findLastCompletelyVisibleItemPositions(int[] into) {
+        if (into == null) {
+            into = new int[mSpanCount];
+        } else if (into.length < mSpanCount) {
+            throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
+                    + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
+        }
+        for (int i = 0; i < mSpanCount; i++) {
+            into[i] = mSpans[i].findLastCompletelyVisibleItemPosition();
+        }
+        return into;
+    }
+
+    @Override
+    public int computeHorizontalScrollOffset(RecyclerView.State state) {
+        return computeScrollOffset(state);
+    }
+
+    private int computeScrollOffset(RecyclerView.State state) {
+        if (getChildCount() == 0) {
+            return 0;
+        }
+        return ScrollbarHelper.computeScrollOffset(state, mPrimaryOrientation,
+                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled)
+                , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled),
+                this, mSmoothScrollbarEnabled, mShouldReverseLayout);
+    }
+
+    @Override
+    public int computeVerticalScrollOffset(RecyclerView.State state) {
+        return computeScrollOffset(state);
+    }
+
+    @Override
+    public int computeHorizontalScrollExtent(RecyclerView.State state) {
+        return computeScrollExtent(state);
+    }
+
+    private int computeScrollExtent(RecyclerView.State state) {
+        if (getChildCount() == 0) {
+            return 0;
+        }
+        return ScrollbarHelper.computeScrollExtent(state, mPrimaryOrientation,
+                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled)
+                , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled),
+                this, mSmoothScrollbarEnabled);
+    }
+
+    @Override
+    public int computeVerticalScrollExtent(RecyclerView.State state) {
+        return computeScrollExtent(state);
+    }
+
+    @Override
+    public int computeHorizontalScrollRange(RecyclerView.State state) {
+        return computeScrollRange(state);
+    }
+
+    private int computeScrollRange(RecyclerView.State state) {
+        if (getChildCount() == 0) {
+            return 0;
+        }
+        return ScrollbarHelper.computeScrollRange(state, mPrimaryOrientation,
+                findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled)
+                , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled),
+                this, mSmoothScrollbarEnabled);
+    }
+
+    @Override
+    public int computeVerticalScrollRange(RecyclerView.State state) {
+        return computeScrollRange(state);
+    }
+
+    private void measureChildWithDecorationsAndMargin(View child, LayoutParams lp) {
+        if (lp.mFullSpan) {
+            if (mOrientation == VERTICAL) {
+                measureChildWithDecorationsAndMargin(child, mFullSizeSpec, mHeightSpec);
+            } else {
+                measureChildWithDecorationsAndMargin(child, mWidthSpec, mFullSizeSpec);
+            }
+        } else {
+            measureChildWithDecorationsAndMargin(child, mWidthSpec, mHeightSpec);
+        }
+    }
+
+    private void measureChildWithDecorationsAndMargin(View child, int widthSpec,
+            int heightSpec) {
+        final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
+        LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + insets.left,
+                lp.rightMargin + insets.right);
+        heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + insets.top,
+                lp.bottomMargin + insets.bottom);
+        child.measure(widthSpec, heightSpec);
+    }
+
+    private int updateSpecWithExtra(int spec, int startInset, int endInset) {
+        if (startInset == 0 && endInset == 0) {
+            return spec;
+        }
+        final int mode = View.MeasureSpec.getMode(spec);
+        if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) {
+            return View.MeasureSpec.makeMeasureSpec(
+                    View.MeasureSpec.getSize(spec) - startInset - endInset, mode);
+        }
+        return spec;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        if (state instanceof SavedState) {
+            mPendingSavedState = (SavedState) state;
+            requestLayout();
+        } else if (DEBUG) {
+            Log.d(TAG, "invalid saved state class");
+        }
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        if (mPendingSavedState != null) {
+            return new SavedState(mPendingSavedState);
+        }
+        SavedState state = new SavedState();
+        state.mReverseLayout = mReverseLayout;
+        state.mAnchorLayoutFromEnd = mLastLayoutFromEnd;
+        state.mLastLayoutRTL = mLastLayoutRTL;
+
+        if (mLazySpanLookup != null && mLazySpanLookup.mData != null) {
+            state.mSpanLookup = mLazySpanLookup.mData;
+            state.mSpanLookupSize = state.mSpanLookup.length;
+            state.mFullSpanItems = mLazySpanLookup.mFullSpanItems;
+        } else {
+            state.mSpanLookupSize = 0;
+        }
+
+        if (getChildCount() > 0) {
+            state.mAnchorPosition = mLastLayoutFromEnd ? getLastChildPosition()
+                    : getFirstChildPosition();
+            state.mVisibleAnchorPosition = findFirstVisibleItemPositionInt();
+            state.mSpanOffsetsSize = mSpanCount;
+            state.mSpanOffsets = new int[mSpanCount];
+            for (int i = 0; i < mSpanCount; i++) {
+                int line;
+                if (mLastLayoutFromEnd) {
+                    line = mSpans[i].getEndLine(Span.INVALID_LINE);
+                    if (line != Span.INVALID_LINE) {
+                        line -= mPrimaryOrientation.getEndAfterPadding();
+                    }
+                } else {
+                    line = mSpans[i].getStartLine(Span.INVALID_LINE);
+                    if (line != Span.INVALID_LINE) {
+                        line -= mPrimaryOrientation.getStartAfterPadding();
+                    }
+                }
+                state.mSpanOffsets[i] = line;
+            }
+        } else {
+            state.mAnchorPosition = NO_POSITION;
+            state.mVisibleAnchorPosition = NO_POSITION;
+            state.mSpanOffsetsSize = 0;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "saved state:\n" + state);
+        }
+        return state;
+    }
+
+    /**
+     * Finds the first fully visible child to be used as an anchor child if span count changes when
+     * state is restored.
+     */
+    int findFirstVisibleItemPositionInt() {
+        final View first = mShouldReverseLayout ? findFirstVisibleItemClosestToEnd(true) :
+                findFirstVisibleItemClosestToStart(true);
+        return first == null ? NO_POSITION : getPosition(first);
+    }
+
+    View findFirstVisibleItemClosestToStart(boolean fullyVisible) {
+        final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
+        final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
+        final int limit = getChildCount();
+        for (int i = 0; i < limit; i ++) {
+            final View child = getChildAt(i);
+            if ((!fullyVisible || mPrimaryOrientation.getDecoratedStart(child) >= boundsStart)
+                    && mPrimaryOrientation.getDecoratedEnd(child) <= boundsEnd) {
+                return child;
+            }
+        }
+        return null;
+    }
+
+    View findFirstVisibleItemClosestToEnd(boolean fullyVisible) {
+        final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
+        final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
+        for (int i = getChildCount() - 1; i >= 0; i --) {
+            final View child = getChildAt(i);
+            if (mPrimaryOrientation.getDecoratedStart(child) >= boundsStart && (!fullyVisible
+                    || mPrimaryOrientation.getDecoratedEnd(child) <= boundsEnd)) {
+                return child;
+            }
+        }
+        return null;
+    }
+
+    private void fixEndGap(RecyclerView.Recycler recycler, RecyclerView.State state,
+            boolean canOffsetChildren) {
+        final int maxEndLine = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
+        int gap = mPrimaryOrientation.getEndAfterPadding() - maxEndLine;
+        int fixOffset;
+        if (gap > 0) {
+            fixOffset = -scrollBy(-gap, recycler, state);
+        } else {
+            return; // nothing to fix
+        }
+        gap -= fixOffset;
+        if (canOffsetChildren && gap > 0) {
+            mPrimaryOrientation.offsetChildren(gap);
+        }
+    }
+
+    private void fixStartGap(RecyclerView.Recycler recycler, RecyclerView.State state,
+            boolean canOffsetChildren) {
+        final int minStartLine = getMinStart(mPrimaryOrientation.getStartAfterPadding());
+        int gap = minStartLine - mPrimaryOrientation.getStartAfterPadding();
+        int fixOffset;
+        if (gap > 0) {
+            fixOffset = scrollBy(gap, recycler, state);
+        } else {
+            return; // nothing to fix
+        }
+        gap -= fixOffset;
+        if (canOffsetChildren && gap > 0) {
+            mPrimaryOrientation.offsetChildren(-gap);
+        }
+    }
+
+    private void updateLayoutStateToFillStart(int anchorPosition, RecyclerView.State state) {
+        mLayoutState.mAvailable = 0;
+        mLayoutState.mCurrentPosition = anchorPosition;
+        if (isSmoothScrolling()) {
+            final int targetPos = state.getTargetScrollPosition();
+            if (mShouldReverseLayout == targetPos < anchorPosition) {
+                mLayoutState.mExtra = 0;
+            } else {
+                mLayoutState.mExtra = mPrimaryOrientation.getTotalSpace();
+            }
+        } else {
+            mLayoutState.mExtra = 0;
+        }
+        mLayoutState.mLayoutDirection = LAYOUT_START;
+        mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_TAIL
+                : ITEM_DIRECTION_HEAD;
+    }
+
+    private void updateLayoutStateToFillEnd(int anchorPosition, RecyclerView.State state) {
+        mLayoutState.mAvailable = 0;
+        mLayoutState.mCurrentPosition = anchorPosition;
+        if (isSmoothScrolling()) {
+            final int targetPos = state.getTargetScrollPosition();
+            if (mShouldReverseLayout == targetPos > anchorPosition) {
+                mLayoutState.mExtra = 0;
+            } else {
+                mLayoutState.mExtra = mPrimaryOrientation.getTotalSpace();
+            }
+        } else {
+            mLayoutState.mExtra = 0;
+        }
+        mLayoutState.mLayoutDirection = LAYOUT_END;
+        mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_HEAD
+                : ITEM_DIRECTION_TAIL;
+    }
+
+    @Override
+    public void offsetChildrenHorizontal(int dx) {
+        super.offsetChildrenHorizontal(dx);
+        for (int i = 0; i < mSpanCount; i++) {
+            mSpans[i].onOffset(dx);
+        }
+    }
+
+    @Override
+    public void offsetChildrenVertical(int dy) {
+        super.offsetChildrenVertical(dy);
+        for (int i = 0; i < mSpanCount; i++) {
+            mSpans[i].onOffset(dy);
+        }
+    }
+
+    @Override
+    public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
+        handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.REMOVE);
+    }
+
+    @Override
+    public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
+        handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.ADD);
+    }
+
+    @Override
+    public void onItemsChanged(RecyclerView recyclerView) {
+        mLazySpanLookup.clear();
+        requestLayout();
+    }
+
+    @Override
+    public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
+        handleUpdate(from, to, AdapterHelper.UpdateOp.MOVE);
+    }
+
+    @Override
+    public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
+        handleUpdate(positionStart, itemCount, AdapterHelper.UpdateOp.UPDATE);
+    }
+
+    /**
+     * Checks whether it should invalidate span assignments in response to an adapter change.
+     */
+    private void handleUpdate(int positionStart, int itemCountOrToPosition, int cmd) {
+        int minPosition = mShouldReverseLayout ? getLastChildPosition() : getFirstChildPosition();
+        mLazySpanLookup.invalidateAfter(positionStart);
+        switch (cmd) {
+            case AdapterHelper.UpdateOp.ADD:
+                mLazySpanLookup.offsetForAddition(positionStart, itemCountOrToPosition);
+                break;
+            case AdapterHelper.UpdateOp.REMOVE:
+                mLazySpanLookup.offsetForRemoval(positionStart, itemCountOrToPosition);
+                break;
+            case AdapterHelper.UpdateOp.MOVE:
+                // TODO optimize
+                mLazySpanLookup.offsetForRemoval(positionStart, 1);
+                mLazySpanLookup.offsetForAddition(itemCountOrToPosition, 1);
+                break;
+        }
+
+        if (positionStart + itemCountOrToPosition <= minPosition) {
+            return;
+
+        }
+        int maxPosition = mShouldReverseLayout ? getFirstChildPosition() : getLastChildPosition();
+        if (positionStart <= maxPosition) {
+            requestLayout();
+        }
+    }
+
+    private int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
+            RecyclerView.State state) {
+        mRemainingSpans.set(0, mSpanCount, true);
+        // The target position we are trying to reach.
+        final int targetLine;
+        /*
+        * The line until which we can recycle, as long as we add views.
+        * Keep in mind, it is still the line in layout direction which means; to calculate the
+        * actual recycle line, we should subtract/add the size in orientation.
+        */
+        final int recycleLine;
+        // Line of the furthest row.
+        if (layoutState.mLayoutDirection == LAYOUT_END) {
+            // ignore padding for recycler
+            recycleLine = mPrimaryOrientation.getEndAfterPadding() + mLayoutState.mAvailable;
+            targetLine = recycleLine + mLayoutState.mExtra + mPrimaryOrientation.getEndPadding();
+
+        } else { // LAYOUT_START
+            // ignore padding for recycler
+            recycleLine = mPrimaryOrientation.getStartAfterPadding() - mLayoutState.mAvailable;
+            targetLine = recycleLine - mLayoutState.mExtra -
+                    mPrimaryOrientation.getStartAfterPadding();
+        }
+        updateAllRemainingSpans(layoutState.mLayoutDirection, targetLine);
+
+        // the default coordinate to add new view.
+        final int defaultNewViewLine = mShouldReverseLayout
+                ? mPrimaryOrientation.getEndAfterPadding()
+                : mPrimaryOrientation.getStartAfterPadding();
+
+        while (layoutState.hasMore(state) && !mRemainingSpans.isEmpty()) {
+            View view = layoutState.next(recycler);
+            LayoutParams lp = ((LayoutParams) view.getLayoutParams());
+            if (layoutState.mLayoutDirection == LAYOUT_END) {
+                addView(view);
+            } else {
+                addView(view, 0);
+            }
+            measureChildWithDecorationsAndMargin(view, lp);
+
+            final int position = lp.getViewPosition();
+            final int spanIndex = mLazySpanLookup.getSpan(position);
+            Span currentSpan;
+            boolean assignSpan = spanIndex == LayoutParams.INVALID_SPAN_ID;
+            if (assignSpan) {
+                currentSpan = lp.mFullSpan ? mSpans[0] : getNextSpan(layoutState);
+                mLazySpanLookup.setSpan(position, currentSpan);
+                if (DEBUG) {
+                    Log.d(TAG, "assigned " + currentSpan.mIndex + " for " + position);
+                }
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, "using " + spanIndex + " for pos " + position);
+                }
+                currentSpan = mSpans[spanIndex];
+            }
+            final int start;
+            final int end;
+
+            if (layoutState.mLayoutDirection == LAYOUT_END) {
+                start = lp.mFullSpan ? getMaxEnd(defaultNewViewLine)
+                        : currentSpan.getEndLine(defaultNewViewLine);
+                end = start + mPrimaryOrientation.getDecoratedMeasurement(view);
+                if (assignSpan && lp.mFullSpan) {
+                    LazySpanLookup.FullSpanItem fullSpanItem;
+                    fullSpanItem = createFullSpanItemFromEnd(start);
+                    fullSpanItem.mGapDir = LAYOUT_START;
+                    fullSpanItem.mPosition = position;
+                    mLazySpanLookup.addFullSpanItem(fullSpanItem);
+                }
+            } else {
+                end = lp.mFullSpan ? getMinStart(defaultNewViewLine)
+                        : currentSpan.getStartLine(defaultNewViewLine);
+                start = end - mPrimaryOrientation.getDecoratedMeasurement(view);
+                if (assignSpan && lp.mFullSpan) {
+                    LazySpanLookup.FullSpanItem fullSpanItem;
+                    fullSpanItem = createFullSpanItemFromStart(end);
+                    fullSpanItem.mGapDir = LAYOUT_END;
+                    fullSpanItem.mPosition = position;
+                    mLazySpanLookup.addFullSpanItem(fullSpanItem);
+                }
+            }
+
+            // check if this item may create gaps in the future
+            if (lp.mFullSpan && layoutState.mItemDirection == ITEM_DIRECTION_HEAD && assignSpan) {
+                mLaidOutInvalidFullSpan = true;
+            }
+
+            lp.mSpan = currentSpan;
+            attachViewToSpans(view, lp, layoutState);
+            final int otherStart = lp.mFullSpan ? mSecondaryOrientation.getStartAfterPadding()
+                    : currentSpan.mIndex * mSizePerSpan +
+                            mSecondaryOrientation.getStartAfterPadding();
+            final int otherEnd = otherStart + mSecondaryOrientation.getDecoratedMeasurement(view);
+            if (mOrientation == VERTICAL) {
+                layoutDecoratedWithMargins(view, otherStart, start, otherEnd, end);
+            } else {
+                layoutDecoratedWithMargins(view, start, otherStart, end, otherEnd);
+            }
+
+            if (lp.mFullSpan) {
+                updateAllRemainingSpans(mLayoutState.mLayoutDirection, targetLine);
+            } else {
+                updateRemainingSpans(currentSpan, mLayoutState.mLayoutDirection, targetLine);
+            }
+            recycle(recycler, mLayoutState, currentSpan, recycleLine);
+        }
+        if (DEBUG) {
+            Log.d(TAG, "fill, " + getChildCount());
+        }
+        if (mLayoutState.mLayoutDirection == LAYOUT_START) {
+            final int minStart = getMinStart(mPrimaryOrientation.getStartAfterPadding());
+            return Math.max(0, mLayoutState.mAvailable + (recycleLine - minStart));
+        } else {
+            final int max = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
+            return Math.max(0, mLayoutState.mAvailable + (max - recycleLine));
+        }
+    }
+
+    private LazySpanLookup.FullSpanItem createFullSpanItemFromEnd(int newItemTop) {
+        LazySpanLookup.FullSpanItem fsi = new LazySpanLookup.FullSpanItem();
+        fsi.mGapPerSpan = new int[mSpanCount];
+        for (int i = 0; i < mSpanCount; i++) {
+            fsi.mGapPerSpan[i] = newItemTop - mSpans[i].getEndLine(newItemTop);
+        }
+        return fsi;
+    }
+
+    private LazySpanLookup.FullSpanItem createFullSpanItemFromStart(int newItemBottom) {
+        LazySpanLookup.FullSpanItem fsi = new LazySpanLookup.FullSpanItem();
+        fsi.mGapPerSpan = new int[mSpanCount];
+        for (int i = 0; i < mSpanCount; i++) {
+            fsi.mGapPerSpan[i] = mSpans[i].getStartLine(newItemBottom) - newItemBottom;
+        }
+        return fsi;
+    }
+
+    private void attachViewToSpans(View view, LayoutParams lp, LayoutState layoutState) {
+        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_END) {
+            if (lp.mFullSpan) {
+                appendViewToAllSpans(view);
+            } else {
+                lp.mSpan.appendToSpan(view);
+            }
+        } else {
+            if (lp.mFullSpan) {
+                prependViewToAllSpans(view);
+            } else {
+                lp.mSpan.prependToSpan(view);
+            }
+        }
+    }
+
+    private void recycle(RecyclerView.Recycler recycler, LayoutState layoutState,
+            Span updatedSpan, int recycleLine) {
+        if (layoutState.mLayoutDirection == LAYOUT_START) {
+            // calculate recycle line
+            int maxStart = getMaxStart(updatedSpan.getStartLine());
+            recycleFromEnd(recycler, Math.max(recycleLine, maxStart) +
+                    (mPrimaryOrientation.getEnd() - mPrimaryOrientation.getStartAfterPadding()));
+        } else {
+            // calculate recycle line
+            int minEnd = getMinEnd(updatedSpan.getEndLine());
+            recycleFromStart(recycler, Math.min(recycleLine, minEnd) -
+                    (mPrimaryOrientation.getEnd() - mPrimaryOrientation.getStartAfterPadding()));
+        }
+    }
+
+    private void appendViewToAllSpans(View view) {
+        // traverse in reverse so that we end up assigning full span items to 0
+        for (int i = mSpanCount - 1; i >= 0; i--) {
+            mSpans[i].appendToSpan(view);
+        }
+    }
+
+    private void prependViewToAllSpans(View view) {
+        // traverse in reverse so that we end up assigning full span items to 0
+        for (int i = mSpanCount - 1; i >= 0; i--) {
+            mSpans[i].prependToSpan(view);
+        }
+    }
+
+    private void layoutDecoratedWithMargins(View child, int left, int top, int right, int bottom) {
+        LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        if (DEBUG) {
+            Log.d(TAG, "layout decorated pos: " + lp.getViewPosition() + ", span:"
+                    + lp.getSpanIndex() + ", fullspan:" + lp.mFullSpan
+                    + ". l:" + left + ",t:" + top
+                    + ", r:" + right + ", b:" + bottom);
+        }
+        layoutDecorated(child, left + lp.leftMargin, top + lp.topMargin, right - lp.rightMargin
+                , bottom - lp.bottomMargin);
+    }
+
+    private void updateAllRemainingSpans(int layoutDir, int targetLine) {
+        for (int i = 0; i < mSpanCount; i++) {
+            if (mSpans[i].mViews.isEmpty()) {
+                continue;
+            }
+            updateRemainingSpans(mSpans[i], layoutDir, targetLine);
+        }
+    }
+
+    private void updateRemainingSpans(Span span, int layoutDir, int targetLine) {
+        final int deletedSize = span.getDeletedSize();
+        if (layoutDir == LAYOUT_START) {
+            final int line = span.getStartLine();
+            if (line + deletedSize < targetLine) {
+                mRemainingSpans.set(span.mIndex, false);
+            }
+        } else {
+            final int line = span.getEndLine();
+            if (line - deletedSize > targetLine) {
+                mRemainingSpans.set(span.mIndex, false);
+            }
+        }
+    }
+
+    private int getMaxStart(int def) {
+        int maxStart = mSpans[0].getStartLine(def);
+        for (int i = 1; i < mSpanCount; i++) {
+            final int spanStart = mSpans[i].getStartLine(def);
+            if (spanStart > maxStart) {
+                maxStart = spanStart;
+            }
+        }
+        return maxStart;
+    }
+
+    private int getMinStart(int def) {
+        int minStart = mSpans[0].getStartLine(def);
+        for (int i = 1; i < mSpanCount; i++) {
+            final int spanStart = mSpans[i].getStartLine(def);
+            if (spanStart < minStart) {
+                minStart = spanStart;
+            }
+        }
+        return minStart;
+    }
+
+    private int getMaxEnd(int def) {
+        int maxEnd = mSpans[0].getEndLine(def);
+        for (int i = 1; i < mSpanCount; i++) {
+            final int spanEnd = mSpans[i].getEndLine(def);
+            if (spanEnd > maxEnd) {
+                maxEnd = spanEnd;
+            }
+        }
+        return maxEnd;
+    }
+
+    private int getMinEnd(int def) {
+        int minEnd = mSpans[0].getEndLine(def);
+        for (int i = 1; i < mSpanCount; i++) {
+            final int spanEnd = mSpans[i].getEndLine(def);
+            if (spanEnd < minEnd) {
+                minEnd = spanEnd;
+            }
+        }
+        return minEnd;
+    }
+
+    private void recycleFromStart(RecyclerView.Recycler recycler, int line) {
+        if (DEBUG) {
+            Log.d(TAG, "recycling from start for line " + line);
+        }
+        while (getChildCount() > 0) {
+            View child = getChildAt(0);
+            if (mPrimaryOrientation.getDecoratedEnd(child) < line) {
+                LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if (lp.mFullSpan) {
+                    for (int j = 0; j < mSpanCount; j++) {
+                        mSpans[j].popStart();
+                    }
+                } else {
+                    lp.mSpan.popStart();
+                }
+                removeAndRecycleView(child, recycler);
+            } else {
+                return;// done
+            }
+        }
+    }
+
+    private void recycleFromEnd(RecyclerView.Recycler recycler, int line) {
+        final int childCount = getChildCount();
+        int i;
+        for (i = childCount - 1; i >= 0; i--) {
+            View child = getChildAt(i);
+            if (mPrimaryOrientation.getDecoratedStart(child) > line) {
+                LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if (lp.mFullSpan) {
+                    for (int j = 0; j < mSpanCount; j++) {
+                        mSpans[j].popEnd();
+                    }
+                } else {
+                    lp.mSpan.popEnd();
+                }
+                removeAndRecycleView(child, recycler);
+            } else {
+                return;// done
+            }
+        }
+    }
+
+    /**
+     * @return True if last span is the first one we want to fill
+     */
+    private boolean preferLastSpan(int layoutDir) {
+        if (mOrientation == HORIZONTAL) {
+            return (layoutDir == LAYOUT_START) != mShouldReverseLayout;
+        }
+        return ((layoutDir == LAYOUT_START) == mShouldReverseLayout) == isLayoutRTL();
+    }
+
+    /**
+     * Finds the span for the next view.
+     */
+    private Span getNextSpan(LayoutState layoutState) {
+        final boolean preferLastSpan = preferLastSpan(layoutState.mLayoutDirection);
+        final int startIndex, endIndex, diff;
+        if (preferLastSpan) {
+            startIndex = mSpanCount - 1;
+            endIndex = -1;
+            diff = -1;
+        } else {
+            startIndex = 0;
+            endIndex = mSpanCount;
+            diff = 1;
+        }
+        if (layoutState.mLayoutDirection == LAYOUT_END) {
+            Span min = null;
+            int minLine = Integer.MAX_VALUE;
+            final int defaultLine = mPrimaryOrientation.getStartAfterPadding();
+            for (int i = startIndex; i != endIndex; i += diff) {
+                final Span other = mSpans[i];
+                int otherLine = other.getEndLine(defaultLine);
+                if (otherLine < minLine) {
+                    min = other;
+                    minLine = otherLine;
+                }
+            }
+            return min;
+        } else {
+            Span max = null;
+            int maxLine = Integer.MIN_VALUE;
+            final int defaultLine = mPrimaryOrientation.getEndAfterPadding();
+            for (int i = startIndex; i != endIndex; i += diff) {
+                final Span other = mSpans[i];
+                int otherLine = other.getStartLine(defaultLine);
+                if (otherLine > maxLine) {
+                    max = other;
+                    maxLine = otherLine;
+                }
+            }
+            return max;
+        }
+    }
+
+    @Override
+    public boolean canScrollVertically() {
+        return mOrientation == VERTICAL;
+    }
+
+    @Override
+    public boolean canScrollHorizontally() {
+        return mOrientation == HORIZONTAL;
+    }
+
+    @Override
+    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
+            RecyclerView.State state) {
+        return scrollBy(dx, recycler, state);
+    }
+
+    @Override
+    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+            RecyclerView.State state) {
+        return scrollBy(dy, recycler, state);
+    }
+
+    private int calculateScrollDirectionForPosition(int position) {
+        if (getChildCount() == 0) {
+            return mShouldReverseLayout ? LAYOUT_END : LAYOUT_START;
+        }
+        final int firstChildPos = getFirstChildPosition();
+        return position < firstChildPos != mShouldReverseLayout ? LAYOUT_START : LAYOUT_END;
+    }
+
+    @Override
+    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
+            int position) {
+        LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()) {
+            @Override
+            public PointF computeScrollVectorForPosition(int targetPosition) {
+                final int direction = calculateScrollDirectionForPosition(targetPosition);
+                if (direction == 0) {
+                    return null;
+                }
+                if (mOrientation == HORIZONTAL) {
+                    return new PointF(direction, 0);
+                } else {
+                    return new PointF(0, direction);
+                }
+            }
+        };
+        scroller.setTargetPosition(position);
+        startSmoothScroll(scroller);
+    }
+
+    @Override
+    public void scrollToPosition(int position) {
+        if (mPendingSavedState != null && mPendingSavedState.mAnchorPosition != position) {
+            mPendingSavedState.invalidateAnchorPositionInfo();
+        }
+        mPendingScrollPosition = position;
+        mPendingScrollPositionOffset = INVALID_OFFSET;
+        requestLayout();
+    }
+
+    /**
+     * Scroll to the specified adapter position with the given offset from layout start.
+     * <p>
+     * Note that scroll position change will not be reflected until the next layout call.
+     * <p>
+     * If you are just trying to make a position visible, use {@link #scrollToPosition(int)}.
+     *
+     * @param position Index (starting at 0) of the reference item.
+     * @param offset   The distance (in pixels) between the start edge of the item view and
+     *                 start edge of the RecyclerView.
+     * @see #setReverseLayout(boolean)
+     * @see #scrollToPosition(int)
+     */
+    public void scrollToPositionWithOffset(int position, int offset) {
+        if (mPendingSavedState != null) {
+            mPendingSavedState.invalidateAnchorPositionInfo();
+        }
+        mPendingScrollPosition = position;
+        mPendingScrollPositionOffset = offset;
+        requestLayout();
+    }
+
+    int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) {
+        ensureOrientationHelper();
+        final int referenceChildPosition;
+        if (dt > 0) { // layout towards end
+            mLayoutState.mLayoutDirection = LAYOUT_END;
+            mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_HEAD
+                    : ITEM_DIRECTION_TAIL;
+            referenceChildPosition = getLastChildPosition();
+        } else {
+            mLayoutState.mLayoutDirection = LAYOUT_START;
+            mLayoutState.mItemDirection = mShouldReverseLayout ? ITEM_DIRECTION_TAIL
+                    : ITEM_DIRECTION_HEAD;
+            referenceChildPosition = getFirstChildPosition();
+        }
+        mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection;
+        final int absDt = Math.abs(dt);
+        mLayoutState.mAvailable = absDt;
+        mLayoutState.mExtra = isSmoothScrolling() ? mPrimaryOrientation.getTotalSpace() : 0;
+        int consumed = fill(recycler, mLayoutState, state);
+        final int totalScroll;
+        if (absDt < consumed) {
+            totalScroll = dt;
+        } else if (dt < 0) {
+            totalScroll = -consumed;
+        } else { // dt > 0
+            totalScroll = consumed;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "asked " + dt + " scrolled" + totalScroll);
+        }
+
+        mPrimaryOrientation.offsetChildren(-totalScroll);
+        // always reset this if we scroll for a proper save instance state
+        mLastLayoutFromEnd = mShouldReverseLayout;
+        return totalScroll;
+    }
+
+    private int getLastChildPosition() {
+        final int childCount = getChildCount();
+        return childCount == 0 ? 0 : getPosition(getChildAt(childCount - 1));
+    }
+
+    private int getFirstChildPosition() {
+        final int childCount = getChildCount();
+        return childCount == 0 ? 0 : getPosition(getChildAt(0));
+    }
+
+    /**
+     * Finds the first View that can be used as an anchor View.
+     *
+     * @return Position of the View or 0 if it cannot find any such View.
+     */
+    private int findFirstReferenceChildPosition(int itemCount) {
+        final int limit = getChildCount();
+        for (int i = 0; i < limit; i++) {
+            final View view = getChildAt(i);
+            final int position = getPosition(view);
+            if (position >= 0 && position < itemCount) {
+                return position;
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Finds the last View that can be used as an anchor View.
+     *
+     * @return Position of the View or 0 if it cannot find any such View.
+     */
+    private int findLastReferenceChildPosition(int itemCount) {
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            final View view = getChildAt(i);
+            final int position = getPosition(view);
+            if (position >= 0 && position < itemCount) {
+                return position;
+            }
+        }
+        return 0;
+    }
+
+    @Override
+    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
+        return new LayoutParams(c, attrs);
+    }
+
+    @Override
+    public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+        if (lp instanceof ViewGroup.MarginLayoutParams) {
+            return new LayoutParams((ViewGroup.MarginLayoutParams) lp);
+        } else {
+            return new LayoutParams(lp);
+        }
+    }
+
+    @Override
+    public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
+        return lp instanceof LayoutParams;
+    }
+
+    public int getOrientation() {
+        return mOrientation;
+    }
+
+
+    /**
+     * LayoutParams used by StaggeredGridLayoutManager.
+     */
+    public static class LayoutParams extends RecyclerView.LayoutParams {
+
+        /**
+         * Span Id for Views that are not laid out yet.
+         */
+        public static final int INVALID_SPAN_ID = -1;
+
+        // Package scope to be able to access from tests.
+        Span mSpan;
+
+        boolean mFullSpan;
+
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+        }
+
+        public LayoutParams(int width, int height) {
+            super(width, height);
+        }
+
+        public LayoutParams(ViewGroup.MarginLayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(ViewGroup.LayoutParams source) {
+            super(source);
+        }
+
+        public LayoutParams(RecyclerView.LayoutParams source) {
+            super(source);
+        }
+
+        /**
+         * When set to true, the item will layout using all span area. That means, if orientation
+         * is vertical, the view will have full width; if orientation is horizontal, the view will
+         * have full height.
+         *
+         * @param fullSpan True if this item should traverse all spans.
+         */
+        public void setFullSpan(boolean fullSpan) {
+            mFullSpan = fullSpan;
+        }
+
+        /**
+         * Returns the Span index to which this View is assigned.
+         *
+         * @return The Span index of the View. If View is not yet assigned to any span, returns
+         * {@link #INVALID_SPAN_ID}.
+         */
+        public final int getSpanIndex() {
+            if (mSpan == null) {
+                return INVALID_SPAN_ID;
+            }
+            return mSpan.mIndex;
+        }
+    }
+
+    // Package scoped to access from tests.
+    class Span {
+
+        static final int INVALID_LINE = Integer.MIN_VALUE;
+        private ArrayList<View> mViews = new ArrayList<View>();
+        int mCachedStart = INVALID_LINE;
+        int mCachedEnd = INVALID_LINE;
+        int mDeletedSize = 0;
+        final int mIndex;
+
+        private Span(int index) {
+            mIndex = index;
+        }
+
+        int getStartLine(int def) {
+            if (mCachedStart != INVALID_LINE) {
+                return mCachedStart;
+            }
+            if (mViews.size() == 0) {
+                return def;
+            }
+            calculateCachedStart();
+            return mCachedStart;
+        }
+
+        void calculateCachedStart() {
+            final View startView = mViews.get(0);
+            final LayoutParams lp = getLayoutParams(startView);
+            mCachedStart = mPrimaryOrientation.getDecoratedStart(startView);
+            if (lp.mFullSpan) {
+                LazySpanLookup.FullSpanItem fsi = mLazySpanLookup
+                        .getFullSpanItem(lp.getViewPosition());
+                if (fsi != null && fsi.mGapDir == LAYOUT_START) {
+                    mCachedStart -= fsi.getGapForSpan(mIndex);
+                }
+            }
+        }
+
+        // Use this one when default value does not make sense and not having a value means a bug.
+        int getStartLine() {
+            if (mCachedStart != INVALID_LINE) {
+                return mCachedStart;
+            }
+            calculateCachedStart();
+            return mCachedStart;
+        }
+
+        int getEndLine(int def) {
+            if (mCachedEnd != INVALID_LINE) {
+                return mCachedEnd;
+            }
+            final int size = mViews.size();
+            if (size == 0) {
+                return def;
+            }
+            calculateCachedEnd();
+            return mCachedEnd;
+        }
+
+        void calculateCachedEnd() {
+            final View endView = mViews.get(mViews.size() - 1);
+            final LayoutParams lp = getLayoutParams(endView);
+            mCachedEnd = mPrimaryOrientation.getDecoratedEnd(endView);
+            if (lp.mFullSpan) {
+                LazySpanLookup.FullSpanItem fsi = mLazySpanLookup
+                        .getFullSpanItem(lp.getViewPosition());
+                if (fsi != null && fsi.mGapDir == LAYOUT_END) {
+                    mCachedEnd += fsi.getGapForSpan(mIndex);
+                }
+            }
+        }
+
+        // Use this one when default value does not make sense and not having a value means a bug.
+        int getEndLine() {
+            if (mCachedEnd != INVALID_LINE) {
+                return mCachedEnd;
+            }
+            calculateCachedEnd();
+            return mCachedEnd;
+        }
+
+        void prependToSpan(View view) {
+            LayoutParams lp = getLayoutParams(view);
+            lp.mSpan = this;
+            mViews.add(0, view);
+            mCachedStart = INVALID_LINE;
+            if (mViews.size() == 1) {
+                mCachedEnd = INVALID_LINE;
+            }
+            if (lp.isItemRemoved() || lp.isItemChanged()) {
+                mDeletedSize += mPrimaryOrientation.getDecoratedMeasurement(view);
+            }
+        }
+
+        void appendToSpan(View view) {
+            LayoutParams lp = getLayoutParams(view);
+            lp.mSpan = this;
+            mViews.add(view);
+            mCachedEnd = INVALID_LINE;
+            if (mViews.size() == 1) {
+                mCachedStart = INVALID_LINE;
+            }
+            if (lp.isItemRemoved() || lp.isItemChanged()) {
+                mDeletedSize += mPrimaryOrientation.getDecoratedMeasurement(view);
+            }
+        }
+
+        // Useful method to preserve positions on a re-layout.
+        void cacheReferenceLineAndClear(boolean reverseLayout, int offset) {
+            int reference;
+            if (reverseLayout) {
+                reference = getEndLine(INVALID_LINE);
+            } else {
+                reference = getStartLine(INVALID_LINE);
+            }
+            clear();
+            if (reference == INVALID_LINE) {
+                return;
+            }
+            if ((reverseLayout && reference < mPrimaryOrientation.getEndAfterPadding()) ||
+                    (!reverseLayout && reference > mPrimaryOrientation.getStartAfterPadding()) ) {
+                return;
+            }
+            if (offset != INVALID_OFFSET) {
+                reference += offset;
+            }
+            mCachedStart = mCachedEnd = reference;
+        }
+
+        void clear() {
+            mViews.clear();
+            invalidateCache();
+            mDeletedSize = 0;
+        }
+
+        void invalidateCache() {
+            mCachedStart = INVALID_LINE;
+            mCachedEnd = INVALID_LINE;
+        }
+
+        void setLine(int line) {
+            mCachedEnd = mCachedStart = line;
+        }
+
+        void popEnd() {
+            final int size = mViews.size();
+            View end = mViews.remove(size - 1);
+            final LayoutParams lp = getLayoutParams(end);
+            lp.mSpan = null;
+            if (lp.isItemRemoved() || lp.isItemChanged()) {
+                mDeletedSize -= mPrimaryOrientation.getDecoratedMeasurement(end);
+            }
+            if (size == 1) {
+                mCachedStart = INVALID_LINE;
+            }
+            mCachedEnd = INVALID_LINE;
+        }
+
+        void popStart() {
+            View start = mViews.remove(0);
+            final LayoutParams lp = getLayoutParams(start);
+            lp.mSpan = null;
+            if (mViews.size() == 0) {
+                mCachedEnd = INVALID_LINE;
+            }
+            if (lp.isItemRemoved() || lp.isItemChanged()) {
+                mDeletedSize -= mPrimaryOrientation.getDecoratedMeasurement(start);
+            }
+            mCachedStart = INVALID_LINE;
+        }
+
+        public int getDeletedSize() {
+            return mDeletedSize;
+        }
+
+        LayoutParams getLayoutParams(View view) {
+            return (LayoutParams) view.getLayoutParams();
+        }
+
+        void onOffset(int dt) {
+            if (mCachedStart != INVALID_LINE) {
+                mCachedStart += dt;
+            }
+            if (mCachedEnd != INVALID_LINE) {
+                mCachedEnd += dt;
+            }
+        }
+
+        // normalized offset is how much this span can scroll
+        int getNormalizedOffset(int dt, int targetStart, int targetEnd) {
+            if (mViews.size() == 0) {
+                return 0;
+            }
+            if (dt < 0) {
+                final int endSpace = getEndLine() - targetEnd;
+                if (endSpace <= 0) {
+                    return 0;
+                }
+                return -dt > endSpace ? -endSpace : dt;
+            } else {
+                final int startSpace = targetStart - getStartLine();
+                if (startSpace <= 0) {
+                    return 0;
+                }
+                return startSpace < dt ? startSpace : dt;
+            }
+        }
+
+        /**
+         * Returns if there is no child between start-end lines
+         *
+         * @param start The start line
+         * @param end   The end line
+         * @return true if a new child can be added between start and end
+         */
+        boolean isEmpty(int start, int end) {
+            final int count = mViews.size();
+            for (int i = 0; i < count; i++) {
+                final View view = mViews.get(i);
+                if (mPrimaryOrientation.getDecoratedStart(view) < end &&
+                        mPrimaryOrientation.getDecoratedEnd(view) > start) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public int findFirstVisibleItemPosition() {
+            return mReverseLayout
+                    ? findOneVisibleChild(mViews.size() -1, -1, false)
+                    : findOneVisibleChild(0, mViews.size(), false);
+        }
+
+        public int findFirstCompletelyVisibleItemPosition() {
+            return mReverseLayout
+                    ? findOneVisibleChild(mViews.size() -1, -1, true)
+                    : findOneVisibleChild(0, mViews.size(), true);
+        }
+
+        public int findLastVisibleItemPosition() {
+            return mReverseLayout
+                    ? findOneVisibleChild(0, mViews.size(), false)
+                    : findOneVisibleChild(mViews.size() -1, -1, false);
+        }
+
+        public int findLastCompletelyVisibleItemPosition() {
+            return mReverseLayout
+                    ? findOneVisibleChild(0, mViews.size(), true)
+                    : findOneVisibleChild(mViews.size() -1, -1, true);
+        }
+
+        int findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) {
+            final int start = mPrimaryOrientation.getStartAfterPadding();
+            final int end = mPrimaryOrientation.getEndAfterPadding();
+            final int next = toIndex > fromIndex ? 1 : -1;
+            for (int i = fromIndex; i != toIndex; i += next) {
+                final View child = mViews.get(i);
+                final int childStart = mPrimaryOrientation.getDecoratedStart(child);
+                final int childEnd = mPrimaryOrientation.getDecoratedEnd(child);
+                if (childStart < end && childEnd > start) {
+                    if (completelyVisible) {
+                        if (childStart >= start && childEnd <= end) {
+                            return getPosition(child);
+                        }
+                    } else {
+                        return getPosition(child);
+                    }
+                }
+            }
+            return NO_POSITION;
+        }
+    }
+
+    /**
+     * An array of mappings from adapter position to span.
+     * This only grows when a write happens and it grows up to the size of the adapter.
+     */
+    static class LazySpanLookup {
+
+        private static final int MIN_SIZE = 10;
+        int[] mData;
+        int mAdapterSize; // we don't want to grow beyond that, unless it grows
+        List<FullSpanItem> mFullSpanItems;
+
+
+        /**
+         * Invalidates everything after this position, including full span information
+         */
+        int forceInvalidateAfter(int position) {
+            if (mFullSpanItems != null) {
+                for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
+                    FullSpanItem fsi = mFullSpanItems.get(i);
+                    if (fsi.mPosition >= position) {
+                        mFullSpanItems.remove(i);
+                    }
+                }
+            }
+            return invalidateAfter(position);
+        }
+
+        /**
+         * returns end position for invalidation.
+         */
+        int invalidateAfter(int position) {
+            if (mData == null) {
+                return RecyclerView.NO_POSITION;
+            }
+            if (position >= mData.length) {
+                return RecyclerView.NO_POSITION;
+            }
+            int endPosition = invalidateFullSpansAfter(position);
+            if (endPosition == RecyclerView.NO_POSITION) {
+                Arrays.fill(mData, position, mData.length, LayoutParams.INVALID_SPAN_ID);
+                return mData.length;
+            } else {
+                // just invalidate items in between
+                Arrays.fill(mData, position, endPosition + 1, LayoutParams.INVALID_SPAN_ID);
+                return endPosition + 1;
+            }
+        }
+
+        int getSpan(int position) {
+            if (mData == null || position >= mData.length) {
+                return LayoutParams.INVALID_SPAN_ID;
+            } else {
+                return mData[position];
+            }
+        }
+
+        void setSpan(int position, Span span) {
+            ensureSize(position);
+            mData[position] = span.mIndex;
+        }
+
+        int sizeForPosition(int position) {
+            int len = mData.length;
+            while (len <= position) {
+                len *= 2;
+            }
+            if (len > mAdapterSize) {
+                len = mAdapterSize;
+            }
+            return len;
+        }
+
+        void ensureSize(int position) {
+            if (mData == null) {
+                mData = new int[Math.max(position, MIN_SIZE) + 1];
+                Arrays.fill(mData, LayoutParams.INVALID_SPAN_ID);
+            } else if (position >= mData.length) {
+                int[] old = mData;
+                mData = new int[sizeForPosition(position)];
+                System.arraycopy(old, 0, mData, 0, old.length);
+                Arrays.fill(mData, old.length, mData.length, LayoutParams.INVALID_SPAN_ID);
+            }
+        }
+
+        void clear() {
+            if (mData != null) {
+                Arrays.fill(mData, LayoutParams.INVALID_SPAN_ID);
+            }
+            mFullSpanItems = null;
+        }
+
+        void offsetForRemoval(int positionStart, int itemCount) {
+            if (mData == null || positionStart >= mData.length) {
+                return;
+            }
+            ensureSize(positionStart + itemCount);
+            System.arraycopy(mData, positionStart + itemCount, mData, positionStart,
+                    mData.length - positionStart - itemCount);
+            Arrays.fill(mData, mData.length - itemCount, mData.length,
+                    LayoutParams.INVALID_SPAN_ID);
+            offsetFullSpansForRemoval(positionStart, itemCount);
+        }
+
+        private void offsetFullSpansForRemoval(int positionStart, int itemCount) {
+            if (mFullSpanItems == null) {
+                return;
+            }
+            final int end = positionStart + itemCount;
+            for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
+                FullSpanItem fsi = mFullSpanItems.get(i);
+                if (fsi.mPosition < positionStart) {
+                    continue;
+                }
+                if (fsi.mPosition < end) {
+                    mFullSpanItems.remove(i);
+                } else {
+                    fsi.mPosition -= itemCount;
+                }
+            }
+        }
+
+        void offsetForAddition(int positionStart, int itemCount) {
+            if (mData == null || positionStart >= mData.length) {
+                return;
+            }
+            ensureSize(positionStart + itemCount);
+            System.arraycopy(mData, positionStart, mData, positionStart + itemCount,
+                    mData.length - positionStart - itemCount);
+            Arrays.fill(mData, positionStart, positionStart + itemCount,
+                    LayoutParams.INVALID_SPAN_ID);
+            offsetFullSpansForAddition(positionStart, itemCount);
+        }
+
+        private void offsetFullSpansForAddition(int positionStart, int itemCount) {
+            if (mFullSpanItems == null) {
+                return;
+            }
+            for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
+                FullSpanItem fsi = mFullSpanItems.get(i);
+                if (fsi.mPosition < positionStart) {
+                    continue;
+                }
+                fsi.mPosition += itemCount;
+            }
+        }
+
+        /**
+         * Returns when invalidation should end. e.g. hitting a full span position.
+         * Returned position SHOULD BE invalidated.
+         */
+        private int invalidateFullSpansAfter(int position) {
+            if (mFullSpanItems == null) {
+                return RecyclerView.NO_POSITION;
+            }
+            final FullSpanItem item = getFullSpanItem(position);
+            // if there is an fsi at this position, get rid of it.
+            if (item != null) {
+                mFullSpanItems.remove(item);
+            }
+            int nextFsiIndex = -1;
+            final int count = mFullSpanItems.size();
+            for (int i = 0; i < count; i++) {
+                FullSpanItem fsi = mFullSpanItems.get(i);
+                if (fsi.mPosition >= position) {
+                    nextFsiIndex = i;
+                    break;
+                }
+            }
+            if (nextFsiIndex != -1) {
+                FullSpanItem fsi = mFullSpanItems.get(nextFsiIndex);
+                mFullSpanItems.remove(nextFsiIndex);
+                return fsi.mPosition;
+            }
+            return RecyclerView.NO_POSITION;
+        }
+
+        public void addFullSpanItem(FullSpanItem fullSpanItem) {
+            if (mFullSpanItems == null) {
+                mFullSpanItems = new ArrayList<FullSpanItem>();
+            }
+            final int size = mFullSpanItems.size();
+            for (int i = 0; i < size; i++) {
+                FullSpanItem other = mFullSpanItems.get(i);
+                if (other.mPosition == fullSpanItem.mPosition) {
+                    if (DEBUG) {
+                        throw new IllegalStateException("two fsis for same position");
+                    } else {
+                        mFullSpanItems.remove(i);
+                    }
+                }
+                if (other.mPosition >= fullSpanItem.mPosition) {
+                    mFullSpanItems.add(i, fullSpanItem);
+                    return;
+                }
+            }
+            // if it is not added to a position.
+            mFullSpanItems.add(fullSpanItem);
+        }
+
+        public FullSpanItem getFullSpanItem(int position) {
+            if (mFullSpanItems == null) {
+                return null;
+            }
+            for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
+                final FullSpanItem fsi = mFullSpanItems.get(i);
+                if (fsi.mPosition == position) {
+                    return fsi;
+                }
+            }
+            return null;
+        }
+
+        /**
+         * @param minPos inclusive
+         * @param maxPos exclusive
+         * @param gapDir if not 0, returns FSIs on in that direction
+         */
+        public FullSpanItem getFirstFullSpanItemInRange(int minPos, int maxPos, int gapDir) {
+            if (mFullSpanItems == null) {
+                return null;
+            }
+            for (int i = 0; i < mFullSpanItems.size(); i++) {
+                FullSpanItem fsi = mFullSpanItems.get(i);
+                if (fsi.mPosition >= maxPos) {
+                    return null;
+                }
+                if (fsi.mPosition >= minPos && (gapDir == 0 || fsi.mGapDir == gapDir)) {
+                    return fsi;
+                }
+            }
+            return null;
+        }
+
+        /**
+         * We keep information about full span items because they may create gaps in the UI.
+         */
+        static class FullSpanItem implements Parcelable {
+
+            int mPosition;
+            int mGapDir;
+            int[] mGapPerSpan;
+
+            public FullSpanItem(Parcel in) {
+                mPosition = in.readInt();
+                mGapDir = in.readInt();
+                int spanCount = in.readInt();
+                if (spanCount > 0) {
+                    mGapPerSpan = new int[spanCount];
+                    in.readIntArray(mGapPerSpan);
+                }
+            }
+
+            public FullSpanItem() {
+            }
+
+            int getGapForSpan(int spanIndex) {
+                return mGapPerSpan == null ? 0 : mGapPerSpan[spanIndex];
+            }
+
+            public void invalidateSpanGaps() {
+                mGapPerSpan = null;
+            }
+
+            @Override
+            public int describeContents() {
+                return 0;
+            }
+
+            @Override
+            public void writeToParcel(Parcel dest, int flags) {
+                dest.writeInt(mPosition);
+                dest.writeInt(mGapDir);
+                if (mGapPerSpan != null && mGapPerSpan.length > 0) {
+                    dest.writeInt(mGapPerSpan.length);
+                    dest.writeIntArray(mGapPerSpan);
+                } else {
+                    dest.writeInt(0);
+                }
+            }
+
+            @Override
+            public String toString() {
+                return "FullSpanItem{" +
+                        "mPosition=" + mPosition +
+                        ", mGapDir=" + mGapDir +
+                        ", mGapPerSpan=" + Arrays.toString(mGapPerSpan) +
+                        '}';
+            }
+
+            public static final Parcelable.Creator<FullSpanItem> CREATOR
+                    = new Parcelable.Creator<FullSpanItem>() {
+                @Override
+                public FullSpanItem createFromParcel(Parcel in) {
+                    return new FullSpanItem(in);
+                }
+
+                @Override
+                public FullSpanItem[] newArray(int size) {
+                    return new FullSpanItem[size];
+                }
+            };
+        }
+    }
+
+    static class SavedState implements Parcelable {
+
+        int mAnchorPosition;
+        int mVisibleAnchorPosition; // Replacement for span info when spans are invalidated
+        int mSpanOffsetsSize;
+        int[] mSpanOffsets;
+        int mSpanLookupSize;
+        int[] mSpanLookup;
+        List<LazySpanLookup.FullSpanItem> mFullSpanItems;
+        boolean mReverseLayout;
+        boolean mAnchorLayoutFromEnd;
+        boolean mLastLayoutRTL;
+
+        public SavedState() {
+        }
+
+        SavedState(Parcel in) {
+            mAnchorPosition = in.readInt();
+            mVisibleAnchorPosition = in.readInt();
+            mSpanOffsetsSize = in.readInt();
+            if (mSpanOffsetsSize > 0) {
+                mSpanOffsets = new int[mSpanOffsetsSize];
+                in.readIntArray(mSpanOffsets);
+            }
+
+            mSpanLookupSize = in.readInt();
+            if (mSpanLookupSize > 0) {
+                mSpanLookup = new int[mSpanLookupSize];
+                in.readIntArray(mSpanLookup);
+            }
+            mReverseLayout = in.readInt() == 1;
+            mAnchorLayoutFromEnd = in.readInt() == 1;
+            mLastLayoutRTL = in.readInt() == 1;
+            mFullSpanItems = in.readArrayList(
+                    LazySpanLookup.FullSpanItem.class.getClassLoader());
+        }
+
+        public SavedState(SavedState other) {
+            mSpanOffsetsSize = other.mSpanOffsetsSize;
+            mAnchorPosition = other.mAnchorPosition;
+            mVisibleAnchorPosition = other.mVisibleAnchorPosition;
+            mSpanOffsets = other.mSpanOffsets;
+            mSpanLookupSize = other.mSpanLookupSize;
+            mSpanLookup = other.mSpanLookup;
+            mReverseLayout = other.mReverseLayout;
+            mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd;
+            mLastLayoutRTL = other.mLastLayoutRTL;
+            mFullSpanItems = other.mFullSpanItems;
+        }
+
+        void invalidateSpanInfo() {
+            mSpanOffsets = null;
+            mSpanOffsetsSize = 0;
+            mSpanLookupSize = 0;
+            mSpanLookup = null;
+            mFullSpanItems = null;
+        }
+
+        void invalidateAnchorPositionInfo() {
+            mSpanOffsets = null;
+            mSpanOffsetsSize = 0;
+            mAnchorPosition = NO_POSITION;
+            mVisibleAnchorPosition = NO_POSITION;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mAnchorPosition);
+            dest.writeInt(mVisibleAnchorPosition);
+            dest.writeInt(mSpanOffsetsSize);
+            if (mSpanOffsetsSize > 0) {
+                dest.writeIntArray(mSpanOffsets);
+            }
+            dest.writeInt(mSpanLookupSize);
+            if (mSpanLookupSize > 0) {
+                dest.writeIntArray(mSpanLookup);
+            }
+            dest.writeInt(mReverseLayout ? 1 : 0);
+            dest.writeInt(mAnchorLayoutFromEnd ? 1 : 0);
+            dest.writeInt(mLastLayoutRTL ? 1 : 0);
+            dest.writeList(mFullSpanItems);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            @Override
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            @Override
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    /**
+     * Data class to hold the information about an anchor position which is used in onLayout call.
+     */
+    private class AnchorInfo {
+
+        int mPosition;
+        int mOffset;
+        boolean mLayoutFromEnd;
+        boolean mInvalidateOffsets;
+
+        void reset() {
+            mPosition = NO_POSITION;
+            mOffset = INVALID_OFFSET;
+            mLayoutFromEnd = false;
+            mInvalidateOffsets = false;
+        }
+
+        void assignCoordinateFromPadding() {
+            mOffset = mLayoutFromEnd ? mPrimaryOrientation.getEndAfterPadding()
+                    : mPrimaryOrientation.getStartAfterPadding();
+        }
+
+        void assignCoordinateFromPadding(int addedDistance) {
+            if (mLayoutFromEnd) {
+                mOffset = mPrimaryOrientation.getEndAfterPadding() - addedDistance;
+            } else {
+                mOffset = mPrimaryOrientation.getStartAfterPadding() + addedDistance;
+            }
+        }
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/AdapterHelperTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/AdapterHelperTest.java
new file mode 100644
index 0000000..12ac57e
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/AdapterHelperTest.java
@@ -0,0 +1,792 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static android.support.v7.widget.RecyclerView.*;
+
+public class AdapterHelperTest extends AndroidTestCase {
+
+    private static final boolean DEBUG = false;
+
+    private static final String TAG = "AHT";
+
+    List<ViewHolder> mViewHolders;
+
+    AdapterHelper mAdapterHelper;
+
+    List<AdapterHelper.UpdateOp> mFirstPassUpdates, mSecondPassUpdates;
+
+    TestAdapter mTestAdapter;
+
+    TestAdapter mPreProcessClone; // we clone adapter pre-process to run operations to see result
+
+    private List<TestAdapter.Item> mPreLayoutItems;
+
+    @Override
+    protected void setUp() throws Exception {
+        cleanState();
+    }
+
+    private void cleanState() {
+        mPreLayoutItems = new ArrayList<TestAdapter.Item>();
+        mViewHolders = new ArrayList<ViewHolder>();
+        mFirstPassUpdates = new ArrayList<AdapterHelper.UpdateOp>();
+        mSecondPassUpdates = new ArrayList<AdapterHelper.UpdateOp>();
+        mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() {
+            @Override
+            public RecyclerView.ViewHolder findViewHolder(int position) {
+                for (ViewHolder vh : mViewHolders) {
+                    if (vh.mPosition == position) {
+                        return vh;
+                    }
+                }
+                return null;
+            }
+
+            @Override
+            public void offsetPositionsForRemovingInvisible(int positionStart, int itemCount) {
+                final int positionEnd = positionStart + itemCount;
+                for (ViewHolder holder : mViewHolders) {
+                    if (holder.mPosition >= positionEnd) {
+                        holder.offsetPosition(-itemCount, true);
+                    } else if (holder.mPosition >= positionStart) {
+                        holder.addFlags(ViewHolder.FLAG_REMOVED);
+                        holder.offsetPosition(-itemCount, true);
+                    }
+                }
+            }
+
+            @Override
+            public void offsetPositionsForRemovingLaidOutOrNewView(int positionStart,
+                    int itemCount) {
+                final int positionEnd = positionStart + itemCount;
+                for (ViewHolder holder : mViewHolders) {
+                    if (holder.mPosition >= positionEnd) {
+                        holder.offsetPosition(-itemCount, false);
+                    } else if (holder.mPosition >= positionStart) {
+                        holder.addFlags(ViewHolder.FLAG_REMOVED);
+                        holder.offsetPosition(-itemCount, false);
+                    }
+                }
+            }
+
+            @Override
+            public void markViewHoldersUpdated(int positionStart, int itemCount) {
+                final int positionEnd = positionStart + itemCount;
+                for (ViewHolder holder : mViewHolders) {
+                    if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) {
+                        holder.addFlags(ViewHolder.FLAG_UPDATE);
+                    }
+                }
+            }
+
+            @Override
+            public void onDispatchFirstPass(AdapterHelper.UpdateOp updateOp) {
+                if (DEBUG) {
+                    Log.d(TAG, "first pass:" + updateOp.toString());
+                }
+                for (ViewHolder viewHolder : mViewHolders) {
+                    for (int i = 0; i < updateOp.itemCount; i ++) {
+                        assertFalse("update op should not match any existing view holders",
+                                viewHolder.getPosition() == updateOp.positionStart + i);
+                    }
+                }
+
+                mFirstPassUpdates.add(updateOp);
+            }
+
+            @Override
+            public void onDispatchSecondPass(AdapterHelper.UpdateOp updateOp) {
+                if (DEBUG) {
+                    Log.d(TAG, "second pass:" + updateOp.toString());
+                }
+                mSecondPassUpdates.add(updateOp);
+            }
+
+            @Override
+            public void offsetPositionsForAdd(int positionStart, int itemCount) {
+                for (ViewHolder holder : mViewHolders) {
+                    if (holder != null && holder.mPosition >= positionStart) {
+                        holder.offsetPosition(itemCount, false);
+                    }
+                }
+            }
+
+            @Override
+            public void offsetPositionsForMove(int from, int to) {
+                final int start, end, inBetweenOffset;
+                if (from < to) {
+                    start = from;
+                    end = to;
+                    inBetweenOffset = -1;
+                } else {
+                    start = to;
+                    end = from;
+                    inBetweenOffset = 1;
+                }
+                for (ViewHolder holder : mViewHolders) {
+                    if (holder == null || holder.mPosition < start || holder.mPosition > end) {
+                        continue;
+                    }
+                    holder.offsetPosition(inBetweenOffset, false);
+                }
+            }
+        }, true) {
+            @Override
+            void createFakeAddForRemovedMove(int adapterIndex, int pendingUpdateIndex) {
+                int addsBefore = 0;
+                for (int i = 0; i < pendingUpdateIndex; i ++) {
+                    final UpdateOp updateOp = mPendingUpdates.get(i);
+                    if (updateOp.cmd == UpdateOp.ADD) {
+                        addsBefore += updateOp.itemCount;
+                    }
+                }
+                mTestAdapter.createFakeItemAt(addsBefore);
+                super.createFakeAddForRemovedMove(adapterIndex, pendingUpdateIndex);
+            }
+        };
+    }
+
+    void setupBasic(int count, int visibleStart, int visibleCount) {
+        if (DEBUG) {
+            Log.d(TAG, "setupBasic(" + count + "," + visibleStart + "," + visibleCount + ");");
+        }
+        mTestAdapter = new TestAdapter(count, mAdapterHelper);
+        for (int i = 0; i < visibleCount; i++) {
+            addViewHolder(visibleStart + i);
+        }
+        mPreProcessClone = mTestAdapter.createCopy();
+    }
+
+    private void addViewHolder(int posiiton) {
+        ViewHolder viewHolder = new RecyclerViewBasicTest.MockViewHolder(
+                new TextView(getContext()));
+        viewHolder.mPosition = posiiton;
+        mViewHolders.add(viewHolder);
+    }
+
+    public void testFindPositionOffsetInPreLayout() {
+        setupBasic(50, 25, 10);
+        rm(24, 5);
+        mAdapterHelper.preProcess();
+        // since 25 is invisible, we offset by one while checking
+        assertEquals("find position for view 23",
+                23, mAdapterHelper.findPositionOffset(23));
+        assertEquals("find position for view 24",
+                -1, mAdapterHelper.findPositionOffset(24));
+        assertEquals("find position for view 25",
+                -1, mAdapterHelper.findPositionOffset(25));
+        assertEquals("find position for view 26",
+                -1, mAdapterHelper.findPositionOffset(26));
+        assertEquals("find position for view 27",
+                -1, mAdapterHelper.findPositionOffset(27));
+        assertEquals("find position for view 28",
+                24, mAdapterHelper.findPositionOffset(28));
+        assertEquals("find position for view 29",
+                25, mAdapterHelper.findPositionOffset(29));
+    }
+
+    public void testSinglePass() {
+        setupBasic(10, 2, 3);
+        add(2, 1);
+        rm(1, 2);
+        add(1, 5);
+        mAdapterHelper.consumeUpdatesInOnePass();
+        assertDispatch(0, 3);
+    }
+
+    public void testDeleteVisible() {
+        setupBasic(10, 2, 3);
+        rm(2, 1);
+        preProcess();
+        assertDispatch(0, 1);
+    }
+
+    public void testDeleteInvisible() {
+        setupBasic(10, 3, 4);
+        rm(2, 1);
+        preProcess();
+        assertDispatch(1, 0);
+    }
+
+    public void testAddCount() {
+        setupBasic(0, 0, 0);
+        add(0, 1);
+        assertEquals(1, mAdapterHelper.mPendingUpdates.size());
+    }
+
+    public void testDeleteCount() {
+        setupBasic(1, 0, 0);
+        rm(0, 1);
+        assertEquals(1, mAdapterHelper.mPendingUpdates.size());
+    }
+
+    public void testAddProcess() {
+        setupBasic(0, 0, 0);
+        add(0, 1);
+        preProcess();
+        assertEquals(0, mAdapterHelper.mPendingUpdates.size());
+    }
+
+    public void testAddRemoveSeparate() {
+        setupBasic(10, 2, 2);
+        add(6, 1);
+        rm(5, 1);
+        preProcess();
+        assertDispatch(1, 1);
+    }
+
+    public void testScenario1() {
+        setupBasic(10, 3, 2);
+        rm(4, 1);
+        rm(3, 1);
+        rm(3, 1);
+        preProcess();
+        assertDispatch(1, 2);
+    }
+
+    public void testDivideDelete() {
+        setupBasic(10, 3, 4);
+        rm(2, 2);
+        preProcess();
+        assertDispatch(1, 1);
+    }
+
+    public void testScenario2() {
+        setupBasic(10, 3, 3); // 3-4-5
+        add(4, 2); // 3 a b 4 5
+        rm(0, 1); // (0) 3(2) a(3) b(4) 4(3) 5(4)
+        rm(1, 3); // (1,2) (x) a(1) b(2) 4(3)
+        preProcess();
+        assertDispatch(2, 2);
+    }
+
+    public void testScenario3() {
+        setupBasic(10, 2, 2);
+        rm(0, 5);
+        preProcess();
+        assertDispatch(2, 1);
+        assertOps(mFirstPassUpdates, rmOp(0, 2), rmOp(2, 1));
+        assertOps(mSecondPassUpdates, rmOp(0, 2));
+    }
+    // TODO test MOVE then remove items in between.
+    // TODO test MOVE then remove it, make sure it is not dispatched
+
+    public void testScenario4() {
+        setupBasic(5, 0, 5);
+        // 0 1 2 3 4
+        // 0 1 2 a b 3 4
+        // 0 2 a b 3 4
+        // 0 c d 2 a b 3 4
+        // 0 c d 2 a 4
+        // c d 2 a 4
+        // pre: 0 1 2 3 4
+        add(3, 2);
+        rm(1, 1);
+        add(1, 2);
+        rm(5, 2);
+        rm(0, 1);
+        preProcess();
+    }
+
+    public void testScenario5() {
+        setupBasic(5, 0, 5);
+        // 0 1 2 3 4
+        // 0 1 2 a b 3 4
+        // 0 1 b 3 4
+        // pre: 0 1 2 3 4
+        // pre w/ adap: 0 1 2 b 3 4
+        add(3, 2);
+        rm(2, 2);
+        preProcess();
+    }
+
+    public void testScenario6() {
+//        setupBasic(47, 19, 24);
+//        mv(11, 12);
+//        add(24, 16);
+//        rm(9, 3);
+        setupBasic(10, 5, 3);
+        mv(2, 3);
+        add(6, 4);
+        rm(4, 1);
+        preProcess();
+    }
+
+    public void testScenario8() {
+        setupBasic(68, 51, 13);
+        mv(22, 11);
+        mv(22, 52);
+        rm(37, 19);
+        add(12, 38);
+        preProcess();
+    }
+
+    public void testScenario9() {
+        setupBasic(44, 3, 7);
+        add(7, 21);
+        rm(31, 3);
+        rm(32, 11);
+        mv(29, 5);
+        mv(30, 32);
+        add(25, 32);
+        rm(15, 66);
+        preProcess();
+    }
+
+    public void testScenario10() {
+        setupBasic(14, 10, 3);
+        rm(4, 4);
+        add(5, 11);
+        mv(5, 18);
+        rm(2, 9);
+        preProcess();
+    }
+
+    public void testScenario11() {
+        setupBasic(78, 3, 64);
+        mv(34, 28);
+        add(1, 11);
+        rm(9, 74);
+        preProcess();
+    }
+
+    public void testScenario12() {
+        setupBasic(38, 9, 7);
+        rm(26, 3);
+        mv(29, 15);
+        rm(30, 1);
+        preProcess();
+    }
+
+    public void testScenario13() {
+        setupBasic(49, 41, 3);
+        rm(30, 13);
+        add(4, 10);
+        mv(3, 38);
+        mv(20, 17);
+        rm(18, 23);
+        preProcess();
+    }
+
+    public void testScenario14() {
+        setupBasic(24, 3, 11);
+        rm(2, 15);
+        mv(2, 1);
+        add(2, 34);
+        add(11, 3);
+        rm(10, 25);
+        rm(13, 6);
+        rm(4, 4);
+        rm(6, 4);
+        preProcess();
+    }
+
+    public void testScenario15() {
+        setupBasic(10, 8, 1);
+        mv(6, 1);
+        mv(1, 4);
+        rm(3, 1);
+        preProcess();
+    }
+
+    public void testScenario16() {
+        setupBasic(10, 3, 3);
+        rm(2, 1);
+        rm(1, 7);
+        rm(0, 1);
+        preProcess();
+    }
+
+    public void testScenario17() {
+        setupBasic(10, 8, 1);
+        mv(1, 0);
+        mv(5, 1);
+        rm(1, 7);
+        preProcess();
+    }
+
+    public void testScenario18() throws InterruptedException {
+        setupBasic(10, 1, 4);
+        add(2, 11);
+        rm(16, 1);
+        add(3, 1);
+        rm(9, 10);
+        preProcess();
+    }
+
+    public void testScenario19() {
+        setupBasic(10,8,1);
+        mv(9,7);
+        mv(9,3);
+        rm(5,4);
+        preProcess();
+    }
+
+    public void testScenario20() {
+        setupBasic(10,7,1);
+        mv(9,1);
+        mv(3,9);
+        rm(7,2);
+        preProcess();
+    }
+
+    public void testScenario21() {
+        setupBasic(10,5,2);
+        mv(1,0);
+        mv(9,1);
+        rm(2,3);
+        preProcess();
+    }
+
+    public void testScenario22() {
+        setupBasic(10,7,2);
+        add(2,16);
+        mv(20,9);
+        rm(17,6);
+        preProcess();
+    }
+
+    public void testScenario23() {
+        setupBasic(10,5,3);
+        mv(9,6);
+        add(4,15);
+        rm(21,3);
+        preProcess();
+    }
+
+    public void testScenario24() {
+        setupBasic(10,1,6);
+        add(6,5);
+        mv(14,6);
+        rm(7,6);
+        preProcess();
+    }
+
+    public void testScenario25() {
+        setupBasic(10,3,4);
+        mv(3,9);
+        mv(2,9);
+        rm(5,4);
+        preProcess();
+    }
+
+    public void testScenario26() {
+        setupBasic(10,4,4);
+        rm(3,5);
+        mv(2,0);
+        mv(1,0);
+        rm(1,1);
+        mv(0,2);
+        preProcess();
+    }
+
+    public void testScenario27() {
+        setupBasic(10,0,3);
+        mv(9,4);
+        mv(8,4);
+        add(7,6);
+        rm(5,5);
+        preProcess();
+    }
+
+    public void testMoveAdded() {
+        setupBasic(10, 2, 2);
+        add(3, 5);
+        mv(4, 2);
+        preProcess();
+    }
+
+    public void testRandom() {
+        Random random = new Random(System.nanoTime());
+        for (int i = 0; i < 100; i++) {
+            randomTest(random, i + 10);
+        }
+    }
+
+    public void randomTest(Random random, int opCount) {
+        cleanState();
+        if (DEBUG) {
+            Log.d(TAG, "randomTest");
+        }
+        final int count = 10;// + random.nextInt(100);
+        final int start = random.nextInt(count - 1);
+        final int layoutCount = Math.max(1, random.nextInt(count - start));
+        setupBasic(count, start, layoutCount);
+
+        while (opCount-- > 0) {
+            final int op = random.nextInt(3);
+            switch (op) {
+                case 0:
+                    if (mTestAdapter.mItems.size() > 1) {
+                        int s = random.nextInt(mTestAdapter.mItems.size() - 1);
+                        int len = Math.max(1, random.nextInt(mTestAdapter.mItems.size() - s));
+                        rm(s, len);
+                    }
+                    break;
+                case 1:
+                    int s = mTestAdapter.mItems.size() == 0 ? 0 :
+                            random.nextInt(mTestAdapter.mItems.size());
+                    add(s, random.nextInt(50));
+                    break;
+                case 2:
+                    if (mTestAdapter.mItems.size() >= 2) {
+                        int from = random.nextInt(mTestAdapter.mItems.size());
+                        int to;
+                        do {
+                            to = random.nextInt(mTestAdapter.mItems.size());
+                        } while (to == from);
+                        mv(from, to);
+                    }
+            }
+        }
+        preProcess();
+    }
+
+    public void assertOps(List<AdapterHelper.UpdateOp> actual,
+            AdapterHelper.UpdateOp... expected) {
+        assertEquals(expected.length, actual.size());
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals(expected[i], actual.get(i));
+        }
+    }
+
+    void assertDispatch(int firstPass, int secondPass) {
+        assertEquals(firstPass, mFirstPassUpdates.size());
+        assertEquals(secondPass, mSecondPassUpdates.size());
+    }
+
+    void preProcess() {
+        mAdapterHelper.preProcess();
+        for (int i = 0; i < mPreProcessClone.mItems.size(); i++) {
+            TestAdapter.Item item = mPreProcessClone.mItems.get(i);
+            final int preLayoutIndex = mPreLayoutItems.indexOf(item);
+            final int endIndex = mTestAdapter.mItems.indexOf(item);
+            if (preLayoutIndex != -1) {
+                assertEquals("find position offset should work properly for existing elements" + i
+                        + " at pre layout position " + preLayoutIndex + " and post layout position "
+                        + endIndex, endIndex, mAdapterHelper.findPositionOffset(preLayoutIndex));
+            }
+        }
+        mAdapterHelper.consumePostponedUpdates();
+        // now assert these two adapters have identical data.
+        mPreProcessClone.applyOps(mFirstPassUpdates, mTestAdapter);
+        mPreProcessClone.applyOps(mSecondPassUpdates, mTestAdapter);
+        assertAdaptersEqual(mTestAdapter, mPreProcessClone);
+    }
+
+    private void assertAdaptersEqual(TestAdapter a1, TestAdapter a2) {
+        assertEquals(a1.mItems.size(), a2.mItems.size());
+        for (int i = 0; i < a1.mItems.size(); i++) {
+            TestAdapter.Item item = a1.mItems.get(i);
+            assertSame(item, a2.mItems.get(i));
+            assertEquals(0, item.getUpdateCount());
+        }
+        assertEquals(0, a1.mPendingAdded.size());
+        assertEquals(0, a2.mPendingAdded.size());
+    }
+
+    AdapterHelper.UpdateOp op(int cmd, int start, int count) {
+        return new AdapterHelper.UpdateOp(cmd, start, count);
+    }
+
+    AdapterHelper.UpdateOp addOp(int start, int count) {
+        return op(AdapterHelper.UpdateOp.ADD, start, count);
+    }
+
+    AdapterHelper.UpdateOp rmOp(int start, int count) {
+        return op(AdapterHelper.UpdateOp.REMOVE, start, count);
+    }
+
+    AdapterHelper.UpdateOp upOp(int start, int count) {
+        return op(AdapterHelper.UpdateOp.UPDATE, start, count);
+    }
+
+    void add(int start, int count) {
+        if (DEBUG) {
+            Log.d(TAG, "add(" + start + "," + count + ");");
+        }
+        mTestAdapter.add(start, count);
+    }
+
+    boolean isItemLaidOut(int pos) {
+        for (ViewHolder viewHolder : mViewHolders) {
+            if (viewHolder.mOldPosition == pos) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void mv(int from, int to) {
+        if (DEBUG) {
+            Log.d(TAG, "mv(" + from + "," + to + ");");
+        }
+        mTestAdapter.move(from, to);
+    }
+
+    void rm(int start, int count) {
+        if (DEBUG) {
+            Log.d(TAG, "rm(" + start + "," + count + ");");
+        }
+        for (int i = start; i < start + count; i++) {
+            if (!isItemLaidOut(i)) {
+                TestAdapter.Item item = mTestAdapter.mItems.get(i);
+                mPreLayoutItems.remove(item);
+            }
+        }
+        mTestAdapter.remove(start, count);
+    }
+
+    void up(int start, int count) {
+        mTestAdapter.update(start, count);
+    }
+
+    static class TestAdapter {
+
+        List<Item> mItems;
+
+        final AdapterHelper mAdapterHelper;
+
+        Queue<Item> mPendingAdded;
+
+        public TestAdapter(int initialCount, AdapterHelper container) {
+            mItems = new ArrayList<Item>();
+            mAdapterHelper = container;
+            mPendingAdded = new LinkedList<Item>();
+            for (int i = 0; i < initialCount; i++) {
+                mItems.add(new Item());
+            }
+        }
+
+        public void add(int index, int count) {
+            for (int i = 0; i < count; i++) {
+                Item item = new Item();
+                mPendingAdded.add(item);
+                mItems.add(index + i, item);
+            }
+            mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp(
+                    AdapterHelper.UpdateOp.ADD, index, count
+            ));
+        }
+
+        public void move(int from, int to) {
+            mItems.add(to, mItems.remove(from));
+            mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp(
+                    AdapterHelper.UpdateOp.MOVE, from, to
+            ));
+        }
+        public void remove(int index, int count) {
+            for (int i = 0; i < count; i++) {
+                mItems.remove(index);
+            }
+            mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp(
+                    AdapterHelper.UpdateOp.REMOVE, index, count
+            ));
+        }
+
+        public void update(int index, int count) {
+            for (int i = 0; i < count; i++) {
+                mItems.get(index + i).update();
+            }
+            mAdapterHelper.addUpdateOp(new AdapterHelper.UpdateOp(
+                    AdapterHelper.UpdateOp.UPDATE, index, count
+            ));
+        }
+
+        protected TestAdapter createCopy() {
+            TestAdapter adapter = new TestAdapter(0, mAdapterHelper);
+            for (Item item : mItems) {
+                adapter.mItems.add(item);
+            }
+            return adapter;
+        }
+
+        public void applyOps(List<AdapterHelper.UpdateOp> updates,
+                TestAdapter dataSource) {
+            for (AdapterHelper.UpdateOp op : updates) {
+                switch (op.cmd) {
+                    case AdapterHelper.UpdateOp.ADD:
+                        for (int i = 0; i < op.itemCount; i++) {
+                            mItems.add(op.positionStart + i, dataSource.consumeNextAdded());
+                        }
+                        break;
+                    case AdapterHelper.UpdateOp.REMOVE:
+                        for (int i = 0; i < op.itemCount; i++) {
+                            mItems.remove(op.positionStart);
+                        }
+                        break;
+                    case AdapterHelper.UpdateOp.UPDATE:
+                        for (int i = 0; i < op.itemCount; i++) {
+                            mItems.get(i).handleUpdate();
+                        }
+                        break;
+                    case AdapterHelper.UpdateOp.MOVE:
+                        mItems.add(op.itemCount, mItems.remove(op.positionStart));
+                        break;
+                }
+            }
+        }
+
+        private Item consumeNextAdded() {
+            return mPendingAdded.remove();
+        }
+
+        public void createFakeItemAt(int fakeAddedItemIndex) {
+            Item fakeItem = new Item();
+            ((LinkedList<Item>)mPendingAdded).add(fakeAddedItemIndex, fakeItem);
+        }
+
+        public static class Item {
+
+            private static AtomicInteger itemCounter = new AtomicInteger();
+
+            private final int id;
+
+            private int mVersionCount = 0;
+
+            private int mUpdateCount;
+
+            public Item() {
+                id = itemCounter.incrementAndGet();
+            }
+
+            public void update() {
+                mVersionCount++;
+            }
+
+            public void handleUpdate() {
+                mVersionCount--;
+            }
+
+            public int getUpdateCount() {
+                return mUpdateCount;
+            }
+        }
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
new file mode 100644
index 0000000..c583398
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
@@ -0,0 +1,607 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.graphics.Rect;
+import android.os.Looper;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+abstract public class BaseRecyclerViewInstrumentationTest extends
+        ActivityInstrumentationTestCase2<TestActivity> {
+
+    private static final String TAG = "RecyclerViewTest";
+
+    private boolean mDebug;
+
+    protected RecyclerView mRecyclerView;
+
+    protected AdapterHelper mAdapterHelper;
+
+    Throwable mainThreadException;
+
+    public BaseRecyclerViewInstrumentationTest() {
+        this(false);
+    }
+
+    public BaseRecyclerViewInstrumentationTest(boolean debug) {
+        super("android.support.v7.recyclerview", TestActivity.class);
+        mDebug = debug;
+    }
+
+    void checkForMainThreadException() throws Throwable {
+        if (mainThreadException != null) {
+            throw mainThreadException;
+        }
+    }
+
+    void setAdapter(final RecyclerView.Adapter adapter) throws Throwable {
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mRecyclerView.setAdapter(adapter);
+            }
+        });
+    }
+
+    void swapAdapter(final RecyclerView.Adapter adapter,
+            final boolean removeAndRecycleExistingViews) throws Throwable {
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mRecyclerView.swapAdapter(adapter, removeAndRecycleExistingViews);
+            }
+        });
+    }
+
+    void postExceptionToInstrumentation(Throwable t) {
+        if (mDebug) {
+            Log.e(TAG, "captured exception on main thread", t);
+        }
+        if (mainThreadException != null) {
+            Log.e(TAG, "receiving another main thread exception. dropping.", t);
+        } else {
+            mainThreadException = t;
+        }
+
+        if (mRecyclerView != null && mRecyclerView
+                .getLayoutManager() instanceof TestLayoutManager) {
+            TestLayoutManager lm = (TestLayoutManager) mRecyclerView.getLayoutManager();
+            // finish all layouts so that we get the correct exception
+            while (lm.layoutLatch.getCount() > 0) {
+                lm.layoutLatch.countDown();
+            }
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mRecyclerView != null) {
+            try {
+                removeRecyclerView();
+            } catch (Throwable throwable) {
+                throwable.printStackTrace();
+            }
+        }
+        getInstrumentation().waitForIdleSync();
+        try {
+            checkForMainThreadException();
+        } catch (Exception e) {
+            throw e;
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+        }
+        super.tearDown();
+    }
+
+    public Rect getDecoratedRecyclerViewBounds() {
+        return new Rect(
+                mRecyclerView.getPaddingLeft(),
+                mRecyclerView.getPaddingTop(),
+                mRecyclerView.getPaddingLeft() + mRecyclerView.getWidth(),
+                mRecyclerView.getPaddingTop() + mRecyclerView.getHeight()
+        );
+    }
+
+    public void removeRecyclerView() throws Throwable {
+        if (mRecyclerView == null) {
+            return;
+        }
+        mRecyclerView = null;
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                getActivity().mContainer.removeAllViews();
+            }
+        });
+    }
+
+    void waitForAnimations(int seconds) throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(2);
+        boolean running = mRecyclerView.mItemAnimator
+                .isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
+                    @Override
+                    public void onAnimationsFinished() {
+                        latch.countDown();
+                    }
+                });
+        if (running) {
+            latch.await(seconds, TimeUnit.SECONDS);
+        }
+    }
+
+    public void setRecyclerView(final RecyclerView recyclerView) throws Throwable {
+        setRecyclerView(recyclerView, true);
+    }
+    public void setRecyclerView(final RecyclerView recyclerView, boolean assignDummyPool)
+            throws Throwable {
+        mRecyclerView = recyclerView;
+        if (assignDummyPool) {
+            RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() {
+                @Override
+                public RecyclerView.ViewHolder getRecycledView(int viewType) {
+                    RecyclerView.ViewHolder viewHolder = super.getRecycledView(viewType);
+                    if (viewHolder == null) {
+                        return null;
+                    }
+                    viewHolder.addFlags(RecyclerView.ViewHolder.FLAG_BOUND);
+                    viewHolder.mPosition = 200;
+                    viewHolder.mOldPosition = 300;
+                    viewHolder.mPreLayoutPosition = 500;
+                    return viewHolder;
+                }
+
+                @Override
+                public void putRecycledView(RecyclerView.ViewHolder scrap) {
+                    super.putRecycledView(scrap);
+                }
+            };
+            mRecyclerView.setRecycledViewPool(pool);
+        }
+        mAdapterHelper = recyclerView.mAdapterHelper;
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                getActivity().mContainer.addView(recyclerView);
+            }
+        });
+    }
+
+    protected FrameLayout getRecyclerViewContainer() {
+        return getActivity().mContainer;
+    }
+
+    public void requestLayoutOnUIThread(final View view) throws Throwable {
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                view.requestLayout();
+            }
+        });
+    }
+
+    public void scrollBy(final int dt) throws Throwable {
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (mRecyclerView.getLayoutManager().canScrollHorizontally()) {
+                    mRecyclerView.scrollBy(dt, 0);
+                } else {
+                    mRecyclerView.scrollBy(0, dt);
+                }
+
+            }
+        });
+    }
+
+    void scrollToPosition(final int position) throws Throwable {
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mRecyclerView.getLayoutManager().scrollToPosition(position);
+            }
+        });
+    }
+
+    void smoothScrollToPosition(final int position)
+            throws Throwable {
+        Log.d(TAG, "SMOOTH scrolling to " + position);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mRecyclerView.smoothScrollToPosition(position);
+            }
+        });
+        while (mRecyclerView.getLayoutManager().isSmoothScrolling() ||
+                mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
+            if (mDebug) {
+                Log.d(TAG, "SMOOTH scrolling step");
+            }
+            Thread.sleep(200);
+        }
+        Log.d(TAG, "SMOOTH scrolling done");
+        getInstrumentation().waitForIdleSync();
+    }
+
+    class TestViewHolder extends RecyclerView.ViewHolder {
+
+        Item mBindedItem;
+
+        public TestViewHolder(View itemView) {
+            super(itemView);
+            itemView.setFocusable(true);
+        }
+
+        @Override
+        public String toString() {
+            return super.toString() + " item:" + mBindedItem;
+        }
+    }
+
+    class TestLayoutManager extends RecyclerView.LayoutManager {
+
+        CountDownLatch layoutLatch;
+
+        public void expectLayouts(int count) {
+            layoutLatch = new CountDownLatch(count);
+        }
+
+        public void waitForLayout(long timeout, TimeUnit timeUnit) throws Throwable {
+            layoutLatch.await(timeout * (mDebug ? 100 : 1), timeUnit);
+            assertEquals("all expected layouts should be executed at the expected time",
+                    0, layoutLatch.getCount());
+            getInstrumentation().waitForIdleSync();
+        }
+
+        public void assertLayoutCount(int count, String msg, long timeout) throws Throwable {
+            layoutLatch.await(timeout, TimeUnit.SECONDS);
+            assertEquals(msg, count, layoutLatch.getCount());
+        }
+
+        public void assertNoLayout(String msg, long timeout) throws Throwable {
+            layoutLatch.await(timeout, TimeUnit.SECONDS);
+            assertFalse(msg, layoutLatch.getCount() == 0);
+        }
+
+        public void waitForLayout(long timeout) throws Throwable {
+            waitForLayout(timeout * (mDebug ? 10000 : 1), TimeUnit.SECONDS);
+        }
+
+        @Override
+        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT);
+        }
+
+        void assertVisibleItemPositions() {
+            int i = getChildCount();
+            TestAdapter testAdapter = (TestAdapter) mRecyclerView.getAdapter();
+            while (i-- > 0) {
+                View view = getChildAt(i);
+                RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
+                Item item = ((TestViewHolder) lp.mViewHolder).mBindedItem;
+                if (mDebug) {
+                    Log.d(TAG, "testing item " + i);
+                }
+                if (!lp.isItemRemoved()) {
+                    RecyclerView.ViewHolder vh = mRecyclerView.getChildViewHolder(view);
+                    assertSame("item position in LP should match adapter value :" + vh,
+                            testAdapter.mItems.get(vh.mPosition), item);
+                }
+            }
+        }
+
+        RecyclerView.LayoutParams getLp(View v) {
+            return (RecyclerView.LayoutParams) v.getLayoutParams();
+        }
+
+        void layoutRange(RecyclerView.Recycler recycler, int start, int end) {
+            assertScrap(recycler);
+            if (mDebug) {
+                Log.d(TAG, "will layout items from " + start + " to " + end);
+            }
+            int diff = end > start ? 1 : -1;
+            int top = 0;
+            for (int i = start; i != end; i+=diff) {
+                if (mDebug) {
+                    Log.d(TAG, "laying out item " + i);
+                }
+                View view = recycler.getViewForPosition(i);
+                assertNotNull("view should not be null for valid position. "
+                        + "got null view at position " + i, view);
+                if (!mRecyclerView.mState.isPreLayout()) {
+                    RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view
+                            .getLayoutParams();
+                    assertFalse("In post layout, getViewForPosition should never return a view "
+                            + "that is removed", layoutParams != null
+                            && layoutParams.isItemRemoved());
+
+                }
+                assertEquals("getViewForPosition should return correct position",
+                        i, getPosition(view));
+                addView(view);
+
+                measureChildWithMargins(view, 0, 0);
+                layoutDecorated(view, 0, top, getDecoratedMeasuredWidth(view)
+                        , top + getDecoratedMeasuredHeight(view));
+                top += view.getMeasuredHeight();
+            }
+        }
+
+        private void assertScrap(RecyclerView.Recycler recycler) {
+            if (mRecyclerView.getAdapter() != null &&
+                    !mRecyclerView.getAdapter().hasStableIds()) {
+                for (RecyclerView.ViewHolder viewHolder : recycler.getScrapList()) {
+                    assertFalse("Invalid scrap should be no kept", viewHolder.isInvalid());
+                }
+            }
+        }
+    }
+
+    static class Item {
+        final static AtomicInteger idCounter = new AtomicInteger(0);
+        final public int mId = idCounter.incrementAndGet();
+
+        int mAdapterIndex;
+
+        final String mText;
+
+        Item(int adapterIndex, String text) {
+            mAdapterIndex = adapterIndex;
+            mText = text;
+        }
+
+        @Override
+        public String toString() {
+            return "Item{" +
+                    "mId=" + mId +
+                    ", originalIndex=" + mAdapterIndex +
+                    ", text='" + mText + '\'' +
+                    '}';
+        }
+    }
+
+    class TestAdapter extends RecyclerView.Adapter<TestViewHolder> {
+
+        List<Item> mItems;
+
+        TestAdapter(int count) {
+            mItems = new ArrayList<Item>(count);
+            for (int i = 0; i < count; i++) {
+                mItems.add(new Item(i, "Item " + i));
+            }
+        }
+
+        @Override
+        public TestViewHolder onCreateViewHolder(ViewGroup parent,
+                int viewType) {
+            return new TestViewHolder(new TextView(parent.getContext()));
+        }
+
+        @Override
+        public void onBindViewHolder(TestViewHolder holder, int position) {
+            final Item item = mItems.get(position);
+            ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mAdapterIndex + ")");
+            holder.mBindedItem = item;
+        }
+
+        public void deleteAndNotify(final int start, final int count) throws Throwable {
+            deleteAndNotify(new int[]{start, count});
+        }
+
+        /**
+         * Deletes items in the given ranges.
+         * <p>
+         * Note that each operation affects the one after so you should offset them properly.
+         * <p>
+         * For example, if adapter has 5 items (A,B,C,D,E), and then you call this method with
+         * <code>[1, 2],[2, 1]</code>, it will first delete items B,C and the new adapter will be
+         * A D E. Then it will delete 2,1 which means it will delete E.
+         */
+        public void deleteAndNotify(final int[]... startCountTuples) throws Throwable {
+            for (int[] tuple : startCountTuples) {
+                tuple[1] = -tuple[1];
+            }
+            new AddRemoveRunnable(startCountTuples).runOnMainThread();
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return hasStableIds() ? mItems.get(position).mId : super.getItemId(position);
+        }
+
+        public void offsetOriginalIndices(int start, int offset) {
+            for (int i = start; i < mItems.size(); i++) {
+                mItems.get(i).mAdapterIndex += offset;
+            }
+        }
+
+        /**
+         * @param start inclusive
+         * @param end exclusive
+         * @param offset
+         */
+        public void offsetOriginalIndicesBetween(int start, int end, int offset) {
+            for (int i = start; i < end && i < mItems.size(); i++) {
+                mItems.get(i).mAdapterIndex += offset;
+            }
+        }
+
+        public void addAndNotify(final int start, final int count) throws Throwable {
+            addAndNotify(new int[]{start, count});
+        }
+
+        public void addAndNotify(final int[]... startCountTuples) throws Throwable {
+            new AddRemoveRunnable(startCountTuples).runOnMainThread();
+        }
+
+        public void dispatchDataSetChanged() throws Throwable {
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    notifyDataSetChanged();
+                }
+            });
+        }
+
+        public void changeAndNotify(final int start, final int count) throws Throwable {
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    notifyItemRangeChanged(start, count);
+                }
+            });
+        }
+
+        public void changePositionsAndNotify(final int... positions) throws Throwable {
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    for (int i = 0; i < positions.length; i += 1) {
+                        TestAdapter.super.notifyItemRangeChanged(positions[i], 1);
+                    }
+                }
+            });
+        }
+
+        /**
+         * Similar to other methods but negative count means delete and position count means add.
+         * <p>
+         * For instance, calling this method with <code>[1,1], [2,-1]</code> it will first add an
+         * item to index 1, then remove an item from index 2 (updated index 2)
+         */
+        public void addDeleteAndNotify(final int[]... startCountTuples) throws Throwable {
+            new AddRemoveRunnable(startCountTuples).runOnMainThread();
+        }
+
+        @Override
+        public int getItemCount() {
+            return mItems.size();
+        }
+
+        public void moveItems(boolean notifyChange, int[]... fromToTuples) throws Throwable {
+            for (int i = 0; i < fromToTuples.length; i += 1) {
+                int[] tuple = fromToTuples[i];
+                moveItem(tuple[0], tuple[1], false);
+            }
+            if (notifyChange) {
+                dispatchDataSetChanged();
+            }
+        }
+
+        public void moveItem(final int from, final int to, final boolean notifyChange)
+                throws Throwable {
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    Item item = mItems.remove(from);
+                    mItems.add(to, item);
+                    offsetOriginalIndices(from, to - 1);
+                    item.mAdapterIndex = to;
+                    if (notifyChange) {
+                        notifyDataSetChanged();
+                    }
+                }
+            });
+        }
+
+
+        private class AddRemoveRunnable implements Runnable {
+            final int[][] mStartCountTuples;
+
+            public AddRemoveRunnable(int[][] startCountTuples) {
+                mStartCountTuples = startCountTuples;
+            }
+
+            public void runOnMainThread() throws Throwable {
+                if (Looper.myLooper() == Looper.getMainLooper()) {
+                    run();
+                } else {
+                    runTestOnUiThread(this);
+                }
+            }
+
+            @Override
+            public void run() {
+                for (int[] tuple : mStartCountTuples) {
+                    if (tuple[1] < 0) {
+                        delete(tuple);
+                    } else {
+                        add(tuple);
+                    }
+                }
+            }
+
+            private void add(int[] tuple) {
+                // offset others
+                offsetOriginalIndices(tuple[0], tuple[1]);
+                for (int i = 0; i < tuple[1]; i++) {
+                    mItems.add(tuple[0], new Item(i, "new item " + i));
+                }
+                notifyItemRangeInserted(tuple[0], tuple[1]);
+            }
+
+            private void delete(int[] tuple) {
+                final int count = -tuple[1];
+                offsetOriginalIndices(tuple[0] + count, tuple[1]);
+                for (int i = 0; i < count; i++) {
+                    mItems.remove(tuple[0]);
+                }
+                notifyItemRangeRemoved(tuple[0], count);
+            }
+        }
+    }
+
+    @Override
+    public void runTestOnUiThread(Runnable r) throws Throwable {
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            r.run();
+        } else {
+            super.runTestOnUiThread(r);
+        }
+    }
+
+    static class TargetTuple {
+
+        final int mPosition;
+
+        final int mLayoutDirection;
+
+        TargetTuple(int position, int layoutDirection) {
+            this.mPosition = position;
+            this.mLayoutDirection = layoutDirection;
+        }
+
+        @Override
+        public String toString() {
+            return "TargetTuple{" +
+                    "mPosition=" + mPosition +
+                    ", mLayoutDirection=" + mLayoutDirection +
+                    '}';
+        }
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BucketTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BucketTest.java
new file mode 100644
index 0000000..bbe2cf8
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BucketTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.test.AndroidTestCase;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+public class BucketTest extends AndroidTestCase {
+
+    ChildHelper.Bucket mBucket;
+
+    ArrayList<Integer> mArr;
+
+    Set<Integer> mSet;
+
+    int max = 0;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mBucket = new ChildHelper.Bucket();
+        mArr = new ArrayList<Integer>();
+        Collections.addAll(mArr, 0, 1, 2, 3, 4, 5, 6, 10, 12, 13, 21, 22, 122, 14, 44, 29, 205, 19);
+        for (int i = 1; i < 4; i++) {
+            mArr.add(i * (ChildHelper.Bucket.BITS_PER_WORD - 1));
+            mArr.add(i * (ChildHelper.Bucket.BITS_PER_WORD));
+            mArr.add(i * (ChildHelper.Bucket.BITS_PER_WORD + 1));
+            mArr.add(i * ChildHelper.Bucket.BITS_PER_WORD - 1);
+            mArr.add(i * ChildHelper.Bucket.BITS_PER_WORD);
+            mArr.add(i * ChildHelper.Bucket.BITS_PER_WORD + 1);
+        }
+
+        mSet = new HashSet<Integer>();
+        max = 0;
+        for (int i = mArr.size() - 1; i >= 0; i--) {
+            if (!mSet.add(mArr.get(i))) {
+                mArr.remove(i);
+            }
+            max = Math.max(max, i);
+        }
+    }
+
+
+    public void testSetClear() {
+        for (int i : mArr) {
+            mBucket.set(i);
+            assertTrue(mBucket.get(i));
+        }
+        for (int i = 0; i < max + 100; i++) {
+            assertEquals(mBucket.get(i), mSet.contains(i));
+        }
+
+        for (int i : mArr) {
+            mBucket.clear(i);
+            assertFalse(mBucket.get(i));
+        }
+
+        for (int i = 0; i < max + 100; i++) {
+            assertFalse(mBucket.get(i));
+        }
+    }
+
+
+    public void testRemove() {
+        for (int i : mArr) {
+            mBucket.reset();
+            for (int j : mArr) {
+                mBucket.set(j);
+            }
+            mBucket.remove(i);
+            for (int j : mArr) {
+                if (i == j) {
+                    continue;
+                }
+                if (j < i) {
+                    assertTrue(mBucket.get(j));
+                } else {
+                    assertEquals(mSet.contains(j + 1), mBucket.get(j));
+                }
+            }
+        }
+    }
+
+    public void testInsert() {
+        for (int i : mArr) {
+            for (boolean val : new boolean[]{true, false}) {
+                mBucket.reset();
+                for (int j : mArr) {
+                    mBucket.set(j);
+                }
+                mBucket.insert(i, val);
+                assertEquals(mBucket.get(i), val);
+                for (int j : mArr) {
+                    if (j < i) {
+                        assertTrue(mBucket.get(j));
+                    } else if (j == i) {
+                        assertEquals(mBucket.get(j), val);
+                    } else {
+                        assertEquals(mSet.contains(j - 1), mBucket.get(j));
+                        assertTrue(mBucket.get(j + 1));
+                    }
+                }
+            }
+        }
+    }
+
+
+    public void testCountOnesBefore() {
+        assertEquals(mBucket.countOnesBefore(0), 0);
+        for (int i : mArr) {
+            mBucket.set(i);
+            max = Math.max(i, max);
+        }
+        assertEquals(mBucket.countOnesBefore(0), 0);
+        for (int i = 0; i < max + 200; i++) {
+            int count = 0;
+            for (int j : mArr) {
+                if (j < i) {
+                    count++;
+                }
+            }
+            assertEquals(count, mBucket.countOnesBefore(i));
+        }
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java
new file mode 100644
index 0000000..a453e6e
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class DefaultItemAnimatorTest extends ActivityInstrumentationTestCase2<TestActivity> {
+
+    DefaultItemAnimator mAnimator;
+    Adapter mAdapter;
+    ViewGroup mDummyParent;
+    CountDownLatch mExpectedItems;
+
+    Set<RecyclerView.ViewHolder> mRemoveFinished = new HashSet<RecyclerView.ViewHolder>();
+    Set<RecyclerView.ViewHolder> mAddFinished = new HashSet<RecyclerView.ViewHolder>();
+    Set<RecyclerView.ViewHolder> mMoveFinished = new HashSet<RecyclerView.ViewHolder>();
+    Set<RecyclerView.ViewHolder> mChangeFinished = new HashSet<RecyclerView.ViewHolder>();
+
+    public DefaultItemAnimatorTest() {
+        super("android.support.v7.recyclerview", TestActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mAnimator = new DefaultItemAnimator();
+        mAdapter = new Adapter(20);
+        mDummyParent = getActivity().mContainer;
+        mAnimator.setListener(new RecyclerView.ItemAnimator.ItemAnimatorListener() {
+            @Override
+            public void onRemoveFinished(RecyclerView.ViewHolder item) {
+                assertTrue(mRemoveFinished.add(item));
+                onFinished();
+            }
+
+            @Override
+            public void onAddFinished(RecyclerView.ViewHolder item) {
+                assertTrue(mAddFinished.add(item));
+                onFinished();
+            }
+
+            @Override
+            public void onMoveFinished(RecyclerView.ViewHolder item) {
+                assertTrue(mMoveFinished.add(item));
+                onFinished();
+            }
+
+            @Override
+            public void onChangeFinished(RecyclerView.ViewHolder item) {
+                assertTrue(mChangeFinished.add(item));
+                onFinished();
+            }
+
+            private void onFinished() {
+                if (mExpectedItems != null) {
+                    mExpectedItems.countDown();
+                }
+            }
+        });
+    }
+
+    void expectItems(int count) {
+        mExpectedItems = new CountDownLatch(count);
+    }
+
+    void runAndWait(int seconds) throws Throwable {
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mAnimator.runPendingAnimations();
+            }
+        });
+        waitForItems(seconds);
+    }
+
+    void waitForItems(int seconds) throws InterruptedException {
+        mExpectedItems.await(seconds, TimeUnit.SECONDS);
+        assertEquals("all expected finish events should happen", 0, mExpectedItems.getCount());
+    }
+
+    public void testAnimateAdd() throws Throwable {
+        ViewHolder vh = createViewHolder(1);
+        expectItems(1);
+        assertTrue(animateAdd(vh));
+        assertTrue(mAnimator.isRunning());
+        runAndWait(1);
+    }
+
+    public void testAnimateRemove() throws Throwable {
+        ViewHolder vh = createViewHolder(1);
+        expectItems(1);
+        assertTrue(animateRemove(vh));
+        assertTrue(mAnimator.isRunning());
+        runAndWait(1);
+    }
+
+    public void testAnimateMove() throws Throwable {
+        ViewHolder vh = createViewHolder(1);
+        expectItems(1);
+        assertTrue(animateMove(vh, 0, 0, 100, 100));
+        assertTrue(mAnimator.isRunning());
+        runAndWait(1);
+    }
+
+    public void testAnimateChange() throws Throwable {
+        ViewHolder vh = createViewHolder(1);
+        ViewHolder vh2 = createViewHolder(2);
+        expectItems(2);
+        assertTrue(animateChange(vh, vh2, 0, 0, 100, 100));
+        assertTrue(mAnimator.isRunning());
+        runAndWait(1);
+    }
+
+    boolean animateAdd(final RecyclerView.ViewHolder vh) throws Throwable {
+        final boolean[] result = new boolean[1];
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                result[0] = mAnimator.animateAdd(vh);
+            }
+        });
+        return result[0];
+    }
+
+    boolean animateRemove(final RecyclerView.ViewHolder vh) throws Throwable {
+        final boolean[] result = new boolean[1];
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                result[0] = mAnimator.animateRemove(vh);
+            }
+        });
+        return result[0];
+    }
+
+    boolean animateMove(final RecyclerView.ViewHolder vh, final int fromX, final int fromY,
+            final int toX, final int toY) throws Throwable {
+        final boolean[] result = new boolean[1];
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                result[0] = mAnimator.animateMove(vh, fromX, fromY, toX, toY);
+            }
+        });
+        return result[0];
+    }
+
+    boolean animateChange(final RecyclerView.ViewHolder oldHolder,
+            final RecyclerView.ViewHolder newHolder,
+            final int fromX, final int fromY, final int toX, final int toY) throws Throwable {
+        final boolean[] result = new boolean[1];
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                result[0] = mAnimator.animateChange(oldHolder, newHolder, fromX, fromY, toX, toY);
+            }
+        });
+        return result[0];
+    }
+
+    private ViewHolder createViewHolder(final int pos) throws Throwable {
+        final ViewHolder vh = mAdapter.createViewHolder(mDummyParent, 1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mAdapter.bindViewHolder(vh, pos);
+                mDummyParent.addView(vh.itemView);
+            }
+        });
+
+        return vh;
+    }
+
+
+    private class Adapter extends RecyclerView.Adapter<ViewHolder> {
+
+        List<String> mItems;
+
+        private Adapter(int count) {
+            mItems = new ArrayList<String>();
+            for (int i = 0; i < count; i++) {
+                mItems.add("item-" + i);
+            }
+        }
+
+        @Override
+        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            return new ViewHolder(new TextView(parent.getContext()));
+        }
+
+        @Override
+        public void onBindViewHolder(ViewHolder holder, int position) {
+            holder.bind(mItems.get(position));
+        }
+
+        @Override
+        public int getItemCount() {
+            return mItems.size();
+        }
+    }
+
+    private class ViewHolder extends RecyclerView.ViewHolder {
+
+        String mBindedText;
+
+        public ViewHolder(View itemView) {
+            super(itemView);
+        }
+
+        public void bind(String text) {
+            mBindedText = text;
+            ((TextView) itemView).setText(text);
+        }
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
new file mode 100644
index 0000000..91873da
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+public class GridLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
+    public void testAnchorUpdate() throws InterruptedException {
+        GridLayoutManager glm = new GridLayoutManager(getActivity(), 11);
+        final GridLayoutManager.SpanSizeLookup spanSizeLookup
+                = new GridLayoutManager.SpanSizeLookup() {
+            @Override
+            public int getSpanSize(int position) {
+                if (position > 200) {
+                    return 100;
+                }
+                if (position > 20) {
+                    return 2;
+                }
+                return 1;
+            }
+        };
+        glm.setSpanSizeLookup(spanSizeLookup);
+        glm.mAnchorInfo.mPosition = 11;
+        glm.onAnchorReady(glm.mAnchorInfo);
+        assertEquals("gm should keep anchor in first span", 11, glm.mAnchorInfo.mPosition);
+
+        glm.mAnchorInfo.mPosition = 13;
+        glm.onAnchorReady(glm.mAnchorInfo);
+        assertEquals("gm should move anchor to first span", 11, glm.mAnchorInfo.mPosition);
+
+        glm.mAnchorInfo.mPosition = 23;
+        glm.onAnchorReady(glm.mAnchorInfo);
+        assertEquals("gm should move anchor to first span", 21, glm.mAnchorInfo.mPosition);
+
+        glm.mAnchorInfo.mPosition = 35;
+        glm.onAnchorReady(glm.mAnchorInfo);
+        assertEquals("gm should move anchor to first span", 31, glm.mAnchorInfo.mPosition);
+    }
+
+    public void testSpanLookup() {
+        final GridLayoutManager.SpanSizeLookup ssl
+                = new GridLayoutManager.SpanSizeLookup() {
+            @Override
+            public int getSpanSize(int position) {
+                if (position > 200) {
+                    return 100;
+                }
+                if (position > 6) {
+                    return 2;
+                }
+                return 1;
+            }
+        };
+        assertEquals(0, ssl.getSpanIndex(0, 5));
+        assertEquals(4, ssl.getSpanIndex(4, 5));
+        assertEquals(0, ssl.getSpanIndex(5, 5));
+        assertEquals(1, ssl.getSpanIndex(6, 5));
+        assertEquals(2, ssl.getSpanIndex(7, 5));
+        assertEquals(2, ssl.getSpanIndex(9, 5));
+        assertEquals(0, ssl.getSpanIndex(8, 5));
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
new file mode 100644
index 0000000..911bfaf
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
@@ -0,0 +1,1142 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import static android.support.v7.widget.LayoutState.LAYOUT_END;
+import static android.support.v7.widget.LayoutState.LAYOUT_START;
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Includes tests for {@link LinearLayoutManager}.
+ * <p>
+ * Since most UI tests are not practical, these tests are focused on internal data representation
+ * and stability of LinearLayoutManager in response to different events (state change, scrolling
+ * etc) where it is very hard to do manual testing.
+ */
+public class LinearLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
+
+    private static final boolean DEBUG = false;
+
+    private static final String TAG = "LinearLayoutManagerTest";
+
+    WrappedLinearLayoutManager mLayoutManager;
+
+    TestAdapter mTestAdapter;
+
+    final List<Config> mBaseVariations = new ArrayList<Config>();
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
+            for (boolean reverseLayout : new boolean[]{false, true}) {
+                for (boolean stackFromBottom : new boolean[]{false, true}) {
+                    mBaseVariations.add(new Config(orientation, reverseLayout, stackFromBottom));
+                }
+            }
+        }
+    }
+
+    protected List<Config> addConfigVariation(List<Config> base, String fieldName,
+            Object... variations)
+            throws CloneNotSupportedException, NoSuchFieldException, IllegalAccessException {
+        List<Config> newConfigs = new ArrayList<Config>();
+        Field field = Config.class.getDeclaredField(fieldName);
+        for (Config config : base) {
+            for (Object variation : variations) {
+                Config newConfig = (Config) config.clone();
+                field.set(newConfig, variation);
+                newConfigs.add(newConfig);
+            }
+        }
+        return newConfigs;
+    }
+
+    void setupByConfig(Config config, boolean waitForFirstLayout) throws Throwable {
+        mRecyclerView = new RecyclerView(getActivity());
+        mRecyclerView.setHasFixedSize(true);
+        mTestAdapter = config.mTestAdapter == null ? new TestAdapter(config.mItemCount)
+                : config.mTestAdapter;
+        mRecyclerView.setAdapter(mTestAdapter);
+        mLayoutManager = new WrappedLinearLayoutManager(getActivity(), config.mOrientation,
+                config.mReverseLayout);
+        mLayoutManager.setStackFromEnd(config.mStackFromEnd);
+        mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
+        mRecyclerView.setLayoutManager(mLayoutManager);
+        if (waitForFirstLayout) {
+            waitForFirstLayout();
+        }
+    }
+
+    public void testKeepFocusOnRelayout() throws Throwable {
+        setupByConfig(new Config(VERTICAL, false, false).itemCount(500), true);
+        int center = (mLayoutManager.findLastVisibleItemPosition()
+                - mLayoutManager.findFirstVisibleItemPosition()) / 2;
+        final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForPosition(center);
+        final int top = mLayoutManager.mOrientationHelper.getDecoratedStart(vh.itemView);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                vh.itemView.requestFocus();
+            }
+        });
+        assertTrue("view should have the focus", vh.itemView.hasFocus());
+        // add a bunch of items right before that view, make sure it keeps its position
+        mLayoutManager.expectLayouts(2);
+        final int childCountToAdd = mRecyclerView.getChildCount() * 2;
+        mTestAdapter.addAndNotify(center, childCountToAdd);
+        center += childCountToAdd; // offset item
+        mLayoutManager.waitForLayout(2);
+        mLayoutManager.waitForAnimationsToEnd(20);
+        final RecyclerView.ViewHolder postVH = mRecyclerView.findViewHolderForPosition(center);
+        assertNotNull("focused child should stay in layout", postVH);
+        assertSame("same view holder should be kept for unchanged child", vh, postVH);
+        assertEquals("focused child's screen position should stay unchanged", top,
+                mLayoutManager.mOrientationHelper.getDecoratedStart(postVH.itemView));
+    }
+
+    public void testStackFromEnd() throws Throwable {
+        for(Config config : addConfigVariation(mBaseVariations, "mItemCount", 5
+                , Config.DEFAULT_ITEM_COUNT)) {
+            stackFromEndTest(config);
+            removeRecyclerView();
+        }
+    }
+
+    public void testScrollToPositionWithOffset() throws Throwable {
+        for (Config config : mBaseVariations) {
+            scrollToPositionWithOffsetTest(config.itemCount(300));
+            removeRecyclerView();
+        }
+    }
+
+    public void scrollToPositionWithOffsetTest(Config config) throws Throwable {
+        setupByConfig(config, true);
+        OrientationHelper orientationHelper = OrientationHelper
+                .createOrientationHelper(mLayoutManager, config.mOrientation);
+        Rect layoutBounds = getDecoratedRecyclerViewBounds();
+        // try scrolling towards head, should not affect anything
+        Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
+        if (config.mStackFromEnd) {
+            scrollToPositionWithOffset(mTestAdapter.getItemCount() - 1,
+                    mLayoutManager.mOrientationHelper.getEnd() - 500);
+        } else {
+            scrollToPositionWithOffset(0, 20);
+        }
+        assertRectSetsEqual(config + " trying to over scroll with offset should be no-op",
+                before, mLayoutManager.collectChildCoordinates());
+        // try offsetting some visible children
+        int testCount = 10;
+        while (testCount-- > 0) {
+            // get middle child
+            final View child = mLayoutManager.getChildAt(mLayoutManager.getChildCount() / 2);
+            final int position = mRecyclerView.getChildPosition(child);
+            final int startOffset = config.mReverseLayout ?
+                    orientationHelper.getEndAfterPadding() - orientationHelper
+                            .getDecoratedEnd(child)
+                    : orientationHelper.getDecoratedStart(child) - orientationHelper
+                            .getStartAfterPadding();
+            final int scrollOffset = config.mStackFromEnd ? startOffset + startOffset / 2
+                    : startOffset / 2;
+            mLayoutManager.expectLayouts(1);
+            scrollToPositionWithOffset(position, scrollOffset);
+            mLayoutManager.waitForLayout(2);
+            final int finalOffset = config.mReverseLayout ?
+                    orientationHelper.getEndAfterPadding() - orientationHelper
+                            .getDecoratedEnd(child)
+                    : orientationHelper.getDecoratedStart(child) - orientationHelper
+                            .getStartAfterPadding();
+            assertEquals(config + " scroll with offset on a visible child should work fine " +
+                    " offset:" + finalOffset + " , existing offset:" + startOffset + ", "
+                            + "child " + position,
+                    scrollOffset, finalOffset);
+        }
+
+        // try scrolling to invisible children
+        testCount = 10;
+        // we test above and below, one by one
+        int offsetMultiplier = -1;
+        while (testCount-- > 0) {
+            final TargetTuple target = findInvisibleTarget(config);
+            final String logPrefix = config + " " + target;
+            mLayoutManager.expectLayouts(1);
+            final int offset = offsetMultiplier
+                    * orientationHelper.getDecoratedMeasurement(mLayoutManager.getChildAt(0)) / 3;
+            scrollToPositionWithOffset(target.mPosition, offset);
+            mLayoutManager.waitForLayout(2);
+            final View child = mLayoutManager.findViewByPosition(target.mPosition);
+            assertNotNull(logPrefix + " scrolling to a mPosition with offset " + offset
+                    + " should layout it", child);
+            final Rect bounds = mLayoutManager.getViewBounds(child);
+            if (DEBUG) {
+                Log.d(TAG, logPrefix + " post scroll to invisible mPosition " + bounds + " in "
+                        + layoutBounds + " with offset " + offset);
+            }
+
+            if (config.mReverseLayout) {
+                assertEquals(logPrefix + " when scrolling with offset to an invisible in reverse "
+                                + "layout, its end should align with recycler view's end - offset",
+                        orientationHelper.getEndAfterPadding() - offset,
+                        orientationHelper.getDecoratedEnd(child)
+                );
+            } else {
+                assertEquals(logPrefix + " when scrolling with offset to an invisible child in normal"
+                                + " layout its start should align with recycler view's start + "
+                                + "offset",
+                        orientationHelper.getStartAfterPadding() + offset,
+                        orientationHelper.getDecoratedStart(child)
+                );
+            }
+            offsetMultiplier *= -1;
+        }
+    }
+
+    private TargetTuple findInvisibleTarget(Config config) {
+        int minPosition = Integer.MAX_VALUE, maxPosition = Integer.MIN_VALUE;
+        for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
+            View child = mLayoutManager.getChildAt(i);
+            int position = mRecyclerView.getChildPosition(child);
+            if (position < minPosition) {
+                minPosition = position;
+            }
+            if (position > maxPosition) {
+                maxPosition = position;
+            }
+        }
+        final int tailTarget = maxPosition +
+                (mRecyclerView.getAdapter().getItemCount() - maxPosition) / 2;
+        final int headTarget = minPosition / 2;
+        final int target;
+        // where will the child come from ?
+        final int itemLayoutDirection;
+        if (Math.abs(tailTarget - maxPosition) > Math.abs(headTarget - minPosition)) {
+            target = tailTarget;
+            itemLayoutDirection = config.mReverseLayout ? LAYOUT_START : LAYOUT_END;
+        } else {
+            target = headTarget;
+            itemLayoutDirection = config.mReverseLayout ? LAYOUT_END : LAYOUT_START;
+        }
+        if (DEBUG) {
+            Log.d(TAG,
+                    config + " target:" + target + " min:" + minPosition + ", max:" + maxPosition);
+        }
+        return new TargetTuple(target, itemLayoutDirection);
+    }
+
+    public void stackFromEndTest(final Config config) throws Throwable {
+        final FrameLayout container = getRecyclerViewContainer();
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                container.setPadding(0, 0, 0, 0);
+            }
+        });
+
+        setupByConfig(config, true);
+        int lastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition();
+        int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();
+        int lastCompletelyVisibleItemPosition = mLayoutManager.findLastCompletelyVisibleItemPosition();
+        int firstCompletelyVisibleItemPosition = mLayoutManager.findFirstCompletelyVisibleItemPosition();
+
+        mLayoutManager.expectLayouts(1);
+        // resize the recycler view to half
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (config.mOrientation == HORIZONTAL) {
+                    container.setPadding(0, 0, container.getWidth() / 2, 0);
+                } else {
+                    container.setPadding(0, 0, 0, container.getWidth() / 2);
+                }
+            }
+        });
+        mLayoutManager.waitForLayout(1);
+        if (config.mStackFromEnd) {
+            assertEquals("[" + config + "]: last visible position should not change.",
+                    lastVisibleItemPosition, mLayoutManager.findLastVisibleItemPosition());
+            assertEquals("[" + config + "]: last completely visible position should not change",
+                    lastCompletelyVisibleItemPosition,
+                    mLayoutManager.findLastCompletelyVisibleItemPosition());
+        } else {
+            assertEquals("[" + config + "]: first visible position should not change.",
+                    firstVisibleItemPosition, mLayoutManager.findFirstVisibleItemPosition());
+            assertEquals("[" + config + "]: last completely visible position should not change",
+                    firstCompletelyVisibleItemPosition,
+                    mLayoutManager.findFirstCompletelyVisibleItemPosition());
+        }
+    }
+
+    public void testScrollToPositionWithPredictive() throws Throwable {
+        scrollToPositionWithPredictive(0, LinearLayoutManager.INVALID_OFFSET);
+        removeRecyclerView();
+        scrollToPositionWithPredictive(3, 20);
+        removeRecyclerView();
+        scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2,
+                LinearLayoutManager.INVALID_OFFSET);
+        removeRecyclerView();
+        scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2, 10);
+    }
+
+    public void scrollToPositionWithPredictive(final int scrollPosition, final int scrollOffset)
+            throws Throwable {
+        setupByConfig(new Config(VERTICAL, false, false), true);
+
+        mLayoutManager.mOnLayoutListener = new OnLayoutListener() {
+            @Override
+            void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                if (state.isPreLayout()) {
+                    assertEquals("pending scroll position should still be pending",
+                            scrollPosition, mLayoutManager.mPendingScrollPosition);
+                    if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
+                        assertEquals("pending scroll position offset should still be pending",
+                                scrollOffset, mLayoutManager.mPendingScrollPositionOffset);
+                    }
+                } else {
+                    RecyclerView.ViewHolder vh =
+                            mRecyclerView.findViewHolderForPosition(scrollPosition);
+                    assertNotNull("scroll to position should work", vh);
+                    if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
+                        assertEquals("scroll offset should be applied properly",
+                                mLayoutManager.getPaddingTop() + scrollOffset +
+                                        ((RecyclerView.LayoutParams) vh.itemView
+                                                .getLayoutParams()).topMargin,
+                                mLayoutManager.getDecoratedTop(vh.itemView));
+                    }
+                }
+            }
+        };
+        mLayoutManager.expectLayouts(2);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    mTestAdapter.addAndNotify(0, 1);
+                    if (scrollOffset == LinearLayoutManager.INVALID_OFFSET) {
+                        mLayoutManager.scrollToPosition(scrollPosition);
+                    } else {
+                        mLayoutManager.scrollToPositionWithOffset(scrollPosition,
+                                scrollOffset);
+                    }
+
+                } catch (Throwable throwable) {
+                    throwable.printStackTrace();
+                }
+
+            }
+        });
+        mLayoutManager.waitForLayout(2);
+        checkForMainThreadException();
+    }
+
+    private void waitForFirstLayout() throws Throwable {
+        mLayoutManager.expectLayouts(1);
+        setRecyclerView(mRecyclerView);
+        mLayoutManager.waitForLayout(2);
+    }
+
+    public void testRecycleDuringAnimations() throws Throwable {
+        final AtomicInteger childCount = new AtomicInteger(0);
+        final TestAdapter adapter = new TestAdapter(300) {
+            @Override
+            public TestViewHolder onCreateViewHolder(ViewGroup parent,
+                    int viewType) {
+                final int cnt = childCount.incrementAndGet();
+                final TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
+                if (DEBUG) {
+                    Log.d(TAG, "CHILD_CNT(create):" + cnt + ", " + testViewHolder);
+                }
+                return testViewHolder;
+            }
+        };
+        setupByConfig(new Config(VERTICAL, false, false).itemCount(300)
+                .adapter(adapter), true);
+
+        final RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() {
+            @Override
+            public void putRecycledView(RecyclerView.ViewHolder scrap) {
+                super.putRecycledView(scrap);
+                int cnt = childCount.decrementAndGet();
+                if (DEBUG) {
+                    Log.d(TAG, "CHILD_CNT(put):" + cnt + ", " + scrap);
+                }
+            }
+
+            @Override
+            public RecyclerView.ViewHolder getRecycledView(int viewType) {
+                final RecyclerView.ViewHolder recycledView = super.getRecycledView(viewType);
+                if (recycledView != null) {
+                    final int cnt = childCount.incrementAndGet();
+                    if (DEBUG) {
+                        Log.d(TAG, "CHILD_CNT(get):" + cnt + ", " + recycledView);
+                    }
+                }
+                return recycledView;
+            }
+        };
+        pool.setMaxRecycledViews(mTestAdapter.getItemViewType(0), 500);
+        mRecyclerView.setRecycledViewPool(pool);
+
+
+        // now keep adding children to trigger more children being created etc.
+        for (int i = 0; i < 100; i ++) {
+            adapter.addAndNotify(15, 1);
+            Thread.sleep(15);
+        }
+        getInstrumentation().waitForIdleSync();
+        waitForAnimations(2);
+        assertEquals("Children count should add up", childCount.get(),
+                mRecyclerView.getChildCount() + mRecyclerView.mRecycler.mCachedViews.size());
+
+        // now trigger lots of add again, followed by a scroll to position
+        for (int i = 0; i < 100; i ++) {
+            adapter.addAndNotify(5 + (i % 3) * 3, 1);
+            Thread.sleep(25);
+        }
+        smoothScrollToPosition(mLayoutManager.findLastVisibleItemPosition() + 20);
+        waitForAnimations(2);
+        getInstrumentation().waitForIdleSync();
+        assertEquals("Children count should add up", childCount.get(),
+                mRecyclerView.getChildCount() + mRecyclerView.mRecycler.mCachedViews.size());
+    }
+
+
+    public void testGetFirstLastChildrenTest() throws Throwable {
+        for (Config config : mBaseVariations) {
+            getFirstLastChildrenTest(config);
+        }
+    }
+
+    public void testDontRecycleChildrenOnDetach() throws Throwable {
+        setupByConfig(new Config().recycleChildrenOnDetach(false), true);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                int recyclerSize = mRecyclerView.mRecycler.getRecycledViewPool().size();
+                mRecyclerView.setLayoutManager(new TestLayoutManager());
+                assertEquals("No views are recycled", recyclerSize,
+                        mRecyclerView.mRecycler.getRecycledViewPool().size());
+            }
+        });
+    }
+
+    public void testRecycleChildrenOnDetach() throws Throwable {
+        setupByConfig(new Config().recycleChildrenOnDetach(true), true);
+        final int childCount = mLayoutManager.getChildCount();
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                int recyclerSize = mRecyclerView.mRecycler.getRecycledViewPool().size();
+                mRecyclerView.mRecycler.getRecycledViewPool().setMaxRecycledViews(
+                        mTestAdapter.getItemViewType(0), recyclerSize + childCount);
+                mRecyclerView.setLayoutManager(new TestLayoutManager());
+                assertEquals("All children should be recycled", childCount + recyclerSize,
+                        mRecyclerView.mRecycler.getRecycledViewPool().size());
+            }
+        });
+    }
+
+    public void getFirstLastChildrenTest(final Config config) throws Throwable {
+        setupByConfig(config, true);
+        Runnable viewInBoundsTest = new Runnable() {
+            @Override
+            public void run() {
+                VisibleChildren visibleChildren = mLayoutManager.traverseAndFindVisibleChildren();
+                final String boundsLog = mLayoutManager.getBoundsLog();
+                assertEquals(config + ":\nfirst visible child should match traversal result\n"
+                                + boundsLog, visibleChildren.firstVisiblePosition,
+                        mLayoutManager.findFirstVisibleItemPosition()
+                );
+                assertEquals(
+                        config + ":\nfirst fully visible child should match traversal result\n"
+                                + boundsLog, visibleChildren.firstFullyVisiblePosition,
+                        mLayoutManager.findFirstCompletelyVisibleItemPosition()
+                );
+
+                assertEquals(config + ":\nlast visible child should match traversal result\n"
+                                + boundsLog, visibleChildren.lastVisiblePosition,
+                        mLayoutManager.findLastVisibleItemPosition()
+                );
+                assertEquals(
+                        config + ":\nlast fully visible child should match traversal result\n"
+                                + boundsLog, visibleChildren.lastFullyVisiblePosition,
+                        mLayoutManager.findLastCompletelyVisibleItemPosition()
+                );
+            }
+        };
+        runTestOnUiThread(viewInBoundsTest);
+        // smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching
+        // case
+        final int scrollPosition = config.mStackFromEnd ? 0 : mTestAdapter.getItemCount();
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mRecyclerView.smoothScrollToPosition(scrollPosition);
+            }
+        });
+        while (mLayoutManager.isSmoothScrolling() ||
+                mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
+            runTestOnUiThread(viewInBoundsTest);
+            Thread.sleep(400);
+        }
+        // delete all items
+        mLayoutManager.expectLayouts(2);
+        mTestAdapter.deleteAndNotify(0, mTestAdapter.getItemCount());
+        mLayoutManager.waitForLayout(2);
+        // test empty case
+        runTestOnUiThread(viewInBoundsTest);
+        // set a new adapter with huge items to test full bounds check
+        mLayoutManager.expectLayouts(1);
+        final int totalSpace = mLayoutManager.mOrientationHelper.getTotalSpace();
+        final TestAdapter newAdapter = new TestAdapter(100) {
+            @Override
+            public void onBindViewHolder(TestViewHolder holder,
+                    int position) {
+                super.onBindViewHolder(holder, position);
+                if (config.mOrientation == HORIZONTAL) {
+                    holder.itemView.setMinimumWidth(totalSpace + 5);
+                } else {
+                    holder.itemView.setMinimumHeight(totalSpace + 5);
+                }
+            }
+        };
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mRecyclerView.setAdapter(newAdapter);
+            }
+        });
+        mLayoutManager.waitForLayout(2);
+        runTestOnUiThread(viewInBoundsTest);
+    }
+
+    public void testSavedState() throws Throwable {
+        PostLayoutRunnable[] postLayoutOptions = new PostLayoutRunnable[]{
+                new PostLayoutRunnable() {
+                    @Override
+                    public void run() throws Throwable {
+                        // do nothing
+                    }
+
+                    @Override
+                    public String describe() {
+                        return "doing nothing";
+                    }
+                },
+                new PostLayoutRunnable() {
+                    @Override
+                    public void run() throws Throwable {
+                        mLayoutManager.expectLayouts(1);
+                        scrollToPosition(mTestAdapter.getItemCount() * 3 / 4);
+                        mLayoutManager.waitForLayout(2);
+                    }
+
+                    @Override
+                    public String describe() {
+                        return "scroll to position";
+                    }
+                },
+                new PostLayoutRunnable() {
+                    @Override
+                    public void run() throws Throwable {
+                        mLayoutManager.expectLayouts(1);
+                        scrollToPositionWithOffset(mTestAdapter.getItemCount() * 1 / 3,
+                                50);
+                        mLayoutManager.waitForLayout(2);
+                    }
+
+                    @Override
+                    public String describe() {
+                        return "scroll to position with positive offset";
+                    }
+                },
+                new PostLayoutRunnable() {
+                    @Override
+                    public void run() throws Throwable {
+                        mLayoutManager.expectLayouts(1);
+                        scrollToPositionWithOffset(mTestAdapter.getItemCount() * 2 / 3,
+                                -50);
+                        mLayoutManager.waitForLayout(2);
+                    }
+
+                    @Override
+                    public String describe() {
+                        return "scroll to position with negative offset";
+                    }
+                }
+        };
+
+        PostRestoreRunnable[] postRestoreOptions = new PostRestoreRunnable[]{
+                new PostRestoreRunnable() {
+                    @Override
+                    public String describe() {
+                        return "Doing nothing";
+                    }
+                },
+                new PostRestoreRunnable() {
+                    @Override
+                    void onAfterRestore(Config config) throws Throwable {
+                        // update config as well so that restore assertions will work
+                        config.mOrientation = 1 - config.mOrientation;
+                        mLayoutManager.setOrientation(config.mOrientation);
+                    }
+
+                    @Override
+                    boolean shouldLayoutMatch(Config config) {
+                        return config.mItemCount == 0;
+                    }
+
+                    @Override
+                    public String describe() {
+                        return "Changing orientation";
+                    }
+                },
+                new PostRestoreRunnable() {
+                    @Override
+                    void onAfterRestore(Config config) throws Throwable {
+                        config.mStackFromEnd = !config.mStackFromEnd;
+                        mLayoutManager.setStackFromEnd(config.mStackFromEnd);
+                    }
+
+                    @Override
+                    boolean shouldLayoutMatch(Config config) {
+                        return true; //stack from end should not move items on change
+                    }
+
+                    @Override
+                    public String describe() {
+                        return "Changing stack from end";
+                    }
+                },
+                new PostRestoreRunnable() {
+                    @Override
+                    void onAfterRestore(Config config) throws Throwable {
+                        config.mReverseLayout = !config.mReverseLayout;
+                        mLayoutManager.setReverseLayout(config.mReverseLayout);
+                    }
+
+                    @Override
+                    boolean shouldLayoutMatch(Config config) {
+                        return config.mItemCount == 0;
+                    }
+
+                    @Override
+                    public String describe() {
+                        return "Changing reverse layout";
+                    }
+                },
+                new PostRestoreRunnable() {
+                    @Override
+                    void onAfterRestore(Config config) throws Throwable {
+                        config.mRecycleChildrenOnDetach = !config.mRecycleChildrenOnDetach;
+                        mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
+                    }
+
+                    @Override
+                    boolean shouldLayoutMatch(Config config) {
+                        return true;
+                    }
+
+                    @Override
+                    String describe() {
+                        return "Change should recycle children";
+                    }
+                },
+                new PostRestoreRunnable() {
+                    int position;
+                    @Override
+                    void onAfterRestore(Config config) throws Throwable {
+                        position = mTestAdapter.getItemCount() / 2;
+                        mLayoutManager.scrollToPosition(position);
+                    }
+
+                    @Override
+                    boolean shouldLayoutMatch(Config config) {
+                        return mTestAdapter.getItemCount() == 0;
+                    }
+
+                    @Override
+                    String describe() {
+                        return "Scroll to position " + position ;
+                    }
+
+                    @Override
+                    void onAfterReLayout(Config config) {
+                        if (mTestAdapter.getItemCount() > 0) {
+                            assertEquals(config + ":scrolled view should be last completely visible",
+                                    position,
+                                    config.mStackFromEnd ?
+                                            mLayoutManager.findLastCompletelyVisibleItemPosition()
+                                        : mLayoutManager.findFirstCompletelyVisibleItemPosition());
+                        }
+                    }
+                }
+        };
+        boolean[] waitForLayoutOptions = new boolean[]{true, false};
+        List<Config> variations = addConfigVariation(mBaseVariations, "mItemCount", 0, 300);
+        variations = addConfigVariation(variations, "mRecycleChildrenOnDetach", true);
+        for (Config config : variations) {
+            for (PostLayoutRunnable postLayoutRunnable : postLayoutOptions) {
+                for (boolean waitForLayout : waitForLayoutOptions) {
+                    for (PostRestoreRunnable postRestoreRunnable : postRestoreOptions) {
+                        savedStateTest((Config) config.clone(), waitForLayout, postLayoutRunnable,
+                                postRestoreRunnable);
+                        removeRecyclerView();
+                    }
+
+                }
+            }
+        }
+    }
+
+    public void savedStateTest(Config config, boolean waitForLayout,
+            PostLayoutRunnable postLayoutOperation, PostRestoreRunnable postRestoreOperation)
+            throws Throwable {
+        if (DEBUG) {
+            Log.d(TAG, "testing saved state with wait for layout = " + waitForLayout + " config " +
+                    config + " post layout action " + postLayoutOperation.describe() +
+                    "post restore action " + postRestoreOperation.describe());
+        }
+        setupByConfig(config, false);
+        if (waitForLayout) {
+            waitForFirstLayout();
+            postLayoutOperation.run();
+        }
+        Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
+        Parcelable savedState = mRecyclerView.onSaveInstanceState();
+        // we append a suffix to the parcelable to test out of bounds
+        String parcelSuffix = UUID.randomUUID().toString();
+        Parcel parcel = Parcel.obtain();
+        savedState.writeToParcel(parcel, 0);
+        parcel.writeString(parcelSuffix);
+        removeRecyclerView();
+        // reset for reading
+        parcel.setDataPosition(0);
+        // re-create
+        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
+        removeRecyclerView();
+
+        RecyclerView restored = new RecyclerView(getActivity());
+        // this config should be no op.
+        mLayoutManager = new WrappedLinearLayoutManager(getActivity(),
+                config.mOrientation, config.mReverseLayout);
+        mLayoutManager.setStackFromEnd(config.mStackFromEnd);
+        restored.setLayoutManager(mLayoutManager);
+        // use the same adapter for Rect matching
+        restored.setAdapter(mTestAdapter);
+        restored.onRestoreInstanceState(savedState);
+        postRestoreOperation.onAfterRestore(config);
+        assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
+                parcel.readString());
+        mLayoutManager.expectLayouts(1);
+        setRecyclerView(restored);
+        mLayoutManager.waitForLayout(2);
+        // calculate prefix here instead of above to include post restore changes
+        final String logPrefix = config + "\npostLayout:" + postLayoutOperation.describe() +
+                "\npostRestore:" + postRestoreOperation.describe() + "\n";
+        assertEquals(logPrefix + " on saved state, reverse layout should be preserved",
+                config.mReverseLayout, mLayoutManager.getReverseLayout());
+        assertEquals(logPrefix + " on saved state, orientation should be preserved",
+                config.mOrientation, mLayoutManager.getOrientation());
+        assertEquals(logPrefix + " on saved state, stack from end should be preserved",
+                config.mStackFromEnd, mLayoutManager.getStackFromEnd());
+        if (waitForLayout) {
+            if (postRestoreOperation.shouldLayoutMatch(config)) {
+                assertRectSetsEqual(
+                        logPrefix + ": on restore, previous view positions should be preserved",
+                        before, mLayoutManager.collectChildCoordinates());
+            } else {
+                assertRectSetsNotEqual(
+                        logPrefix
+                                + ": on restore with changes, previous view positions should NOT "
+                                + "be preserved",
+                        before, mLayoutManager.collectChildCoordinates());
+            }
+            postRestoreOperation.onAfterReLayout(config);
+        }
+    }
+
+    void scrollToPositionWithOffset(final int position, final int offset) throws Throwable {
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mLayoutManager.scrollToPositionWithOffset(position, offset);
+            }
+        });
+    }
+
+    public void assertRectSetsNotEqual(String message, Map<Item, Rect> before,
+            Map<Item, Rect> after) {
+        Throwable throwable = null;
+        try {
+            assertRectSetsEqual("NOT " + message, before, after);
+        } catch (Throwable t) {
+            throwable = t;
+        }
+        assertNotNull(message + "\ntwo layout should be different", throwable);
+    }
+
+    public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("checking rectangle equality.");
+         sb.append("before:\n");
+        for (Map.Entry<Item, Rect> entry : before.entrySet()) {
+            sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
+        }
+        sb.append("after:\n");
+        for (Map.Entry<Item, Rect> entry : after.entrySet()) {
+            sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
+        }
+        message = message + "\n" + sb.toString();
+        assertEquals(message + ":\nitem counts should be equal", before.size()
+                , after.size());
+        for (Map.Entry<Item, Rect> entry : before.entrySet()) {
+            Rect afterRect = after.get(entry.getKey());
+            assertNotNull(message + ":\nSame item should be visible after simple re-layout",
+                    afterRect);
+            assertEquals(message + ":\nItem should be laid out at the same coordinates",
+                    entry.getValue(), afterRect);
+        }
+    }
+
+    static class VisibleChildren {
+
+        int firstVisiblePosition = RecyclerView.NO_POSITION;
+
+        int firstFullyVisiblePosition = RecyclerView.NO_POSITION;
+
+        int lastVisiblePosition = RecyclerView.NO_POSITION;
+
+        int lastFullyVisiblePosition = RecyclerView.NO_POSITION;
+
+        @Override
+        public String toString() {
+            return "VisibleChildren{" +
+                    "firstVisiblePosition=" + firstVisiblePosition +
+                    ", firstFullyVisiblePosition=" + firstFullyVisiblePosition +
+                    ", lastVisiblePosition=" + lastVisiblePosition +
+                    ", lastFullyVisiblePosition=" + lastFullyVisiblePosition +
+                    '}';
+        }
+    }
+
+    abstract private class PostLayoutRunnable {
+
+        abstract void run() throws Throwable;
+
+        abstract String describe();
+    }
+
+    abstract private class PostRestoreRunnable {
+
+        void onAfterRestore(Config config) throws Throwable {
+        }
+
+        abstract String describe();
+
+        boolean shouldLayoutMatch(Config config) {
+            return true;
+        }
+
+        void onAfterReLayout(Config config) {
+
+        };
+    }
+
+    class WrappedLinearLayoutManager extends LinearLayoutManager {
+
+        CountDownLatch layoutLatch;
+
+        OrientationHelper mSecondaryOrientation;
+
+        OnLayoutListener mOnLayoutListener;
+
+        public WrappedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
+            super(context, orientation, reverseLayout);
+        }
+
+        public void expectLayouts(int count) {
+            layoutLatch = new CountDownLatch(count);
+        }
+
+        public void waitForLayout(long timeout) throws InterruptedException {
+            waitForLayout(timeout, TimeUnit.SECONDS);
+        }
+
+        @Override
+        public void setOrientation(int orientation) {
+            super.setOrientation(orientation);
+            mSecondaryOrientation = null;
+        }
+
+        @Override
+        public void removeAndRecycleView(View child, RecyclerView.Recycler recycler) {
+            if (DEBUG) {
+                Log.d(TAG, "recycling view " + mRecyclerView.getChildViewHolder(child));
+            }
+            super.removeAndRecycleView(child, recycler);
+        }
+
+        @Override
+        public void removeAndRecycleViewAt(int index, RecyclerView.Recycler recycler) {
+            if (DEBUG) {
+                Log.d(TAG, "recycling view at" + mRecyclerView.getChildViewHolder(getChildAt(index)));
+            }
+            super.removeAndRecycleViewAt(index, recycler);
+        }
+
+        @Override
+        void ensureLayoutState() {
+            super.ensureLayoutState();
+            if (mSecondaryOrientation == null) {
+                mSecondaryOrientation = OrientationHelper.createOrientationHelper(this,
+                        1 - getOrientation());
+            }
+        }
+
+        private void waitForLayout(long timeout, TimeUnit timeUnit) throws InterruptedException {
+            layoutLatch.await(timeout * (DEBUG ? 100 : 1), timeUnit);
+            assertEquals("all expected layouts should be executed at the expected time",
+                    0, layoutLatch.getCount());
+            getInstrumentation().waitForIdleSync();
+        }
+
+        public String getBoundsLog() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("view bounds:[start:").append(mOrientationHelper.getStartAfterPadding())
+                    .append(",").append(" end").append(mOrientationHelper.getEndAfterPadding());
+            sb.append("\nchildren bounds\n");
+            final int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                View child = getChildAt(i);
+                sb.append("child (ind:").append(i).append(", pos:").append(getPosition(child))
+                        .append("[").append("start:").append(
+                        mOrientationHelper.getDecoratedStart(child)).append(", end:")
+                        .append(mOrientationHelper.getDecoratedEnd(child)).append("]\n");
+            }
+            return sb.toString();
+        }
+
+        public void waitForAnimationsToEnd(int timeoutInSeconds) throws InterruptedException {
+            RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
+            if (itemAnimator == null) {
+                return;
+            }
+            final CountDownLatch latch = new CountDownLatch(1);
+            final boolean running = itemAnimator.isRunning(
+                    new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
+                        @Override
+                        public void onAnimationsFinished() {
+                            latch.countDown();
+                        }
+                    }
+            );
+            if (running) {
+                latch.await(timeoutInSeconds, TimeUnit.SECONDS);
+            }
+        }
+
+        public VisibleChildren traverseAndFindVisibleChildren() {
+            int childCount = getChildCount();
+            final VisibleChildren visibleChildren = new VisibleChildren();
+            final int start = mOrientationHelper.getStartAfterPadding();
+            final int end = mOrientationHelper.getEndAfterPadding();
+            for (int i = 0; i < childCount; i++) {
+                View child = getChildAt(i);
+                final int childStart = mOrientationHelper.getDecoratedStart(child);
+                final int childEnd = mOrientationHelper.getDecoratedEnd(child);
+                final boolean fullyVisible = childStart >= start && childEnd <= end;
+                final boolean hidden = childEnd <= start || childStart >= end;
+                if (hidden) {
+                    continue;
+                }
+                final int position = getPosition(child);
+                if (fullyVisible) {
+                    if (position < visibleChildren.firstFullyVisiblePosition ||
+                            visibleChildren.firstFullyVisiblePosition == RecyclerView.NO_POSITION) {
+                        visibleChildren.firstFullyVisiblePosition = position;
+                    }
+
+                    if (position > visibleChildren.lastFullyVisiblePosition) {
+                        visibleChildren.lastFullyVisiblePosition = position;
+                    }
+                }
+
+                if (position < visibleChildren.firstVisiblePosition ||
+                        visibleChildren.firstVisiblePosition == RecyclerView.NO_POSITION) {
+                    visibleChildren.firstVisiblePosition = position;
+                }
+
+                if (position > visibleChildren.lastVisiblePosition) {
+                    visibleChildren.lastVisiblePosition = position;
+                }
+
+            }
+            return visibleChildren;
+        }
+
+        Rect getViewBounds(View view) {
+            if (getOrientation() == HORIZONTAL) {
+                return new Rect(
+                        mOrientationHelper.getDecoratedStart(view),
+                        mSecondaryOrientation.getDecoratedStart(view),
+                        mOrientationHelper.getDecoratedEnd(view),
+                        mSecondaryOrientation.getDecoratedEnd(view));
+            } else {
+                return new Rect(
+                        mSecondaryOrientation.getDecoratedStart(view),
+                        mOrientationHelper.getDecoratedStart(view),
+                        mSecondaryOrientation.getDecoratedEnd(view),
+                        mOrientationHelper.getDecoratedEnd(view));
+            }
+
+        }
+
+        Map<Item, Rect> collectChildCoordinates() throws Throwable {
+            final Map<Item, Rect> items = new LinkedHashMap<Item, Rect>();
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    final int childCount = getChildCount();
+                    for (int i = 0; i < childCount; i++) {
+                        View child = getChildAt(i);
+                        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child
+                                .getLayoutParams();
+                        TestViewHolder vh = (TestViewHolder) lp.mViewHolder;
+                        items.put(vh.mBindedItem, getViewBounds(child));
+                    }
+                }
+            });
+            return items;
+        }
+
+        @Override
+        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+            try {
+                if (mOnLayoutListener != null) {
+                    mOnLayoutListener.before(recycler, state);
+                }
+                super.onLayoutChildren(recycler, state);
+                if (mOnLayoutListener != null) {
+                    mOnLayoutListener.after(recycler, state);
+                }
+            } catch (Throwable t) {
+                postExceptionToInstrumentation(t);
+            }
+            layoutLatch.countDown();
+        }
+
+
+    }
+
+    static class OnLayoutListener {
+        void before(RecyclerView.Recycler recycler, RecyclerView.State state){}
+        void after(RecyclerView.Recycler recycler, RecyclerView.State state){}
+    }
+
+    static class Config implements Cloneable {
+
+        private static final int DEFAULT_ITEM_COUNT = 100;
+
+        private boolean mStackFromEnd;
+
+        int mOrientation = VERTICAL;
+
+        boolean mReverseLayout = false;
+
+        boolean mRecycleChildrenOnDetach = false;
+
+        int mItemCount = DEFAULT_ITEM_COUNT;
+
+        TestAdapter mTestAdapter;
+
+        Config(int orientation, boolean reverseLayout, boolean stackFromEnd) {
+            mOrientation = orientation;
+            mReverseLayout = reverseLayout;
+            mStackFromEnd = stackFromEnd;
+        }
+
+        public Config() {
+
+        }
+
+        Config adapter(TestAdapter adapter) {
+            mTestAdapter = adapter;
+            return this;
+        }
+
+        Config recycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
+            mRecycleChildrenOnDetach = recycleChildrenOnDetach;
+            return this;
+        }
+
+        Config orientation(int orientation) {
+            mOrientation = orientation;
+            return this;
+        }
+
+        Config stackFromBottom(boolean stackFromBottom) {
+            mStackFromEnd = stackFromBottom;
+            return this;
+        }
+
+        Config reverseLayout(boolean reverseLayout) {
+            mReverseLayout = reverseLayout;
+            return this;
+        }
+
+        public Config itemCount(int itemCount) {
+            mItemCount = itemCount;
+            return this;
+        }
+
+        // required by convention
+        @Override
+        public Object clone() throws CloneNotSupportedException {
+            return super.clone();
+        }
+
+        @Override
+        public String toString() {
+            return "Config{" +
+                    "mStackFromEnd=" + mStackFromEnd +
+                    ", mOrientation=" + mOrientation +
+                    ", mReverseLayout=" + mReverseLayout +
+                    ", mRecycleChildrenOnDetach=" + mRecycleChildrenOnDetach +
+                    ", mItemCount=" + mItemCount +
+                    '}';
+        }
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
new file mode 100644
index 0000000..0432264
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
@@ -0,0 +1,1198 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class RecyclerViewAnimationsTest extends BaseRecyclerViewInstrumentationTest {
+
+    private static final boolean DEBUG = false;
+
+    private static final String TAG = "RecyclerViewAnimationsTest";
+
+    AnimationLayoutManager mLayoutManager;
+
+    TestAdapter mTestAdapter;
+
+    public RecyclerViewAnimationsTest() {
+        super(DEBUG);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    RecyclerView setupBasic(int itemCount) throws Throwable {
+        return setupBasic(itemCount, 0, itemCount);
+    }
+
+    RecyclerView setupBasic(int itemCount, int firstLayoutStartIndex, int firstLayoutItemCount)
+            throws Throwable {
+        return setupBasic(itemCount, firstLayoutStartIndex, firstLayoutItemCount, null);
+    }
+
+    RecyclerView setupBasic(int itemCount, int firstLayoutStartIndex, int firstLayoutItemCount,
+            TestAdapter testAdapter)
+            throws Throwable {
+        final TestRecyclerView recyclerView = new TestRecyclerView(getActivity());
+        recyclerView.setHasFixedSize(true);
+        if (testAdapter == null) {
+            mTestAdapter = new TestAdapter(itemCount);
+        } else {
+            mTestAdapter = testAdapter;
+        }
+        recyclerView.setAdapter(mTestAdapter);
+        mLayoutManager = new AnimationLayoutManager();
+        recyclerView.setLayoutManager(mLayoutManager);
+        mLayoutManager.mOnLayoutCallbacks.mLayoutMin = firstLayoutStartIndex;
+        mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = firstLayoutItemCount;
+
+        mLayoutManager.expectLayouts(1);
+        recyclerView.expectDraw(1);
+        setRecyclerView(recyclerView);
+        mLayoutManager.waitForLayout(2);
+        recyclerView.waitForDraw(1);
+        mLayoutManager.mOnLayoutCallbacks.reset();
+        getInstrumentation().waitForIdleSync();
+        assertEquals("extra layouts should not happen", 1, mLayoutManager.getTotalLayoutCount());
+        assertEquals("all expected children should be laid out", firstLayoutItemCount,
+                mLayoutManager.getChildCount());
+        return recyclerView;
+    }
+
+    public void testChangeAnimations()  throws Throwable {
+        final boolean[] booleans = {true, false};
+        for (boolean supportsChange : booleans) {
+            for (boolean changeType : booleans) {
+                for (boolean hasStableIds : booleans) {
+                    for (boolean deleteSomeItems : booleans) {
+                        changeAnimTest(supportsChange, changeType, hasStableIds, deleteSomeItems);
+                    }
+                    removeRecyclerView();
+                }
+            }
+        }
+    }
+    public void changeAnimTest(final boolean supportsChangeAnim, final boolean changeType,
+            final boolean hasStableIds, final boolean deleteSomeItems)  throws Throwable {
+        final int changedIndex = 3;
+        final int defaultType = 1;
+        final AtomicInteger changedIndexNewType = new AtomicInteger(defaultType);
+        final String logPrefix = "supportsChangeAnim:" + supportsChangeAnim +
+                ", change view type:" + changeType +
+                ", has stable ids:" + hasStableIds +
+                ", force predictive:" + deleteSomeItems;
+        TestAdapter testAdapter = new TestAdapter(10) {
+            @Override
+            public int getItemViewType(int position) {
+                return position == changedIndex ? changedIndexNewType.get() : defaultType;
+            }
+
+            @Override
+            public TestViewHolder onCreateViewHolder(ViewGroup parent,
+                    int viewType) {
+                TestViewHolder vh = super.onCreateViewHolder(parent, viewType);
+                if (DEBUG) {
+                    Log.d(TAG, logPrefix + " onCreateVH" + vh.toString());
+                }
+                return vh;
+            }
+
+            @Override
+            public void onBindViewHolder(TestViewHolder holder,
+                    int position) {
+                super.onBindViewHolder(holder, position);
+                if (DEBUG) {
+                    Log.d(TAG, logPrefix + " onBind to " + position + "" + holder.toString());
+                }
+            }
+        };
+        testAdapter.setHasStableIds(hasStableIds);
+        setupBasic(testAdapter.getItemCount(), 0, 10, testAdapter);
+        mRecyclerView.getItemAnimator().setSupportsChangeAnimations(supportsChangeAnim);
+
+        final RecyclerView.ViewHolder toBeChangedVH =
+                mRecyclerView.findViewHolderForPosition(changedIndex);
+        mLayoutManager.mOnLayoutCallbacks = new OnLayoutCallbacks() {
+            @Override
+            void afterPreLayout(RecyclerView.Recycler recycler,
+                    AnimationLayoutManager layoutManager,
+                    RecyclerView.State state) {
+                RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForPosition(changedIndex);
+                if (supportsChangeAnim) {
+                    assertTrue(logPrefix + " changed view holder should have correct flag"
+                            , vh.isChanged());
+                } else {
+                    assertFalse(logPrefix + " changed view holder should have correct flag"
+                            , vh.isChanged());
+                }
+            }
+
+            @Override
+            void afterPostLayout(RecyclerView.Recycler recycler,
+                    AnimationLayoutManager layoutManager, RecyclerView.State state) {
+                RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForPosition(changedIndex);
+                assertFalse(logPrefix + "VH should not be marked as changed", vh.isChanged());
+                if (supportsChangeAnim) {
+                    assertNotSame(logPrefix + "a new VH should be given if change is supported",
+                            toBeChangedVH, vh);
+                } else if (!changeType && hasStableIds) {
+                    assertSame(logPrefix + "if change animations are not supported but we have "
+                            + "stable ids, same view holder should be returned", toBeChangedVH, vh);
+                }
+                super.beforePostLayout(recycler, layoutManager, state);
+            }
+        };
+        mLayoutManager.expectLayouts(1);
+        if (changeType) {
+            changedIndexNewType.set(defaultType + 1);
+        }
+        if (deleteSomeItems) {
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        mTestAdapter.deleteAndNotify(changedIndex + 2, 1);
+                        mTestAdapter.notifyItemChanged(3);
+                    } catch (Throwable throwable) {
+                        throwable.printStackTrace();
+                    }
+
+                }
+            });
+        } else {
+            mTestAdapter.notifyItemChanged(3);
+        }
+
+        mLayoutManager.waitForLayout(2);
+    }
+
+    public void testRecycleDuringAnimations() throws Throwable {
+        final AtomicInteger childCount = new AtomicInteger(0);
+        final TestAdapter adapter = new TestAdapter(1000) {
+            @Override
+            public TestViewHolder onCreateViewHolder(ViewGroup parent,
+                    int viewType) {
+                childCount.incrementAndGet();
+                return super.onCreateViewHolder(parent, viewType);
+            }
+        };
+        setupBasic(1000, 10, 20, adapter);
+        mLayoutManager.mOnLayoutCallbacks.mLayoutMin = 10;
+        mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 20;
+
+        mRecyclerView.setRecycledViewPool(new RecyclerView.RecycledViewPool() {
+            @Override
+            public void putRecycledView(RecyclerView.ViewHolder scrap) {
+                super.putRecycledView(scrap);
+                childCount.decrementAndGet();
+            }
+
+            @Override
+            public RecyclerView.ViewHolder getRecycledView(int viewType) {
+                final RecyclerView.ViewHolder recycledView = super.getRecycledView(viewType);
+                if (recycledView != null) {
+                    childCount.incrementAndGet();
+                }
+                return recycledView;
+            }
+        });
+
+        // now keep adding children to trigger more children being created etc.
+        for (int i = 0; i < 100; i ++) {
+            adapter.addAndNotify(15, 1);
+            Thread.sleep(50);
+        }
+        getInstrumentation().waitForIdleSync();
+        waitForAnimations(2);
+        assertEquals("Children count should add up", childCount.get(),
+                mRecyclerView.getChildCount() + mRecyclerView.mRecycler.mCachedViews.size());
+    }
+
+    public void testNotifyDataSetChanged() throws Throwable {
+        setupBasic(10, 3, 4);
+        int layoutCount = mLayoutManager.mTotalLayoutCount;
+        mLayoutManager.expectLayouts(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    mTestAdapter.deleteAndNotify(4, 1);
+                    mTestAdapter.dispatchDataSetChanged();
+                } catch (Throwable throwable) {
+                    throwable.printStackTrace();
+                }
+
+            }
+        });
+        mLayoutManager.waitForLayout(2);
+        getInstrumentation().waitForIdleSync();
+        assertEquals("on notify data set changed, predictive animations should not run",
+                layoutCount + 1, mLayoutManager.mTotalLayoutCount);
+        mLayoutManager.expectLayouts(2);
+        mTestAdapter.addAndNotify(4, 2);
+        // make sure animations recover
+        mLayoutManager.waitForLayout(2);
+    }
+
+    public void testStableIdNotifyDataSetChanged() throws Throwable {
+        final int itemCount = 20;
+        List<Item> initialSet = new ArrayList<Item>();
+        final TestAdapter adapter = new TestAdapter(itemCount) {
+            @Override
+            public long getItemId(int position) {
+                return mItems.get(position).mId;
+            }
+        };
+        adapter.setHasStableIds(true);
+        initialSet.addAll(adapter.mItems);
+        positionStatesTest(itemCount, 5, 5, adapter, new AdapterOps() {
+            @Override
+            void onRun(TestAdapter testAdapter) throws Throwable {
+                Item item5 = adapter.mItems.get(5);
+                Item item6 = adapter.mItems.get(6);
+                item5.mAdapterIndex = 6;
+                item6.mAdapterIndex = 5;
+                adapter.mItems.remove(5);
+                adapter.mItems.add(6, item5);
+                adapter.dispatchDataSetChanged();
+                //hacky, we support only 1 layout pass
+                mLayoutManager.layoutLatch.countDown();
+            }
+        }, PositionConstraint.scrap(6, -1, 5), PositionConstraint.scrap(5, -1, 6),
+                PositionConstraint.scrap(7, -1, 7), PositionConstraint.scrap(8, -1, 8),
+                PositionConstraint.scrap(9, -1, 9));
+        // now mix items.
+    }
+
+
+    public void testGetItemForDeletedView() throws Throwable {
+        getItemForDeletedViewTest(false);
+        getItemForDeletedViewTest(true);
+    }
+
+    public void getItemForDeletedViewTest(boolean stableIds) throws Throwable {
+        final Set<Integer> itemViewTypeQueries = new HashSet<Integer>();
+        final Set<Integer> itemIdQueries = new HashSet<Integer>();
+        TestAdapter adapter = new TestAdapter(10) {
+            @Override
+            public int getItemViewType(int position) {
+                itemViewTypeQueries.add(position);
+                return super.getItemViewType(position);
+            }
+
+            @Override
+            public long getItemId(int position) {
+                itemIdQueries.add(position);
+                return mItems.get(position).mId;
+            }
+        };
+        adapter.setHasStableIds(stableIds);
+        setupBasic(10, 0, 10, adapter);
+        assertEquals("getItemViewType for all items should be called", 10,
+                itemViewTypeQueries.size());
+        if (adapter.hasStableIds()) {
+            assertEquals("getItemId should be called when adapter has stable ids", 10,
+                    itemIdQueries.size());
+        } else {
+            assertEquals("getItemId should not be called when adapter does not have stable ids", 0,
+                    itemIdQueries.size());
+        }
+        itemViewTypeQueries.clear();
+        itemIdQueries.clear();
+        mLayoutManager.expectLayouts(2);
+        // delete last two
+        final int deleteStart = 8;
+        final int deleteCount = adapter.getItemCount() - deleteStart;
+        adapter.deleteAndNotify(deleteStart, deleteCount);
+        mLayoutManager.waitForLayout(2);
+        for (int i = 0; i < deleteStart; i++) {
+            assertTrue("getItemViewType for existing item " + i + " should be called",
+                    itemViewTypeQueries.contains(i));
+            if (adapter.hasStableIds()) {
+                assertTrue("getItemId for existing item " + i
+                        + " should be called when adapter has stable ids",
+                        itemIdQueries.contains(i));
+            }
+        }
+        for (int i = deleteStart; i < deleteStart + deleteCount; i++) {
+            assertFalse("getItemViewType for deleted item " + i + " SHOULD NOT be called",
+                    itemViewTypeQueries.contains(i));
+            if (adapter.hasStableIds()) {
+                assertFalse("getItemId for deleted item " + i + " SHOULD NOT be called",
+                        itemIdQueries.contains(i));
+            }
+        }
+    }
+
+    public void testDeleteInvisibleMultiStep() throws Throwable {
+        setupBasic(1000, 1, 7);
+        mLayoutManager.mOnLayoutCallbacks.mLayoutMin = 1;
+        mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 7;
+        mLayoutManager.expectLayouts(1);
+        // try to trigger race conditions
+        int targetItemCount = mTestAdapter.getItemCount();
+        for (int i = 0; i < 100; i++) {
+            mTestAdapter.deleteAndNotify(new int[]{0, 1}, new int[]{7, 1});
+            targetItemCount -= 2;
+        }
+        // wait until main thread runnables are consumed
+        while (targetItemCount != mTestAdapter.getItemCount()) {
+            Thread.sleep(100);
+        }
+        mLayoutManager.waitForLayout(2);
+    }
+
+    public void testAddManyMultiStep() throws Throwable {
+        setupBasic(10, 1, 7);
+        mLayoutManager.mOnLayoutCallbacks.mLayoutMin = 1;
+        mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 7;
+        mLayoutManager.expectLayouts(1);
+        // try to trigger race conditions
+        int targetItemCount = mTestAdapter.getItemCount();
+        for (int i = 0; i < 100; i++) {
+            mTestAdapter.addAndNotify(0, 1);
+            mTestAdapter.addAndNotify(7, 1);
+            targetItemCount += 2;
+        }
+        // wait until main thread runnables are consumed
+        while (targetItemCount != mTestAdapter.getItemCount()) {
+            Thread.sleep(100);
+        }
+        mLayoutManager.waitForLayout(2);
+    }
+
+    public void testBasicDelete() throws Throwable {
+        setupBasic(10);
+        final OnLayoutCallbacks callbacks = new OnLayoutCallbacks() {
+            @Override
+            public void postDispatchLayout() {
+                // verify this only in first layout
+                assertEquals("deleted views should still be children of RV",
+                        mLayoutManager.getChildCount() + mDeletedViewCount
+                        , mRecyclerView.getChildCount());
+            }
+
+            @Override
+            void afterPreLayout(RecyclerView.Recycler recycler,
+                    AnimationLayoutManager layoutManager,
+                    RecyclerView.State state) {
+                super.afterPreLayout(recycler, layoutManager, state);
+                mLayoutItemCount = 3;
+                mLayoutMin = 0;
+            }
+        };
+        callbacks.mLayoutItemCount = 10;
+        callbacks.setExpectedItemCounts(10, 3);
+        mLayoutManager.setOnLayoutCallbacks(callbacks);
+
+        mLayoutManager.expectLayouts(2);
+        mTestAdapter.deleteAndNotify(0, 7);
+        mLayoutManager.waitForLayout(2);
+        callbacks.reset();// when animations end another layout will happen
+    }
+
+
+    public void testAdapterChangeDuringScrolling() throws Throwable {
+        setupBasic(10);
+        final AtomicInteger onLayoutItemCount = new AtomicInteger(0);
+        final AtomicInteger onScrollItemCount = new AtomicInteger(0);
+
+        mLayoutManager.setOnLayoutCallbacks(new OnLayoutCallbacks() {
+            @Override
+            void onLayoutChildren(RecyclerView.Recycler recycler,
+                    AnimationLayoutManager lm, RecyclerView.State state) {
+                onLayoutItemCount.set(state.getItemCount());
+                super.onLayoutChildren(recycler, lm, state);
+            }
+
+            @Override
+            public void onScroll(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
+                onScrollItemCount.set(state.getItemCount());
+                super.onScroll(dx, recycler, state);
+            }
+        });
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mTestAdapter.mItems.remove(5);
+                mTestAdapter.notifyItemRangeRemoved(5, 1);
+                mRecyclerView.scrollBy(0, 100);
+                assertTrue("scrolling while there are pending adapter updates should "
+                        + "trigger a layout", mLayoutManager.mOnLayoutCallbacks.mLayoutCount > 0);
+                assertEquals("scroll by should be called w/ updated adapter count",
+                        mTestAdapter.mItems.size(), onScrollItemCount.get());
+
+            }
+        });
+    }
+
+    public void testAddInvisibleAndVisible() throws Throwable {
+        setupBasic(10, 1, 7);
+        mLayoutManager.expectLayouts(2);
+        mLayoutManager.mOnLayoutCallbacks.setExpectedItemCounts(10, 12);
+        mTestAdapter.addAndNotify(new int[]{0, 1}, new int[]{7, 1});// add a new item 0 // invisible
+        mLayoutManager.waitForLayout(2);
+    }
+
+    public void testAddInvisible() throws Throwable {
+        setupBasic(10, 1, 7);
+        mLayoutManager.expectLayouts(1);
+        mLayoutManager.mOnLayoutCallbacks.setExpectedItemCounts(10, 12);
+        mTestAdapter.addAndNotify(new int[]{0, 1}, new int[]{8, 1});// add a new item 0
+        mLayoutManager.waitForLayout(2);
+    }
+
+    public void testBasicAdd() throws Throwable {
+        setupBasic(10);
+        mLayoutManager.expectLayouts(2);
+        setExpectedItemCounts(10, 13);
+        mTestAdapter.addAndNotify(2, 3);
+        mLayoutManager.waitForLayout(2);
+    }
+
+    public TestRecyclerView getTestRecyclerView() {
+        return (TestRecyclerView) mRecyclerView;
+    }
+
+    public void testRemoveScrapInvalidate() throws Throwable {
+        setupBasic(10);
+        TestRecyclerView testRecyclerView = getTestRecyclerView();
+        mLayoutManager.expectLayouts(1);
+        testRecyclerView.expectDraw(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mTestAdapter.mItems.clear();
+                mTestAdapter.notifyDataSetChanged();
+            }
+        });
+        mLayoutManager.waitForLayout(2);
+        testRecyclerView.waitForDraw(2);
+    }
+
+    public void testDeleteVisibleAndInvisible() throws Throwable {
+        setupBasic(11, 3, 5); //layout items  3 4 5 6 7
+        mLayoutManager.expectLayouts(2);
+        setLayoutRange(3, 5); //layout previously invisible child 10 from end of the list
+        setExpectedItemCounts(9, 8);
+        mTestAdapter.deleteAndNotify(new int[]{4, 1}, new int[]{7, 2});// delete items 4, 8, 9
+        mLayoutManager.waitForLayout(2);
+    }
+
+    public void testFindPositionOffset() throws Throwable {
+        setupBasic(10);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                // [0,1,2,3,4]
+                // delete 1
+                mTestAdapter.notifyItemRangeRemoved(1, 1);
+                // delete 3
+                mTestAdapter.notifyItemRangeRemoved(2, 1);
+                mAdapterHelper.preProcess();
+                // [0,2,4]
+                assertEquals("offset check", 0, mAdapterHelper.findPositionOffset(0));
+                assertEquals("offset check", 1, mAdapterHelper.findPositionOffset(2));
+                assertEquals("offset check", 2, mAdapterHelper.findPositionOffset(4));
+
+            }
+        });
+    }
+
+    private void setLayoutRange(int start, int count) {
+        mLayoutManager.mOnLayoutCallbacks.mLayoutMin = start;
+        mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = count;
+    }
+
+    private void setExpectedItemCounts(int preLayout, int postLayout) {
+        mLayoutManager.mOnLayoutCallbacks.setExpectedItemCounts(preLayout, postLayout);
+    }
+
+    public void testDeleteInvisible() throws Throwable {
+        setupBasic(10, 1, 7);
+        mLayoutManager.mOnLayoutCallbacks.mLayoutMin = 1;
+        mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 7;
+        mLayoutManager.expectLayouts(1);
+        mLayoutManager.mOnLayoutCallbacks.setExpectedItemCounts(8, 8);
+        mTestAdapter.deleteAndNotify(new int[]{0, 1}, new int[]{7, 1});// delete item id 0,8
+        mLayoutManager.waitForLayout(2);
+    }
+
+    private CollectPositionResult findByPos(RecyclerView recyclerView,
+            RecyclerView.Recycler recycler, RecyclerView.State state, int position) {
+        View view = recycler.getViewForPosition(position, true);
+        RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(view);
+        if (vh.wasReturnedFromScrap()) {
+            vh.clearReturnedFromScrapFlag(); //keep data consistent.
+            return CollectPositionResult.fromScrap(vh);
+        } else {
+            return CollectPositionResult.fromAdapter(vh);
+        }
+    }
+
+    public Map<Integer, CollectPositionResult> collectPositions(RecyclerView recyclerView,
+            RecyclerView.Recycler recycler, RecyclerView.State state, int... positions) {
+        Map<Integer, CollectPositionResult> positionToAdapterMapping
+                = new HashMap<Integer, CollectPositionResult>();
+        for (int position : positions) {
+            if (position < 0) {
+                continue;
+            }
+            positionToAdapterMapping.put(position,
+                    findByPos(recyclerView, recycler, state, position));
+        }
+        return positionToAdapterMapping;
+    }
+
+    public void testAddDelete2() throws Throwable {
+        positionStatesTest(5, 0, 5, new AdapterOps() {
+            // 0 1 2 3 4
+            // 0 1 2 a b 3 4
+            // 0 1 b 3 4
+            // pre: 0 1 2 3 4
+            // pre w/ adap: 0 1 2 b 3 4
+            @Override
+            void onRun(TestAdapter adapter) throws Throwable {
+                adapter.addDeleteAndNotify(new int[]{3, 2}, new int[]{2, -2});
+            }
+        }, PositionConstraint.scrap(2, 2, -1), PositionConstraint.scrap(1, 1, 1),
+                PositionConstraint.scrap(3, 3, 3)
+        );
+    }
+
+    public void testAddDelete1() throws Throwable {
+        positionStatesTest(5, 0, 5, new AdapterOps() {
+            // 0 1 2 3 4
+            // 0 1 2 a b 3 4
+            // 0 2 a b 3 4
+            // 0 c d 2 a b 3 4
+            // 0 c d 2 a 4
+            // c d 2 a 4
+            // pre: 0 1 2 3 4
+            @Override
+            void onRun(TestAdapter adapter) throws Throwable {
+                adapter.addDeleteAndNotify(new int[]{3, 2}, new int[]{1, -1},
+                        new int[]{1, 2}, new int[]{5, -2}, new int[]{0, -1});
+            }
+        }, PositionConstraint.scrap(0, 0, -1), PositionConstraint.scrap(1, 1, -1),
+                PositionConstraint.scrap(2, 2, 2), PositionConstraint.scrap(3, 3, -1),
+                PositionConstraint.scrap(4, 4, 4), PositionConstraint.adapter(0),
+                PositionConstraint.adapter(1), PositionConstraint.adapter(3)
+        );
+    }
+
+    public void testAddSameIndexTwice() throws Throwable {
+        positionStatesTest(12, 2, 7, new AdapterOps() {
+            @Override
+            void onRun(TestAdapter adapter) throws Throwable {
+                adapter.addAndNotify(new int[]{1, 2}, new int[]{5, 1}, new int[]{5, 1},
+                        new int[]{11, 1});
+            }
+        }, PositionConstraint.adapterScrap(0, 0), PositionConstraint.adapterScrap(1, 3),
+                PositionConstraint.scrap(2, 2, 4), PositionConstraint.scrap(3, 3, 7),
+                PositionConstraint.scrap(4, 4, 8), PositionConstraint.scrap(7, 7, 12),
+                PositionConstraint.scrap(8, 8, 13)
+        );
+    }
+
+    public void testDeleteTwice() throws Throwable {
+        positionStatesTest(12, 2, 7, new AdapterOps() {
+            @Override
+            void onRun(TestAdapter adapter) throws Throwable {
+                adapter.deleteAndNotify(new int[]{0, 1}, new int[]{1, 1}, new int[]{7, 1},
+                        new int[]{0, 1});// delete item ids 0,2,9,1
+            }
+        }, PositionConstraint.scrap(2, 0, -1), PositionConstraint.scrap(3, 1, 0),
+                PositionConstraint.scrap(4, 2, 1), PositionConstraint.scrap(5, 3, 2),
+                PositionConstraint.scrap(6, 4, 3), PositionConstraint.scrap(8, 6, 5),
+                PositionConstraint.adapterScrap(7, 6), PositionConstraint.adapterScrap(8, 7)
+        );
+    }
+
+
+    public void positionStatesTest(int itemCount, int firstLayoutStartIndex,
+            int firstLayoutItemCount, AdapterOps adapterChanges,
+            final PositionConstraint... constraints) throws Throwable {
+        positionStatesTest(itemCount, firstLayoutStartIndex, firstLayoutItemCount, null,
+                adapterChanges,  constraints);
+    }
+    public void positionStatesTest(int itemCount, int firstLayoutStartIndex,
+            int firstLayoutItemCount,TestAdapter adapter, AdapterOps adapterChanges,
+            final PositionConstraint... constraints) throws Throwable {
+        setupBasic(itemCount, firstLayoutStartIndex, firstLayoutItemCount, adapter);
+        mLayoutManager.expectLayouts(2);
+        mLayoutManager.mOnLayoutCallbacks = new OnLayoutCallbacks() {
+            @Override
+            void beforePreLayout(RecyclerView.Recycler recycler, AnimationLayoutManager lm,
+                    RecyclerView.State state) {
+                super.beforePreLayout(recycler, lm, state);
+                //harmless
+                lm.detachAndScrapAttachedViews(recycler);
+                final int[] ids = new int[constraints.length];
+                for (int i = 0; i < constraints.length; i++) {
+                    ids[i] = constraints[i].mPreLayoutPos;
+                }
+                Map<Integer, CollectPositionResult> positions
+                        = collectPositions(lm.mRecyclerView, recycler, state, ids);
+                for (PositionConstraint constraint : constraints) {
+                    if (constraint.mPreLayoutPos != -1) {
+                        constraint.validate(state, positions.get(constraint.mPreLayoutPos),
+                                lm.getLog());
+                    }
+                }
+            }
+
+            @Override
+            void beforePostLayout(RecyclerView.Recycler recycler, AnimationLayoutManager lm,
+                    RecyclerView.State state) {
+                super.beforePostLayout(recycler, lm, state);
+                lm.detachAndScrapAttachedViews(recycler);
+                final int[] ids = new int[constraints.length];
+                for (int i = 0; i < constraints.length; i++) {
+                    ids[i] = constraints[i].mPostLayoutPos;
+                }
+                Map<Integer, CollectPositionResult> positions
+                        = collectPositions(lm.mRecyclerView, recycler, state, ids);
+                for (PositionConstraint constraint : constraints) {
+                    if (constraint.mPostLayoutPos >= 0) {
+                        constraint.validate(state, positions.get(constraint.mPostLayoutPos),
+                                lm.getLog());
+                    }
+                }
+            }
+        };
+        adapterChanges.run(mTestAdapter);
+        mLayoutManager.waitForLayout(2);
+        checkForMainThreadException();
+        for (PositionConstraint constraint : constraints) {
+            constraint.assertValidate();
+        }
+    }
+
+    class AnimationLayoutManager extends TestLayoutManager {
+
+        private int mTotalLayoutCount = 0;
+        private String log;
+
+        OnLayoutCallbacks mOnLayoutCallbacks = new OnLayoutCallbacks() {
+        };
+
+
+
+        @Override
+        public boolean supportsPredictiveItemAnimations() {
+            return true;
+        }
+
+        public String getLog() {
+            return log;
+        }
+
+        private String prepareLog(RecyclerView.Recycler recycler, RecyclerView.State state, boolean done) {
+            StringBuilder builder = new StringBuilder();
+            builder.append("is pre layout:").append(state.isPreLayout()).append(", done:").append(done);
+            builder.append("\nViewHolders:\n");
+            for (RecyclerView.ViewHolder vh : ((TestRecyclerView)mRecyclerView).collectViewHolders()) {
+                builder.append(vh).append("\n");
+            }
+            builder.append("scrap:\n");
+            for (RecyclerView.ViewHolder vh : recycler.getScrapList()) {
+                builder.append(vh).append("\n");
+            }
+
+            if (state.isPreLayout() && !done) {
+                log = "\n" + builder.toString();
+            } else {
+                log += "\n" + builder.toString();
+            }
+            return log;
+        }
+
+        @Override
+        public void expectLayouts(int count) {
+            super.expectLayouts(count);
+            mOnLayoutCallbacks.mLayoutCount = 0;
+        }
+
+        public void setOnLayoutCallbacks(OnLayoutCallbacks onLayoutCallbacks) {
+            mOnLayoutCallbacks = onLayoutCallbacks;
+        }
+
+        @Override
+        public final void onLayoutChildren(RecyclerView.Recycler recycler,
+                RecyclerView.State state) {
+            try {
+                mTotalLayoutCount++;
+                prepareLog(recycler, state, false);
+                if (state.isPreLayout()) {
+                    validateOldPositions(recycler, state);
+                } else {
+                    validateClearedOldPositions(recycler, state);
+                }
+                mOnLayoutCallbacks.onLayoutChildren(recycler, this, state);
+                prepareLog(recycler, state, true);
+            } finally {
+                layoutLatch.countDown();
+            }
+        }
+
+        private void validateClearedOldPositions(RecyclerView.Recycler recycler,
+                RecyclerView.State state) {
+            if (getTestRecyclerView() == null) {
+                return;
+            }
+            for (RecyclerView.ViewHolder viewHolder : getTestRecyclerView().collectViewHolders()) {
+                assertEquals("there should NOT be an old position in post layout",
+                        RecyclerView.NO_POSITION, viewHolder.mOldPosition);
+                assertEquals("there should NOT be a pre layout position in post layout",
+                        RecyclerView.NO_POSITION, viewHolder.mPreLayoutPosition);
+            }
+        }
+
+        private void validateOldPositions(RecyclerView.Recycler recycler,
+                RecyclerView.State state) {
+            if (getTestRecyclerView() == null) {
+                return;
+            }
+            for (RecyclerView.ViewHolder viewHolder : getTestRecyclerView().collectViewHolders()) {
+                if (!viewHolder.isRemoved() && !viewHolder.isInvalid()) {
+                    assertTrue("there should be an old position in pre-layout",
+                            viewHolder.mOldPosition != RecyclerView.NO_POSITION);
+                }
+            }
+        }
+
+        public int getTotalLayoutCount() {
+            return mTotalLayoutCount;
+        }
+
+        @Override
+        public boolean canScrollVertically() {
+            return true;
+        }
+
+        @Override
+        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+                RecyclerView.State state) {
+            mOnLayoutCallbacks.onScroll(dy, recycler, state);
+            return super.scrollVerticallyBy(dy, recycler, state);
+        }
+
+        public void onPostDispatchLayout() {
+            mOnLayoutCallbacks.postDispatchLayout();
+        }
+
+        @Override
+        public void waitForLayout(long timeout, TimeUnit timeUnit) throws Throwable {
+            super.waitForLayout(timeout, timeUnit);
+            checkForMainThreadException();
+        }
+    }
+
+    abstract class OnLayoutCallbacks {
+
+        int mLayoutMin = Integer.MIN_VALUE;
+
+        int mLayoutItemCount = Integer.MAX_VALUE;
+
+        int expectedPreLayoutItemCount = -1;
+
+        int expectedPostLayoutItemCount = -1;
+
+        int mDeletedViewCount;
+
+        int mLayoutCount = 0;
+
+        void setExpectedItemCounts(int preLayout, int postLayout) {
+            expectedPreLayoutItemCount = preLayout;
+            expectedPostLayoutItemCount = postLayout;
+        }
+
+        void reset() {
+            mLayoutMin = Integer.MIN_VALUE;
+            mLayoutItemCount = Integer.MAX_VALUE;
+            expectedPreLayoutItemCount = -1;
+            expectedPostLayoutItemCount = -1;
+            mLayoutCount = 0;
+        }
+
+        void beforePreLayout(RecyclerView.Recycler recycler,
+                AnimationLayoutManager lm, RecyclerView.State state) {
+            mDeletedViewCount = 0;
+            for (int i = 0; i < lm.getChildCount(); i++) {
+                View v = lm.getChildAt(i);
+                if (lm.getLp(v).isItemRemoved()) {
+                    mDeletedViewCount++;
+                }
+            }
+        }
+
+        void doLayout(RecyclerView.Recycler recycler, AnimationLayoutManager lm,
+                RecyclerView.State state) {
+            if (DEBUG) {
+                Log.d(TAG, "item count " + state.getItemCount());
+            }
+            lm.detachAndScrapAttachedViews(recycler);
+            final int start = mLayoutMin == Integer.MIN_VALUE ? 0 : mLayoutMin;
+            final int count = mLayoutItemCount
+                    == Integer.MAX_VALUE ? state.getItemCount() : mLayoutItemCount;
+            lm.layoutRange(recycler, start, start + count);
+            assertEquals("correct # of children should be laid out",
+                    count, lm.getChildCount());
+            lm.assertVisibleItemPositions();
+        }
+
+        void onLayoutChildren(RecyclerView.Recycler recycler, AnimationLayoutManager lm,
+                RecyclerView.State state) {
+
+            if (state.isPreLayout()) {
+                if (expectedPreLayoutItemCount != -1) {
+                    assertEquals("on pre layout, state should return abstracted adapter size",
+                            expectedPreLayoutItemCount, state.getItemCount());
+                }
+                beforePreLayout(recycler, lm, state);
+            } else {
+                if (expectedPostLayoutItemCount != -1) {
+                    assertEquals("on post layout, state should return real adapter size",
+                            expectedPostLayoutItemCount, state.getItemCount());
+                }
+                beforePostLayout(recycler, lm, state);
+            }
+            doLayout(recycler, lm, state);
+            if (state.isPreLayout()) {
+                afterPreLayout(recycler, lm, state);
+            } else {
+                afterPostLayout(recycler, lm, state);
+            }
+            mLayoutCount++;
+        }
+
+        void afterPreLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager,
+                RecyclerView.State state) {
+        }
+
+        void beforePostLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager,
+                RecyclerView.State state) {
+        }
+
+        void afterPostLayout(RecyclerView.Recycler recycler, AnimationLayoutManager layoutManager,
+                RecyclerView.State state) {
+        }
+
+        void postDispatchLayout() {
+        }
+
+        public void onScroll(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
+
+        }
+    }
+
+    class TestRecyclerView extends RecyclerView {
+
+        CountDownLatch drawLatch;
+
+        public TestRecyclerView(Context context) {
+            super(context);
+        }
+
+        public TestRecyclerView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        public TestRecyclerView(Context context, AttributeSet attrs, int defStyle) {
+            super(context, attrs, defStyle);
+        }
+
+        @Override
+        void initAdapterManager() {
+            super.initAdapterManager();
+            mAdapterHelper.mOnItemProcessedCallback = new Runnable() {
+                @Override
+                public void run() {
+                    validatePostUpdateOp();
+                }
+            };
+        }
+
+        public void expectDraw(int count) {
+            drawLatch = new CountDownLatch(count);
+        }
+
+        public void waitForDraw(long timeout) throws Throwable {
+            drawLatch.await(timeout * (DEBUG ? 100 : 1), TimeUnit.SECONDS);
+            assertEquals("all expected draws should happen at the expected time frame",
+                    0, drawLatch.getCount());
+        }
+
+        List<ViewHolder> collectViewHolders() {
+            List<ViewHolder> holders = new ArrayList<ViewHolder>();
+            final int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                ViewHolder holder = getChildViewHolderInt(getChildAt(i));
+                if (holder != null) {
+                    holders.add(holder);
+                }
+            }
+            return holders;
+        }
+
+
+        private void validateViewHolderPositions() {
+            final Set<Integer> existingOffsets = new HashSet<Integer>();
+            int childCount = getChildCount();
+            StringBuilder log = new StringBuilder();
+            for (int i = 0; i < childCount; i++) {
+                ViewHolder vh = getChildViewHolderInt(getChildAt(i));
+                TestViewHolder tvh = (TestViewHolder) vh;
+                log.append(tvh.mBindedItem).append(vh)
+                        .append(" hidden:")
+                        .append(mChildHelper.mHiddenViews.contains(vh.itemView))
+                        .append("\n");
+            }
+            for (int i = 0; i < childCount; i++) {
+                ViewHolder vh = getChildViewHolderInt(getChildAt(i));
+                if (vh.isInvalid()) {
+                    continue;
+                }
+                if (vh.getPosition() < 0) {
+                    LayoutManager lm = getLayoutManager();
+                    for (int j = 0; j < lm.getChildCount(); j ++) {
+                        assertNotSame("removed view holder should not be in LM's child list",
+                                vh.itemView, lm.getChildAt(j));
+                    }
+                } else if (!mChildHelper.mHiddenViews.contains(vh.itemView)) {
+                    if (!existingOffsets.add(vh.getPosition())) {
+                        throw new IllegalStateException("view holder position conflict for "
+                                + "existing views " + vh + "\n" + log);
+                    }
+                }
+            }
+        }
+
+        void validatePostUpdateOp() {
+            try {
+                validateViewHolderPositions();
+                if (super.mState.isPreLayout()) {
+                    validatePreLayoutSequence((AnimationLayoutManager) getLayoutManager());
+                }
+                validateAdapterPosition((AnimationLayoutManager) getLayoutManager());
+            } catch (Throwable t) {
+                postExceptionToInstrumentation(t);
+            }
+        }
+
+
+
+        private void validateAdapterPosition(AnimationLayoutManager lm) {
+            for (ViewHolder vh : collectViewHolders()) {
+                if (!vh.isRemoved() && vh.mPreLayoutPosition >= 0) {
+                    assertEquals("adapter position calculations should match view holder "
+                            + "pre layout:" + mState.isPreLayout()
+                            + " positions\n" + vh + "\n" + lm.getLog(),
+                            mAdapterHelper.findPositionOffset(vh.mPreLayoutPosition), vh.mPosition);
+                }
+            }
+        }
+
+        // ensures pre layout positions are continuous block. This is not necessarily a case
+        // but valid in test RV
+        private void validatePreLayoutSequence(AnimationLayoutManager lm) {
+            Set<Integer> preLayoutPositions = new HashSet<Integer>();
+            for (ViewHolder vh : collectViewHolders()) {
+                assertTrue("pre layout positions should be distinct " + lm.getLog(),
+                        preLayoutPositions.add(vh.mPreLayoutPosition));
+            }
+            int minPos = Integer.MAX_VALUE;
+            for (Integer pos : preLayoutPositions) {
+                if (pos < minPos) {
+                    minPos = pos;
+                }
+            }
+            for (int i = 1; i < preLayoutPositions.size(); i++) {
+                assertNotNull("next position should exist " + lm.getLog(),
+                        preLayoutPositions.contains(minPos + i));
+            }
+        }
+
+        @Override
+        protected void dispatchDraw(Canvas canvas) {
+            super.dispatchDraw(canvas);
+            if (drawLatch != null) {
+                drawLatch.countDown();
+            }
+        }
+
+        @Override
+        void dispatchLayout() {
+            try {
+                super.dispatchLayout();
+                if (getLayoutManager() instanceof AnimationLayoutManager) {
+                    ((AnimationLayoutManager) getLayoutManager()).onPostDispatchLayout();
+                }
+            } catch (Throwable t) {
+                postExceptionToInstrumentation(t);
+            }
+
+        }
+
+
+    }
+
+    abstract class AdapterOps {
+
+        final public void run(TestAdapter adapter) throws Throwable {
+            onRun(adapter);
+        }
+
+        abstract void onRun(TestAdapter testAdapter) throws Throwable;
+    }
+
+    static class CollectPositionResult {
+
+        // true if found in scrap
+        public RecyclerView.ViewHolder scrapResult;
+
+        public RecyclerView.ViewHolder adapterResult;
+
+        static CollectPositionResult fromScrap(RecyclerView.ViewHolder viewHolder) {
+            CollectPositionResult cpr = new CollectPositionResult();
+            cpr.scrapResult = viewHolder;
+            return cpr;
+        }
+
+        static CollectPositionResult fromAdapter(RecyclerView.ViewHolder viewHolder) {
+            CollectPositionResult cpr = new CollectPositionResult();
+            cpr.adapterResult = viewHolder;
+            return cpr;
+        }
+    }
+
+    static class PositionConstraint {
+
+        public static enum Type {
+            scrap,
+            adapter,
+            adapterScrap /*first pass adapter, second pass scrap*/
+        }
+
+        Type mType;
+
+        int mOldPos; // if VH
+
+        int mPreLayoutPos;
+
+        int mPostLayoutPos;
+
+        int mValidateCount = 0;
+
+        public static PositionConstraint scrap(int oldPos, int preLayoutPos, int postLayoutPos) {
+            PositionConstraint constraint = new PositionConstraint();
+            constraint.mType = Type.scrap;
+            constraint.mOldPos = oldPos;
+            constraint.mPreLayoutPos = preLayoutPos;
+            constraint.mPostLayoutPos = postLayoutPos;
+            return constraint;
+        }
+
+        public static PositionConstraint adapterScrap(int preLayoutPos, int position) {
+            PositionConstraint constraint = new PositionConstraint();
+            constraint.mType = Type.adapterScrap;
+            constraint.mOldPos = RecyclerView.NO_POSITION;
+            constraint.mPreLayoutPos = preLayoutPos;
+            constraint.mPostLayoutPos = position;// adapter pos does not change
+            return constraint;
+        }
+
+        public static PositionConstraint adapter(int position) {
+            PositionConstraint constraint = new PositionConstraint();
+            constraint.mType = Type.adapter;
+            constraint.mPreLayoutPos = RecyclerView.NO_POSITION;
+            constraint.mOldPos = RecyclerView.NO_POSITION;
+            constraint.mPostLayoutPos = position;// adapter pos does not change
+            return constraint;
+        }
+
+        public void assertValidate() {
+            int expectedValidate = 0;
+            if (mPreLayoutPos >= 0) {
+                expectedValidate ++;
+            }
+            if (mPostLayoutPos >= 0) {
+                expectedValidate ++;
+            }
+            assertEquals("should run all validates", expectedValidate, mValidateCount);
+        }
+
+        @Override
+        public String toString() {
+            return "Cons{" +
+                    "t=" + mType.name() +
+                    ", old=" + mOldPos +
+                    ", pre=" + mPreLayoutPos +
+                    ", post=" + mPostLayoutPos +
+                    '}';
+        }
+
+        public void validate(RecyclerView.State state, CollectPositionResult result, String log) {
+            mValidateCount ++;
+            assertNotNull(this + ": result should not be null\n" + log, result);
+            RecyclerView.ViewHolder viewHolder;
+            if (mType == Type.scrap || (mType == Type.adapterScrap && !state.isPreLayout())) {
+                assertNotNull(this + ": result should come from scrap\n" + log, result.scrapResult);
+                viewHolder = result.scrapResult;
+            } else {
+                assertNotNull(this + ": result should come from adapter\n"  + log,
+                        result.adapterResult);
+                assertEquals(this + ": old position should be none when it came from adapter\n" + log,
+                        RecyclerView.NO_POSITION, result.adapterResult.getOldPosition());
+                viewHolder = result.adapterResult;
+            }
+            if (state.isPreLayout()) {
+                assertEquals(this + ": pre-layout position should match\n" + log, mPreLayoutPos,
+                        viewHolder.mPreLayoutPosition == -1 ? viewHolder.mPosition :
+                        viewHolder.mPreLayoutPosition);
+                assertEquals(this + ": pre-layout getPosition should match\n" + log, mPreLayoutPos,
+                        viewHolder.getPosition());
+                if (mType == Type.scrap) {
+                    assertEquals(this + ": old position should match\n" + log, mOldPos,
+                            result.scrapResult.getOldPosition());
+                }
+            } else if (mType == Type.adapter || mType == Type.adapterScrap || !result.scrapResult
+                    .isRemoved()) {
+                assertEquals(this + ": post-layout position should match\n" + log + "\n\n"
+                        + viewHolder, mPostLayoutPos, viewHolder.getPosition());
+            }
+        }
+    }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
new file mode 100644
index 0000000..488ccb8
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.test.AndroidTestCase;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.UUID;
+
+public class RecyclerViewBasicTest extends AndroidTestCase {
+
+    RecyclerView mRecyclerView;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mRecyclerView = new RecyclerView(mContext);
+    }
+
+    public void testMeasureWithoutLayoutManager() {
+        Throwable measureThrowable = null;
+        try {
+            measure();
+        } catch (Throwable throwable) {
+            measureThrowable = throwable;
+        }
+        assertTrue("Calling measure without a layout manager should throw exception"
+                , measureThrowable instanceof NullPointerException);
+    }
+
+    private void measure() {
+        mRecyclerView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 240);
+    }
+
+    private void layout() {
+        mRecyclerView.layout(0, 0, 320, 320);
+    }
+
+    private void safeLayout() {
+        try {
+            layout();
+        } catch (Throwable t) {
+
+        }
+    }
+
+    public void testLayoutWithoutLayoutManager() throws InterruptedException {
+        MockLayoutManager layoutManager = new MockLayoutManager();
+        mRecyclerView.setLayoutManager(layoutManager);
+        safeLayout();
+        assertEquals("layout manager should not be called if there is no adapter attached",
+                0, layoutManager.mLayoutCount);
+    }
+
+    public void testLayout() throws InterruptedException {
+        MockLayoutManager layoutManager = new MockLayoutManager();
+        mRecyclerView.setLayoutManager(layoutManager);
+        mRecyclerView.setAdapter(new MockAdapter(3));
+        layout();
+        assertEquals("when both layout manager and activity is set, recycler view should call"
+                + " layout manager's layout method", 1, layoutManager.mLayoutCount);
+    }
+
+    public void testObservingAdapters() {
+        MockAdapter adapterOld = new MockAdapter(1);
+        mRecyclerView.setAdapter(adapterOld);
+        assertTrue("attached adapter should have observables", adapterOld.hasObservers());
+
+        MockAdapter adapterNew = new MockAdapter(2);
+        mRecyclerView.setAdapter(adapterNew);
+        assertFalse("detached adapter should lose observable", adapterOld.hasObservers());
+        assertTrue("new adapter should have observers", adapterNew.hasObservers());
+
+        mRecyclerView.setAdapter(null);
+        assertNull("adapter should be removed successfully", mRecyclerView.getAdapter());
+        assertFalse("when adapter is removed, observables should be removed too",
+                adapterNew.hasObservers());
+    }
+
+    public void testAdapterChangeCallbacks() {
+        MockLayoutManager layoutManager = new MockLayoutManager();
+        mRecyclerView.setLayoutManager(layoutManager);
+        MockAdapter adapterOld = new MockAdapter(1);
+        mRecyclerView.setAdapter(adapterOld);
+        layoutManager.assertPrevNextAdapters(null, adapterOld);
+
+        MockAdapter adapterNew = new MockAdapter(2);
+        mRecyclerView.setAdapter(adapterNew);
+        layoutManager.assertPrevNextAdapters("switching adapters should trigger correct callbacks"
+                , adapterOld, adapterNew);
+
+        mRecyclerView.setAdapter(null);
+        layoutManager.assertPrevNextAdapters(
+                "Setting adapter null should trigger correct callbacks",
+                adapterNew, null);
+    }
+
+    public void testSavedStateWithStatelessLayoutManager() throws InterruptedException {
+        mRecyclerView.setLayoutManager(new MockLayoutManager() {
+            @Override
+            public Parcelable onSaveInstanceState() {
+                return null;
+            }
+        });
+        mRecyclerView.setAdapter(new MockAdapter(3));
+        Parcel parcel = Parcel.obtain();
+        String parcelSuffix = UUID.randomUUID().toString();
+        Parcelable savedState = mRecyclerView.onSaveInstanceState();
+        savedState.writeToParcel(parcel, 0);
+        parcel.writeString(parcelSuffix);
+
+        // reset position for reading
+        parcel.setDataPosition(0);
+        RecyclerView restored = new RecyclerView(mContext);
+        restored.setLayoutManager(new MockLayoutManager());
+        mRecyclerView.setAdapter(new MockAdapter(3));
+        // restore
+        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
+        restored.onRestoreInstanceState(savedState);
+
+        assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
+                parcel.readString());
+        assertEquals("When unmarshalling, all of the parcel should be read", 0, parcel.dataAvail());
+
+    }
+
+    public void testSavedState() throws InterruptedException {
+        MockLayoutManager mlm = new MockLayoutManager();
+        mRecyclerView.setLayoutManager(mlm);
+        mRecyclerView.setAdapter(new MockAdapter(3));
+        layout();
+        Parcelable savedState = mRecyclerView.onSaveInstanceState();
+        // we append a suffix to the parcelable to test out of bounds
+        String parcelSuffix = UUID.randomUUID().toString();
+        Parcel parcel = Parcel.obtain();
+        savedState.writeToParcel(parcel, 0);
+        parcel.writeString(parcelSuffix);
+
+        // reset for reading
+        parcel.setDataPosition(0);
+        // re-create
+        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
+
+        RecyclerView restored = new RecyclerView(mContext);
+        MockLayoutManager mlmRestored = new MockLayoutManager();
+        restored.setLayoutManager(mlmRestored);
+        restored.setAdapter(new MockAdapter(3));
+        restored.onRestoreInstanceState(savedState);
+
+        assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
+                parcel.readString());
+        assertEquals("When unmarshalling, all of the parcel should be read", 0, parcel.dataAvail());
+        assertEquals("uuid in layout manager should be preserved properly", mlm.mUuid,
+                mlmRestored.mUuid);
+        assertNotSame("stateless parameter should not be preserved", mlm.mLayoutCount,
+                mlmRestored.mLayoutCount);
+        layout();
+    }
+
+    static class MockLayoutManager extends RecyclerView.LayoutManager {
+
+        int mLayoutCount = 0;
+
+        int mAdapterChangedCount = 0;
+
+        RecyclerView.Adapter mPrevAdapter;
+
+        RecyclerView.Adapter mNextAdapter;
+
+        String mUuid = UUID.randomUUID().toString();
+
+        @Override
+        public void onAdapterChanged(RecyclerView.Adapter oldAdapter,
+                RecyclerView.Adapter newAdapter) {
+            super.onAdapterChanged(oldAdapter, newAdapter);
+            mPrevAdapter = oldAdapter;
+            mNextAdapter = newAdapter;
+            mAdapterChangedCount++;
+        }
+
+        @Override
+        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+            mLayoutCount += 1;
+        }
+
+        @Override
+        public Parcelable onSaveInstanceState() {
+            LayoutManagerSavedState lss = new LayoutManagerSavedState();
+            lss.mUuid = mUuid;
+            return lss;
+        }
+
+        @Override
+        public void onRestoreInstanceState(Parcelable state) {
+            super.onRestoreInstanceState(state);
+            if (state instanceof LayoutManagerSavedState) {
+                mUuid = ((LayoutManagerSavedState) state).mUuid;
+            }
+        }
+
+        @Override
+        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT);
+        }
+
+        public void assertPrevNextAdapters(String message, RecyclerView.Adapter prevAdapter,
+                RecyclerView.Adapter nextAdapter) {
+            assertSame(message, prevAdapter, mPrevAdapter);
+            assertSame(message, nextAdapter, mNextAdapter);
+        }
+
+        public void assertPrevNextAdapters(RecyclerView.Adapter prevAdapter,
+                RecyclerView.Adapter nextAdapter) {
+            assertPrevNextAdapters("Adapters from onAdapterChanged callback should match",
+                    prevAdapter, nextAdapter);
+        }
+    }
+
+    static class LayoutManagerSavedState implements Parcelable {
+
+        String mUuid;
+
+        public LayoutManagerSavedState(Parcel in) {
+            mUuid = in.readString();
+        }
+
+        public LayoutManagerSavedState() {
+
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(mUuid);
+        }
+
+        public static final Parcelable.Creator<LayoutManagerSavedState> CREATOR
+                = new Parcelable.Creator<LayoutManagerSavedState>() {
+            @Override
+            public LayoutManagerSavedState createFromParcel(Parcel in) {
+                return new LayoutManagerSavedState(in);
+            }
+
+            @Override
+            public LayoutManagerSavedState[] newArray(int size) {
+                return new LayoutManagerSavedState[size];
+            }
+        };
+    }
+
+    static class MockAdapter extends RecyclerView.Adapter {
+
+        private int mCount = 0;
+
+
+        MockAdapter(int count) {
+            this.mCount = count;
+        }
+
+        @Override
+        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            return new MockViewHolder(new TextView(parent.getContext()));
+        }
+
+        @Override
+        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+
+        }
+
+        @Override
+        public int getItemCount() {
+            return mCount;
+        }
+
+        void removeItems(int start, int count) {
+            mCount -= count;
+            notifyItemRangeRemoved(start, count);
+        }
+
+        void addItems(int start, int count) {
+            mCount += count;
+            notifyItemRangeInserted(start, count);
+        }
+    }
+
+    static class MockViewHolder extends RecyclerView.ViewHolder {
+
+        public MockViewHolder(View itemView) {
+            super(itemView);
+        }
+    }
+}
\ No newline at end of file
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
new file mode 100644
index 0000000..ac1dfbb
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -0,0 +1,1416 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v7.widget;
+
+import android.graphics.PointF;
+import android.support.v4.view.ViewCompat;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class RecyclerViewLayoutTest extends BaseRecyclerViewInstrumentationTest {
+
+    private static final boolean DEBUG = false;
+
+    private static final String TAG = "RecyclerViewLayoutTest";
+
+    public RecyclerViewLayoutTest() {
+        super(DEBUG);
+    }
+    public void testRecycleScrap() throws Throwable {
+        recycleScrapTest(false);
+        removeRecyclerView();
+        recycleScrapTest(true);
+    }
+
+    public void recycleScrapTest(final boolean useRecycler) throws Throwable {
+        TestAdapter testAdapter = new TestAdapter(10);
+        final AtomicBoolean test = new AtomicBoolean(false);
+        TestLayoutManager lm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                if (test.get()) {
+                    try {
+                        detachAndScrapAttachedViews(recycler);
+                        for (int i = recycler.getScrapList().size() - 1; i >= 0; i--) {
+                            if (useRecycler) {
+                                recycler.recycleView(recycler.getScrapList().get(i).itemView);
+                            } else {
+                                removeAndRecycleView(recycler.getScrapList().get(i).itemView,
+                                        recycler);
+                            }
+                        }
+                        if (state.mOldChangedHolders != null) {
+                            for (int i = state.mOldChangedHolders.size() - 1; i >= 0; i--) {
+                                if (useRecycler) {
+                                    recycler.recycleView(
+                                            state.mOldChangedHolders.valueAt(i).itemView);
+                                } else {
+                                    removeAndRecycleView(
+                                            state.mOldChangedHolders.valueAt(i).itemView, recycler);
+                                }
+                            }
+                        }
+                        assertEquals("no scrap should be left over", 0, recycler.getScrapCount());
+                        assertEquals("pre layout map should be empty", 0,
+                                state.mPreLayoutHolderMap.size());
+                        assertEquals("post layout map should be empty", 0,
+                                state.mPostLayoutHolderMap.size());
+                        if (state.mOldChangedHolders != null) {
+                            assertEquals("post old change map should be empty", 0,
+                                    state.mOldChangedHolders.size());
+                        }
+                    } catch (Throwable t) {
+                        postExceptionToInstrumentation(t);
+                    }
+
+                }
+                layoutRange(recycler, 0, 5);
+                layoutLatch.countDown();
+                super.onLayoutChildren(recycler, state);
+            }
+        };
+        RecyclerView recyclerView = new RecyclerView(getActivity());
+        recyclerView.setAdapter(testAdapter);
+        recyclerView.setLayoutManager(lm);
+        recyclerView.getItemAnimator().setSupportsChangeAnimations(true);
+        lm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        lm.waitForLayout(2);
+        test.set(true);
+        lm.expectLayouts(1);
+        testAdapter.changeAndNotify(3, 1);
+        lm.waitForLayout(2);
+        checkForMainThreadException();
+    }
+
+    public void testAccessRecyclerOnOnMeasure() throws Throwable {
+        accessRecyclerOnOnMeasureTest(false);
+        removeRecyclerView();
+        accessRecyclerOnOnMeasureTest(true);
+    }
+
+    public void testSmoothScrollWithRemovedItems() throws Throwable {
+        smoothScrollTest(false);
+        removeRecyclerView();
+        smoothScrollTest(true);
+    }
+
+    public void smoothScrollTest(final boolean removeItem) throws Throwable {
+        final LinearSmoothScroller[] lss = new LinearSmoothScroller[1];
+        final CountDownLatch calledOnStart = new CountDownLatch(1);
+        final CountDownLatch calledOnStop = new CountDownLatch(1);
+        final int visibleChildCount = 10;
+        TestLayoutManager lm = new TestLayoutManager() {
+            int start = 0;
+
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                super.onLayoutChildren(recycler, state);
+                layoutRange(recycler, start, visibleChildCount);
+                layoutLatch.countDown();
+            }
+
+            @Override
+            public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+                    RecyclerView.State state) {
+                start++;
+                if (DEBUG) {
+                    Log.d(TAG, "on scroll, remove and recycling. start:" + start + ", cnt:"
+                            + visibleChildCount);
+                }
+                removeAndRecycleAllViews(recycler);
+                layoutRange(recycler, start,
+                        Math.max(state.getItemCount(), start + visibleChildCount));
+                return dy;
+            }
+
+            @Override
+            public boolean canScrollVertically() {
+                return true;
+            }
+
+            @Override
+            public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
+                    int position) {
+                LinearSmoothScroller linearSmoothScroller =
+                        new LinearSmoothScroller(recyclerView.getContext()) {
+                            @Override
+                            public PointF computeScrollVectorForPosition(int targetPosition) {
+                                return new PointF(0, 1);
+                            }
+
+                            @Override
+                            protected void onStart() {
+                                super.onStart();
+                                calledOnStart.countDown();
+                            }
+
+                            @Override
+                            protected void onStop() {
+                                super.onStop();
+                                calledOnStop.countDown();
+                            }
+                        };
+                linearSmoothScroller.setTargetPosition(position);
+                lss[0] = linearSmoothScroller;
+                startSmoothScroll(linearSmoothScroller);
+            }
+        };
+        final RecyclerView rv = new RecyclerView(getActivity());
+        TestAdapter testAdapter = new TestAdapter(500);
+        rv.setLayoutManager(lm);
+        rv.setAdapter(testAdapter);
+        lm.expectLayouts(1);
+        setRecyclerView(rv);
+        lm.waitForLayout(1);
+        // regular scroll
+        final int targetPosition = visibleChildCount * (removeItem ? 30 : 4);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                rv.smoothScrollToPosition(targetPosition);
+            }
+        });
+        if (DEBUG) {
+            Log.d(TAG, "scrolling to target position " + targetPosition);
+        }
+        assertTrue("on start should be called very soon", calledOnStart.await(2, TimeUnit.SECONDS));
+        if (removeItem) {
+            final int newTarget = targetPosition - 10;
+            testAdapter.deleteAndNotify(newTarget + 1, testAdapter.getItemCount() - newTarget - 1);
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    ViewCompat.postOnAnimationDelayed(rv, new Runnable() {
+                        @Override
+                        public void run() {
+                            try {
+                                assertEquals("scroll position should be updated to next available",
+                                        newTarget, lss[0].getTargetPosition());
+                            } catch (Throwable t) {
+                                postExceptionToInstrumentation(t);
+                            }
+                        }
+                    }, 200);
+                }
+            });
+            checkForMainThreadException();
+            assertTrue("on stop should be called", calledOnStop.await(30, TimeUnit.SECONDS));
+            checkForMainThreadException();
+            assertNotNull("should scroll to new target " + newTarget
+                    , rv.findViewHolderForPosition(newTarget));
+            if (DEBUG) {
+                Log.d(TAG, "on stop has been called on time");
+            }
+        } else {
+            assertTrue("on stop should be called eventually",
+                    calledOnStop.await(30, TimeUnit.SECONDS));
+            assertNotNull("scroll to position should succeed",
+                    rv.findViewHolderForPosition(targetPosition));
+        }
+        checkForMainThreadException();
+    }
+
+    public void accessRecyclerOnOnMeasureTest(final boolean enablePredictiveAnimations)
+            throws Throwable {
+        TestAdapter testAdapter = new TestAdapter(10);
+        final AtomicInteger expectedOnMeasureStateCount = new AtomicInteger(10);
+        TestLayoutManager lm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                super.onLayoutChildren(recycler, state);
+                try {
+                    layoutRange(recycler, 0, state.getItemCount());
+                    layoutLatch.countDown();
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                } finally {
+                    layoutLatch.countDown();
+                }
+            }
+
+            @Override
+            public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
+                    int widthSpec, int heightSpec) {
+                try {
+                    // make sure we access all views
+                    for (int i = 0; i < state.getItemCount(); i++) {
+                        View view = recycler.getViewForPosition(i);
+                        assertNotNull(view);
+                        assertEquals(i, getPosition(view));
+                    }
+                    assertEquals(state.toString(),
+                            expectedOnMeasureStateCount.get(), state.getItemCount());
+                } catch(Throwable t) {
+                    postExceptionToInstrumentation(t);
+                }
+                super.onMeasure(recycler, state, widthSpec, heightSpec);
+            }
+
+            @Override
+            public boolean supportsPredictiveItemAnimations() {
+                return enablePredictiveAnimations;
+            }
+        };
+        RecyclerView recyclerView = new RecyclerView(getActivity());
+        recyclerView.setLayoutManager(lm);
+        recyclerView.setAdapter(testAdapter);
+        recyclerView.setLayoutManager(lm);
+        lm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        lm.waitForLayout(2);
+        checkForMainThreadException();
+        lm.expectLayouts(1);
+        if (!enablePredictiveAnimations) {
+            expectedOnMeasureStateCount.set(15);
+        }
+        testAdapter.addAndNotify(4, 5);
+        lm.waitForLayout(2);
+        checkForMainThreadException();
+    }
+
+    public void testSetCompatibleAdapter() throws Throwable {
+        compatibleAdapterTest(true, true);
+        removeRecyclerView();
+        compatibleAdapterTest(false, true);
+        removeRecyclerView();
+        compatibleAdapterTest(true, false);
+        removeRecyclerView();
+        compatibleAdapterTest(false, false);
+        removeRecyclerView();
+    }
+
+    private void compatibleAdapterTest(boolean useCustomPool, boolean removeAndRecycleExistingViews)
+            throws Throwable {
+        TestAdapter testAdapter = new TestAdapter(10);
+        final AtomicInteger recycledViewCount = new AtomicInteger();
+        TestLayoutManager lm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                super.onLayoutChildren(recycler, state);
+                try {
+                    layoutRange(recycler, 0, state.getItemCount());
+                    layoutLatch.countDown();
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                } finally {
+                    layoutLatch.countDown();
+                }
+            }
+        };
+        RecyclerView recyclerView = new RecyclerView(getActivity());
+        recyclerView.setLayoutManager(lm);
+        recyclerView.setAdapter(testAdapter);
+        recyclerView.setLayoutManager(lm);
+        recyclerView.setRecyclerListener(new RecyclerView.RecyclerListener() {
+            @Override
+            public void onViewRecycled(RecyclerView.ViewHolder holder) {
+                recycledViewCount.incrementAndGet();
+            }
+        });
+        lm.expectLayouts(1);
+        setRecyclerView(recyclerView, !useCustomPool);
+        lm.waitForLayout(2);
+        checkForMainThreadException();
+        lm.expectLayouts(1);
+        swapAdapter(new TestAdapter(10), removeAndRecycleExistingViews);
+        lm.waitForLayout(2);
+        checkForMainThreadException();
+        if (removeAndRecycleExistingViews) {
+            assertTrue("Previous views should be recycled", recycledViewCount.get() > 0);
+        } else {
+            assertEquals("No views should be recycled if adapters are compatible and developer "
+                    + "did not request a recycle", 0, recycledViewCount.get());
+        }
+    }
+
+    public void testSetIncompatibleAdapter() throws Throwable {
+        incompatibleAdapterTest(true);
+        incompatibleAdapterTest(false);
+    }
+
+    public void incompatibleAdapterTest(boolean useCustomPool) throws Throwable {
+        TestAdapter testAdapter = new TestAdapter(10);
+        TestLayoutManager lm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                super.onLayoutChildren(recycler, state);
+                try {
+                    layoutRange(recycler, 0, state.getItemCount());
+                    layoutLatch.countDown();
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                } finally {
+                    layoutLatch.countDown();
+                }
+            }
+        };
+        RecyclerView recyclerView = new RecyclerView(getActivity());
+        recyclerView.setLayoutManager(lm);
+        recyclerView.setAdapter(testAdapter);
+        recyclerView.setLayoutManager(lm);
+        lm.expectLayouts(1);
+        setRecyclerView(recyclerView, !useCustomPool);
+        lm.waitForLayout(2);
+        checkForMainThreadException();
+        lm.expectLayouts(1);
+        setAdapter(new TestAdapter2(10));
+        lm.waitForLayout(2);
+        checkForMainThreadException();
+    }
+
+    public void testRecycleIgnored() throws Throwable {
+        final TestAdapter adapter = new TestAdapter(10);
+        final TestLayoutManager lm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                layoutRange(recycler, 0, 5);
+                layoutLatch.countDown();
+            }
+        };
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        recyclerView.setAdapter(adapter);
+        recyclerView.setLayoutManager(lm);
+        lm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        lm.waitForLayout(2);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                View child1 = lm.findViewByPosition(0);
+                View child2 = lm.findViewByPosition(1);
+                lm.ignoreView(child1);
+                lm.ignoreView(child2);
+
+                lm.removeAndRecycleAllViews(recyclerView.mRecycler);
+                assertEquals("ignored child should not be recycled or removed", 2,
+                        lm.getChildCount());
+
+                Throwable[] throwables = new Throwable[1];
+                try {
+                    lm.removeAndRecycleView(child1, mRecyclerView.mRecycler);
+                } catch (Throwable t) {
+                    throwables[0] = t;
+                }
+                assertTrue("Trying to recycle an ignored view should throw IllegalArgException "
+                        , throwables[0] instanceof IllegalArgumentException);
+                lm.removeAllViews();
+                assertEquals("ignored child should be removed as well ", 0, lm.getChildCount());
+            }
+        });
+    }
+
+    public void testFindIgnoredByPosition() throws Throwable {
+        final TestAdapter adapter = new TestAdapter(10);
+        final TestLayoutManager lm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                detachAndScrapAttachedViews(recycler);
+                layoutRange(recycler, 0, 5);
+                layoutLatch.countDown();
+            }
+        };
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        recyclerView.setAdapter(adapter);
+        recyclerView.setLayoutManager(lm);
+        lm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        lm.waitForLayout(2);
+        Thread.sleep(5000);
+        final int pos = 1;
+        final View[] ignored = new View[1];
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                View child = lm.findViewByPosition(pos);
+                lm.ignoreView(child);
+                ignored[0] = child;
+            }
+        });
+        assertNotNull("ignored child should not be null", ignored[0]);
+        assertNull("find view by position should not return ignored child",
+                lm.findViewByPosition(pos));
+        lm.expectLayouts(1);
+        requestLayoutOnUIThread(mRecyclerView);
+        lm.waitForLayout(1);
+        assertEquals("child count should be ", 6, lm.getChildCount());
+        View replacement = lm.findViewByPosition(pos);
+        assertNotNull("re-layout should replace ignored child w/ another one", replacement);
+        assertNotSame("replacement should be a different view", replacement, ignored[0]);
+    }
+
+    public void testInvalidateAllDecorOffsets() throws Throwable {
+        final TestAdapter adapter = new TestAdapter(10);
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        final AtomicBoolean invalidatedOffsets = new AtomicBoolean(true);
+        recyclerView.setAdapter(adapter);
+        final AtomicInteger layoutCount = new AtomicInteger(4);
+        final RecyclerView.ItemDecoration dummyItemDecoration = new RecyclerView.ItemDecoration() {
+        };
+        TestLayoutManager testLayoutManager = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                try {
+                    // test
+                    for (int i = 0; i < getChildCount(); i ++) {
+                        View child = getChildAt(i);
+                        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)
+                                child.getLayoutParams();
+                        assertEquals(
+                                "Decor insets validation for VH should have expected value.",
+                                invalidatedOffsets.get(), lp.mInsetsDirty);
+                    }
+                    for (RecyclerView.ViewHolder vh : mRecyclerView.mRecycler.mCachedViews) {
+                        RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)
+                                vh.itemView.getLayoutParams();
+                        assertEquals(
+                                "Decor insets invalidation in cache for VH should have expected "
+                                        + "value.",
+                                invalidatedOffsets.get(), lp.mInsetsDirty);
+                    }
+                    detachAndScrapAttachedViews(recycler);
+                    layoutRange(recycler, 0, layoutCount.get());
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                } finally {
+                    layoutLatch.countDown();
+                }
+            }
+
+            @Override
+            public boolean supportsPredictiveItemAnimations() {
+                return false;
+            }
+        };
+        // first layout
+        recyclerView.setItemViewCacheSize(5);
+        recyclerView.setLayoutManager(testLayoutManager);
+        testLayoutManager.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        testLayoutManager.waitForLayout(2);
+        checkForMainThreadException();
+
+        // re-layout w/o any change
+        invalidatedOffsets.set(false);
+        testLayoutManager.expectLayouts(1);
+        requestLayoutOnUIThread(recyclerView);
+        testLayoutManager.waitForLayout(1);
+        checkForMainThreadException();
+
+        // invalidate w/o an item decorator
+        invalidateDecorOffsets(recyclerView);
+        testLayoutManager.expectLayouts(1);
+        invalidateDecorOffsets(recyclerView);
+        testLayoutManager.assertNoLayout("layout should not happen", 2);
+        checkForMainThreadException();
+
+        // set item decorator, should invalidate
+        invalidatedOffsets.set(true);
+        testLayoutManager.expectLayouts(1);
+        addItemDecoration(mRecyclerView, dummyItemDecoration);
+        testLayoutManager.waitForLayout(1);
+        checkForMainThreadException();
+
+        // re-layout w/o any change
+        invalidatedOffsets.set(false);
+        testLayoutManager.expectLayouts(1);
+        requestLayoutOnUIThread(recyclerView);
+        testLayoutManager.waitForLayout(1);
+        checkForMainThreadException();
+
+        // invalidate w/ item decorator
+        invalidatedOffsets.set(true);
+        invalidateDecorOffsets(recyclerView);
+        testLayoutManager.expectLayouts(1);
+        invalidateDecorOffsets(recyclerView);
+        testLayoutManager.waitForLayout(2);
+        checkForMainThreadException();
+
+        // trigger cache.
+        layoutCount.set(3);
+        invalidatedOffsets.set(false);
+        testLayoutManager.expectLayouts(1);
+        requestLayoutOnUIThread(mRecyclerView);
+        testLayoutManager.waitForLayout(1);
+        checkForMainThreadException();
+        assertEquals("a view should be cached", 1, mRecyclerView.mRecycler.mCachedViews.size());
+
+        layoutCount.set(5);
+        invalidatedOffsets.set(true);
+        testLayoutManager.expectLayouts(1);
+        invalidateDecorOffsets(recyclerView);
+        testLayoutManager.waitForLayout(1);
+        checkForMainThreadException();
+
+        // remove item decorator
+        invalidatedOffsets.set(true);
+        testLayoutManager.expectLayouts(1);
+        removeItemDecoration(mRecyclerView, dummyItemDecoration);
+        testLayoutManager.waitForLayout(1);
+        checkForMainThreadException();
+    }
+
+    public void addItemDecoration(final RecyclerView recyclerView, final
+            RecyclerView.ItemDecoration itemDecoration) throws Throwable {
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                recyclerView.addItemDecoration(itemDecoration);
+            }
+        });
+    }
+
+    public void removeItemDecoration(final RecyclerView recyclerView, final
+    RecyclerView.ItemDecoration itemDecoration) throws Throwable {
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                recyclerView.removeItemDecoration(itemDecoration);
+            }
+        });
+    }
+
+    public void invalidateDecorOffsets(final RecyclerView recyclerView) throws Throwable {
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                recyclerView.invalidateItemDecorations();
+            }
+        });
+    }
+
+    public void testInvalidateDecorOffsets() throws Throwable {
+        final TestAdapter adapter = new TestAdapter(10);
+        adapter.setHasStableIds(true);
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        recyclerView.setAdapter(adapter);
+
+        final Map<Long, Boolean> changes = new HashMap<Long, Boolean>();
+
+        TestLayoutManager testLayoutManager = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                try {
+                    if (changes.size() > 0) {
+                        // test
+                        for (int i = 0; i < getChildCount(); i ++) {
+                            View child = getChildAt(i);
+                            RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)
+                                    child.getLayoutParams();
+                            RecyclerView.ViewHolder vh = lp.mViewHolder;
+                            if (!changes.containsKey(vh.getItemId())) {
+                                continue; //nothing to test
+                            }
+                            assertEquals(
+                                    "Decord insets validation for VH should have expected value.",
+                                    changes.get(vh.getItemId()).booleanValue(),
+                                    lp.mInsetsDirty);
+                        }
+                    }
+                    detachAndScrapAttachedViews(recycler);
+                    layoutRange(recycler, 0, state.getItemCount());
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                } finally {
+                    layoutLatch.countDown();
+                }
+            }
+
+            @Override
+            public boolean supportsPredictiveItemAnimations() {
+                return false;
+            }
+        };
+        recyclerView.setLayoutManager(testLayoutManager);
+        testLayoutManager.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        testLayoutManager.waitForLayout(2);
+        int itemAddedTo = 5;
+        for (int i = 0; i < itemAddedTo; i++) {
+            changes.put(mRecyclerView.findViewHolderForPosition(i).getItemId(), false);
+        }
+        for (int i = itemAddedTo; i < mRecyclerView.getChildCount(); i++) {
+            changes.put(mRecyclerView.findViewHolderForPosition(i).getItemId(), true);
+        }
+        testLayoutManager.expectLayouts(1);
+        adapter.addAndNotify(5, 1);
+        testLayoutManager.waitForLayout(2);
+        checkForMainThreadException();
+
+        changes.clear();
+        int[] changedItems = new int[]{3, 5, 6};
+        for (int i = 0; i < adapter.getItemCount(); i ++) {
+            changes.put(mRecyclerView.findViewHolderForPosition(i).getItemId(), false);
+        }
+        for (int i = 0; i < changedItems.length; i ++) {
+            changes.put(mRecyclerView.findViewHolderForPosition(changedItems[i]).getItemId(), true);
+        }
+        testLayoutManager.expectLayouts(1);
+        adapter.changePositionsAndNotify(changedItems);
+        testLayoutManager.waitForLayout(2);
+        checkForMainThreadException();
+
+        for (int i = 0; i < adapter.getItemCount(); i ++) {
+            changes.put(mRecyclerView.findViewHolderForPosition(i).getItemId(), true);
+        }
+        testLayoutManager.expectLayouts(1);
+        adapter.dispatchDataSetChanged();
+        testLayoutManager.waitForLayout(2);
+        checkForMainThreadException();
+    }
+
+    public void testMovingViaStableIds() throws Throwable {
+        stableIdsMoveTest(true);
+        removeRecyclerView();
+        stableIdsMoveTest(false);
+        removeRecyclerView();
+    }
+
+    public void stableIdsMoveTest(final boolean supportsPredictive) throws Throwable {
+        final TestAdapter testAdapter = new TestAdapter(10);
+        testAdapter.setHasStableIds(true);
+        final AtomicBoolean test = new AtomicBoolean(false);
+        final int movedViewFromIndex = 3;
+        final int movedViewToIndex = 6;
+        final View[] movedView = new View[1];
+        TestLayoutManager lm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                detachAndScrapAttachedViews(recycler);
+                try {
+                    if (test.get()) {
+                        if (state.isPreLayout()) {
+                            View view = recycler.getViewForPosition(movedViewFromIndex, true);
+                            assertSame("In pre layout, should be able to get moved view w/ old "
+                                    + "position", movedView[0], view);
+                            RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(view);
+                            assertTrue("it should come from scrap", holder.wasReturnedFromScrap());
+                            // clear scrap flag
+                            holder.clearReturnedFromScrapFlag();
+                        } else {
+                            View view = recycler.getViewForPosition(movedViewToIndex, true);
+                            assertSame("In post layout, should be able to get moved view w/ new "
+                                    + "position", movedView[0], view);
+                            RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(view);
+                            assertTrue("it should come from scrap", holder.wasReturnedFromScrap());
+                            // clear scrap flag
+                            holder.clearReturnedFromScrapFlag();
+                        }
+                    }
+                    layoutRange(recycler, 0, state.getItemCount());
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                } finally {
+                    layoutLatch.countDown();
+                }
+
+
+            }
+
+            @Override
+            public boolean supportsPredictiveItemAnimations() {
+                return supportsPredictive;
+            }
+        };
+        RecyclerView recyclerView = new RecyclerView(this.getActivity());
+        recyclerView.setAdapter(testAdapter);
+        recyclerView.setLayoutManager(lm);
+        lm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        lm.waitForLayout(1);
+
+        movedView[0] = recyclerView.getChildAt(movedViewFromIndex);
+        test.set(true);
+        lm.expectLayouts(supportsPredictive ? 2 : 1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Item item = testAdapter.mItems.remove(movedViewFromIndex);
+                testAdapter.mItems.add(movedViewToIndex, item);
+                testAdapter.notifyItemRemoved(movedViewFromIndex);
+                testAdapter.notifyItemInserted(movedViewToIndex);
+            }
+        });
+        lm.waitForLayout(2);
+        checkForMainThreadException();
+    }
+
+    public void testAdapterChangeDuringLayout() throws Throwable {
+        adapterChangeInMainThreadTest("notifyDataSetChanged", new Runnable() {
+            @Override
+            public void run() {
+                mRecyclerView.getAdapter().notifyDataSetChanged();
+            }
+        });
+
+        adapterChangeInMainThreadTest("notifyItemChanged", new Runnable() {
+            @Override
+            public void run() {
+                mRecyclerView.getAdapter().notifyItemChanged(2);
+            }
+        });
+
+        adapterChangeInMainThreadTest("notifyItemInserted", new Runnable() {
+            @Override
+            public void run() {
+                mRecyclerView.getAdapter().notifyItemInserted(2);
+            }
+        });
+        adapterChangeInMainThreadTest("notifyItemRemoved", new Runnable() {
+            @Override
+            public void run() {
+                mRecyclerView.getAdapter().notifyItemRemoved(2);
+            }
+        });
+    }
+
+    public void adapterChangeInMainThreadTest(String msg,
+            final Runnable onLayoutRunnable) throws Throwable {
+        final AtomicBoolean doneFirstLayout = new AtomicBoolean(false);
+        TestAdapter testAdapter = new TestAdapter(10);
+        TestLayoutManager lm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                super.onLayoutChildren(recycler, state);
+                try {
+                    layoutRange(recycler, 0, state.getItemCount());
+                    if (doneFirstLayout.get()) {
+                        onLayoutRunnable.run();
+                    }
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                } finally {
+                    layoutLatch.countDown();
+                }
+
+            }
+        };
+        RecyclerView recyclerView = new RecyclerView(getActivity());
+        recyclerView.setLayoutManager(lm);
+        recyclerView.setAdapter(testAdapter);
+        lm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        lm.waitForLayout(2);
+        doneFirstLayout.set(true);
+        lm.expectLayouts(1);
+        requestLayoutOnUIThread(recyclerView);
+        lm.waitForLayout(2);
+        removeRecyclerView();
+        assertTrue("Invalid data updates should be caught:" + msg,
+                mainThreadException instanceof IllegalStateException);
+        mainThreadException = null;
+    }
+
+    public void testAdapterChangeDuringScroll() throws Throwable {
+        for (int orientation : new int[]{OrientationHelper.HORIZONTAL,
+                OrientationHelper.VERTICAL}) {
+            adapterChangeDuringScrollTest("notifyDataSetChanged", orientation,
+                    new Runnable() {
+                        @Override
+                        public void run() {
+                            mRecyclerView.getAdapter().notifyDataSetChanged();
+                        }
+                    });
+            adapterChangeDuringScrollTest("notifyItemChanged", orientation, new Runnable() {
+                @Override
+                public void run() {
+                    mRecyclerView.getAdapter().notifyItemChanged(2);
+                }
+            });
+
+            adapterChangeDuringScrollTest("notifyItemInserted", orientation, new Runnable() {
+                @Override
+                public void run() {
+                    mRecyclerView.getAdapter().notifyItemInserted(2);
+                }
+            });
+            adapterChangeDuringScrollTest("notifyItemRemoved", orientation, new Runnable() {
+                @Override
+                public void run() {
+                    mRecyclerView.getAdapter().notifyItemRemoved(2);
+                }
+            });
+        }
+    }
+
+    public void adapterChangeDuringScrollTest(String msg, final int orientation,
+            final Runnable onScrollRunnable) throws Throwable {
+        TestAdapter testAdapter = new TestAdapter(100);
+        TestLayoutManager lm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                super.onLayoutChildren(recycler, state);
+                try {
+                    layoutRange(recycler, 0, 10);
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                } finally {
+                    layoutLatch.countDown();
+                }
+            }
+
+            @Override
+            public boolean canScrollVertically() {
+                return orientation == OrientationHelper.VERTICAL;
+            }
+
+            @Override
+            public boolean canScrollHorizontally() {
+                return orientation == OrientationHelper.HORIZONTAL;
+            }
+
+            public int mockScroll() {
+                try {
+                    onScrollRunnable.run();
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                } finally {
+                    layoutLatch.countDown();
+                }
+                return 0;
+            }
+
+            @Override
+            public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
+                    RecyclerView.State state) {
+                return mockScroll();
+            }
+
+            @Override
+            public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+                    RecyclerView.State state) {
+                return mockScroll();
+            }
+        };
+        RecyclerView recyclerView = new RecyclerView(getActivity());
+        recyclerView.setLayoutManager(lm);
+        recyclerView.setAdapter(testAdapter);
+        lm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        lm.waitForLayout(2);
+        lm.expectLayouts(1);
+        scrollBy(200);
+        lm.waitForLayout(2);
+        removeRecyclerView();
+        assertTrue("Invalid data updates should be caught:" + msg,
+                mainThreadException instanceof IllegalStateException);
+        mainThreadException = null;
+    }
+
+    public void testRecycleOnDetach() throws Throwable {
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        final TestAdapter testAdapter = new TestAdapter(10);
+        final AtomicBoolean didRunOnDetach = new AtomicBoolean(false);
+        final TestLayoutManager lm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                super.onLayoutChildren(recycler, state);
+                layoutRange(recycler, 0, state.getItemCount() - 1);
+                layoutLatch.countDown();
+            }
+
+            @Override
+            public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
+                super.onDetachedFromWindow(view, recycler);
+                didRunOnDetach.set(true);
+                removeAndRecycleAllViews(recycler);
+            }
+        };
+        recyclerView.setAdapter(testAdapter);
+        recyclerView.setLayoutManager(lm);
+        lm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        lm.waitForLayout(2);
+        removeRecyclerView();
+        assertTrue("When recycler view is removed, detach should run", didRunOnDetach.get());
+        assertEquals("All children should be recycled", recyclerView.getChildCount(), 0);
+    }
+
+    public void testUpdatesWhileDetached() throws Throwable {
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        final int initialAdapterSize = 20;
+        final TestAdapter adapter = new TestAdapter(initialAdapterSize);
+        final AtomicInteger layoutCount = new AtomicInteger(0);
+        TestLayoutManager lm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                super.onLayoutChildren(recycler, state);
+                layoutRange(recycler, 0, 5);
+                layoutCount.incrementAndGet();
+                layoutLatch.countDown();
+            }
+        };
+        recyclerView.setAdapter(adapter);
+        recyclerView.setLayoutManager(lm);
+        recyclerView.setHasFixedSize(true);
+        lm.expectLayouts(1);
+        adapter.addAndNotify(4, 5);
+        lm.assertNoLayout("When RV is not attached, layout should not happen", 1);
+    }
+
+    public void testUpdatesAfterDetach() throws Throwable {
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        final int initialAdapterSize = 20;
+        final TestAdapter adapter = new TestAdapter(initialAdapterSize);
+        final AtomicInteger layoutCount = new AtomicInteger(0);
+        TestLayoutManager lm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                super.onLayoutChildren(recycler, state);
+                layoutRange(recycler, 0, 5);
+                layoutCount.incrementAndGet();
+                layoutLatch.countDown();
+            }
+        };
+        recyclerView.setAdapter(adapter);
+        recyclerView.setLayoutManager(lm);
+        lm.expectLayouts(1);
+        recyclerView.setHasFixedSize(true);
+        setRecyclerView(recyclerView);
+        lm.waitForLayout(2);
+        lm.expectLayouts(1);
+        final int prevLayoutCount = layoutCount.get();
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    adapter.addAndNotify(4, 5);
+                    removeRecyclerView();
+                } catch (Throwable throwable) {
+                    throwable.printStackTrace();
+                }
+            }
+        });
+
+        lm.assertNoLayout("When RV is not attached, layout should not happen", 1);
+        assertEquals("No extra layout should happen when detached", prevLayoutCount,
+                layoutCount.get());
+    }
+
+    public void testNotifyDataSetChangedWithStableIds() throws Throwable {
+        final int defaultViewType = 1;
+        final Map<Item, Integer> viewTypeMap = new HashMap<Item, Integer>();
+        final Map<Integer, Integer> oldPositionToNewPositionMapping =
+                new HashMap<Integer, Integer>();
+        final TestAdapter adapter = new TestAdapter(100) {
+            @Override
+            public int getItemViewType(int position) {
+                Integer type = viewTypeMap.get(mItems.get(position));
+                return type == null ? defaultViewType : type;
+            }
+
+            @Override
+            public long getItemId(int position) {
+                return mItems.get(position).mId;
+            }
+        };
+        adapter.setHasStableIds(true);
+        final ArrayList<Item> previousItems = new ArrayList<Item>();
+        previousItems.addAll(adapter.mItems);
+
+        final AtomicInteger layoutStart = new AtomicInteger(50);
+        final AtomicBoolean validate = new AtomicBoolean(false);
+        final int childCount = 10;
+        final TestLayoutManager lm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                try {
+                    super.onLayoutChildren(recycler, state);
+                    if (validate.get()) {
+                        assertEquals("Cached views should be kept", 5, recycler
+                                .mCachedViews.size());
+                        for (RecyclerView.ViewHolder vh : recycler.mCachedViews) {
+                            TestViewHolder tvh = (TestViewHolder) vh;
+                            assertTrue("view holder should be marked for update",
+                                    tvh.needsUpdate());
+                            assertTrue("view holder should be marked as invalid", tvh.isInvalid());
+                        }
+                    }
+                    detachAndScrapAttachedViews(recycler);
+                    if (validate.get()) {
+                        assertEquals("cache size should stay the same", 5,
+                                recycler.mCachedViews.size());
+                        assertEquals("all views should be scrapped", childCount,
+                                recycler.getScrapList().size());
+                        for (RecyclerView.ViewHolder vh : recycler.getScrapList()) {
+                            // TODO create test case for type change
+                            TestViewHolder tvh = (TestViewHolder) vh;
+                            assertTrue("view holder should be marked for update",
+                                    tvh.needsUpdate());
+                            assertTrue("view holder should be marked as invalid", tvh.isInvalid());
+                        }
+                    }
+                    layoutRange(recycler, layoutStart.get(), layoutStart.get() + childCount);
+                    if (validate.get()) {
+                        for (int i = 0; i < getChildCount(); i++) {
+                            View view = getChildAt(i);
+                            TestViewHolder tvh = (TestViewHolder) mRecyclerView
+                                    .getChildViewHolder(view);
+                            final int oldPos = previousItems.indexOf(tvh.mBindedItem);
+                            assertEquals("view holder's position should be correct",
+                                    oldPositionToNewPositionMapping.get(oldPos).intValue(),
+                                    tvh.getPosition());
+                            ;
+                        }
+                    }
+                } catch (Throwable t) {
+                    postExceptionToInstrumentation(t);
+                } finally {
+                    layoutLatch.countDown();
+                }
+            }
+        };
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        recyclerView.setItemAnimator(null);
+        recyclerView.setAdapter(adapter);
+        recyclerView.setLayoutManager(lm);
+        recyclerView.setItemViewCacheSize(10);
+        lm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        lm.waitForLayout(2);
+        checkForMainThreadException();
+        getInstrumentation().waitForIdleSync();
+        layoutStart.set(layoutStart.get() + 5);//55
+        lm.expectLayouts(1);
+        requestLayoutOnUIThread(recyclerView);
+        lm.waitForLayout(2);
+        validate.set(true);
+        lm.expectLayouts(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    adapter.moveItems(false,
+                            new int[]{50, 56}, new int[]{51, 1}, new int[]{52, 2},
+                            new int[]{53, 54}, new int[]{60, 61}, new int[]{62, 64},
+                            new int[]{75, 58});
+                    for (int i = 0; i < previousItems.size(); i++) {
+                        Item item = previousItems.get(i);
+                        oldPositionToNewPositionMapping.put(i, adapter.mItems.indexOf(item));
+                    }
+                    adapter.dispatchDataSetChanged();
+                } catch (Throwable throwable) {
+                    postExceptionToInstrumentation(throwable);
+                }
+            }
+        });
+        lm.waitForLayout(2);
+        checkForMainThreadException();
+    }
+
+    public void testFindViewById() throws Throwable {
+        findViewByIdTest(false);
+        removeRecyclerView();
+        findViewByIdTest(true);
+    }
+
+    public void findViewByIdTest(final boolean supportPredictive) throws Throwable {
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        final int initialAdapterSize = 20;
+        final TestAdapter adapter = new TestAdapter(initialAdapterSize);
+        final int deleteStart = 6;
+        final int deleteCount = 5;
+        recyclerView.setAdapter(adapter);
+        final AtomicBoolean assertPositions = new AtomicBoolean(false);
+        TestLayoutManager lm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                super.onLayoutChildren(recycler, state);
+                if (assertPositions.get()) {
+                    if (state.isPreLayout()) {
+                        for (int i = 0; i < deleteStart; i++) {
+                            View view = findViewByPosition(i);
+                            assertNotNull("find view by position for existing items should work "
+                                    + "fine", view);
+                            assertFalse("view should not be marked as removed",
+                                    ((RecyclerView.LayoutParams) view.getLayoutParams())
+                                            .isItemRemoved());
+                        }
+                        for (int i = 0; i < deleteCount; i++) {
+                            View view = findViewByPosition(i + deleteStart);
+                            assertNotNull("find view by position should work fine for removed "
+                                    + "views in pre-layout", view);
+                            assertTrue("view should be marked as removed",
+                                    ((RecyclerView.LayoutParams) view.getLayoutParams())
+                                            .isItemRemoved());
+                        }
+                        for (int i = deleteStart + deleteCount; i < 20; i++) {
+                            View view = findViewByPosition(i);
+                            assertNotNull(view);
+                            assertFalse("view should not be marked as removed",
+                                    ((RecyclerView.LayoutParams) view.getLayoutParams())
+                                            .isItemRemoved());
+                        }
+                    } else {
+                        for (int i = 0; i < initialAdapterSize - deleteCount; i++) {
+                            View view = findViewByPosition(i);
+                            assertNotNull("find view by position for existing item " + i +
+                                    " should work fine. child count:" + getChildCount(), view);
+                            TestViewHolder viewHolder =
+                                    (TestViewHolder) mRecyclerView.getChildViewHolder(view);
+                            assertSame("should be the correct item " + viewHolder
+                                    , viewHolder.mBindedItem,
+                                    adapter.mItems.get(viewHolder.mPosition));
+                            assertFalse("view should not be marked as removed",
+                                    ((RecyclerView.LayoutParams) view.getLayoutParams())
+                                            .isItemRemoved());
+                        }
+                    }
+                }
+                detachAndScrapAttachedViews(recycler);
+                layoutRange(recycler, state.getItemCount() - 1, -1);
+                layoutLatch.countDown();
+            }
+
+            @Override
+            public boolean supportsPredictiveItemAnimations() {
+                return supportPredictive;
+            }
+        };
+        recyclerView.setLayoutManager(lm);
+        lm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        lm.waitForLayout(2);
+        getInstrumentation().waitForIdleSync();
+
+        assertPositions.set(true);
+        lm.expectLayouts(supportPredictive ? 2 : 1);
+        adapter.deleteAndNotify(new int[]{deleteStart, deleteCount - 1}, new int[]{deleteStart, 1});
+        lm.waitForLayout(2);
+    }
+
+    public void testTypeForCache() throws Throwable {
+        final AtomicInteger viewType = new AtomicInteger(1);
+        final TestAdapter adapter = new TestAdapter(100) {
+            @Override
+            public int getItemViewType(int position) {
+                return viewType.get();
+            }
+
+            @Override
+            public long getItemId(int position) {
+                return mItems.get(position).mId;
+            }
+        };
+        adapter.setHasStableIds(true);
+        final AtomicInteger layoutStart = new AtomicInteger(2);
+        final int childCount = 10;
+        final TestLayoutManager lm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                super.onLayoutChildren(recycler, state);
+                detachAndScrapAttachedViews(recycler);
+                layoutRange(recycler, layoutStart.get(), layoutStart.get() + childCount);
+                layoutLatch.countDown();
+            }
+        };
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        recyclerView.setItemAnimator(null);
+        recyclerView.setAdapter(adapter);
+        recyclerView.setLayoutManager(lm);
+        recyclerView.setItemViewCacheSize(10);
+        lm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        lm.waitForLayout(2);
+        getInstrumentation().waitForIdleSync();
+        layoutStart.set(4); // trigger a cache for 3,4
+        lm.expectLayouts(1);
+        requestLayoutOnUIThread(recyclerView);
+        lm.waitForLayout(2);
+        //
+        viewType.incrementAndGet();
+        layoutStart.set(2); // go back to bring views from cache
+        lm.expectLayouts(1);
+        adapter.mItems.remove(1);
+        adapter.dispatchDataSetChanged();
+        lm.waitForLayout(2);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                for (int i = 2; i < 4; i++) {
+                    RecyclerView.ViewHolder vh = recyclerView.findViewHolderForPosition(i);
+                    assertEquals("View holder's type should match latest type", viewType.get(),
+                            vh.getItemViewType());
+                }
+            }
+        });
+    }
+
+    public void testTypeForExistingViews() throws Throwable {
+        final AtomicInteger viewType = new AtomicInteger(1);
+        final int invalidatedCount = 2;
+        final int layoutStart = 2;
+        final TestAdapter adapter = new TestAdapter(100) {
+            @Override
+            public int getItemViewType(int position) {
+                return viewType.get();
+            }
+
+            @Override
+            public void onBindViewHolder(TestViewHolder holder,
+                    int position) {
+                super.onBindViewHolder(holder, position);
+                if (position >= layoutStart && position < invalidatedCount + layoutStart) {
+                    try {
+                        assertEquals("holder type should match current view type at position " +
+                                position, viewType.get(), holder.getItemViewType());
+                    } catch (Throwable t) {
+                        postExceptionToInstrumentation(t);
+                    }
+                }
+            }
+
+            @Override
+            public long getItemId(int position) {
+                return mItems.get(position).mId;
+            }
+        };
+        adapter.setHasStableIds(true);
+
+        final int childCount = 10;
+        final TestLayoutManager lm = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                super.onLayoutChildren(recycler, state);
+                detachAndScrapAttachedViews(recycler);
+                layoutRange(recycler, layoutStart, layoutStart + childCount);
+                layoutLatch.countDown();
+            }
+        };
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        recyclerView.setAdapter(adapter);
+        recyclerView.setLayoutManager(lm);
+        lm.expectLayouts(1);
+        setRecyclerView(recyclerView);
+        lm.waitForLayout(2);
+        getInstrumentation().waitForIdleSync();
+        viewType.incrementAndGet();
+        lm.expectLayouts(1);
+        adapter.changeAndNotify(layoutStart, invalidatedCount);
+        lm.waitForLayout(2);
+        checkForMainThreadException();
+    }
+
+
+    public void testState() throws Throwable {
+        final TestAdapter adapter = new TestAdapter(10);
+        final RecyclerView recyclerView = new RecyclerView(getActivity());
+        recyclerView.setAdapter(adapter);
+        recyclerView.setItemAnimator(null);
+        final AtomicInteger itemCount = new AtomicInteger();
+        final AtomicBoolean structureChanged = new AtomicBoolean();
+        TestLayoutManager testLayoutManager = new TestLayoutManager() {
+            @Override
+            public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                detachAndScrapAttachedViews(recycler);
+                layoutRange(recycler, 0, state.getItemCount());
+                itemCount.set(state.getItemCount());
+                structureChanged.set(state.didStructureChange());
+                layoutLatch.countDown();
+            }
+        };
+        recyclerView.setLayoutManager(testLayoutManager);
+        testLayoutManager.expectLayouts(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                getActivity().mContainer.addView(recyclerView);
+            }
+        });
+        testLayoutManager.waitForLayout(2, TimeUnit.SECONDS);
+
+        assertEquals("item count in state should be correct", adapter.getItemCount()
+                , itemCount.get());
+        assertEquals("structure changed should be true for first layout", true,
+                structureChanged.get());
+        Thread.sleep(1000); //wait for other layouts.
+        testLayoutManager.expectLayouts(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                recyclerView.requestLayout();
+            }
+        });
+        testLayoutManager.waitForLayout(2);
+        assertEquals("in second layout,structure changed should be false", false,
+                structureChanged.get());
+        testLayoutManager.expectLayouts(1); //
+        adapter.deleteAndNotify(3, 2);
+        testLayoutManager.waitForLayout(2);
+        assertEquals("when items are removed, item count in state should be updated",
+                adapter.getItemCount(),
+                itemCount.get());
+        assertEquals("structure changed should be true when items are removed", true,
+                structureChanged.get());
+        testLayoutManager.expectLayouts(1);
+        adapter.addAndNotify(2, 5);
+        testLayoutManager.waitForLayout(2);
+
+        assertEquals("when items are added, item count in state should be updated",
+                adapter.getItemCount(),
+                itemCount.get());
+        assertEquals("structure changed should be true when items are removed", true,
+                structureChanged.get());
+    }
+
+    private static class TestViewHolder2 extends RecyclerView.ViewHolder {
+        public TestViewHolder2(View itemView) {
+            super(itemView);
+        }
+    }
+
+    private static class TestAdapter2 extends RecyclerView.Adapter<TestViewHolder2> {
+        List<Item> mItems;
+
+        private TestAdapter2(int count) {
+            mItems = new ArrayList<Item>(count);
+            for (int i = 0; i < count; i++) {
+                mItems.add(new Item(i, "Item " + i));
+            }
+        }
+
+        @Override
+        public TestViewHolder2 onCreateViewHolder(ViewGroup parent,
+                int viewType) {
+            return new TestViewHolder2(new TextView(parent.getContext()));
+        }
+
+        @Override
+        public void onBindViewHolder(TestViewHolder2 holder, int position) {
+            final Item item = mItems.get(position);
+            ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mAdapterIndex + ")");
+        }
+
+        @Override
+        public int getItemCount() {
+            return mItems.size();
+        }
+    }
+
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java
new file mode 100644
index 0000000..cd4250c
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java
@@ -0,0 +1,1714 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v7.widget;
+
+
+import android.graphics.Rect;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static android.support.v7.widget.LayoutState.*;
+import static android.support.v7.widget.StaggeredGridLayoutManager.*;
+
+public class StaggeredGridLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
+
+    private static final boolean DEBUG = false;
+
+    private static final String TAG = "StaggeredGridLayoutManagerTest";
+
+    volatile WrappedLayoutManager mLayoutManager;
+
+    GridTestAdapter mAdapter;
+
+    final List<Config> mBaseVariations = new ArrayList<Config>();
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
+            for (boolean reverseLayout : new boolean[]{false, true}) {
+                for (int spanCount : new int[]{1, 3}) {
+                    for (int gapStrategy : new int[]{GAP_HANDLING_NONE,
+                            GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS}) {
+                        mBaseVariations.add(new Config(orientation, reverseLayout, spanCount,
+                                gapStrategy));
+                    }
+                }
+            }
+        }
+    }
+
+    void setupByConfig(Config config) throws Throwable {
+        mAdapter = new GridTestAdapter(config.mItemCount, config.mOrientation);
+        mRecyclerView = new RecyclerView(getActivity());
+        mRecyclerView.setAdapter(mAdapter);
+        mRecyclerView.setHasFixedSize(true);
+        mLayoutManager = new WrappedLayoutManager(config.mSpanCount,
+                config.mOrientation);
+        mLayoutManager.setGapStrategy(config.mGapStrategy);
+        mLayoutManager.setReverseLayout(config.mReverseLayout);
+        mRecyclerView.setLayoutManager(mLayoutManager);
+    }
+
+    public void testRTL() throws Throwable {
+        for (boolean changeRtlAfter : new boolean[]{false, true}) {
+            for (Config config : mBaseVariations) {
+                rtlTest(config, changeRtlAfter);
+                removeRecyclerView();
+            }
+        }
+    }
+
+    void rtlTest(Config config, boolean changeRtlAfter) throws Throwable {
+        if (config.mSpanCount == 1) {
+            config.mSpanCount = 2;
+        }
+        String logPrefix = config + ", changeRtlAfterLayout:" + changeRtlAfter;
+        setupByConfig(config.itemCount(5));
+        if (changeRtlAfter) {
+            waitFirstLayout();
+            mLayoutManager.expectLayouts(1);
+            mLayoutManager.setFakeRtl(true);
+            mLayoutManager.waitForLayout(2);
+        } else {
+            mLayoutManager.mFakeRTL = true;
+            waitFirstLayout();
+        }
+
+        assertEquals("view should become rtl", true, mLayoutManager.isLayoutRTL());
+        OrientationHelper helper = OrientationHelper.createHorizontalHelper(mLayoutManager);
+        View child0 = mLayoutManager.findViewByPosition(0);
+        View child1 = mLayoutManager.findViewByPosition(config.mOrientation == VERTICAL ? 1
+            : config.mSpanCount);
+        assertNotNull(logPrefix + " child position 0 should be laid out", child0);
+        assertNotNull(logPrefix + " child position 0 should be laid out", child1);
+        if (config.mOrientation == VERTICAL || !config.mReverseLayout) {
+            assertTrue(logPrefix + " second child should be to the left of first child",
+                    helper.getDecoratedStart(child0) >= helper.getDecoratedEnd(child1));
+            assertEquals(logPrefix + " first child should be right aligned",
+                    helper.getDecoratedEnd(child0), helper.getEndAfterPadding());
+        } else {
+            assertTrue(logPrefix + " first child should be to the left of second child",
+                    helper.getDecoratedStart(child1) >= helper.getDecoratedEnd(child0));
+            assertEquals(logPrefix + " first child should be left aligned",
+                    helper.getDecoratedStart(child0), helper.getStartAfterPadding());
+        }
+        checkForMainThreadException();
+    }
+
+    public void testScrollBackAndPreservePositions() throws Throwable {
+        for (boolean saveRestore : new boolean[]{false, true}) {
+            for (Config config : mBaseVariations) {
+                scrollBackAndPreservePositionsTest(config, saveRestore);
+                removeRecyclerView();
+            }
+        }
+    }
+
+    public void scrollBackAndPreservePositionsTest(final Config config, final boolean saveRestoreInBetween)
+            throws Throwable {
+        setupByConfig(config);
+        mAdapter.mOnBindHandler = new OnBindHandler() {
+            @Override
+            public void onBoundItem(TestViewHolder vh, int postion) {
+                LayoutParams lp = (LayoutParams) vh.itemView.getLayoutParams();
+                lp.setFullSpan((postion * 7) % (config.mSpanCount + 1) == 0);
+            }
+        };
+        waitFirstLayout();
+        final int[] globalPositions = new int[mAdapter.getItemCount()];
+        Arrays.fill(globalPositions, Integer.MIN_VALUE);
+        final int scrollStep = (mLayoutManager.mPrimaryOrientation.getTotalSpace() / 10)
+                * (config.mReverseLayout ? -1 : 1);
+
+
+        final int[] globalPos = new int[1];
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                int globalScrollPosition = 0;
+                while (globalPositions[mAdapter.getItemCount() - 1] == Integer.MIN_VALUE) {
+                    for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
+                        View child = mRecyclerView.getChildAt(i);
+                        final int pos = mRecyclerView.getChildPosition(child);
+                        if (globalPositions[pos] != Integer.MIN_VALUE) {
+                            continue;
+                        }
+                        if (config.mReverseLayout) {
+                            globalPositions[pos] = globalScrollPosition +
+                                    mLayoutManager.mPrimaryOrientation.getDecoratedEnd(child);
+                        } else {
+                            globalPositions[pos] = globalScrollPosition +
+                                    mLayoutManager.mPrimaryOrientation.getDecoratedStart(child);
+                        }
+                    }
+                    globalScrollPosition += mLayoutManager.scrollBy(scrollStep,
+                            mRecyclerView.mRecycler, mRecyclerView.mState);
+                }
+                if (DEBUG) {
+                    Log.d(TAG, "done recording positions " + Arrays.toString(globalPositions));
+                }
+                globalPos[0] = globalScrollPosition;
+            }
+        });
+        checkForMainThreadException();
+
+        if (saveRestoreInBetween) {
+            saveRestore(config);
+        }
+
+        checkForMainThreadException();
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                int globalScrollPosition = globalPos[0];
+                // now scroll back and make sure global positions match
+                BitSet shouldTest = new BitSet(mAdapter.getItemCount());
+                shouldTest.set(0, mAdapter.getItemCount() - 1, true);
+                String assertPrefix = config + ", restored in between:" + saveRestoreInBetween
+                        + " global pos must match when scrolling in reverse for position ";
+                int scrollAmount = Integer.MAX_VALUE;
+                while (!shouldTest.isEmpty() && scrollAmount != 0) {
+                    for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
+                        View child = mRecyclerView.getChildAt(i);
+                        int pos = mRecyclerView.getChildPosition(child);
+                        if (!shouldTest.get(pos)) {
+                            continue;
+                        }
+                        shouldTest.clear(pos);
+                        int globalPos;
+                        if (config.mReverseLayout) {
+                            globalPos = globalScrollPosition +
+                                    mLayoutManager.mPrimaryOrientation.getDecoratedEnd(child);
+                        } else {
+                            globalPos = globalScrollPosition +
+                                    mLayoutManager.mPrimaryOrientation.getDecoratedStart(child);
+                        }
+                        assertEquals(assertPrefix + pos,
+                                globalPositions[pos], globalPos);
+                    }
+                    scrollAmount = mLayoutManager.scrollBy(-scrollStep,
+                            mRecyclerView.mRecycler, mRecyclerView.mState);
+                    globalScrollPosition += scrollAmount;
+                }
+                assertTrue("all views should be seen", shouldTest.isEmpty());
+            }
+        });
+        checkForMainThreadException();
+    }
+
+    public void testScrollToPositionWithPredictive() throws Throwable {
+        scrollToPositionWithPredictive(0, LinearLayoutManager.INVALID_OFFSET);
+        removeRecyclerView();
+        scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2,
+                LinearLayoutManager.INVALID_OFFSET);
+        removeRecyclerView();
+        scrollToPositionWithPredictive(9, 20);
+        removeRecyclerView();
+        scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2, 10);
+
+    }
+
+    public void scrollToPositionWithPredictive(final int scrollPosition, final int scrollOffset)
+            throws Throwable {
+        setupByConfig(new Config(StaggeredGridLayoutManager.VERTICAL,
+                false, 3, StaggeredGridLayoutManager.GAP_HANDLING_NONE));
+        waitFirstLayout();
+        mLayoutManager.mOnLayoutListener = new OnLayoutListener() {
+            @Override
+            void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
+                RecyclerView rv = mLayoutManager.mRecyclerView;
+                if (state.isPreLayout()) {
+                    assertEquals("pending scroll position should still be pending",
+                            scrollPosition, mLayoutManager.mPendingScrollPosition);
+                    if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
+                        assertEquals("pending scroll position offset should still be pending",
+                                scrollOffset, mLayoutManager.mPendingScrollPositionOffset);
+                    }
+                } else {
+                    RecyclerView.ViewHolder vh = rv.findViewHolderForPosition(scrollPosition);
+                    assertNotNull("scroll to position should work", vh);
+                    if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
+                        assertEquals("scroll offset should be applied properly",
+                                mLayoutManager.getPaddingTop() + scrollOffset
+                                        + ((RecyclerView.LayoutParams) vh.itemView
+                                            .getLayoutParams()).topMargin,
+                                mLayoutManager.getDecoratedTop(vh.itemView));
+                    }
+                }
+            }
+        };
+        mLayoutManager.expectLayouts(2);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    mAdapter.addAndNotify(0, 1);
+                    if (scrollOffset == LinearLayoutManager.INVALID_OFFSET) {
+                        mLayoutManager.scrollToPosition(scrollPosition);
+                    } else {
+                        mLayoutManager.scrollToPositionWithOffset(scrollPosition,
+                                scrollOffset);
+                    }
+
+                } catch (Throwable throwable) {
+                    throwable.printStackTrace();
+                }
+
+            }
+        });
+        mLayoutManager.waitForLayout(2);
+        checkForMainThreadException();
+    }
+
+    LayoutParams getLp(View view) {
+        return (LayoutParams) view.getLayoutParams();
+    }
+
+    public void testGetFirstLastChildrenTest() throws Throwable {
+        for (boolean provideArr : new boolean[]{true, false}) {
+            for (Config config : mBaseVariations) {
+                getFirstLastChildrenTest(config, provideArr);
+                removeRecyclerView();
+            }
+        }
+    }
+
+    public void getFirstLastChildrenTest(final Config config, final boolean provideArr)
+            throws Throwable {
+        setupByConfig(config);
+        waitFirstLayout();
+        Runnable viewInBoundsTest = new Runnable() {
+            @Override
+            public void run() {
+                VisibleChildren visibleChildren = mLayoutManager.traverseAndFindVisibleChildren();
+                final String boundsLog = mLayoutManager.getBoundsLog();
+                VisibleChildren queryResult = new VisibleChildren(mLayoutManager.getSpanCount());
+                queryResult.firstFullyVisiblePositions = mLayoutManager
+                        .findFirstCompletelyVisibleItemPositions(
+                                provideArr ? new int[mLayoutManager.getSpanCount()] : null);
+                queryResult.firstVisiblePositions = mLayoutManager
+                        .findFirstVisibleItemPositions(
+                                provideArr ? new int[mLayoutManager.getSpanCount()] : null);
+                queryResult.lastFullyVisiblePositions = mLayoutManager
+                        .findLastCompletelyVisibleItemPositions(
+                                provideArr ? new int[mLayoutManager.getSpanCount()] : null);
+                queryResult.lastVisiblePositions = mLayoutManager
+                        .findLastVisibleItemPositions(
+                                provideArr ? new int[mLayoutManager.getSpanCount()] : null);
+                assertEquals(config + ":\nfirst visible child should match traversal result\n"
+                                + "traversed:" + visibleChildren + "\n"
+                                + "queried:" + queryResult + "\n"
+                                + boundsLog, visibleChildren, queryResult
+                );
+            }
+        };
+        runTestOnUiThread(viewInBoundsTest);
+        // smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching
+        // case
+        final int scrollPosition = mAdapter.getItemCount();
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mRecyclerView.smoothScrollToPosition(scrollPosition);
+            }
+        });
+        while (mLayoutManager.isSmoothScrolling() ||
+                mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
+            runTestOnUiThread(viewInBoundsTest);
+            Thread.sleep(400);
+        }
+        // delete all items
+        mLayoutManager.expectLayouts(2);
+        mAdapter.deleteAndNotify(0, mAdapter.getItemCount());
+        mLayoutManager.waitForLayout(2);
+        // test empty case
+        runTestOnUiThread(viewInBoundsTest);
+        // set a new adapter with huge items to test full bounds check
+        mLayoutManager.expectLayouts(1);
+        final int totalSpace = mLayoutManager.mPrimaryOrientation.getTotalSpace();
+        final TestAdapter newAdapter = new TestAdapter(100) {
+            @Override
+            public void onBindViewHolder(TestViewHolder holder,
+                    int position) {
+                super.onBindViewHolder(holder, position);
+                if (config.mOrientation == LinearLayoutManager.HORIZONTAL) {
+                    holder.itemView.setMinimumWidth(totalSpace + 5);
+                } else {
+                    holder.itemView.setMinimumHeight(totalSpace + 5);
+                }
+            }
+        };
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mRecyclerView.setAdapter(newAdapter);
+            }
+        });
+        mLayoutManager.waitForLayout(2);
+        runTestOnUiThread(viewInBoundsTest);
+    }
+
+    public void testInnerGapHandling() throws Throwable {
+        innerGapHandlingTest(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
+        innerGapHandlingTest(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
+    }
+
+    public void innerGapHandlingTest(int strategy) throws Throwable {
+        Config config = new Config().spanCount(3).itemCount(500);
+        setupByConfig(config);
+        mLayoutManager.setGapStrategy(strategy);
+        mAdapter.mFullSpanItems.add(100);
+        mAdapter.mFullSpanItems.add(104);
+        mAdapter.mViewsHaveEqualSize = true;
+        waitFirstLayout();
+        mLayoutManager.expectLayouts(1);
+        scrollToPosition(400);
+        mLayoutManager.waitForLayout(2);
+        mLayoutManager.expectLayouts(2);
+        mAdapter.addAndNotify(101, 1);
+        mLayoutManager.waitForLayout(2);
+        if (strategy == GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS) {
+            mLayoutManager.expectLayouts(1);
+        }
+        // state
+        // now smooth scroll to 99 to trigger a layout around 100
+        smoothScrollToPosition(99);
+        switch (strategy) {
+            case GAP_HANDLING_NONE:
+                assertSpans("gap handling:" + Config.gapStrategyName(strategy), new int[]{100, 0},
+                        new int[]{101, 2}, new int[]{102, 0}, new int[]{103, 1}, new int[]{104, 2},
+                        new int[]{105, 0});
+                break;
+            case GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS:
+                mLayoutManager.waitForLayout(2);
+                assertSpans("swap items between spans", new int[]{100, 0}, new int[]{101, 0},
+                        new int[]{102, 1}, new int[]{103, 2}, new int[]{104, 0}, new int[]{105, 0});
+                break;
+        }
+
+    }
+
+    public void testFullSizeSpans() throws Throwable {
+        Config config = new Config().spanCount(5).itemCount(30);
+        setupByConfig(config);
+        mAdapter.mFullSpanItems.add(3);
+        waitFirstLayout();
+        assertSpans("Testing full size span", new int[]{0, 0}, new int[]{1, 1}, new int[]{2, 2},
+                new int[]{3, 0}, new int[]{4, 0}, new int[]{5, 1}, new int[]{6, 2},
+                new int[]{7, 3}, new int[]{8, 4});
+    }
+
+    void assertSpans(String msg, int[]... childSpanTuples) {
+        for (int i = 0; i < childSpanTuples.length; i++) {
+            assertSpan(msg, childSpanTuples[i][0], childSpanTuples[i][1]);
+        }
+    }
+
+    void assertSpan(String msg, int childPosition, int expectedSpan) {
+        View view = mLayoutManager.findViewByPosition(childPosition);
+        assertNotNull(msg + "view at position " + childPosition + " should exists", view);
+        assertEquals(msg + "[child:" + childPosition + "]", expectedSpan,
+                getLp(view).mSpan.mIndex);
+    }
+
+    public void gapInTheMiddle(Config config) throws Throwable {
+
+    }
+
+    public void testGapAtTheBeginning() throws Throwable {
+        for (Config config : mBaseVariations) {
+            for (int deleteCount = 1; deleteCount < config.mSpanCount * 2; deleteCount ++) {
+                for (int deletePosition = config.mSpanCount - 1;
+                        deletePosition < config.mSpanCount + 2; deletePosition ++) {
+                    gapAtTheBeginningOfTheListTest(config, deletePosition, deleteCount);
+                    removeRecyclerView();
+                }
+            }
+        }
+    }
+
+    public void gapAtTheBeginningOfTheListTest(final Config config, int deletePosition,
+            int deleteCount) throws Throwable {
+        if (config.mSpanCount < 2 || config.mGapStrategy == GAP_HANDLING_NONE) {
+            return;
+        }
+        if (config.mItemCount < 100) {
+            config.itemCount(100);
+        }
+        final String logPrefix = config + ", deletePos:" + deletePosition + ", deleteCount:"
+                + deleteCount;
+        setupByConfig(config);
+        final RecyclerView.Adapter adapter = mAdapter;
+        waitFirstLayout();
+        // scroll far away
+        smoothScrollToPosition(config.mItemCount / 2);
+        // assert to be deleted child is not visible
+        assertNull(logPrefix + " test sanity, to be deleted child should be invisible",
+                mRecyclerView.findViewHolderForPosition(deletePosition));
+        // delete the child and notify
+        mAdapter.deleteAndNotify(deletePosition, deleteCount);
+        getInstrumentation().waitForIdleSync();
+        mLayoutManager.expectLayouts(1);
+        smoothScrollToPosition(0);
+        mLayoutManager.waitForLayout(2);
+        // due to data changes, first item may become visible before others which will cause
+        // smooth scrolling to stop. Triggering it twice more is a naive hack.
+        // Until we have time to consider it as a bug, this is the only workaround.
+        smoothScrollToPosition(0);
+        Thread.sleep(300);
+        smoothScrollToPosition(0);
+        Thread.sleep(500);
+        // some animations should happen and we should recover layout
+        final Map<Item, Rect> actualCoords = mLayoutManager.collectChildCoordinates();
+        // now layout another RV with same adapter
+        removeRecyclerView();
+        setupByConfig(config);
+        mRecyclerView.setAdapter(adapter);// use same adapter so that items can be matched
+        waitFirstLayout();
+        final Map<Item, Rect> desiredCoords = mLayoutManager.collectChildCoordinates();
+        assertRectSetsEqual(logPrefix + " when an item from the start of the list is deleted, "
+                        + "layout should recover the state once scrolling is stopped",
+                desiredCoords, actualCoords);
+    }
+
+    public void testPartialSpanInvalidation() throws Throwable {
+        Config config = new Config().spanCount(5).itemCount(100);
+        setupByConfig(config);
+        for (int i = 20; i < mAdapter.getItemCount(); i += 20) {
+            mAdapter.mFullSpanItems.add(i);
+        }
+        waitFirstLayout();
+        smoothScrollToPosition(50);
+        int prevSpanId = mLayoutManager.mLazySpanLookup.mData[30];
+        mAdapter.changeAndNotify(15, 2);
+        Thread.sleep(200);
+        assertEquals("Invalidation should happen within full span item boundaries", prevSpanId,
+                mLayoutManager.mLazySpanLookup.mData[30]);
+        assertEquals("item in invalidated range should have clear span id",
+                LayoutParams.INVALID_SPAN_ID, mLayoutManager.mLazySpanLookup.mData[16]);
+        smoothScrollToPosition(85);
+        int[] prevSpans = copyOfRange(mLayoutManager.mLazySpanLookup.mData, 62, 85);
+        mAdapter.deleteAndNotify(55, 2);
+        Thread.sleep(200);
+        assertEquals("item in invalidated range should have clear span id",
+                LayoutParams.INVALID_SPAN_ID, mLayoutManager.mLazySpanLookup.mData[16]);
+        int[] newSpans = copyOfRange(mLayoutManager.mLazySpanLookup.mData, 60, 83);
+        assertSpanAssignmentEquality("valid spans should be shifted for deleted item", prevSpans,
+                newSpans, 0, 0, newSpans.length);
+    }
+
+    // Same as Arrays.copyOfRange but for API 7
+    private int[] copyOfRange(int[] original, int from, int to) {
+        int newLength = to - from;
+        if (newLength < 0)
+            throw new IllegalArgumentException(from + " > " + to);
+        int[] copy = new int[newLength];
+        System.arraycopy(original, from, copy, 0,
+                Math.min(original.length - from, newLength));
+        return copy;
+    }
+
+    public void testSpanReassignmentsOnItemChange() throws Throwable {
+        Config config = new Config().spanCount(5);
+        setupByConfig(config);
+        waitFirstLayout();
+        smoothScrollToPosition(mAdapter.getItemCount() / 2);
+        final int changePosition = mAdapter.getItemCount() / 4;
+        mLayoutManager.expectLayouts(1);
+        mAdapter.changeAndNotify(changePosition, 1);
+        mLayoutManager.assertNoLayout("no layout should happen when an invisible child is updated",
+                1);
+        // delete an item before visible area
+        int deletedPosition = mLayoutManager.getPosition(mLayoutManager.getChildAt(0)) - 2;
+        Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
+        if (DEBUG) {
+            Log.d(TAG, "before:");
+            for (Map.Entry<Item, Rect> entry : before.entrySet()) {
+                Log.d(TAG, entry.getKey().mAdapterIndex + ":" + entry.getValue());
+            }
+        }
+        mLayoutManager.expectLayouts(1);
+        mAdapter.deleteAndNotify(deletedPosition, 1);
+        mLayoutManager.waitForLayout(2);
+        assertRectSetsEqual(config + " when an item towards the head of the list is deleted, it "
+                        + "should not affect the layout if it is not visible", before,
+                mLayoutManager.collectChildCoordinates()
+        );
+        deletedPosition = mLayoutManager.getPosition(mLayoutManager.getChildAt(2));
+        mLayoutManager.expectLayouts(1);
+        mAdapter.deleteAndNotify(deletedPosition, 1);
+        mLayoutManager.waitForLayout(2);
+        assertRectSetsNotEqual(config + " when a visible item is deleted, it should affect the "
+                + "layout", before, mLayoutManager.collectChildCoordinates());
+    }
+
+    void assertSpanAssignmentEquality(String msg, int[] set1, int[] set2, int start, int end) {
+        for (int i = start; i < end; i++) {
+            assertEquals(msg + " ind:" + i, set1[i], set2[i]);
+        }
+    }
+
+    void assertSpanAssignmentEquality(String msg, int[] set1, int[] set2, int start1, int start2,
+            int length) {
+        for (int i = 0; i < length; i++) {
+            assertEquals(msg + " ind1:" + (start1 + i) + ", ind2:" + (start2 + i), set1[start1 + i],
+                    set2[start2 + i]);
+        }
+    }
+
+    public void testViewSnapping() throws Throwable {
+        for (Config config : mBaseVariations) {
+            viewSnapTest(config.itemCount(config.mSpanCount + 1));
+            removeRecyclerView();
+        }
+    }
+
+    public void viewSnapTest(Config config) throws Throwable {
+        setupByConfig(config);
+        waitFirstLayout();
+        // run these tests twice. once initial layout, once after scroll
+        String logSuffix = "";
+        for (int i = 0; i < 2; i++) {
+            Map<Item, Rect> itemRectMap = mLayoutManager.collectChildCoordinates();
+            Rect recyclerViewBounds = getDecoratedRecyclerViewBounds();
+            Rect usedLayoutBounds = new Rect();
+            for (Rect rect : itemRectMap.values()) {
+                usedLayoutBounds.union(rect);
+            }
+            if (DEBUG) {
+                Log.d(TAG, "testing view snapping (" + logSuffix + ") for config " + config);
+            }
+            if (config.mOrientation == VERTICAL) {
+                assertEquals(config + " there should be no gap on left" + logSuffix,
+                        usedLayoutBounds.left, recyclerViewBounds.left);
+                assertEquals(config + " there should be no gap on right" + logSuffix,
+                        usedLayoutBounds.right, recyclerViewBounds.right);
+                if (config.mReverseLayout) {
+                    assertEquals(config + " there should be no gap on bottom" + logSuffix,
+                            usedLayoutBounds.bottom, recyclerViewBounds.bottom);
+                    assertTrue(config + " there should be some gap on top" + logSuffix,
+                            usedLayoutBounds.top > recyclerViewBounds.top);
+                } else {
+                    assertEquals(config + " there should be no gap on top" + logSuffix,
+                            usedLayoutBounds.top, recyclerViewBounds.top);
+                    assertTrue(config + " there should be some gap at the bottom" + logSuffix,
+                            usedLayoutBounds.bottom < recyclerViewBounds.bottom);
+                }
+            } else {
+                assertEquals(config + " there should be no gap on top" + logSuffix,
+                        usedLayoutBounds.top, recyclerViewBounds.top);
+                assertEquals(config + " there should be no gap at the bottom" + logSuffix,
+                        usedLayoutBounds.bottom, recyclerViewBounds.bottom);
+                if (config.mReverseLayout) {
+                    assertEquals(config + " there should be no on right" + logSuffix,
+                            usedLayoutBounds.right, recyclerViewBounds.right);
+                    assertTrue(config + " there should be some gap on left" + logSuffix,
+                            usedLayoutBounds.left > recyclerViewBounds.left);
+                } else {
+                    assertEquals(config + " there should be no gap on left" + logSuffix,
+                            usedLayoutBounds.left, recyclerViewBounds.left);
+                    assertTrue(config + " there should be some gap on right" + logSuffix,
+                            usedLayoutBounds.right < recyclerViewBounds.right);
+                }
+            }
+            final int scroll = config.mReverseLayout ? -500 : 500;
+            scrollBy(scroll);
+            logSuffix = " scrolled " + scroll;
+        }
+
+    }
+
+    public void testSpanCountChangeOnRestoreSavedState() throws Throwable {
+        Config config = new Config(HORIZONTAL, true, 5, GAP_HANDLING_NONE);
+        setupByConfig(config);
+        waitFirstLayout();
+
+        int beforeChildCount = mLayoutManager.getChildCount();
+        Parcelable savedState = mRecyclerView.onSaveInstanceState();
+        // we append a suffix to the parcelable to test out of bounds
+        String parcelSuffix = UUID.randomUUID().toString();
+        Parcel parcel = Parcel.obtain();
+        savedState.writeToParcel(parcel, 0);
+        parcel.writeString(parcelSuffix);
+        removeRecyclerView();
+        // reset for reading
+        parcel.setDataPosition(0);
+        // re-create
+        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
+        removeRecyclerView();
+
+        RecyclerView restored = new RecyclerView(getActivity());
+        mLayoutManager = new WrappedLayoutManager(config.mSpanCount, config.mOrientation);
+        mLayoutManager.setReverseLayout(config.mReverseLayout);
+        mLayoutManager.setGapStrategy(config.mGapStrategy);
+        restored.setLayoutManager(mLayoutManager);
+        // use the same adapter for Rect matching
+        restored.setAdapter(mAdapter);
+        restored.onRestoreInstanceState(savedState);
+        mLayoutManager.setSpanCount(1);
+        mLayoutManager.expectLayouts(1);
+        setRecyclerView(restored);
+        mLayoutManager.waitForLayout(2);
+        assertEquals("on saved state, reverse layout should be preserved",
+                config.mReverseLayout, mLayoutManager.getReverseLayout());
+        assertEquals("on saved state, orientation should be preserved",
+                config.mOrientation, mLayoutManager.getOrientation());
+        assertEquals("after setting new span count, layout manager should keep new value",
+                1, mLayoutManager.getSpanCount());
+        assertEquals("on saved state, gap strategy should be preserved",
+                config.mGapStrategy, mLayoutManager.getGapStrategy());
+        assertTrue("when span count is dramatically changed after restore, # of child views "
+                + "should change", beforeChildCount > mLayoutManager.getChildCount());
+        // make sure LLM can layout all children. is some span info is leaked, this would crash
+        smoothScrollToPosition(mAdapter.getItemCount() - 1);
+    }
+
+    public void testSavedState() throws Throwable {
+        PostLayoutRunnable[] postLayoutOptions = new PostLayoutRunnable[]{
+                new PostLayoutRunnable() {
+                    @Override
+                    public void run() throws Throwable {
+                        // do nothing
+                    }
+
+                    @Override
+                    public String describe() {
+                        return "doing nothing";
+                    }
+                },
+                new PostLayoutRunnable() {
+                    @Override
+                    public void run() throws Throwable {
+                        mLayoutManager.expectLayouts(1);
+                        scrollToPosition(mAdapter.getItemCount() * 3 / 4);
+                        mLayoutManager.waitForLayout(2);
+                    }
+
+                    @Override
+                    public String describe() {
+                        return "scroll to position " + (mAdapter == null ? "" :
+                                mAdapter.getItemCount() * 3 / 4);
+                    }
+                },
+                new PostLayoutRunnable() {
+                    @Override
+                    public void run() throws Throwable {
+                        mLayoutManager.expectLayouts(1);
+                        scrollToPositionWithOffset(mAdapter.getItemCount() / 3,
+                                50);
+                        mLayoutManager.waitForLayout(2);
+                    }
+
+                    @Override
+                    public String describe() {
+                        return "scroll to position " + (mAdapter == null ? "" :
+                                mAdapter.getItemCount() / 3) + "with positive offset";
+                    }
+                },
+                new PostLayoutRunnable() {
+                    @Override
+                    public void run() throws Throwable {
+                        mLayoutManager.expectLayouts(1);
+                        scrollToPositionWithOffset(mAdapter.getItemCount() * 2 / 3,
+                                -50);
+                        mLayoutManager.waitForLayout(2);
+                    }
+
+                    @Override
+                    public String describe() {
+                        return "scroll to position with negative offset";
+                    }
+                }
+        };
+        boolean[] waitForLayoutOptions = new boolean[]{false, true};
+        List<Config> testVariations = new ArrayList<Config>();
+        testVariations.addAll(mBaseVariations);
+        for (Config config : mBaseVariations) {
+            if (config.mSpanCount < 2) {
+                continue;
+            }
+            final Config clone = (Config) config.clone();
+            clone.mItemCount = clone.mSpanCount - 1;
+            testVariations.add(clone);
+        }
+
+        for (Config config : testVariations) {
+            for (PostLayoutRunnable runnable : postLayoutOptions) {
+                for (boolean waitForLayout : waitForLayoutOptions) {
+                    savedStateTest(config, waitForLayout, runnable);
+                    removeRecyclerView();
+                }
+            }
+        }
+    }
+
+    private void saveRestore(Config config) throws Throwable {
+        Parcelable savedState = mRecyclerView.onSaveInstanceState();
+        // we append a suffix to the parcelable to test out of bounds
+        String parcelSuffix = UUID.randomUUID().toString();
+        Parcel parcel = Parcel.obtain();
+        savedState.writeToParcel(parcel, 0);
+        parcel.writeString(parcelSuffix);
+        removeRecyclerView();
+        // reset for reading
+        parcel.setDataPosition(0);
+        // re-create
+        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
+        RecyclerView restored = new RecyclerView(getActivity());
+        mLayoutManager = new WrappedLayoutManager(config.mSpanCount, config.mOrientation);
+        mLayoutManager.setGapStrategy(config.mGapStrategy);
+        restored.setLayoutManager(mLayoutManager);
+        // use the same adapter for Rect matching
+        restored.setAdapter(mAdapter);
+        restored.onRestoreInstanceState(savedState);
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            mLayoutManager.expectLayouts(1);
+            setRecyclerView(restored);
+        } else {
+            mLayoutManager.expectLayouts(1);
+            setRecyclerView(restored);
+            mLayoutManager.waitForLayout(2);
+        }
+    }
+
+    public void savedStateTest(Config config, boolean waitForLayout,
+            PostLayoutRunnable postLayoutOperations)
+            throws Throwable {
+        if (DEBUG) {
+            Log.d(TAG, "testing saved state with wait for layout = " + waitForLayout + " config "
+                    + config + " post layout action " + postLayoutOperations.describe());
+        }
+        setupByConfig(config);
+        waitFirstLayout();
+        if (waitForLayout) {
+            postLayoutOperations.run();
+        }
+        final int firstCompletelyVisiblePosition = mLayoutManager.findFirstVisibleItemPositionInt();
+        Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
+        Parcelable savedState = mRecyclerView.onSaveInstanceState();
+        // we append a suffix to the parcelable to test out of bounds
+        String parcelSuffix = UUID.randomUUID().toString();
+        Parcel parcel = Parcel.obtain();
+        savedState.writeToParcel(parcel, 0);
+        parcel.writeString(parcelSuffix);
+        removeRecyclerView();
+        // reset for reading
+        parcel.setDataPosition(0);
+        // re-create
+        savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
+        removeRecyclerView();
+
+        RecyclerView restored = new RecyclerView(getActivity());
+        mLayoutManager = new WrappedLayoutManager(config.mSpanCount, config.mOrientation);
+        mLayoutManager.setGapStrategy(config.mGapStrategy);
+        restored.setLayoutManager(mLayoutManager);
+        // use the same adapter for Rect matching
+        restored.setAdapter(mAdapter);
+        restored.onRestoreInstanceState(savedState);
+        assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
+                parcel.readString());
+        mLayoutManager.expectLayouts(1);
+        setRecyclerView(restored);
+        mLayoutManager.waitForLayout(2);
+        assertEquals(config + " on saved state, reverse layout should be preserved",
+                config.mReverseLayout, mLayoutManager.getReverseLayout());
+        assertEquals(config + " on saved state, orientation should be preserved",
+                config.mOrientation, mLayoutManager.getOrientation());
+        assertEquals(config + " on saved state, span count should be preserved",
+                config.mSpanCount, mLayoutManager.getSpanCount());
+        assertEquals(config + " on saved state, gap strategy should be preserved",
+                config.mGapStrategy, mLayoutManager.getGapStrategy());
+        assertEquals(config + " on saved state, first completely visible child position should"
+                + " be preserved", firstCompletelyVisiblePosition,
+                mLayoutManager.findFirstVisibleItemPositionInt());
+        if (waitForLayout) {
+            assertRectSetsEqual(config + "\npost layout op:" + postLayoutOperations.describe()
+                            + ": on restore, previous view positions should be preserved",
+                    before, mLayoutManager.collectChildCoordinates()
+            );
+        }
+        // TODO add tests for changing values after restore before layout
+    }
+
+    public void testScrollToPositionWithOffset() throws Throwable {
+        for (Config config : mBaseVariations) {
+            scrollToPositionWithOffsetTest(config);
+            removeRecyclerView();
+        }
+    }
+
+    public void scrollToPositionWithOffsetTest(Config config) throws Throwable {
+        setupByConfig(config);
+        waitFirstLayout();
+        OrientationHelper orientationHelper = OrientationHelper
+                .createOrientationHelper(mLayoutManager, config.mOrientation);
+        Rect layoutBounds = getDecoratedRecyclerViewBounds();
+        // try scrolling towards head, should not affect anything
+        Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
+        scrollToPositionWithOffset(0, 20);
+        assertRectSetsEqual(config + " trying to over scroll with offset should be no-op",
+                before, mLayoutManager.collectChildCoordinates());
+        // try offsetting some visible children
+        int testCount = 10;
+        while (testCount-- > 0) {
+            // get middle child
+            final View child = mLayoutManager.getChildAt(mLayoutManager.getChildCount() / 2);
+            final int position = mRecyclerView.getChildPosition(child);
+            final int startOffset = config.mReverseLayout ?
+                    orientationHelper.getEndAfterPadding() - orientationHelper
+                            .getDecoratedEnd(child)
+                    : orientationHelper.getDecoratedStart(child) - orientationHelper
+                            .getStartAfterPadding();
+            final int scrollOffset = startOffset / 2;
+            mLayoutManager.expectLayouts(1);
+            scrollToPositionWithOffset(position, scrollOffset);
+            mLayoutManager.waitForLayout(2);
+            final int finalOffset = config.mReverseLayout ?
+                    orientationHelper.getEndAfterPadding() - orientationHelper
+                            .getDecoratedEnd(child)
+                    : orientationHelper.getDecoratedStart(child) - orientationHelper
+                            .getStartAfterPadding();
+            assertEquals(config + " scroll with offset on a visible child should work fine",
+                    scrollOffset, finalOffset);
+        }
+
+        // try scrolling to invisible children
+        testCount = 10;
+        // we test above and below, one by one
+        int offsetMultiplier = -1;
+        while (testCount-- > 0) {
+            final TargetTuple target = findInvisibleTarget(config);
+            mLayoutManager.expectLayouts(1);
+            final int offset = offsetMultiplier
+                    * orientationHelper.getDecoratedMeasurement(mLayoutManager.getChildAt(0)) / 3;
+            scrollToPositionWithOffset(target.mPosition, offset);
+            mLayoutManager.waitForLayout(2);
+            final View child = mLayoutManager.findViewByPosition(target.mPosition);
+            assertNotNull(config + " scrolling to a mPosition with offset " + offset
+                    + " should layout it", child);
+            final Rect bounds = mLayoutManager.getViewBounds(child);
+            if (DEBUG) {
+                Log.d(TAG, config + " post scroll to invisible mPosition " + bounds + " in "
+                        + layoutBounds + " with offset " + offset);
+            }
+
+            if (config.mReverseLayout) {
+                assertEquals(config + " when scrolling with offset to an invisible in reverse "
+                                + "layout, its end should align with recycler view's end - offset",
+                        orientationHelper.getEndAfterPadding() - offset,
+                        orientationHelper.getDecoratedEnd(child)
+                );
+            } else {
+                assertEquals(config + " when scrolling with offset to an invisible child in normal"
+                                + " layout its start should align with recycler view's start + "
+                                + "offset",
+                        orientationHelper.getStartAfterPadding() + offset,
+                        orientationHelper.getDecoratedStart(child)
+                );
+            }
+            offsetMultiplier *= -1;
+        }
+    }
+
+    public void testScrollToPosition() throws Throwable {
+        for (Config config : mBaseVariations) {
+            scrollToPositionTest(config);
+            removeRecyclerView();
+        }
+    }
+
+    private TargetTuple findInvisibleTarget(Config config) {
+        int minPosition = Integer.MAX_VALUE, maxPosition = Integer.MIN_VALUE;
+        for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
+            View child = mLayoutManager.getChildAt(i);
+            int position = mRecyclerView.getChildPosition(child);
+            if (position < minPosition) {
+                minPosition = position;
+            }
+            if (position > maxPosition) {
+                maxPosition = position;
+            }
+        }
+        final int tailTarget = maxPosition + (mAdapter.getItemCount() - maxPosition) / 2;
+        final int headTarget = minPosition / 2;
+        final int target;
+        // where will the child come from ?
+        final int itemLayoutDirection;
+        if (Math.abs(tailTarget - maxPosition) > Math.abs(headTarget - minPosition)) {
+            target = tailTarget;
+            itemLayoutDirection = config.mReverseLayout ? LAYOUT_START : LAYOUT_END;
+        } else {
+            target = headTarget;
+            itemLayoutDirection = config.mReverseLayout ? LAYOUT_END : LAYOUT_START;
+        }
+        if (DEBUG) {
+            Log.d(TAG,
+                    config + " target:" + target + " min:" + minPosition + ", max:" + maxPosition);
+        }
+        return new TargetTuple(target, itemLayoutDirection);
+    }
+
+    public void scrollToPositionTest(Config config) throws Throwable {
+        setupByConfig(config);
+        waitFirstLayout();
+        OrientationHelper orientationHelper = OrientationHelper
+                .createOrientationHelper(mLayoutManager, config.mOrientation);
+        Rect layoutBounds = getDecoratedRecyclerViewBounds();
+        for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
+            View view = mLayoutManager.getChildAt(i);
+            Rect bounds = mLayoutManager.getViewBounds(view);
+            if (layoutBounds.contains(bounds)) {
+                Map<Item, Rect> initialBounds = mLayoutManager.collectChildCoordinates();
+                final int position = mRecyclerView.getChildPosition(view);
+                LayoutParams layoutParams
+                        = (LayoutParams) (view.getLayoutParams());
+                TestViewHolder vh = (TestViewHolder) layoutParams.mViewHolder;
+                assertEquals("recycler view mPosition should match adapter mPosition", position,
+                        vh.mBindedItem.mAdapterIndex);
+                if (DEBUG) {
+                    Log.d(TAG, "testing scroll to visible mPosition at " + position
+                            + " " + bounds + " inside " + layoutBounds);
+                }
+                mLayoutManager.expectLayouts(1);
+                scrollToPosition(position);
+                mLayoutManager.waitForLayout(2);
+                if (DEBUG) {
+                    view = mLayoutManager.findViewByPosition(position);
+                    Rect newBounds = mLayoutManager.getViewBounds(view);
+                    Log.d(TAG, "after scrolling to visible mPosition " +
+                            bounds + " equals " + newBounds);
+                }
+
+                assertRectSetsEqual(
+                        config + "scroll to mPosition on fully visible child should be no-op",
+                        initialBounds, mLayoutManager.collectChildCoordinates());
+            } else {
+                final int position = mRecyclerView.getChildPosition(view);
+                if (DEBUG) {
+                    Log.d(TAG,
+                            "child(" + position + ") not fully visible " + bounds + " not inside "
+                                    + layoutBounds
+                                    + mRecyclerView.getChildPosition(view)
+                    );
+                }
+                mLayoutManager.expectLayouts(1);
+                runTestOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        mLayoutManager.scrollToPosition(position);
+                    }
+                });
+                mLayoutManager.waitForLayout(2);
+                view = mLayoutManager.findViewByPosition(position);
+                bounds = mLayoutManager.getViewBounds(view);
+                if (DEBUG) {
+                    Log.d(TAG, "after scroll to partially visible child " + bounds + " in "
+                            + layoutBounds);
+                }
+                assertTrue(config
+                                + " after scrolling to a partially visible child, it should become fully "
+                                + " visible. " + bounds + " not inside " + layoutBounds,
+                        layoutBounds.contains(bounds)
+                );
+                assertTrue(config + " when scrolling to a partially visible item, one of its edges "
+                        + "should be on the boundaries", orientationHelper.getStartAfterPadding() ==
+                        orientationHelper.getDecoratedStart(view)
+                        || orientationHelper.getEndAfterPadding() ==
+                        orientationHelper.getDecoratedEnd(view));
+            }
+        }
+
+        // try scrolling to invisible children
+        int testCount = 10;
+        while (testCount-- > 0) {
+            final TargetTuple target = findInvisibleTarget(config);
+            mLayoutManager.expectLayouts(1);
+            scrollToPosition(target.mPosition);
+            mLayoutManager.waitForLayout(2);
+            final View child = mLayoutManager.findViewByPosition(target.mPosition);
+            assertNotNull(config + " scrolling to a mPosition should lay it out", child);
+            final Rect bounds = mLayoutManager.getViewBounds(child);
+            if (DEBUG) {
+                Log.d(TAG, config + " post scroll to invisible mPosition " + bounds + " in "
+                        + layoutBounds);
+            }
+            assertTrue(config + " scrolling to a mPosition should make it fully visible",
+                    layoutBounds.contains(bounds));
+            if (target.mLayoutDirection == LAYOUT_START) {
+                assertEquals(
+                        config + " when scrolling to an invisible child above, its start should"
+                                + " align with recycler view's start",
+                        orientationHelper.getStartAfterPadding(),
+                        orientationHelper.getDecoratedStart(child)
+                );
+            } else {
+                assertEquals(config + " when scrolling to an invisible child below, its end "
+                                + "should align with recycler view's end",
+                        orientationHelper.getEndAfterPadding(),
+                        orientationHelper.getDecoratedEnd(child)
+                );
+            }
+        }
+    }
+
+    private void scrollToPositionWithOffset(final int position, final int offset) throws Throwable {
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mLayoutManager.scrollToPositionWithOffset(position, offset);
+            }
+        });
+    }
+
+    public void testLayoutOrder() throws Throwable {
+        for (Config config : mBaseVariations) {
+            layoutOrderTest(config);
+            removeRecyclerView();
+        }
+    }
+
+    public void layoutOrderTest(Config config) throws Throwable {
+        setupByConfig(config);
+        assertViewPositions(config);
+    }
+
+    void assertViewPositions(Config config) {
+        ArrayList<ArrayList<View>> viewsBySpan = mLayoutManager.collectChildrenBySpan();
+        OrientationHelper orientationHelper = OrientationHelper
+                .createOrientationHelper(mLayoutManager, config.mOrientation);
+        for (ArrayList<View> span : viewsBySpan) {
+            // validate all children's order. first child should have min start mPosition
+            final int count = span.size();
+            for (int i = 0, j = 1; j < count; i++, j++) {
+                View prev = span.get(i);
+                View next = span.get(j);
+                assertTrue(config + " prev item should be above next item",
+                        orientationHelper.getDecoratedEnd(prev) <= orientationHelper
+                                .getDecoratedStart(next)
+                );
+
+            }
+        }
+    }
+
+    public void testScrollBy() throws Throwable {
+        for (Config config : mBaseVariations) {
+            scrollByTest(config);
+            removeRecyclerView();
+        }
+    }
+
+    void waitFirstLayout() throws Throwable {
+        mLayoutManager.expectLayouts(1);
+        setRecyclerView(mRecyclerView);
+        mLayoutManager.waitForLayout(2);
+        getInstrumentation().waitForIdleSync();
+    }
+
+    public void scrollByTest(Config config) throws Throwable {
+        setupByConfig(config);
+        waitFirstLayout();
+        // try invalid scroll. should not happen
+        final View first = mLayoutManager.getChildAt(0);
+        OrientationHelper primaryOrientation = OrientationHelper
+                .createOrientationHelper(mLayoutManager, config.mOrientation);
+        int scrollDist;
+        if (config.mReverseLayout) {
+            scrollDist = primaryOrientation.getDecoratedMeasurement(first) / 2;
+        } else {
+            scrollDist = -primaryOrientation.getDecoratedMeasurement(first) / 2;
+        }
+        Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
+        scrollBy(scrollDist);
+        Map<Item, Rect> after = mLayoutManager.collectChildCoordinates();
+        assertRectSetsEqual(
+                config + " if there are no more items, scroll should not happen (dt:" + scrollDist
+                        + ")",
+                before, after
+        );
+
+        scrollDist = -scrollDist * 3;
+        before = mLayoutManager.collectChildCoordinates();
+        scrollBy(scrollDist);
+        after = mLayoutManager.collectChildCoordinates();
+        int layoutStart = primaryOrientation.getStartAfterPadding();
+        int layoutEnd = primaryOrientation.getEndAfterPadding();
+        for (Map.Entry<Item, Rect> entry : before.entrySet()) {
+            Rect afterRect = after.get(entry.getKey());
+            // offset rect
+            if (config.mOrientation == VERTICAL) {
+                entry.getValue().offset(0, -scrollDist);
+            } else {
+                entry.getValue().offset(-scrollDist, 0);
+            }
+            if (afterRect == null || afterRect.isEmpty()) {
+                // assert item is out of bounds
+                int start, end;
+                if (config.mOrientation == VERTICAL) {
+                    start = entry.getValue().top;
+                    end = entry.getValue().bottom;
+                } else {
+                    start = entry.getValue().left;
+                    end = entry.getValue().right;
+                }
+                assertTrue(
+                        config + " if item is missing after relayout, it should be out of bounds."
+                                + "item start: " + start + ", end:" + end + " layout start:"
+                                + layoutStart +
+                                ", layout end:" + layoutEnd,
+                        start <= layoutStart && end <= layoutEnd ||
+                                start >= layoutEnd && end >= layoutEnd
+                );
+            } else {
+                assertEquals(config + " Item should be laid out at the scroll offset coordinates",
+                        entry.getValue(),
+                        afterRect);
+            }
+        }
+        assertViewPositions(config);
+    }
+
+    public void testConsistentRelayout() throws Throwable {
+        for (Config config : mBaseVariations) {
+            for (boolean firstChildMultiSpan : new boolean[]{false, true}) {
+                consistentRelayoutTest(config, firstChildMultiSpan);
+            }
+            removeRecyclerView();
+        }
+    }
+
+    public void consistentRelayoutTest(Config config, boolean firstChildMultiSpan)
+            throws Throwable {
+        setupByConfig(config);
+        if (firstChildMultiSpan) {
+            mAdapter.mFullSpanItems.add(0);
+        }
+        waitFirstLayout();
+        // record all child positions
+        Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
+        requestLayoutOnUIThread(mRecyclerView);
+        Map<Item, Rect> after = mLayoutManager.collectChildCoordinates();
+        assertRectSetsEqual(
+                config + " simple re-layout, firstChildMultiSpan:" + firstChildMultiSpan, before,
+                after);
+        // scroll some to create inconsistency
+        View firstChild = mLayoutManager.getChildAt(0);
+        final int firstChildStartBeforeScroll = mLayoutManager.mPrimaryOrientation
+                .getDecoratedStart(firstChild);
+        int distance = mLayoutManager.mPrimaryOrientation.getDecoratedMeasurement(firstChild) / 2;
+        if (config.mReverseLayout) {
+            distance *= -1;
+        }
+        scrollBy(distance);
+        waitForMainThread(2);
+        assertTrue("scroll by should move children", firstChildStartBeforeScroll !=
+                mLayoutManager.mPrimaryOrientation.getDecoratedStart(firstChild));
+        before = mLayoutManager.collectChildCoordinates();
+        mLayoutManager.expectLayouts(1);
+        requestLayoutOnUIThread(mRecyclerView);
+        mLayoutManager.waitForLayout(2);
+        after = mLayoutManager.collectChildCoordinates();
+        assertRectSetsEqual(config + " simple re-layout after scroll", before, after);
+    }
+
+    /**
+     * enqueues an empty runnable to main thread so that we can be assured it did run
+     *
+     * @param count Number of times to run
+     */
+    private void waitForMainThread(int count) throws Throwable {
+        final AtomicInteger i = new AtomicInteger(count);
+        while (i.get() > 0) {
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    i.decrementAndGet();
+                }
+            });
+        }
+    }
+
+    public void assertRectSetsNotEqual(String message, Map<Item, Rect> before,
+            Map<Item, Rect> after) {
+        Throwable throwable = null;
+        try {
+            assertRectSetsEqual("NOT " + message, before, after);
+        } catch (Throwable t) {
+            throwable = t;
+        }
+        assertNotNull(message + " two layout should be different", throwable);
+    }
+
+    public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after) {
+        StringBuilder log = new StringBuilder();
+        if (DEBUG) {
+            log.append("checking rectangle equality.\n");
+            log.append("before:");
+            for (Map.Entry<Item, Rect> entry : before.entrySet()) {
+                log.append("\n").append(entry.getKey().mAdapterIndex).append(":")
+                        .append(entry.getValue());
+            }
+            log.append("\nafter:");
+            for (Map.Entry<Item, Rect> entry : after.entrySet()) {
+                log.append("\n").append(entry.getKey().mAdapterIndex).append(":")
+                        .append(entry.getValue());
+            }
+            message += "\n\n" + log.toString();
+        }
+        assertEquals(message + ": item counts should be equal", before.size()
+                , after.size());
+        for (Map.Entry<Item, Rect> entry : before.entrySet()) {
+            Rect afterRect = after.get(entry.getKey());
+            assertNotNull(message + ": Same item should be visible after simple re-layout",
+                    afterRect);
+            assertEquals(message + ": Item should be laid out at the same coordinates",
+                    entry.getValue(),
+                    afterRect);
+        }
+    }
+
+    // test layout params assignment
+
+    static class OnLayoutListener {
+        void before(RecyclerView.Recycler recycler, RecyclerView.State state){}
+        void after(RecyclerView.Recycler recycler, RecyclerView.State state){}
+    }
+
+    class WrappedLayoutManager extends StaggeredGridLayoutManager {
+
+        CountDownLatch layoutLatch;
+        OnLayoutListener mOnLayoutListener;
+        // gradle does not yet let us customize manifest for tests which is necessary to test RTL.
+        // until bug is fixed, we'll fake it.
+        // public issue id: 57819
+        Boolean mFakeRTL;
+
+        @Override
+        boolean isLayoutRTL() {
+            return mFakeRTL == null ? super.isLayoutRTL() : mFakeRTL;
+        }
+
+        public void expectLayouts(int count) {
+            layoutLatch = new CountDownLatch(count);
+        }
+
+        public void waitForLayout(long timeout) throws InterruptedException {
+            waitForLayout(timeout * (DEBUG ? 1000 : 1), TimeUnit.SECONDS);
+        }
+
+        public void waitForLayout(long timeout, TimeUnit timeUnit) throws InterruptedException {
+            layoutLatch.await(timeout, timeUnit);
+            assertEquals("all expected layouts should be executed at the expected time",
+                    0, layoutLatch.getCount());
+        }
+
+        public void assertNoLayout(String msg, long timeout) throws Throwable {
+            layoutLatch.await(timeout, TimeUnit.SECONDS);
+            assertFalse(msg, layoutLatch.getCount() == 0);
+        }
+
+        @Override
+        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+            try {
+                if (mOnLayoutListener != null) {
+                    mOnLayoutListener.before(recycler, state);
+                }
+                super.onLayoutChildren(recycler, state);
+                if (mOnLayoutListener != null) {
+                    mOnLayoutListener.after(recycler, state);
+                }
+            } catch (Throwable t) {
+                postExceptionToInstrumentation(t);
+            }
+            layoutLatch.countDown();
+        }
+
+        public WrappedLayoutManager(int spanCount, int orientation) {
+            super(spanCount, orientation);
+        }
+
+        ArrayList<ArrayList<View>> collectChildrenBySpan() {
+            ArrayList<ArrayList<View>> viewsBySpan = new ArrayList<ArrayList<View>>();
+            for (int i = 0; i < getSpanCount(); i++) {
+                viewsBySpan.add(new ArrayList<View>());
+            }
+            for (int i = 0; i < getChildCount(); i++) {
+                View view = getChildAt(i);
+                LayoutParams lp
+                        = (LayoutParams) view
+                        .getLayoutParams();
+                viewsBySpan.get(lp.mSpan.mIndex).add(view);
+            }
+            return viewsBySpan;
+        }
+
+        Rect getViewBounds(View view) {
+            if (getOrientation() == HORIZONTAL) {
+                return new Rect(
+                        mPrimaryOrientation.getDecoratedStart(view),
+                        mSecondaryOrientation.getDecoratedStart(view),
+                        mPrimaryOrientation.getDecoratedEnd(view),
+                        mSecondaryOrientation.getDecoratedEnd(view));
+            } else {
+                return new Rect(
+                        mSecondaryOrientation.getDecoratedStart(view),
+                        mPrimaryOrientation.getDecoratedStart(view),
+                        mSecondaryOrientation.getDecoratedEnd(view),
+                        mPrimaryOrientation.getDecoratedEnd(view));
+            }
+        }
+
+        public String getBoundsLog() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("view bounds:[start:").append(mPrimaryOrientation.getStartAfterPadding())
+                    .append(",").append(" end").append(mPrimaryOrientation.getEndAfterPadding());
+            sb.append("\nchildren bounds\n");
+            final int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                View child = getChildAt(i);
+                sb.append("child (ind:").append(i).append(", pos:").append(getPosition(child))
+                        .append("[").append("start:").append(
+                        mPrimaryOrientation.getDecoratedStart(child)).append(", end:")
+                        .append(mPrimaryOrientation.getDecoratedEnd(child)).append("]\n");
+            }
+            return sb.toString();
+        }
+
+        public VisibleChildren traverseAndFindVisibleChildren() {
+            int childCount = getChildCount();
+            final VisibleChildren visibleChildren = new VisibleChildren(getSpanCount());
+            final int start = mPrimaryOrientation.getStartAfterPadding();
+            final int end = mPrimaryOrientation.getEndAfterPadding();
+            for (int i = 0; i < childCount; i++) {
+                View child = getChildAt(i);
+                final int childStart = mPrimaryOrientation.getDecoratedStart(child);
+                final int childEnd = mPrimaryOrientation.getDecoratedEnd(child);
+                final boolean fullyVisible = childStart >= start && childEnd <= end;
+                final boolean hidden = childEnd <= start || childStart >= end;
+                if (hidden) {
+                    continue;
+                }
+                final int position = getPosition(child);
+                final int span = getLp(child).getSpanIndex();
+                if (fullyVisible) {
+                    if (position < visibleChildren.firstFullyVisiblePositions[span] ||
+                            visibleChildren.firstFullyVisiblePositions[span]
+                                    == RecyclerView.NO_POSITION) {
+                        visibleChildren.firstFullyVisiblePositions[span] = position;
+                    }
+
+                    if (position > visibleChildren.lastFullyVisiblePositions[span]) {
+                        visibleChildren.lastFullyVisiblePositions[span] = position;
+                    }
+                }
+
+                if (position < visibleChildren.firstVisiblePositions[span] ||
+                        visibleChildren.firstVisiblePositions[span] == RecyclerView.NO_POSITION) {
+                    visibleChildren.firstVisiblePositions[span] = position;
+                }
+
+                if (position > visibleChildren.lastVisiblePositions[span]) {
+                    visibleChildren.lastVisiblePositions[span] = position;
+                }
+
+            }
+            return visibleChildren;
+        }
+
+        Map<Item, Rect> collectChildCoordinates() throws Throwable {
+            final Map<Item, Rect> items = new LinkedHashMap<Item, Rect>();
+            runTestOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    final int childCount = getChildCount();
+                    for (int i = 0; i < childCount; i++) {
+                        View child = getChildAt(i);
+                        // do it if and only if child is visible
+                        if (child.getRight() < 0 || child.getBottom() < 0 ||
+                                child.getLeft() >= getWidth() || child.getTop() >= getHeight()) {
+                            // invisible children may be drawn in cases like scrolling so we should
+                            // ignore them
+                            continue;
+                        }
+                        LayoutParams lp = (LayoutParams) child
+                                .getLayoutParams();
+                        TestViewHolder vh = (TestViewHolder) lp.mViewHolder;
+                        items.put(vh.mBindedItem, getViewBounds(child));
+                    }
+                }
+            });
+            return items;
+        }
+
+
+        public void setFakeRtl(Boolean fakeRtl) {
+            mFakeRTL = fakeRtl;
+            try {
+                requestLayoutOnUIThread(mRecyclerView);
+            } catch (Throwable throwable) {
+                postExceptionToInstrumentation(throwable);
+            }
+        }
+    }
+
+    static class VisibleChildren {
+
+        int[] firstVisiblePositions;
+
+        int[] firstFullyVisiblePositions;
+
+        int[] lastVisiblePositions;
+
+        int[] lastFullyVisiblePositions;
+
+        VisibleChildren(int spanCount) {
+            firstFullyVisiblePositions = new int[spanCount];
+            firstVisiblePositions = new int[spanCount];
+            lastVisiblePositions = new int[spanCount];
+            lastFullyVisiblePositions = new int[spanCount];
+            for (int i = 0; i < spanCount; i++) {
+                firstFullyVisiblePositions[i] = RecyclerView.NO_POSITION;
+                firstVisiblePositions[i] = RecyclerView.NO_POSITION;
+                lastVisiblePositions[i] = RecyclerView.NO_POSITION;
+                lastFullyVisiblePositions[i] = RecyclerView.NO_POSITION;
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            VisibleChildren that = (VisibleChildren) o;
+
+            if (!Arrays.equals(firstFullyVisiblePositions, that.firstFullyVisiblePositions)) {
+                return false;
+            }
+            if (!Arrays.equals(firstVisiblePositions, that.firstVisiblePositions)) {
+                return false;
+            }
+            if (!Arrays.equals(lastFullyVisiblePositions, that.lastFullyVisiblePositions)) {
+                return false;
+            }
+            if (!Arrays.equals(lastVisiblePositions, that.lastVisiblePositions)) {
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = firstVisiblePositions != null ? Arrays.hashCode(firstVisiblePositions) : 0;
+            result = 31 * result + (firstFullyVisiblePositions != null ? Arrays
+                    .hashCode(firstFullyVisiblePositions) : 0);
+            result = 31 * result + (lastVisiblePositions != null ? Arrays
+                    .hashCode(lastVisiblePositions)
+                    : 0);
+            result = 31 * result + (lastFullyVisiblePositions != null ? Arrays
+                    .hashCode(lastFullyVisiblePositions) : 0);
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "VisibleChildren{" +
+                    "firstVisiblePositions=" + Arrays.toString(firstVisiblePositions) +
+                    ", firstFullyVisiblePositions=" + Arrays.toString(firstFullyVisiblePositions) +
+                    ", lastVisiblePositions=" + Arrays.toString(lastVisiblePositions) +
+                    ", lastFullyVisiblePositions=" + Arrays.toString(lastFullyVisiblePositions) +
+                    '}';
+        }
+    }
+
+    class GridTestAdapter extends TestAdapter {
+
+        int mOrientation;
+
+        // original ids of items that should be full span
+        HashSet<Integer> mFullSpanItems = new HashSet<Integer>();
+
+        private boolean mViewsHaveEqualSize = false; // size in the scrollable direction
+
+        private OnBindHandler mOnBindHandler;
+
+        GridTestAdapter(int count, int orientation) {
+            super(count);
+            mOrientation = orientation;
+        }
+
+        @Override
+        public void offsetOriginalIndices(int start, int offset) {
+            if (mFullSpanItems.size() > 0) {
+                HashSet<Integer> old = mFullSpanItems;
+                mFullSpanItems = new HashSet<Integer>();
+                for (Integer i : old) {
+                    if (i < start) {
+                        mFullSpanItems.add(i);
+                    } else if (offset > 0 || (start + Math.abs(offset)) <= i) {
+                        mFullSpanItems.add(i + offset);
+                    } else if (DEBUG) {
+                        Log.d(TAG, "removed full span item " + i);
+                    }
+                }
+            }
+            super.offsetOriginalIndices(start, offset);
+        }
+
+        @Override
+        public void onBindViewHolder(TestViewHolder holder,
+                int position) {
+            super.onBindViewHolder(holder, position);
+            Item item = mItems.get(position);
+            final int minSize = mViewsHaveEqualSize ? 200 : 200 + 20 * (position % 10);
+            if (mOrientation == OrientationHelper.HORIZONTAL) {
+                holder.itemView.setMinimumWidth(minSize);
+            } else {
+                holder.itemView.setMinimumHeight(minSize);
+            }
+            RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) holder.itemView
+                    .getLayoutParams();
+            if (lp instanceof LayoutParams) {
+                ((LayoutParams) lp).setFullSpan(mFullSpanItems.contains(item.mAdapterIndex));
+            } else {
+                LayoutParams slp = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT);
+                holder.itemView.setLayoutParams(slp);
+                slp.setFullSpan(mFullSpanItems.contains(item.mAdapterIndex));
+                lp = slp;
+            }
+            lp.topMargin = 3;
+            lp.leftMargin = 5;
+            lp.rightMargin = 7;
+            lp.bottomMargin = 9;
+
+            if (mOnBindHandler != null) {
+                mOnBindHandler.onBoundItem(holder, position);
+            }
+        }
+    }
+
+    static interface OnBindHandler {
+        void onBoundItem(TestViewHolder vh, int postion);
+    }
+
+    static class Config implements Cloneable {
+
+        private static final int DEFAULT_ITEM_COUNT = 300;
+
+        int mOrientation = OrientationHelper.VERTICAL;
+
+        boolean mReverseLayout = false;
+
+        int mSpanCount = 3;
+
+        int mGapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
+
+        int mItemCount = DEFAULT_ITEM_COUNT;
+
+        Config(int orientation, boolean reverseLayout, int spanCount, int gapStrategy) {
+            mOrientation = orientation;
+            mReverseLayout = reverseLayout;
+            mSpanCount = spanCount;
+            mGapStrategy = gapStrategy;
+        }
+
+        public Config() {
+
+        }
+
+        Config orientation(int orientation) {
+            mOrientation = orientation;
+            return this;
+        }
+
+        Config reverseLayout(boolean reverseLayout) {
+            mReverseLayout = reverseLayout;
+            return this;
+        }
+
+        Config spanCount(int spanCount) {
+            mSpanCount = spanCount;
+            return this;
+        }
+
+        Config gapStrategy(int gapStrategy) {
+            mGapStrategy = gapStrategy;
+            return this;
+        }
+
+        public Config itemCount(int itemCount) {
+            mItemCount = itemCount;
+            return this;
+        }
+
+        @Override
+        public String toString() {
+            return "[CONFIG:" +
+                    " span:" + mSpanCount + "," +
+                    " orientation:" + (mOrientation == HORIZONTAL ? "horz," : "vert,") +
+                    " reverse:" + (mReverseLayout ? "T" : "F") +
+                    " itemCount:" + mItemCount +
+                    " gap strategy: " + gapStrategyName(mGapStrategy);
+        }
+
+        private static String gapStrategyName(int gapStrategy) {
+            switch (gapStrategy) {
+                case GAP_HANDLING_NONE:
+                    return "none";
+                case GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS:
+                    return "move spans";
+            }
+            return "gap strategy: unknown";
+        }
+
+        @Override
+        public Object clone() throws CloneNotSupportedException {
+            return super.clone();
+        }
+    }
+
+    private interface PostLayoutRunnable {
+
+        void run() throws Throwable;
+
+        String describe();
+    }
+
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/TestActivity.java b/v7/recyclerview/tests/src/android/support/v7/widget/TestActivity.java
new file mode 100644
index 0000000..a0b165c
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/TestActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.FrameLayout;
+
+public class TestActivity extends Activity {
+
+    FrameLayout mContainer;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mContainer = new FrameLayout(this);
+        setContentView(mContainer);
+    }
+}
diff --git a/v8/Android.mk b/v8/Android.mk
new file mode 100644
index 0000000..14ff0aa
--- /dev/null
+++ b/v8/Android.mk
@@ -0,0 +1,16 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/ElementThunker.java b/v8/renderscript/java/src/android/support/v8/renderscript/ElementThunker.java
index fc986b3..9b820e2 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/ElementThunker.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/ElementThunker.java
@@ -57,10 +57,6 @@
             return android.renderscript.Element.DataKind.PIXEL_RGB;
         case PIXEL_RGBA:
             return android.renderscript.Element.DataKind.PIXEL_RGBA;
-        case PIXEL_DEPTH:
-            return android.renderscript.Element.DataKind.PIXEL_DEPTH;
-        case PIXEL_YUV:
-            return android.renderscript.Element.DataKind.PIXEL_YUV;
         }
         return null;
     }
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java b/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java
index 7efbed2..d9ff977 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java
@@ -136,6 +136,13 @@
                         }
                     }
 
+                    // blur issues on some drivers with 4.4
+                    if (info.metaData.getBoolean("com.android.support.v8.renderscript.EnableBlurWorkaround") == true) {
+                        if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.KITKAT) {
+                            //android.util.Log.e("rs", "war on");
+                            sThunk = 0;
+                        }
+                    }
                 }
                 // end of workarounds
             }