Merge changes If022a8f1,Ibb836331,I106d2eba,Ie2541f80,I430f47de into pi-preview1-androidx-dev
am: 268b6f5307

Change-Id: I73091b335582596deb3b65335cb020c03a0c62cb
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index 32eee0b..b08787e 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -41,13 +41,7 @@
 const val RX_JAVA = "io.reactivex.rxjava2:rxjava:2.0.6"
 const val TEST_RUNNER = "com.android.support.test:runner:1.0.1"
 const val TEST_RULES = "com.android.support.test:rules:1.0.1"
-
-const val ESPRESSO_CONTRIB_TMP = "com.android.temp.support.test.espresso:espresso-contrib:3.0.1"
-const val ESPRESSO_CORE_TMP = "com.android.temp.support.test.espresso:espresso-core:3.0.1"
-const val TEST_RUNNER_TMP = "com.android.temp.support.test:runner:1.0.1"
-const val TEST_RULES_TMP = "com.android.temp.support.test:rules:1.0.1"
-
-
+const val TRUTH = "com.google.truth:truth:0.34"
 /**
  * this Xerial version is newer than we want but we need it to fix
  * https://github.com/xerial/sqlite-jdbc/issues/97
@@ -55,6 +49,11 @@
  */
 const val XERIAL = "org.xerial:sqlite-jdbc:3.20.1"
 
+const val ESPRESSO_CONTRIB_TMP = "com.android.temp.support.test.espresso:espresso-contrib:3.0.1"
+const val ESPRESSO_CORE_TMP = "com.android.temp.support.test.espresso:espresso-core:3.0.1"
+const val TEST_RUNNER_TMP = "com.android.temp.support.test:runner:1.0.1"
+const val TEST_RULES_TMP = "com.android.temp.support.test:rules:1.0.1"
+
 // Support library dependencies needed for projects that compile against prebuilt versions
 // instead of source directly.
 // NOTE: _27 versions exist for modules that have opted-in to 27, and tests that depend on those
diff --git a/car/res/drawable/car_button_ripple_background.xml b/car/res/drawable/car_button_ripple_background.xml
new file mode 100644
index 0000000..13d0a49
--- /dev/null
+++ b/car/res/drawable/car_button_ripple_background.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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/car_card_ripple_background" />
diff --git a/car/res/drawable/car_button_ripple_background_day.xml b/car/res/drawable/car_button_ripple_background_day.xml
new file mode 100644
index 0000000..16b1d0c
--- /dev/null
+++ b/car/res/drawable/car_button_ripple_background_day.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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/car_card_ripple_background_dark" />
diff --git a/car/res/drawable/car_button_ripple_background_inverse.xml b/car/res/drawable/car_button_ripple_background_inverse.xml
new file mode 100644
index 0000000..660dbcd
--- /dev/null
+++ b/car/res/drawable/car_button_ripple_background_inverse.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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/car_card_ripple_background_inverse" />
diff --git a/car/res/drawable/car_button_ripple_background_night.xml b/car/res/drawable/car_button_ripple_background_night.xml
new file mode 100644
index 0000000..6160768
--- /dev/null
+++ b/car/res/drawable/car_button_ripple_background_night.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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/car_card_ripple_background_light" />
diff --git a/car/res/layout/car_paged_scrollbar_buttons.xml b/car/res/layout/car_paged_scrollbar_buttons.xml
index b126b48..e982b66 100644
--- a/car/res/layout/car_paged_scrollbar_buttons.xml
+++ b/car/res/layout/car_paged_scrollbar_buttons.xml
@@ -22,7 +22,7 @@
     android:gravity="center"
     android:orientation="vertical">
 
-    <ImageView
+    <ImageButton
         android:id="@+id/page_up"
         android:layout_width="@dimen/car_scroll_bar_button_size"
         android:layout_height="@dimen/car_scroll_bar_button_size"
@@ -47,7 +47,7 @@
             android:background="@drawable/car_scrollbar_thumb" />
     </FrameLayout>
 
-    <ImageView
+    <ImageButton
         android:id="@+id/page_down"
         android:layout_width="@dimen/car_scroll_bar_button_size"
         android:layout_height="@dimen/car_scroll_bar_button_size"
diff --git a/car/src/main/java/androidx/car/widget/PagedScrollBarView.java b/car/src/main/java/androidx/car/widget/PagedScrollBarView.java
index ef0b3c2..8d26916 100644
--- a/car/src/main/java/androidx/car/widget/PagedScrollBarView.java
+++ b/car/src/main/java/androidx/car/widget/PagedScrollBarView.java
@@ -248,22 +248,22 @@
             case DayNightStyle.AUTO:
                 tintResId = R.color.car_tint;
                 thumbColorResId = R.color.car_scrollbar_thumb;
-                upDownBackgroundResId = R.drawable.car_card_ripple_background;
+                upDownBackgroundResId = R.drawable.car_button_ripple_background;
                 break;
             case DayNightStyle.AUTO_INVERSE:
                 tintResId = R.color.car_tint_inverse;
                 thumbColorResId = R.color.car_scrollbar_thumb_inverse;
-                upDownBackgroundResId = R.drawable.car_card_ripple_background_inverse;
+                upDownBackgroundResId = R.drawable.car_button_ripple_background_inverse;
                 break;
             case DayNightStyle.FORCE_NIGHT:
                 tintResId = R.color.car_tint_light;
                 thumbColorResId = R.color.car_scrollbar_thumb_light;
-                upDownBackgroundResId = R.drawable.car_card_ripple_background_night;
+                upDownBackgroundResId = R.drawable.car_button_ripple_background_night;
                 break;
             case DayNightStyle.FORCE_DAY:
                 tintResId =  R.color.car_tint_dark;
                 thumbColorResId = R.color.car_scrollbar_thumb_dark;
-                upDownBackgroundResId = R.drawable.car_card_ripple_background_day;
+                upDownBackgroundResId = R.drawable.car_button_ripple_background_day;
                 break;
             default:
                 throw new IllegalArgumentException("Unknown DayNightStyle: " + mDayNightStyle);
diff --git a/collection-ktx/OWNERS b/collection-ktx/OWNERS
new file mode 100644
index 0000000..e450f4c
--- /dev/null
+++ b/collection-ktx/OWNERS
@@ -0,0 +1 @@
+jakew@google.com
diff --git a/collection-ktx/build.gradle b/collection-ktx/build.gradle
new file mode 100644
index 0000000..83944f2
--- /dev/null
+++ b/collection-ktx/build.gradle
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+
+plugins {
+    id("SupportKotlinLibraryPlugin")
+}
+
+dependencies {
+    compile(project(":collection"))
+    compile(KOTLIN_STDLIB)
+    testCompile(JUNIT)
+    testCompile(TRUTH)
+    testCompile(project(":internal-testutils-ktx"))
+}
+
+supportLibrary {
+    name = "Collections Kotlin Extensions"
+    publish = true
+    mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+    mavenGroup = LibraryGroups.COLLECTION
+    inceptionYear = "2018"
+    description = "Kotlin extensions for 'collection' artifact"
+}
diff --git a/collection-ktx/src/main/java/androidx/collection/ArrayMap.kt b/collection-ktx/src/main/java/androidx/collection/ArrayMap.kt
new file mode 100644
index 0000000..8f3299e
--- /dev/null
+++ b/collection-ktx/src/main/java/androidx/collection/ArrayMap.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("NOTHING_TO_INLINE") // Aliases to public API.
+
+package androidx.collection
+
+/** Returns an empty new [ArrayMap]. */
+inline fun <K, V> arrayMapOf(): ArrayMap<K, V> = ArrayMap()
+
+/**
+ * Returns a new [ArrayMap] with the specified contents, given as a list of pairs where the first
+ * component is the key and the second component is the value.
+ *
+ * If multiple pairs have the same key, the resulting map will contain the value from the last of
+ * those pairs.
+ */
+fun <K, V> arrayMapOf(vararg pairs: Pair<K, V>): ArrayMap<K, V> {
+    val map = ArrayMap<K, V>(pairs.size)
+    for (pair in pairs) {
+        map[pair.first] = pair.second
+    }
+    return map
+}
diff --git a/collection-ktx/src/main/java/androidx/collection/ArraySet.kt b/collection-ktx/src/main/java/androidx/collection/ArraySet.kt
new file mode 100644
index 0000000..07b4be7
--- /dev/null
+++ b/collection-ktx/src/main/java/androidx/collection/ArraySet.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("NOTHING_TO_INLINE") // Aliases to public API.
+
+package androidx.collection
+
+/** Returns an empty new [ArraySet]. */
+inline fun <T> arraySetOf(): ArraySet<T> = ArraySet()
+
+/** Returns a new [ArraySet] with the specified contents. */
+fun <T> arraySetOf(vararg values: T): ArraySet<T> {
+    val set = ArraySet<T>(values.size)
+    @Suppress("LoopToCallChain") // Causes needless copy to a list.
+    for (value in values) {
+        set.add(value)
+    }
+    return set
+}
diff --git a/collection-ktx/src/main/java/androidx/collection/LongSparseArray.kt b/collection-ktx/src/main/java/androidx/collection/LongSparseArray.kt
new file mode 100644
index 0000000..fd6201a
--- /dev/null
+++ b/collection-ktx/src/main/java/androidx/collection/LongSparseArray.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("NOTHING_TO_INLINE") // Aliases to public API.
+
+package androidx.collection
+
+/** Returns the number of key/value pairs in the collection. */
+inline val <T> LongSparseArray<T>.size get() = size()
+
+/** Returns true if the collection contains [key]. */
+inline operator fun <T> LongSparseArray<T>.contains(key: Long) = indexOfKey(key) >= 0
+
+/** Allows the use of the index operator for storing values in the collection. */
+inline operator fun <T> LongSparseArray<T>.set(key: Long, value: T) = put(key, value)
+
+/** Creates a new collection by adding or replacing entries from [other]. */
+operator fun <T> LongSparseArray<T>.plus(other: LongSparseArray<T>): LongSparseArray<T> {
+    val new = LongSparseArray<T>(size() + other.size())
+    new.putAll(this)
+    new.putAll(other)
+    return new
+}
+
+/** Returns true if the collection contains [key]. */
+inline fun <T> LongSparseArray<T>.containsKey(key: Long) = indexOfKey(key) >= 0
+
+/** Returns true if the collection contains [value]. */
+inline fun <T> LongSparseArray<T>.containsValue(value: T) = indexOfValue(value) != -1
+
+/** Return the value corresponding to [key], or [defaultValue] when not present. */
+inline fun <T> LongSparseArray<T>.getOrDefault(key: Long, defaultValue: T) =
+    get(key) ?: defaultValue
+
+/** Return the value corresponding to [key], or from [defaultValue] when not present. */
+inline fun <T> LongSparseArray<T>.getOrElse(key: Long, defaultValue: () -> T) =
+    get(key) ?: defaultValue()
+
+/** Return true when the collection contains elements. */
+inline fun <T> LongSparseArray<T>.isNotEmpty() = size() != 0
+
+/** Removes the entry for [key] only if it is mapped to [value]. */
+fun <T> LongSparseArray<T>.remove(key: Long, value: T): Boolean {
+    val index = indexOfKey(key)
+    if (index != -1 && value == valueAt(index)) {
+        removeAt(index)
+        return true
+    }
+    return false
+}
+
+/** Update this collection by adding or replacing entries from [other]. */
+fun <T> LongSparseArray<T>.putAll(other: LongSparseArray<T>) = other.forEach(::put)
+
+/** Performs the given [action] for each key/value entry. */
+inline fun <T> LongSparseArray<T>.forEach(action: (key: Long, value: T) -> Unit) {
+    for (index in 0 until size()) {
+        action(keyAt(index), valueAt(index))
+    }
+}
+
+/** Return an iterator over the collection's keys. */
+fun <T> LongSparseArray<T>.keyIterator(): LongIterator = object : LongIterator() {
+    var index = 0
+    override fun hasNext() = index < size()
+    override fun nextLong() = keyAt(index++)
+}
+
+/** Return an iterator over the collection's values. */
+fun <T> LongSparseArray<T>.valueIterator(): Iterator<T> = object : Iterator<T> {
+    var index = 0
+    override fun hasNext() = index < size()
+    override fun next() = valueAt(index++)
+}
diff --git a/collection-ktx/src/main/java/androidx/collection/LruCache.kt b/collection-ktx/src/main/java/androidx/collection/LruCache.kt
new file mode 100644
index 0000000..554a012
--- /dev/null
+++ b/collection-ktx/src/main/java/androidx/collection/LruCache.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+/**
+ * Creates an [LruCache] with the given parameters.
+ *
+ * @param maxSize for caches that do not specify [sizeOf], this is
+ * the maximum number of entries in the cache. For all other caches,
+ * this is the maximum sum of the sizes of the entries in this cache.
+ * @param sizeOf function that returns the size of the entry for key and value in
+ * user-defined units. The default implementation returns 1.
+ * @param create a create called after a cache miss to compute a value for the corresponding key.
+ * Returns the computed value or null if no value can be computed. The default implementation
+ * returns null.
+ * @param onEntryRemoved a function called for entries that have been evicted or removed.
+ *
+ * @see LruCache.sizeOf
+ * @see LruCache.create
+ * @see LruCache.entryRemoved
+ */
+inline fun <K : Any, V : Any> lruCache(
+    maxSize: Int,
+    crossinline sizeOf: (key: K, value: V) -> Int = { _, _ -> 1 },
+    @Suppress("USELESS_CAST") // https://youtrack.jetbrains.com/issue/KT-21946
+    crossinline create: (key: K) -> V? = { null as V? },
+    crossinline onEntryRemoved: (evicted: Boolean, key: K, oldValue: V, newValue: V?) -> Unit =
+        { _, _, _, _ -> }
+): LruCache<K, V> {
+    return object : LruCache<K, V>(maxSize) {
+        override fun sizeOf(key: K, value: V) = sizeOf(key, value)
+        override fun create(key: K) = create(key)
+        override fun entryRemoved(evicted: Boolean, key: K, oldValue: V, newValue: V?) {
+            onEntryRemoved(evicted, key, oldValue, newValue)
+        }
+    }
+}
diff --git a/collection-ktx/src/main/java/androidx/collection/SparseArray.kt b/collection-ktx/src/main/java/androidx/collection/SparseArray.kt
new file mode 100644
index 0000000..e1d2382
--- /dev/null
+++ b/collection-ktx/src/main/java/androidx/collection/SparseArray.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("NOTHING_TO_INLINE") // Aliases to public API.
+
+package androidx.collection
+
+/** Returns the number of key/value pairs in the collection. */
+inline val <T> SparseArrayCompat<T>.size get() = size()
+
+/** Returns true if the collection contains [key]. */
+inline operator fun <T> SparseArrayCompat<T>.contains(key: Int) = indexOfKey(key) >= 0
+
+/** Allows the use of the index operator for storing values in the collection. */
+inline operator fun <T> SparseArrayCompat<T>.set(key: Int, value: T) = put(key, value)
+
+/** Creates a new collection by adding or replacing entries from [other]. */
+operator fun <T> SparseArrayCompat<T>.plus(other: SparseArrayCompat<T>): SparseArrayCompat<T> {
+    val new = SparseArrayCompat<T>(size() + other.size())
+    new.putAll(this)
+    new.putAll(other)
+    return new
+}
+
+/** Returns true if the collection contains [key]. */
+inline fun <T> SparseArrayCompat<T>.containsKey(key: Int) = indexOfKey(key) >= 0
+
+/** Returns true if the collection contains [value]. */
+inline fun <T> SparseArrayCompat<T>.containsValue(value: T) = indexOfValue(value) != -1
+
+/** Return the value corresponding to [key], or [defaultValue] when not present. */
+inline fun <T> SparseArrayCompat<T>.getOrDefault(key: Int, defaultValue: T) =
+    get(key) ?: defaultValue
+
+/** Return the value corresponding to [key], or from [defaultValue] when not present. */
+inline fun <T> SparseArrayCompat<T>.getOrElse(key: Int, defaultValue: () -> T) =
+    get(key) ?: defaultValue()
+
+/** Return true when the collection contains elements. */
+inline fun <T> SparseArrayCompat<T>.isNotEmpty() = size() != 0
+
+/** Removes the entry for [key] only if it is mapped to [value]. */
+fun <T> SparseArrayCompat<T>.remove(key: Int, value: T): Boolean {
+    val index = indexOfKey(key)
+    if (index != -1 && value == valueAt(index)) {
+        removeAt(index)
+        return true
+    }
+    return false
+}
+
+/** Update this collection by adding or replacing entries from [other]. */
+fun <T> SparseArrayCompat<T>.putAll(other: SparseArrayCompat<T>) = other.forEach(::put)
+
+/** Performs the given [action] for each key/value entry. */
+inline fun <T> SparseArrayCompat<T>.forEach(action: (key: Int, value: T) -> Unit) {
+    for (index in 0 until size()) {
+        action(keyAt(index), valueAt(index))
+    }
+}
+
+/** Return an iterator over the collection's keys. */
+fun <T> SparseArrayCompat<T>.keyIterator(): IntIterator = object : IntIterator() {
+    var index = 0
+    override fun hasNext() = index < size()
+    override fun nextInt() = keyAt(index++)
+}
+
+/** Return an iterator over the collection's values. */
+fun <T> SparseArrayCompat<T>.valueIterator(): Iterator<T> = object : Iterator<T> {
+    var index = 0
+    override fun hasNext() = index < size()
+    override fun next() = valueAt(index++)
+}
diff --git a/collection-ktx/src/test/java/androidx/collection/ArrayMapTest.kt b/collection-ktx/src/test/java/androidx/collection/ArrayMapTest.kt
new file mode 100644
index 0000000..8c002dd
--- /dev/null
+++ b/collection-ktx/src/test/java/androidx/collection/ArrayMapTest.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class ArrayMapTest {
+    @Test fun empty() {
+        val map = arrayMapOf<String, String>()
+        assertEquals(0, map.size)
+    }
+
+    @Test fun nonEmpty() {
+        val map = arrayMapOf("foo" to "bar", "bar" to "baz")
+        assertThat(map).containsExactly("foo", "bar", "bar", "baz")
+    }
+
+    @Test fun duplicateKeyKeepsLast() {
+        val map = arrayMapOf("foo" to "bar", "foo" to "baz")
+        assertThat(map).containsExactly("foo", "baz")
+    }
+}
diff --git a/collection-ktx/src/test/java/androidx/collection/ArraySetTest.kt b/collection-ktx/src/test/java/androidx/collection/ArraySetTest.kt
new file mode 100644
index 0000000..71a561d
--- /dev/null
+++ b/collection-ktx/src/test/java/androidx/collection/ArraySetTest.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class ArraySetTest {
+    @Test fun empty() {
+        val set = arraySetOf<String>()
+        assertEquals(0, set.size)
+    }
+
+    @Test fun nonEmpty() {
+        val set = arraySetOf("foo", "bar", "baz")
+        assertThat(set).containsExactly("foo", "bar", "baz")
+    }
+}
diff --git a/collection-ktx/src/test/java/androidx/collection/LongSparseArrayTest.kt b/collection-ktx/src/test/java/androidx/collection/LongSparseArrayTest.kt
new file mode 100644
index 0000000..3419664
--- /dev/null
+++ b/collection-ktx/src/test/java/androidx/collection/LongSparseArrayTest.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import androidx.testutils.fail
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertSame
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class LongSparseArrayTest {
+    @Test fun sizeProperty() {
+        val array = LongSparseArray<String>()
+        assertEquals(0, array.size)
+        array.put(1L, "one")
+        assertEquals(1, array.size)
+    }
+
+    @Test fun containsOperator() {
+        val array = LongSparseArray<String>()
+        assertFalse(1L in array)
+        array.put(1L, "one")
+        assertTrue(1L in array)
+    }
+
+    @Test fun containsOperatorWithValue() {
+        val array = LongSparseArray<String>()
+
+        array.put(1L, "one")
+        assertFalse(2L in array)
+
+        array.put(2L, "two")
+        assertTrue(2L in array)
+    }
+
+    @Test fun setOperator() {
+        val array = LongSparseArray<String>()
+        array[1L] = "one"
+        assertEquals("one", array.get(1L))
+    }
+
+    @Test fun plusOperator() {
+        val first = LongSparseArray<String>().apply { put(1L, "one") }
+        val second = LongSparseArray<String>().apply { put(2L, "two") }
+        val combined = first + second
+        assertEquals(2, combined.size())
+        assertEquals(1L, combined.keyAt(0))
+        assertEquals("one", combined.valueAt(0))
+        assertEquals(2L, combined.keyAt(1))
+        assertEquals("two", combined.valueAt(1))
+    }
+
+    @Test fun containsKey() {
+        val array = LongSparseArray<String>()
+        assertFalse(array.containsKey(1L))
+        array.put(1L, "one")
+        assertTrue(array.containsKey(1L))
+    }
+
+    @Test fun containsKeyWithValue() {
+        val array = LongSparseArray<String>()
+
+        array.put(1L, "one")
+        assertFalse(array.containsKey(2L))
+
+        array.put(2L, "one")
+        assertTrue(array.containsKey(2L))
+    }
+
+    @Test fun containsValue() {
+        val array = LongSparseArray<String>()
+        assertFalse(array.containsValue("one"))
+        array.put(1L, "one")
+        assertTrue(array.containsValue("one"))
+    }
+
+    @Test fun getOrDefault() {
+        val array = LongSparseArray<Any>()
+        val default = Any()
+        assertSame(default, array.getOrDefault(1L, default))
+        array.put(1L, "one")
+        assertEquals("one", array.getOrDefault(1L, default))
+    }
+
+    @Test fun getOrElse() {
+        val array = LongSparseArray<Any>()
+        val default = Any()
+        assertSame(default, array.getOrElse(1L) { default })
+        array.put(1L, "one")
+        assertEquals("one", array.getOrElse(1L) { fail() })
+    }
+
+    @Test fun isNotEmpty() {
+        val array = LongSparseArray<String>()
+        assertFalse(array.isNotEmpty())
+        array.put(1L, "one")
+        assertTrue(array.isNotEmpty())
+    }
+
+    @Test fun removeValue() {
+        val array = LongSparseArray<String>()
+        array.put(1L, "one")
+        assertFalse(array.remove(0L, "one"))
+        assertEquals(1, array.size())
+        assertFalse(array.remove(1L, "two"))
+        assertEquals(1, array.size())
+        assertTrue(array.remove(1L, "one"))
+        assertEquals(0, array.size())
+    }
+
+    @Test fun putAll() {
+        val dest = LongSparseArray<String>()
+        val source = LongSparseArray<String>()
+        source.put(1L, "one")
+
+        assertEquals(0, dest.size())
+        dest.putAll(source)
+        assertEquals(1, dest.size())
+    }
+
+    @Test fun forEach() {
+        val array = LongSparseArray<String>()
+        array.forEach { _, _ -> fail() }
+
+        array.put(1L, "one")
+        array.put(2L, "two")
+        array.put(6L, "six")
+
+        val keys = mutableListOf<Long>()
+        val values = mutableListOf<String>()
+        array.forEach { key, value ->
+            keys.add(key)
+            values.add(value)
+        }
+        assertThat(keys).containsExactly(1L, 2L, 6L)
+        assertThat(values).containsExactly("one", "two", "six")
+    }
+
+    @Test fun keyIterator() {
+        val array = LongSparseArray<String>()
+        assertFalse(array.keyIterator().hasNext())
+
+        array.put(1L, "one")
+        array.put(2L, "two")
+        array.put(6L, "six")
+
+        val iterator = array.keyIterator()
+        assertTrue(iterator.hasNext())
+        assertEquals(1L, iterator.nextLong())
+        assertTrue(iterator.hasNext())
+        assertEquals(2L, iterator.nextLong())
+        assertTrue(iterator.hasNext())
+        assertEquals(6L, iterator.nextLong())
+        assertFalse(iterator.hasNext())
+    }
+
+    @Test fun valueIterator() {
+        val array = LongSparseArray<String>()
+        assertFalse(array.valueIterator().hasNext())
+
+        array.put(1L, "one")
+        array.put(2L, "two")
+        array.put(6L, "six")
+
+        val iterator = array.valueIterator()
+        assertTrue(iterator.hasNext())
+        assertEquals("one", iterator.next())
+        assertTrue(iterator.hasNext())
+        assertEquals("two", iterator.next())
+        assertTrue(iterator.hasNext())
+        assertEquals("six", iterator.next())
+        assertFalse(iterator.hasNext())
+    }
+}
diff --git a/collection-ktx/src/test/java/androidx/collection/LruCacheTest.kt b/collection-ktx/src/test/java/androidx/collection/LruCacheTest.kt
new file mode 100644
index 0000000..c47b423
--- /dev/null
+++ b/collection-ktx/src/test/java/androidx/collection/LruCacheTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class LruCacheTest {
+    private data class TestData(val x: String = "bla")
+
+    @Test fun size() {
+        val cache = lruCache<String, TestData>(200, { k, (x) -> k.length * x.length })
+        cache.put("long", TestData())
+        assertEquals(cache.size(), 12)
+    }
+
+    @Test fun create() {
+        val cache = lruCache<String, TestData>(200, create = { key -> TestData("$key foo") })
+        assertEquals(cache.get("kung"), TestData("kung foo"))
+    }
+
+    @Test fun onEntryRemoved() {
+        var wasCalled = false
+
+        val cache = lruCache<String, TestData>(200, onEntryRemoved = { _, _, _, _ ->
+            wasCalled = true
+        })
+        val initial = TestData()
+        cache.put("a", initial)
+        cache.remove("a")
+        assertTrue(wasCalled)
+    }
+}
diff --git a/collection-ktx/src/test/java/androidx/collection/SparseArrayTest.kt b/collection-ktx/src/test/java/androidx/collection/SparseArrayTest.kt
new file mode 100644
index 0000000..90d46c8
--- /dev/null
+++ b/collection-ktx/src/test/java/androidx/collection/SparseArrayTest.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.collection
+
+import androidx.testutils.fail
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertSame
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class SparseArrayCompatTest {
+    @Test fun sizeProperty() {
+        val array = SparseArrayCompat<String>()
+        assertEquals(0, array.size)
+        array.put(1, "one")
+        assertEquals(1, array.size)
+    }
+
+    @Test fun containsOperator() {
+        val array = SparseArrayCompat<String>()
+        assertFalse(1 in array)
+        array.put(1, "one")
+        assertTrue(1 in array)
+    }
+
+    @Test fun containsOperatorWithItem() {
+        val array = SparseArrayCompat<String>()
+
+        array.put(1, "one")
+        assertFalse(2 in array)
+
+        array.put(2, "two")
+        assertTrue(2 in array)
+    }
+
+    @Test fun setOperator() {
+        val array = SparseArrayCompat<String>()
+        array[1] = "one"
+        assertEquals("one", array.get(1))
+    }
+
+    @Test fun plusOperator() {
+        val first = SparseArrayCompat<String>().apply { put(1, "one") }
+        val second = SparseArrayCompat<String>().apply { put(2, "two") }
+        val combined = first + second
+        assertEquals(2, combined.size())
+        assertEquals(1, combined.keyAt(0))
+        assertEquals("one", combined.valueAt(0))
+        assertEquals(2, combined.keyAt(1))
+        assertEquals("two", combined.valueAt(1))
+    }
+
+    @Test fun containsKey() {
+        val array = SparseArrayCompat<String>()
+        assertFalse(array.containsKey(1))
+        array.put(1, "one")
+        assertTrue(array.containsKey(1))
+    }
+
+    @Test fun containsValue() {
+        val array = SparseArrayCompat<String>()
+        assertFalse(array.containsValue("one"))
+        array.put(1, "one")
+        assertTrue(array.containsValue("one"))
+    }
+
+    @Test fun getOrDefault() {
+        val array = SparseArrayCompat<Any>()
+        val default = Any()
+        assertSame(default, array.getOrDefault(1, default))
+        array.put(1, "one")
+        assertEquals("one", array.getOrDefault(1, default))
+    }
+
+    @Test fun getOrElse() {
+        val array = SparseArrayCompat<Any>()
+        val default = Any()
+        assertSame(default, array.getOrElse(1) { default })
+        array.put(1, "one")
+        assertEquals("one", array.getOrElse(1) { fail() })
+    }
+
+    @Test fun isNotEmpty() {
+        val array = SparseArrayCompat<String>()
+        assertFalse(array.isNotEmpty())
+        array.put(1, "one")
+        assertTrue(array.isNotEmpty())
+    }
+
+    @Test fun removeValue() {
+        val array = SparseArrayCompat<String>()
+        array.put(1, "one")
+        assertFalse(array.remove(0, "one"))
+        assertEquals(1, array.size())
+        assertFalse(array.remove(1, "two"))
+        assertEquals(1, array.size())
+        assertTrue(array.remove(1, "one"))
+        assertEquals(0, array.size())
+    }
+
+    @Test fun putAll() {
+        val dest = SparseArrayCompat<String>()
+        val source = SparseArrayCompat<String>()
+        source.put(1, "one")
+
+        assertEquals(0, dest.size())
+        dest.putAll(source)
+        assertEquals(1, dest.size())
+    }
+
+    @Test fun forEach() {
+        val array = SparseArrayCompat<String>()
+        array.forEach { _, _ -> fail() }
+
+        array.put(1, "one")
+        array.put(2, "two")
+        array.put(6, "six")
+
+        val keys = mutableListOf<Int>()
+        val values = mutableListOf<String>()
+        array.forEach { key, value ->
+            keys.add(key)
+            values.add(value)
+        }
+        assertThat(keys).containsExactly(1, 2, 6)
+        assertThat(values).containsExactly("one", "two", "six")
+    }
+
+    @Test fun keyIterator() {
+        val array = SparseArrayCompat<String>()
+        assertFalse(array.keyIterator().hasNext())
+
+        array.put(1, "one")
+        array.put(2, "two")
+        array.put(6, "six")
+
+        val iterator = array.keyIterator()
+        assertTrue(iterator.hasNext())
+        assertEquals(1, iterator.nextInt())
+        assertTrue(iterator.hasNext())
+        assertEquals(2, iterator.nextInt())
+        assertTrue(iterator.hasNext())
+        assertEquals(6, iterator.nextInt())
+        assertFalse(iterator.hasNext())
+    }
+
+    @Test fun valueIterator() {
+        val array = SparseArrayCompat<String>()
+        assertFalse(array.valueIterator().hasNext())
+
+        array.put(1, "one")
+        array.put(2, "two")
+        array.put(6, "six")
+
+        val iterator = array.valueIterator()
+        assertTrue(iterator.hasNext())
+        assertEquals("one", iterator.next())
+        assertTrue(iterator.hasNext())
+        assertEquals("two", iterator.next())
+        assertTrue(iterator.hasNext())
+        assertEquals("six", iterator.next())
+        assertFalse(iterator.hasNext())
+    }
+}
diff --git a/palette-ktx/OWNERS b/palette-ktx/OWNERS
new file mode 100644
index 0000000..e450f4c
--- /dev/null
+++ b/palette-ktx/OWNERS
@@ -0,0 +1 @@
+jakew@google.com
diff --git a/palette-ktx/build.gradle b/palette-ktx/build.gradle
new file mode 100644
index 0000000..f5946aa
--- /dev/null
+++ b/palette-ktx/build.gradle
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+
+plugins {
+    id("SupportAndroidLibraryPlugin")
+    id("org.jetbrains.kotlin.android")
+}
+
+android {
+    buildTypes {
+        debug {
+            testCoverageEnabled = false // Breaks Kotlin compiler.
+        }
+    }
+}
+
+dependencies {
+    api(project(":palette"))
+    api(KOTLIN_STDLIB)
+    androidTestImplementation(JUNIT)
+    androidTestImplementation(TEST_RUNNER_TMP, libs.exclude_for_espresso)
+}
+
+supportLibrary {
+    name = "Palette Kotlin Extensions"
+    publish = true
+    mavenVersion = LibraryVersions.SUPPORT_LIBRARY
+    mavenGroup = LibraryGroups.FRAGMENT
+    inceptionYear = "2018"
+    description = "Kotlin extensions for 'palette' artifact"
+}
diff --git a/palette-ktx/src/androidTest/java/androidx/palette/graphics/PaletteTest.kt b/palette-ktx/src/androidTest/java/androidx/palette/graphics/PaletteTest.kt
new file mode 100644
index 0000000..c108493
--- /dev/null
+++ b/palette-ktx/src/androidTest/java/androidx/palette/graphics/PaletteTest.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.palette.graphics
+
+import android.graphics.Bitmap
+import android.graphics.Bitmap.Config.ARGB_8888
+import android.graphics.Canvas
+import android.graphics.Color.RED
+import androidx.palette.graphics.Target.VIBRANT
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertSame
+import org.junit.Test
+
+class PaletteTest {
+    @Test fun bitmapBuild() {
+        val bitmap = Bitmap.createBitmap(10, 10, ARGB_8888)
+        // There's no easy way to test that the palette was created from our Bitmap.
+        assertNotNull(bitmap.buildPalette())
+    }
+
+    @Test fun operatorGet() {
+        val bitmap = Bitmap.createBitmap(10, 10, ARGB_8888).apply {
+            Canvas(this).drawColor(RED)
+        }
+        val palette = Palette.from(bitmap).generate()
+        assertSame(palette.getSwatchForTarget(VIBRANT), palette[VIBRANT])
+    }
+}
diff --git a/palette-ktx/src/main/AndroidManifest.xml b/palette-ktx/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f25fda5
--- /dev/null
+++ b/palette-ktx/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2018 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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 package="androidx.palette.ktx"/>
diff --git a/palette-ktx/src/main/java/androidx/palette/graphics/Palette.kt b/palette-ktx/src/main/java/androidx/palette/graphics/Palette.kt
new file mode 100644
index 0000000..58da09a
--- /dev/null
+++ b/palette-ktx/src/main/java/androidx/palette/graphics/Palette.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("NOTHING_TO_INLINE") // Aliases to public API.
+
+package androidx.palette.graphics
+
+import android.graphics.Bitmap
+
+/**
+ * Create a [Palette.Builder] from this bitmap.
+ *
+ * @see Palette.from
+ */
+inline fun Bitmap.buildPalette() = Palette.Builder(this)
+
+/**
+ * Returns the selected swatch for the given target from the palette, or `null` if one
+ * could not be found.
+ *
+ * @see Palette.getSwatchForTarget
+ */
+inline operator fun Palette.get(target: Target): Palette.Swatch? = getSwatchForTarget(target)
diff --git a/settings.gradle b/settings.gradle
index c402bf3..2642a19 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -39,6 +39,7 @@
 includeProject(":car", "car")
 includeProject(":cardview", "cardview")
 includeProject(":collection", "collection")
+includeProject(":collection-ktx", "collection-ktx")
 includeProject(":coordinatorlayout", "coordinatorlayout")
 includeProject(":cursoradapter", "cursoradapter")
 includeProject(":browser", "browser")
@@ -58,6 +59,7 @@
 includeProject(":localbroadcastmanager", "localbroadcastmanager")
 includeProject(":mediarouter", "mediarouter")
 includeProject(":palette", "palette")
+includeProject(":palette-ktx", "palette-ktx")
 includeProject(":percentlayout", "percent")
 includeProject(":preference", "preference")
 includeProject(":leanback-preference", "leanback-preference")
@@ -135,6 +137,7 @@
 /////////////////////////////
 
 includeProject(":internal-testutils", "testutils")
+includeProject(":internal-testutils-ktx", "testutils-ktx")
 
 /////////////////////////////
 //
diff --git a/slices/view/src/androidTest/java/androidx/slice/SliceManagerTest.java b/slices/view/src/androidTest/java/androidx/slice/SliceManagerTest.java
index 52e6ef6..2e5ed54 100644
--- a/slices/view/src/androidTest/java/androidx/slice/SliceManagerTest.java
+++ b/slices/view/src/androidTest/java/androidx/slice/SliceManagerTest.java
@@ -42,6 +42,7 @@
 
 import java.util.concurrent.Executor;
 
+import androidx.slice.render.SliceRenderActivity;
 import androidx.slice.widget.SliceLiveData;
 
 @RunWith(AndroidJUnit4.class)
@@ -124,14 +125,14 @@
         assertEquals(SliceLiveData.SUPPORTED_SPECS, mManager.getPinnedSpecs(uri));
     }
 
-    //@Test
-    //public void testMapIntentToUri() {
-    //    Uri expected = Uri.parse("content://androidx.slice.view.test/render");
-    //    Slice s = new Slice.Builder(expected).build();
-    //    when(mSliceProvider.onBindSlice(eq(expected))).thenReturn(s);
-    //    Uri uri = mManager.mapIntentToUri(new Intent(mContext, SliceRenderActivity.class));
-    //    assertEquals(expected, uri);
-    //}
+    @Test
+    public void testMapIntentToUri() {
+        Uri expected = Uri.parse("content://androidx.slice.view.test/render");
+        Slice s = new Slice.Builder(expected).build();
+        when(mSliceProvider.onBindSlice(eq(expected))).thenReturn(s);
+        Uri uri = mManager.mapIntentToUri(new Intent(mContext, SliceRenderActivity.class));
+        assertEquals(expected, uri);
+    }
 
     public static class TestSliceProvider extends SliceProvider {
 
diff --git a/slices/view/src/main/java/androidx/slice/SliceManagerWrapper.java b/slices/view/src/main/java/androidx/slice/SliceManagerWrapper.java
index 6b857f4..1169547 100644
--- a/slices/view/src/main/java/androidx/slice/SliceManagerWrapper.java
+++ b/slices/view/src/main/java/androidx/slice/SliceManagerWrapper.java
@@ -19,7 +19,6 @@
 import static androidx.slice.SliceConvert.unwrap;
 import static androidx.slice.widget.SliceLiveData.SUPPORTED_SPECS;
 
-import android.app.slice.SliceManager;
 import android.app.slice.SliceSpec;
 import android.content.Context;
 import android.content.Intent;
@@ -40,12 +39,6 @@
 
     private final android.app.slice.SliceManager mManager;
     private final List<SliceSpec> mSpecs;
-    private final SliceManager.SliceCallback mCallback = new SliceManager.SliceCallback() {
-        @Override
-        public void onSliceUpdated(@NonNull android.app.slice.Slice s) {
-
-        }
-    };
 
     SliceManagerWrapper(Context context) {
         this(context, context.getSystemService(android.app.slice.SliceManager.class));
@@ -59,12 +52,12 @@
 
     @Override
     public void pinSlice(@NonNull Uri uri) {
-        mManager.registerSliceCallback(uri, mSpecs, mCallback);
+        mManager.pinSlice(uri, mSpecs);
     }
 
     @Override
     public void unpinSlice(@NonNull Uri uri) {
-        mManager.unregisterSliceCallback(uri, mCallback);
+        mManager.unpinSlice(uri);
     }
 
     @Override
diff --git a/testutils-ktx/NO_DOCS b/testutils-ktx/NO_DOCS
new file mode 100644
index 0000000..4dad694
--- /dev/null
+++ b/testutils-ktx/NO_DOCS
@@ -0,0 +1,17 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+Having this file, named NO_DOCS, in a directory will prevent
+Android javadocs from being generated for java files under
+the directory. This is especially useful for test projects.
diff --git a/testutils-ktx/OWNERS b/testutils-ktx/OWNERS
new file mode 100644
index 0000000..e450f4c
--- /dev/null
+++ b/testutils-ktx/OWNERS
@@ -0,0 +1 @@
+jakew@google.com
diff --git a/testutils-ktx/build.gradle b/testutils-ktx/build.gradle
new file mode 100644
index 0000000..5f33da1
--- /dev/null
+++ b/testutils-ktx/build.gradle
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+    id("SupportKotlinLibraryPlugin")
+}
+
+dependencies {
+    compile(KOTLIN_STDLIB)
+    compile(TRUTH)
+}
diff --git a/testutils-ktx/src/main/java/androidx/testutils/assertions.kt b/testutils-ktx/src/main/java/androidx/testutils/assertions.kt
new file mode 100644
index 0000000..b67948a
--- /dev/null
+++ b/testutils-ktx/src/main/java/androidx/testutils/assertions.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.testutils
+
+import com.google.common.truth.ThrowableSubject
+import com.google.common.truth.Truth.assertThat
+
+inline fun <reified T : Throwable> assertThrows(body: () -> Unit): ThrowableSubject {
+    try {
+        body()
+    } catch (e: Throwable) {
+        if (e is T) {
+            return assertThat(e)
+        }
+        throw e
+    }
+    throw AssertionError("Body completed successfully. Expected ${T::class.java.simpleName}.")
+}
+
+fun fail(message: String? = null): Nothing = throw AssertionError(message)
diff --git a/webkit/api/current.txt b/webkit/api/current.txt
new file mode 100644
index 0000000..2a39d42
--- /dev/null
+++ b/webkit/api/current.txt
@@ -0,0 +1,24 @@
+package androidx.webkit {
+
+  public class WebSettingsCompat {
+    method public static int getDisabledActionModeMenuItems(android.webkit.WebSettings);
+    method public static boolean getOffscreenPreRaster(android.webkit.WebSettings);
+    method public static boolean getSafeBrowsingEnabled(android.webkit.WebSettings);
+    method public static void setDisabledActionModeMenuItems(android.webkit.WebSettings, int);
+    method public static void setOffscreenPreRaster(android.webkit.WebSettings, boolean);
+    method public static void setSafeBrowsingEnabled(android.webkit.WebSettings, boolean);
+  }
+
+  public class WebViewCompat {
+    method public static android.net.Uri getSafeBrowsingPrivacyPolicyUrl();
+    method public static void postVisualStateCallback(android.webkit.WebView, long, androidx.webkit.WebViewCompat.VisualStateCallback);
+    method public static void setSafeBrowsingWhitelist(java.util.List<java.lang.String>, android.webkit.ValueCallback<java.lang.Boolean>);
+    method public static void startSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean>);
+  }
+
+  public static abstract interface WebViewCompat.VisualStateCallback {
+    method public abstract void onComplete(long);
+  }
+
+}
+
diff --git a/webkit/build.gradle b/webkit/build.gradle
index 06f3801..9b2b5d3 100644
--- a/webkit/build.gradle
+++ b/webkit/build.gradle
@@ -38,11 +38,15 @@
         // Allow compiling the WebView support library boundary interfaces from this project.
         main.java.srcDirs += new File(webviewBoundaryInterfacesDir, "src").getAbsolutePath()
     }
+
+    buildTypes.all {
+        consumerProguardFiles new File(webviewBoundaryInterfacesDir, "proguard.flags")
+    }
 }
 
 supportLibrary {
     name = "WebView Support Library"
-    publish = false
+    publish = true
     mavenVersion = LibraryVersions.SUPPORT_LIBRARY
     mavenGroup = LibraryGroups.WEBKIT
     inceptionYear = "2017"
diff --git a/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java b/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
index c73cda6..12d7e63 100644
--- a/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
+++ b/webkit/src/main/java/androidx/webkit/WebSettingsCompat.java
@@ -19,6 +19,13 @@
 import android.os.Build;
 import android.webkit.WebSettings;
 
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.RestrictTo;
 import androidx.webkit.internal.WebSettingsAdapter;
 import androidx.webkit.internal.WebViewGlueCommunicator;
 
@@ -102,10 +109,25 @@
     }
 
     /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @IntDef(flag = true, value = {
+            WebSettings.MENU_ITEM_NONE,
+            WebSettings.MENU_ITEM_SHARE,
+            WebSettings.MENU_ITEM_WEB_SEARCH,
+            WebSettings.MENU_ITEM_PROCESS_TEXT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ElementType.PARAMETER, ElementType.METHOD})
+    public @interface MenuItemFlags {}
+
+    /**
      * Disables the action mode menu items according to {@code menuItems} flag.
      * @param menuItems an integer field flag for the menu items to be disabled.
      */
-    public static void setDisabledActionModeMenuItems(WebSettings webSettings, int menuItems) {
+    public static void setDisabledActionModeMenuItems(WebSettings webSettings,
+            @MenuItemFlags int menuItems) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
             webSettings.setDisabledActionModeMenuItems(menuItems);
         } else {
@@ -119,7 +141,7 @@
      *
      * @return all the disabled menu item flags combined with bitwise OR.
      */
-    public static int getDisabledActionModeMenuItems(WebSettings webSettings) {
+    public static @MenuItemFlags int getDisabledActionModeMenuItems(WebSettings webSettings) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
             return webSettings.getDisabledActionModeMenuItems();
         } else {