Added new package for Wearable Input helpers.

This CL adds in WearableButtons from the Wearable Support Library to a
new AndroidX artifact, androidx.wear:wear-input. This also adds in
androidx.wear:wear-input-testing, which includes the test
implementations of WearableButtonsProvider to ease testing.

Bug: 156717151
Test: Tests migrated from WSL and updated to not require Robolectric
shadows

Change-Id: I0ed0ce2a891e982afea3517e3309153751ae4ac4
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
index 8c3a5ec..24180c7 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt
@@ -102,7 +102,7 @@
     val VERSIONEDPARCELABLE = LibraryGroup("androidx.versionedparcelable", null)
     val VIEWPAGER = LibraryGroup("androidx.viewpager", LibraryVersions.VIEWPAGER)
     val VIEWPAGER2 = LibraryGroup("androidx.viewpager2", LibraryVersions.VIEWPAGER2)
-    val WEAR = LibraryGroup("androidx.wear", LibraryVersions.WEAR)
+    val WEAR = LibraryGroup("androidx.wear", null)
     val WEBKIT = LibraryGroup("androidx.webkit", LibraryVersions.WEBKIT)
     val WINDOW = LibraryGroup("androidx.window", null)
     val WORK = LibraryGroup("androidx.work", LibraryVersions.WORK)
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index 0918394..116e2c0 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -117,6 +117,7 @@
     val VIEWPAGER = Version("1.1.0-alpha01")
     val VIEWPAGER2 = Version("1.1.0-alpha02")
     val WEAR = Version("1.2.0-alpha01")
+    val WEAR_INPUT = Version("1.0.0-alpha01")
     val WEBKIT = Version("1.3.0-rc01")
     val WINDOW = Version("1.0.0-alpha02")
     val WINDOW_SIDECAR = Version("0.1.0-alpha01")
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index 7659c54..fd7c65f 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -143,8 +143,10 @@
     prebuilts(LibraryGroups.VERSIONEDPARCELABLE, "versionedparcelable", "1.1.1")
     prebuilts(LibraryGroups.VIEWPAGER, "1.0.0")
     prebuilts(LibraryGroups.VIEWPAGER2, "1.1.0-alpha01")
-    prebuilts(LibraryGroups.WEAR, "1.1.0-rc01")
+    prebuilts(LibraryGroups.WEAR, "wear", "1.1.0-rc01")
             .addStubs("wear/wear_stubs/com.google.android.wearable-stubs.jar")
+    ignore(LibraryGroups.WEAR.group, "wear-input")
+    ignore(LibraryGroups.WEAR.group, "wear-input-testing")
     prebuilts(LibraryGroups.WEBKIT, "1.3.0-alpha03")
     ignore(LibraryGroups.WINDOW.group, "window-sidecar")
     prebuilts(LibraryGroups.WINDOW, "1.0.0-alpha01")
diff --git a/jetifier/jetifier/migration.config b/jetifier/jetifier/migration.config
index 75b29a9..f3371e7 100644
--- a/jetifier/jetifier/migration.config
+++ b/jetifier/jetifier/migration.config
@@ -493,6 +493,14 @@
       "to": "ignore"
     },
     {
+      "from": "androidx/wear/input/(.*)",
+      "to": "ignore"
+    },
+    {
+      "from": "androidx/wear/input/testing/(.*)",
+      "to": "ignore"
+    },
+    {
       "from": "androidx/benchmark/(.*)",
       "to": "ignore"
     },
@@ -1107,6 +1115,14 @@
       "to": "androidx/wear"
     },
     {
+      "from": "androidx/wear/input",
+      "to": "androidx/wear/input"
+    },
+    {
+      "from": "androidx/wear/input/testing",
+      "to": "androidx/wear/input/testing"
+    },
+    {
       "from": "androidx/emoji/appcompat",
       "to": "androidx/emoji/appcompat"
     },
@@ -2066,6 +2082,30 @@
     },
     {
       "from": {
+        "groupId": "androidx.wear",
+        "artifactId": "wear-input",
+        "version": "{newSlVersion}"
+      },
+      "to": {
+        "groupId": "androidx.wear",
+        "artifactId": "wear-input",
+        "version": "{newSlVersion}"
+      }
+    },
+    {
+      "from": {
+        "groupId": "androidx.wear",
+        "artifactId": "wear-input-testing",
+        "version": "{newSlVersion}"
+      },
+      "to": {
+        "groupId": "androidx.wear",
+        "artifactId": "wear-input-testing",
+        "version": "{newSlVersion}"
+      }
+    },
+    {
+      "from": {
         "groupId": "androidx.asynclayoutinflater",
         "artifactId": "asynclayoutinflater",
         "version": "{newSlVersion}"
diff --git a/settings.gradle b/settings.gradle
index 3743d2b..0baccbf 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -320,6 +320,8 @@
 includeProject(":viewpager2:viewpager2", "viewpager2/viewpager2")
 includeProject(":viewpager2:integration-tests:testapp", "viewpager2/integration-tests/testapp")
 includeProject(":wear:wear", "wear/wear")
+includeProject(":wear:wear-input", "wear/wear-input")
+includeProject(":wear:wear-input-testing", "wear/wear-input-testing")
 includeProject(":webkit:webkit", "webkit/webkit")
 includeProject(":webkit:integration-tests:testapp", "webkit/integration-tests/testapp")
 includeProject(":window:window", "window/window")
diff --git a/wear/wear-input-testing/api/1.0.0-alpha01.txt b/wear/wear-input-testing/api/1.0.0-alpha01.txt
new file mode 100644
index 0000000..847f64c
--- /dev/null
+++ b/wear/wear-input-testing/api/1.0.0-alpha01.txt
@@ -0,0 +1,18 @@
+// Signature format: 3.0
+package androidx.wear.input.testing {
+
+  public class TestWearableButtonsProvider implements androidx.wear.input.WearableButtonsProvider {
+    ctor public TestWearableButtonsProvider(java.util.Map<java.lang.Integer!,androidx.wear.input.testing.TestWearableButtonsProvider.TestWearableButtonLocation!>);
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+  public static class TestWearableButtonsProvider.TestWearableButtonLocation {
+    ctor public TestWearableButtonsProvider.TestWearableButtonLocation(float, float);
+    ctor public TestWearableButtonsProvider.TestWearableButtonLocation(float, float, float, float);
+    method public android.graphics.PointF getLocation();
+    method public android.graphics.PointF? getRotatedLocation();
+  }
+
+}
+
diff --git a/wear/wear-input-testing/api/current.txt b/wear/wear-input-testing/api/current.txt
new file mode 100644
index 0000000..847f64c
--- /dev/null
+++ b/wear/wear-input-testing/api/current.txt
@@ -0,0 +1,18 @@
+// Signature format: 3.0
+package androidx.wear.input.testing {
+
+  public class TestWearableButtonsProvider implements androidx.wear.input.WearableButtonsProvider {
+    ctor public TestWearableButtonsProvider(java.util.Map<java.lang.Integer!,androidx.wear.input.testing.TestWearableButtonsProvider.TestWearableButtonLocation!>);
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+  public static class TestWearableButtonsProvider.TestWearableButtonLocation {
+    ctor public TestWearableButtonsProvider.TestWearableButtonLocation(float, float);
+    ctor public TestWearableButtonsProvider.TestWearableButtonLocation(float, float, float, float);
+    method public android.graphics.PointF getLocation();
+    method public android.graphics.PointF? getRotatedLocation();
+  }
+
+}
+
diff --git a/wear/wear-input-testing/api/public_plus_experimental_1.0.0-alpha01.txt b/wear/wear-input-testing/api/public_plus_experimental_1.0.0-alpha01.txt
new file mode 100644
index 0000000..847f64c
--- /dev/null
+++ b/wear/wear-input-testing/api/public_plus_experimental_1.0.0-alpha01.txt
@@ -0,0 +1,18 @@
+// Signature format: 3.0
+package androidx.wear.input.testing {
+
+  public class TestWearableButtonsProvider implements androidx.wear.input.WearableButtonsProvider {
+    ctor public TestWearableButtonsProvider(java.util.Map<java.lang.Integer!,androidx.wear.input.testing.TestWearableButtonsProvider.TestWearableButtonLocation!>);
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+  public static class TestWearableButtonsProvider.TestWearableButtonLocation {
+    ctor public TestWearableButtonsProvider.TestWearableButtonLocation(float, float);
+    ctor public TestWearableButtonsProvider.TestWearableButtonLocation(float, float, float, float);
+    method public android.graphics.PointF getLocation();
+    method public android.graphics.PointF? getRotatedLocation();
+  }
+
+}
+
diff --git a/wear/wear-input-testing/api/public_plus_experimental_current.txt b/wear/wear-input-testing/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..847f64c
--- /dev/null
+++ b/wear/wear-input-testing/api/public_plus_experimental_current.txt
@@ -0,0 +1,18 @@
+// Signature format: 3.0
+package androidx.wear.input.testing {
+
+  public class TestWearableButtonsProvider implements androidx.wear.input.WearableButtonsProvider {
+    ctor public TestWearableButtonsProvider(java.util.Map<java.lang.Integer!,androidx.wear.input.testing.TestWearableButtonsProvider.TestWearableButtonLocation!>);
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+  public static class TestWearableButtonsProvider.TestWearableButtonLocation {
+    ctor public TestWearableButtonsProvider.TestWearableButtonLocation(float, float);
+    ctor public TestWearableButtonsProvider.TestWearableButtonLocation(float, float, float, float);
+    method public android.graphics.PointF getLocation();
+    method public android.graphics.PointF? getRotatedLocation();
+  }
+
+}
+
diff --git a/wear/wear-input-testing/api/res-1.0.0-alpha01.txt b/wear/wear-input-testing/api/res-1.0.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/wear/wear-input-testing/api/res-1.0.0-alpha01.txt
diff --git a/wear/wear-input-testing/api/res-current.txt b/wear/wear-input-testing/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/wear/wear-input-testing/api/res-current.txt
diff --git a/wear/wear-input-testing/api/restricted_1.0.0-alpha01.txt b/wear/wear-input-testing/api/restricted_1.0.0-alpha01.txt
new file mode 100644
index 0000000..847f64c
--- /dev/null
+++ b/wear/wear-input-testing/api/restricted_1.0.0-alpha01.txt
@@ -0,0 +1,18 @@
+// Signature format: 3.0
+package androidx.wear.input.testing {
+
+  public class TestWearableButtonsProvider implements androidx.wear.input.WearableButtonsProvider {
+    ctor public TestWearableButtonsProvider(java.util.Map<java.lang.Integer!,androidx.wear.input.testing.TestWearableButtonsProvider.TestWearableButtonLocation!>);
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+  public static class TestWearableButtonsProvider.TestWearableButtonLocation {
+    ctor public TestWearableButtonsProvider.TestWearableButtonLocation(float, float);
+    ctor public TestWearableButtonsProvider.TestWearableButtonLocation(float, float, float, float);
+    method public android.graphics.PointF getLocation();
+    method public android.graphics.PointF? getRotatedLocation();
+  }
+
+}
+
diff --git a/wear/wear-input-testing/api/restricted_current.txt b/wear/wear-input-testing/api/restricted_current.txt
new file mode 100644
index 0000000..847f64c
--- /dev/null
+++ b/wear/wear-input-testing/api/restricted_current.txt
@@ -0,0 +1,18 @@
+// Signature format: 3.0
+package androidx.wear.input.testing {
+
+  public class TestWearableButtonsProvider implements androidx.wear.input.WearableButtonsProvider {
+    ctor public TestWearableButtonsProvider(java.util.Map<java.lang.Integer!,androidx.wear.input.testing.TestWearableButtonsProvider.TestWearableButtonLocation!>);
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+  public static class TestWearableButtonsProvider.TestWearableButtonLocation {
+    ctor public TestWearableButtonsProvider.TestWearableButtonLocation(float, float);
+    ctor public TestWearableButtonsProvider.TestWearableButtonLocation(float, float, float, float);
+    method public android.graphics.PointF getLocation();
+    method public android.graphics.PointF? getRotatedLocation();
+  }
+
+}
+
diff --git a/wear/wear-input-testing/build.gradle b/wear/wear-input-testing/build.gradle
new file mode 100644
index 0000000..9e70953
--- /dev/null
+++ b/wear/wear-input-testing/build.gradle
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.Publish
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+}
+
+dependencies {
+    api(project(":wear:wear-input"))
+    compileOnly fileTree(dir: '../wear_stubs', include: ['com.google.android.wearable-stubs.jar'])
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 23
+    }
+}
+
+androidx {
+    name = "Android Wear Support Input Testing Helpers"
+    publish = Publish.SNAPSHOT_AND_RELEASE
+    mavenGroup = LibraryGroups.WEAR
+    mavenVersion = LibraryVersions.WEAR_INPUT
+    inceptionYear = "2020"
+    description = "Android Wear Support Input  Testing Helpers"
+}
diff --git a/wear/wear-input-testing/src/main/AndroidManifest.xml b/wear/wear-input-testing/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..70ec2c9
--- /dev/null
+++ b/wear/wear-input-testing/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2020 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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.wear"/>
diff --git a/wear/wear-input-testing/src/main/java/androidx/wear/input/testing/TestWearableButtonsProvider.java b/wear/wear-input-testing/src/main/java/androidx/wear/input/testing/TestWearableButtonsProvider.java
new file mode 100644
index 0000000..b2ddfc5
--- /dev/null
+++ b/wear/wear-input-testing/src/main/java/androidx/wear/input/testing/TestWearableButtonsProvider.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.wear.input.testing;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.wear.input.WearableButtonsProvider;
+
+import com.google.android.wearable.input.WearableInputDevice;
+
+import java.util.Map;
+
+/**
+ * A {@link WearableButtonsProvider} suitable for use in tests.
+ *
+ * <p>This allows for explicitly specifying which buttons are available for testing, and their
+ * coordinates. It is intended to be used by passing in a map, mapping between the button keycode
+ * (typically in the set {@link android.view.KeyEvent#KEYCODE_STEM_PRIMARY}, {@link
+ * android.view.KeyEvent#KEYCODE_STEM_1}, {@link android.view.KeyEvent#KEYCODE_STEM_2}, or {@link
+ * android.view.KeyEvent#KEYCODE_STEM_3}) and the location of the button. Take the following
+ * example:
+ *
+ * <pre>
+ *     Map<Integer, TestWearableButtonLocation> buttons = new HashMap<>();
+ *     buttons.put(KEYCODE_STEM_1, new TestWearableButtonLocation(100, 100);
+ *
+ *     TestWearableButtonsProvider provider = new TestWearableButtonsProvider(buttons);
+ *
+ *     WearableButtons.setWearableButtonsProvider(provider);
+ * </pre>
+ */
+public class TestWearableButtonsProvider implements WearableButtonsProvider {
+
+    /**
+     * Class describing the location of a button on a wearable device. This has two forms; it can
+     * either store the absolute location of the button, or store both the absolute location of the
+     * button, and the absolute location when the screen is rotated through 180 degrees.
+     */
+    public static class TestWearableButtonLocation {
+        private final PointF mLocation;
+        private final PointF mRotatedLocation;
+
+        /**
+         * Build a button location, with just the default button location.
+         *
+         * @param x X coordinate of the button.
+         * @param y Y coordinate of the button.
+         */
+        public TestWearableButtonLocation(float x, float y) {
+            mLocation = new PointF(x, y);
+            mRotatedLocation = null;
+        }
+
+        /**
+         * Build a button location, with both the default button location, and the location when the
+         * device is rotated through 180 degrees.
+         *
+         * @param x X coordinate of the button.
+         * @param y Y coordinate of the button.
+         * @param rotatedX X coordinate of the button when the device is rotated.
+         * @param rotatedY Y coordinate of the button when the device is rotated.
+         */
+        public TestWearableButtonLocation(float x, float y, float rotatedX, float rotatedY) {
+            mLocation = new PointF(x, y);
+            mRotatedLocation = new PointF(rotatedX, rotatedY);
+        }
+
+        /**
+         * Get the location of this button.
+         *
+         * @return A point specifying the location of this button.
+         */
+        @NonNull
+        public PointF getLocation() {
+            return mLocation;
+        }
+
+        /**
+         * Get the location of this button when the device is rotated.
+         *
+         * @return A point specifying the location of this button when the device is rotated.
+         */
+        @Nullable
+        public PointF getRotatedLocation() {
+            return mRotatedLocation;
+        }
+    }
+
+    private final Map<Integer, TestWearableButtonLocation> mButtons;
+
+    /**
+     * Build a button provider, which will respond with the provided set of buttons.
+     *
+     * @param buttons The buttons returned by this provider.
+     */
+    public TestWearableButtonsProvider(@NonNull Map<Integer, TestWearableButtonLocation> buttons) {
+        mButtons = buttons;
+    }
+
+    @NonNull
+    @Override
+    public Bundle getButtonInfo(@NonNull Context context, int keycode) {
+        Bundle bundle = new Bundle();
+
+        TestWearableButtonLocation location = mButtons.get(keycode);
+        if (location != null) {
+            bundle.putFloat(WearableInputDevice.X_KEY, location.getLocation().x);
+            bundle.putFloat(WearableInputDevice.Y_KEY, location.getLocation().y);
+
+            if (location.getRotatedLocation() != null) {
+                bundle.putFloat(WearableInputDevice.X_KEY_ROTATED, location.getRotatedLocation().x);
+                bundle.putFloat(WearableInputDevice.Y_KEY_ROTATED, location.getRotatedLocation().y);
+            }
+        }
+
+        return bundle;
+    }
+
+    @Nullable
+    @Override
+    public int[] getAvailableButtonKeyCodes(@NonNull Context context) {
+        int[] keys = new int[mButtons.size()];
+
+        int i = 0;
+        for (Integer keycode : mButtons.keySet()) {
+            keys[i++] = keycode;
+        }
+
+        return keys;
+    }
+}
diff --git a/wear/wear-input/api/1.0.0-alpha01.txt b/wear/wear-input/api/1.0.0-alpha01.txt
new file mode 100644
index 0000000..9998442
--- /dev/null
+++ b/wear/wear-input/api/1.0.0-alpha01.txt
@@ -0,0 +1,30 @@
+// Signature format: 3.0
+package androidx.wear.input {
+
+  public class DeviceWearableButtonsProvider implements androidx.wear.input.WearableButtonsProvider {
+    ctor public DeviceWearableButtonsProvider();
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+  public final class WearableButtons {
+    method public static int getButtonCount(android.content.Context);
+    method public static android.graphics.drawable.Drawable? getButtonIcon(android.content.Context, int);
+    method public static androidx.wear.input.WearableButtons.ButtonInfo? getButtonInfo(android.content.Context, int);
+    method public static CharSequence getButtonLabel(android.content.Context, int);
+  }
+
+  public static final class WearableButtons.ButtonInfo {
+    method public int getKeycode();
+    method public int getLocationZone();
+    method public float getX();
+    method public float getY();
+  }
+
+  public interface WearableButtonsProvider {
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+}
+
diff --git a/wear/wear-input/api/current.txt b/wear/wear-input/api/current.txt
new file mode 100644
index 0000000..9998442
--- /dev/null
+++ b/wear/wear-input/api/current.txt
@@ -0,0 +1,30 @@
+// Signature format: 3.0
+package androidx.wear.input {
+
+  public class DeviceWearableButtonsProvider implements androidx.wear.input.WearableButtonsProvider {
+    ctor public DeviceWearableButtonsProvider();
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+  public final class WearableButtons {
+    method public static int getButtonCount(android.content.Context);
+    method public static android.graphics.drawable.Drawable? getButtonIcon(android.content.Context, int);
+    method public static androidx.wear.input.WearableButtons.ButtonInfo? getButtonInfo(android.content.Context, int);
+    method public static CharSequence getButtonLabel(android.content.Context, int);
+  }
+
+  public static final class WearableButtons.ButtonInfo {
+    method public int getKeycode();
+    method public int getLocationZone();
+    method public float getX();
+    method public float getY();
+  }
+
+  public interface WearableButtonsProvider {
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+}
+
diff --git a/wear/wear-input/api/public_plus_experimental_1.0.0-alpha01.txt b/wear/wear-input/api/public_plus_experimental_1.0.0-alpha01.txt
new file mode 100644
index 0000000..9998442
--- /dev/null
+++ b/wear/wear-input/api/public_plus_experimental_1.0.0-alpha01.txt
@@ -0,0 +1,30 @@
+// Signature format: 3.0
+package androidx.wear.input {
+
+  public class DeviceWearableButtonsProvider implements androidx.wear.input.WearableButtonsProvider {
+    ctor public DeviceWearableButtonsProvider();
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+  public final class WearableButtons {
+    method public static int getButtonCount(android.content.Context);
+    method public static android.graphics.drawable.Drawable? getButtonIcon(android.content.Context, int);
+    method public static androidx.wear.input.WearableButtons.ButtonInfo? getButtonInfo(android.content.Context, int);
+    method public static CharSequence getButtonLabel(android.content.Context, int);
+  }
+
+  public static final class WearableButtons.ButtonInfo {
+    method public int getKeycode();
+    method public int getLocationZone();
+    method public float getX();
+    method public float getY();
+  }
+
+  public interface WearableButtonsProvider {
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+}
+
diff --git a/wear/wear-input/api/public_plus_experimental_current.txt b/wear/wear-input/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..9998442
--- /dev/null
+++ b/wear/wear-input/api/public_plus_experimental_current.txt
@@ -0,0 +1,30 @@
+// Signature format: 3.0
+package androidx.wear.input {
+
+  public class DeviceWearableButtonsProvider implements androidx.wear.input.WearableButtonsProvider {
+    ctor public DeviceWearableButtonsProvider();
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+  public final class WearableButtons {
+    method public static int getButtonCount(android.content.Context);
+    method public static android.graphics.drawable.Drawable? getButtonIcon(android.content.Context, int);
+    method public static androidx.wear.input.WearableButtons.ButtonInfo? getButtonInfo(android.content.Context, int);
+    method public static CharSequence getButtonLabel(android.content.Context, int);
+  }
+
+  public static final class WearableButtons.ButtonInfo {
+    method public int getKeycode();
+    method public int getLocationZone();
+    method public float getX();
+    method public float getY();
+  }
+
+  public interface WearableButtonsProvider {
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+}
+
diff --git a/wear/wear-input/api/res-1.0.0-alpha01.txt b/wear/wear-input/api/res-1.0.0-alpha01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/wear/wear-input/api/res-1.0.0-alpha01.txt
diff --git a/wear/wear-input/api/res-current.txt b/wear/wear-input/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/wear/wear-input/api/res-current.txt
diff --git a/wear/wear-input/api/restricted_1.0.0-alpha01.txt b/wear/wear-input/api/restricted_1.0.0-alpha01.txt
new file mode 100644
index 0000000..9998442
--- /dev/null
+++ b/wear/wear-input/api/restricted_1.0.0-alpha01.txt
@@ -0,0 +1,30 @@
+// Signature format: 3.0
+package androidx.wear.input {
+
+  public class DeviceWearableButtonsProvider implements androidx.wear.input.WearableButtonsProvider {
+    ctor public DeviceWearableButtonsProvider();
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+  public final class WearableButtons {
+    method public static int getButtonCount(android.content.Context);
+    method public static android.graphics.drawable.Drawable? getButtonIcon(android.content.Context, int);
+    method public static androidx.wear.input.WearableButtons.ButtonInfo? getButtonInfo(android.content.Context, int);
+    method public static CharSequence getButtonLabel(android.content.Context, int);
+  }
+
+  public static final class WearableButtons.ButtonInfo {
+    method public int getKeycode();
+    method public int getLocationZone();
+    method public float getX();
+    method public float getY();
+  }
+
+  public interface WearableButtonsProvider {
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+}
+
diff --git a/wear/wear-input/api/restricted_current.txt b/wear/wear-input/api/restricted_current.txt
new file mode 100644
index 0000000..9998442
--- /dev/null
+++ b/wear/wear-input/api/restricted_current.txt
@@ -0,0 +1,30 @@
+// Signature format: 3.0
+package androidx.wear.input {
+
+  public class DeviceWearableButtonsProvider implements androidx.wear.input.WearableButtonsProvider {
+    ctor public DeviceWearableButtonsProvider();
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+  public final class WearableButtons {
+    method public static int getButtonCount(android.content.Context);
+    method public static android.graphics.drawable.Drawable? getButtonIcon(android.content.Context, int);
+    method public static androidx.wear.input.WearableButtons.ButtonInfo? getButtonInfo(android.content.Context, int);
+    method public static CharSequence getButtonLabel(android.content.Context, int);
+  }
+
+  public static final class WearableButtons.ButtonInfo {
+    method public int getKeycode();
+    method public int getLocationZone();
+    method public float getX();
+    method public float getY();
+  }
+
+  public interface WearableButtonsProvider {
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+}
+
diff --git a/wear/wear-input/build.gradle b/wear/wear-input/build.gradle
new file mode 100644
index 0000000..5ab8fb0
--- /dev/null
+++ b/wear/wear-input/build.gradle
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the Laicense.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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
+import androidx.build.Publish
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+}
+
+dependencies {
+    api("androidx.annotation:annotation:1.1.0")
+
+    testImplementation(ANDROIDX_TEST_EXT_JUNIT)
+    testImplementation(ANDROIDX_TEST_CORE)
+    testImplementation(ANDROIDX_TEST_RUNNER)
+    testImplementation(ANDROIDX_TEST_RULES)
+    testImplementation(ROBOLECTRIC)
+    testImplementation(MOCKITO_CORE)
+    testImplementation(project(":wear:wear-input-testing"))
+
+    compileOnly fileTree(dir: '../wear_stubs', include: ['com.google.android.wearable-stubs.jar'])
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 23
+    }
+
+    // Use Robolectric 4.+
+    testOptions.unitTests.includeAndroidResources = true
+}
+
+androidx {
+    name = "Android Wear Support Input"
+    publish = Publish.SNAPSHOT_AND_RELEASE
+    mavenGroup = LibraryGroups.WEAR
+    mavenVersion = LibraryVersions.WEAR_INPUT
+    inceptionYear = "2020"
+    description = "Android Wear Support Input"
+}
diff --git a/wear/wear-input/src/main/AndroidManifest.xml b/wear/wear-input/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..19e4723a
--- /dev/null
+++ b/wear/wear-input/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2020 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT 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.wear.input"/>
diff --git a/wear/wear-input/src/main/java/androidx/wear/input/DeviceWearableButtonsProvider.java b/wear/wear-input/src/main/java/androidx/wear/input/DeviceWearableButtonsProvider.java
new file mode 100644
index 0000000..a8130fb
--- /dev/null
+++ b/wear/wear-input/src/main/java/androidx/wear/input/DeviceWearableButtonsProvider.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.wear.input;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.android.wearable.input.WearableInputDevice;
+
+/**
+ * Default implementation of {@link WearableButtonsProvider}, that reads the button locations from
+ * the platform.
+ */
+public class DeviceWearableButtonsProvider implements WearableButtonsProvider {
+
+    @NonNull
+    @Override
+    public Bundle getButtonInfo(@NonNull Context context, int keycode) {
+        if (!isSharedLibAvailable()) {
+            return null;
+        }
+
+        return WearableInputDevice.getButtonInfo(context, keycode);
+    }
+
+    @Nullable
+    @Override
+    public int[] getAvailableButtonKeyCodes(@NonNull Context context) {
+        if (!isSharedLibAvailable()) {
+            return null;
+        }
+
+        return WearableInputDevice.getAvailableButtonKeyCodes(context);
+    }
+
+    private boolean isSharedLibAvailable() {
+        return SharedLibraryVersion.version() >= 1;
+    }
+}
diff --git a/wear/wear-input/src/main/java/androidx/wear/input/SharedLibraryVersion.java b/wear/wear-input/src/main/java/androidx/wear/input/SharedLibraryVersion.java
new file mode 100644
index 0000000..2a6b0c9
--- /dev/null
+++ b/wear/wear-input/src/main/java/androidx/wear/input/SharedLibraryVersion.java
@@ -0,0 +1,102 @@
+/*
+ * 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.wear.input;
+
+import android.os.Build;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.google.android.wearable.WearableSharedLib;
+
+/**
+ * Internal class which can be used to determine the version of the wearable shared library that is
+ * available on the current device.
+ */
+final class SharedLibraryVersion {
+
+    private SharedLibraryVersion() {}
+
+    /**
+     * Returns the version of the wearable shared library available on the current device.
+     *
+     * <p>
+     *
+     * <p>Version 1 was introduced on 2016-09-26, so any previous shared library will return 0. In
+     * those cases, it may be necessary to check {@code Build.VERSION.SDK_INT}.
+     *
+     * @throws IllegalStateException if the Wearable Shared Library is not present, which means that
+     *     the {@code <uses-library>} tag is missing.
+     */
+    public static int version() {
+        verifySharedLibraryPresent();
+        return VersionHolder.VERSION;
+    }
+
+    /**
+     * Throws {@link IllegalStateException} if the Wearable Shared Library is not present and API
+     * level is at least LMP MR1.
+     *
+     * <p>
+     *
+     * <p>This validates that the developer hasn't forgotten to include a {@code <uses-library>} tag
+     * in their manifest. The method should be used in combination with API level checks for
+     * features added before {@link #version() version} 1.
+     */
+    public static void verifySharedLibraryPresent() {
+        if (!PresenceHolder.PRESENT) {
+            throw new IllegalStateException(
+                    "Could not find wearable shared library classes. Please add <uses-library"
+                        + " android:name=\"com.google.android.wearable\""
+                        + " android:required=\"false\" /> to the application manifest");
+        }
+    }
+
+    // Lazy initialization holder class (see Effective Java item 71)
+    @VisibleForTesting
+    static final class VersionHolder {
+        static final int VERSION = getSharedLibVersion(Build.VERSION.SDK_INT);
+
+        @VisibleForTesting
+        static int getSharedLibVersion(int sdkInt) {
+            if (sdkInt < Build.VERSION_CODES.N_MR1) {
+                // WearableSharedLib was introduced in N MR1 (Wear FDP 4)
+                return 0;
+            }
+            return WearableSharedLib.version();
+        }
+
+        private VersionHolder() {}
+    }
+
+    // Lazy initialization holder class (see Effective Java item 71)
+    @VisibleForTesting
+    static final class PresenceHolder {
+        static final boolean PRESENT = isSharedLibPresent(Build.VERSION.SDK_INT);
+
+        @VisibleForTesting
+        static boolean isSharedLibPresent(int sdkInt) {
+            try {
+                // A class which has been available on the shared library from the first version.
+                Class.forName("com.google.android.wearable.compat.WearableActivityController");
+            } catch (ClassNotFoundException e) {
+                return false;
+            }
+            return true;
+        }
+
+        private PresenceHolder() {}
+    }
+}
diff --git a/wear/wear-input/src/main/java/androidx/wear/input/WearableButtons.java b/wear/wear-input/src/main/java/androidx/wear/input/WearableButtons.java
new file mode 100644
index 0000000..a2ec7da
--- /dev/null
+++ b/wear/wear-input/src/main/java/androidx/wear/input/WearableButtons.java
@@ -0,0 +1,722 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.wear.input;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.RotateDrawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.view.Surface;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.VisibleForTesting;
+
+import com.google.android.wearable.input.WearableInputDevice;
+
+/** Class containing helpers for managing wearable buttons. */
+@TargetApi(Build.VERSION_CODES.M)
+public final class WearableButtons {
+
+    private static WearableButtonsProvider sButtonsProvider = new DeviceWearableButtonsProvider();
+
+    /** Represents that the count of available device buttons. */
+    private static volatile int sButtonCount = -1;
+
+    private WearableButtons() {
+        throw new RuntimeException("WearableButtons should not be instantiated");
+    }
+
+    /**
+     * Testing call to allow the underlying {@link WearableButtonsProvider} to be substituted in
+     * test code.
+     *
+     * @param provider The new {@link WearableButtonsProvider} to use.
+     */
+    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+    public static void setWearableButtonsProvider(@NonNull WearableButtonsProvider provider) {
+        sButtonsProvider = provider;
+    }
+
+    /** Represents that the location zone is unknown. */
+    @VisibleForTesting static final int LOC_UNKNOWN = -1;
+
+    /** Represents the east position on a round device. */
+    @VisibleForTesting static final int LOC_EAST = 0;
+
+    /** Represents the east-northeast position on a round device. */
+    @VisibleForTesting static final int LOC_ENE = 1;
+
+    /** Represents the northeast position on a round device. */
+    @VisibleForTesting static final int LOC_NE = 2;
+
+    /** Represents the north-northeast position on a round device. */
+    @VisibleForTesting static final int LOC_NNE = 3;
+
+    /** Represents the north position on a round device. */
+    @VisibleForTesting static final int LOC_NORTH = 4;
+
+    /** Represents the north-northwest position on a round device. */
+    @VisibleForTesting static final int LOC_NNW = 5;
+
+    /** Represents the northwest position on a round device. */
+    @VisibleForTesting static final int LOC_NW = 6;
+
+    /** Represents the west-northwest position on a round device. */
+    @VisibleForTesting static final int LOC_WNW = 7;
+
+    /** Represents the west position on a round device. */
+    @VisibleForTesting static final int LOC_WEST = 8;
+
+    /** Represents the west-southwest position on a round device. */
+    @VisibleForTesting static final int LOC_WSW = 9;
+
+    /** Represents the southwest position on a round device. */
+    @VisibleForTesting static final int LOC_SW = 10;
+
+    /** Represents the south-southwest position on a round device. */
+    @VisibleForTesting static final int LOC_SSW = 11;
+
+    /** Represents the south position on a round device. */
+    @VisibleForTesting static final int LOC_SOUTH = 12;
+
+    /** Represents the south-southeast position on a round device. */
+    @VisibleForTesting static final int LOC_SSE = 13;
+
+    /** Represents the southeast position on a round device. */
+    @VisibleForTesting static final int LOC_SE = 14;
+
+    /** Represents the east-southeast position on a round device. */
+    @VisibleForTesting static final int LOC_ESE = 15;
+
+    private static final int LOC_ROUND_COUNT = 16;
+
+    /** Represents the right third of the top side on a square device. */
+    @VisibleForTesting static final int LOC_TOP_RIGHT = 100;
+
+    /** Represents the center third of the top side on a square device. */
+    @VisibleForTesting static final int LOC_TOP_CENTER = 101;
+
+    /** Represents the left third of the top side on a square device. */
+    @VisibleForTesting static final int LOC_TOP_LEFT = 102;
+
+    /** Represents the top third of the left side on a square device. */
+    @VisibleForTesting static final int LOC_LEFT_TOP = 103;
+
+    /** Represents the center third of the left side on a square device. */
+    @VisibleForTesting static final int LOC_LEFT_CENTER = 104;
+
+    /** Represents the bottom third of the left side on a square device. */
+    @VisibleForTesting static final int LOC_LEFT_BOTTOM = 105;
+
+    /** Represents the left third of the bottom side on a square device. */
+    @VisibleForTesting static final int LOC_BOTTOM_LEFT = 106;
+
+    /** Represents the center third of the bottom side on a square device. */
+    @VisibleForTesting static final int LOC_BOTTOM_CENTER = 107;
+
+    /** Represents the right third of the bottom side on a square device. */
+    @VisibleForTesting static final int LOC_BOTTOM_RIGHT = 108;
+
+    /** Represents the bottom third of the right side on a square device. */
+    @VisibleForTesting static final int LOC_RIGHT_BOTTOM = 109;
+
+    /** Represents the center third of the right side on a square device. */
+    @VisibleForTesting static final int LOC_RIGHT_CENTER = 110;
+
+    /** Represents the top third of the right side on a square device. */
+    @VisibleForTesting static final int LOC_RIGHT_TOP = 111;
+
+    /**
+     * Key used with the bundle returned by {@link #getButtonInfo}} to retrieve the x coordinate of
+     * a button when the screen is rotated 180 degrees. (temporary copy from WearableInputDevice)
+     */
+    private static final String X_KEY_ROTATED = "x_key_rotated";
+
+    /**
+     * Key used with the bundle returned by {@link #getButtonInfo}} to retrieve the y coordinate of
+     * a button when the screen is rotated 180 degrees. (temporary copy from WearableInputDevice)
+     */
+    private static final String Y_KEY_ROTATED = "y_key_rotated";
+
+    /**
+     * Returns a {@link ButtonInfo} containing the metadata for a specific button.
+     *
+     * <p>The location will be populated in the following manner:
+     *
+     * <ul>
+     *   <li>The provided point will be on the screen, or more typically, on the edge of the screen.
+     *   <li>The point won't be off the edge of the screen.
+     *   <li>The location returned is a screen coordinate. The unit of measurement is in pixels. The
+     *       coordinates do not take rotation into account and assume that the device is in the
+     *       standard upright position.
+     * </ul>
+     *
+     * <p>Common keycodes to use are {@link android.view.KeyEvent#KEYCODE_STEM_PRIMARY}, {@link
+     * android.view.KeyEvent#KEYCODE_STEM_1}, {@link android.view.KeyEvent#KEYCODE_STEM_2}, and
+     * {@link android.view.KeyEvent#KEYCODE_STEM_3}.
+     *
+     * @param context The context of the current activity
+     * @param keycode The keycode associated with the hardware button of interest
+     * @return A {@link ButtonInfo} containing the metadata for the given keycode or null if the
+     *     information is not available
+     */
+    @Nullable
+    public static ButtonInfo getButtonInfo(@NonNull Context context, int keycode) {
+        Bundle bundle = sButtonsProvider.getButtonInfo(context, keycode);
+
+        if (bundle == null) {
+            return null;
+        }
+
+        // If the information is not available, return null
+        if (!bundle.containsKey(WearableInputDevice.X_KEY)
+                || !bundle.containsKey(WearableInputDevice.Y_KEY)) {
+            return null;
+        }
+
+        float screenLocationX = bundle.getFloat(WearableInputDevice.X_KEY);
+        float screenLocationY = bundle.getFloat(WearableInputDevice.Y_KEY);
+
+        // Get the screen size for the locationZone
+        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        Point screenSize = new Point();
+        wm.getDefaultDisplay().getSize(screenSize);
+
+        if (isLeftyModeEnabled(context)) {
+            // By default, the rotated placement is exactly the opposite.
+            // This may be overridden if there is a remapping of buttons applied as well.
+            float screenRotatedX = screenSize.x - screenLocationX;
+            float screenRotatedY = screenSize.y - screenLocationY;
+
+            if (bundle.containsKey(X_KEY_ROTATED) && bundle.containsKey(Y_KEY_ROTATED)) {
+                screenRotatedX = bundle.getFloat(X_KEY_ROTATED);
+                screenRotatedY = bundle.getFloat(Y_KEY_ROTATED);
+            }
+
+            screenLocationX = screenRotatedX;
+            screenLocationY = screenRotatedY;
+        }
+
+        boolean isRound = context.getResources().getConfiguration().isScreenRound();
+
+        ButtonInfo info =
+                new ButtonInfo(
+                        keycode,
+                        screenLocationX,
+                        screenLocationY,
+                        getLocationZone(isRound, screenSize, screenLocationX, screenLocationY));
+
+        return info;
+    }
+
+    /**
+     * Get the number of hardware buttons available. This function only works on API level 24 or
+     * higher (Wear 2.0). This count includes the primary stem key as well as any secondary stem
+     * keys available.
+     *
+     * @param context The context of the current activity
+     * @return The number of buttons available or -1 if not a Wear 2.0 device.
+     */
+    public static int getButtonCount(@NonNull Context context) {
+        if (sButtonCount == -1) {
+            int[] buttonCodes = sButtonsProvider.getAvailableButtonKeyCodes(context);
+
+            if (buttonCodes == null) {
+                return -1;
+            }
+
+            sButtonCount = buttonCodes.length;
+        }
+
+        return sButtonCount;
+    }
+
+    /**
+     * Returns an icon that can be used to represent the location of a button.
+     *
+     * @param context The context of the current activity
+     * @param keycode The keycode associated with the hardware button of interest
+     * @return A drawable representing the location of a button, or null if unavailable
+     */
+    @Nullable
+    public static Drawable getButtonIcon(@NonNull Context context, int keycode) {
+        ButtonInfo info = getButtonInfo(context, keycode);
+        if (info == null) {
+            return null;
+        }
+        return getButtonIconFromLocationZone(context, info.getLocationZone());
+    }
+
+    @VisibleForTesting
+    static RotateDrawable getButtonIconFromLocationZone(Context context, int locationZone) {
+        // To save memory for assets, we are using 4 icons to represent the 20+ possible
+        // configurations.  These 4 base icons can be rotated to fit any configuration needed.
+        // id is the drawable id for the base icon
+        // degrees is the number of degrees the icon needs to be rotated to match the wanted
+        // position
+        int id;
+        int degrees;
+
+        switch (locationZone) {
+                // Round constants
+            case LOC_EAST:
+                id = R.drawable.ic_cc_settings_button_e;
+                degrees = 0;
+                break;
+            case LOC_ENE:
+            case LOC_NE:
+            case LOC_NNE:
+                id = R.drawable.ic_cc_settings_button_e;
+                degrees = -45;
+                break;
+            case LOC_NORTH:
+                id = R.drawable.ic_cc_settings_button_e;
+                degrees = -90;
+                break;
+            case LOC_NNW:
+            case LOC_NW:
+            case LOC_WNW:
+                id = R.drawable.ic_cc_settings_button_e;
+                degrees = -135;
+                break;
+            case LOC_WEST:
+                id = R.drawable.ic_cc_settings_button_e;
+                degrees = 180;
+                break;
+            case LOC_WSW:
+            case LOC_SW:
+            case LOC_SSW:
+                id = R.drawable.ic_cc_settings_button_e;
+                degrees = 135;
+                break;
+            case LOC_SOUTH:
+                id = R.drawable.ic_cc_settings_button_e;
+                degrees = 90;
+                break;
+            case LOC_SSE:
+            case LOC_SE:
+            case LOC_ESE:
+                id = R.drawable.ic_cc_settings_button_e;
+                degrees = 45;
+                break;
+
+                // Rectangular constants
+            case LOC_LEFT_TOP:
+                id = R.drawable.ic_cc_settings_button_bottom;
+                degrees = 180;
+                break;
+            case LOC_LEFT_CENTER:
+                id = R.drawable.ic_cc_settings_button_center;
+                degrees = 180;
+                break;
+            case LOC_LEFT_BOTTOM:
+                id = R.drawable.ic_cc_settings_button_top;
+                degrees = 180;
+                break;
+            case LOC_RIGHT_TOP:
+                id = R.drawable.ic_cc_settings_button_top;
+                degrees = 0;
+                break;
+            case LOC_RIGHT_CENTER:
+                id = R.drawable.ic_cc_settings_button_center;
+                degrees = 0;
+                break;
+            case LOC_RIGHT_BOTTOM:
+                id = R.drawable.ic_cc_settings_button_bottom;
+                degrees = 0;
+                break;
+            case LOC_TOP_LEFT:
+                id = R.drawable.ic_cc_settings_button_top;
+                degrees = -90;
+                break;
+            case LOC_TOP_CENTER:
+                id = R.drawable.ic_cc_settings_button_center;
+                degrees = -90;
+                break;
+            case LOC_TOP_RIGHT:
+                id = R.drawable.ic_cc_settings_button_bottom;
+                degrees = -90;
+                break;
+            case LOC_BOTTOM_LEFT:
+                id = R.drawable.ic_cc_settings_button_bottom;
+                degrees = 90;
+                break;
+            case LOC_BOTTOM_CENTER:
+                id = R.drawable.ic_cc_settings_button_center;
+                degrees = 90;
+                break;
+            case LOC_BOTTOM_RIGHT:
+                id = R.drawable.ic_cc_settings_button_top;
+                degrees = 90;
+                break;
+            default:
+                throw new IllegalArgumentException("Unexpected location zone");
+        }
+        RotateDrawable rotateIcon = new RotateDrawable();
+        rotateIcon.setDrawable(context.getDrawable(id));
+        rotateIcon.setFromDegrees(degrees);
+        rotateIcon.setToDegrees(degrees);
+        rotateIcon.setLevel(1);
+        return rotateIcon;
+    }
+
+    /**
+     * Returns a CharSequence that describes the placement location of a button. An example might be
+     * "Top right" or "Bottom".
+     *
+     * @param context The context of the current activity
+     * @param keycode The keycode associated with the hardware button of interest
+     * @return A CharSequence describing the placement location of the button
+     */
+    @NonNull
+    public static CharSequence getButtonLabel(@NonNull Context context, int keycode) {
+        // 4 length array where the index uses the standard quadrant counting system (minus 1 for
+        // 0 index)
+        int[] buttonsInQuadrantCount = new int[4];
+
+        // Retrieve ButtonInfo objects and count how many buttons are in a quadrant.  This is only
+        // needed for round devices but will help us come up with friendly strings to show to the
+        // user.
+        // TODO(ahugh): We can cache quadrant counts to optimize. These values should be static for
+        // each
+        // device.
+        int[] buttonCodes = sButtonsProvider.getAvailableButtonKeyCodes(context);
+
+        if (buttonCodes == null) {
+            return null;
+        }
+
+        for (int key : buttonCodes) {
+            ButtonInfo info = getButtonInfo(context, key);
+
+            if (info != null) {
+                int quadrantIndex = getQuadrantIndex(info.getLocationZone());
+                if (quadrantIndex != -1) {
+                    ++buttonsInQuadrantCount[quadrantIndex];
+                }
+            }
+        }
+
+        ButtonInfo info = getButtonInfo(context, keycode);
+        int quadrantIndex = (info != null ? getQuadrantIndex(info.getLocationZone()) : -1);
+        return info == null
+                ? null
+                : context.getString(
+                        getFriendlyLocationZoneStringId(
+                                info.getLocationZone(),
+                                (quadrantIndex == -1 ? 0 : buttonsInQuadrantCount[quadrantIndex])));
+    }
+
+    /**
+     * Returns quadrant index if locationZone is for a round device. Follows the conventional
+     * quadrant system with the top right quadrant being 0, incrementing the index by 1 going
+     * counter-clockwise around.
+     */
+    private static int getQuadrantIndex(int locationZone) {
+        switch (locationZone) {
+            case LOC_ENE:
+            case LOC_NE:
+            case LOC_NNE:
+                return 0;
+            case LOC_NNW:
+            case LOC_NW:
+            case LOC_WNW:
+                return 1;
+            case LOC_WSW:
+            case LOC_SW:
+            case LOC_SSW:
+                return 2;
+            case LOC_SSE:
+            case LOC_SE:
+            case LOC_ESE:
+                return 3;
+
+            default:
+                return -1;
+        }
+    }
+
+    /**
+     * If the screen is round, there is special logic we use to determine the string that should
+     * show on the screen. Simple strings are broad descriptors like "top right". Detailed strings
+     * are narrow descriptors like, "top right, upper" 1) If there are exactly 2 buttons in a
+     * quadrant, use detailed strings to describe button locations. 2) Otherwise, use simple strings
+     * to describe the button locations.
+     *
+     * @param locationZone The location zone to get a string id for
+     * @param buttonsInQuadrantCount The number of buttons in the quadrant of the button
+     * @return The string id to use to represent this button zone
+     */
+    @VisibleForTesting
+    static int getFriendlyLocationZoneStringId(int locationZone, int buttonsInQuadrantCount) {
+        if (buttonsInQuadrantCount == 2) {
+            switch (locationZone) {
+                case LOC_ENE:
+                    return R.string.buttons_round_top_right_lower;
+                case LOC_NE:
+                case LOC_NNE:
+                    return R.string.buttons_round_top_right_upper;
+                case LOC_NNW:
+                case LOC_NW:
+                    return R.string.buttons_round_top_left_upper;
+                case LOC_WNW:
+                    return R.string.buttons_round_top_left_lower;
+                case LOC_ESE:
+                case LOC_SE:
+                    return R.string.buttons_round_bottom_left_upper;
+                case LOC_SSE:
+                    return R.string.buttons_round_bottom_left_lower;
+                case LOC_SSW:
+                    return R.string.buttons_round_bottom_right_lower;
+                case LOC_SW:
+                case LOC_WSW:
+                    return R.string.buttons_round_bottom_right_upper;
+                default: // fall out
+            }
+        }
+
+        // If we couldn't find a detailed string, or we need a simple string
+        switch (locationZone) {
+                // Round constants
+            case LOC_EAST:
+                return R.string.buttons_round_center_right;
+            case LOC_ENE:
+            case LOC_NE:
+            case LOC_NNE:
+                return R.string.buttons_round_top_right;
+            case LOC_NORTH:
+                return R.string.buttons_round_top_center;
+            case LOC_NNW:
+            case LOC_NW:
+            case LOC_WNW:
+                return R.string.buttons_round_top_left;
+            case LOC_WEST:
+                return R.string.buttons_round_center_left;
+            case LOC_WSW:
+            case LOC_SW:
+            case LOC_SSW:
+                return R.string.buttons_round_bottom_left;
+            case LOC_SOUTH:
+                return R.string.buttons_round_bottom_center;
+            case LOC_SSE:
+            case LOC_SE:
+            case LOC_ESE:
+                return R.string.buttons_round_bottom_right;
+
+                // Rectangular constants
+            case LOC_LEFT_TOP:
+                return R.string.buttons_rect_left_top;
+            case LOC_LEFT_CENTER:
+                return R.string.buttons_rect_left_center;
+            case LOC_LEFT_BOTTOM:
+                return R.string.buttons_rect_left_bottom;
+            case LOC_RIGHT_TOP:
+                return R.string.buttons_rect_right_top;
+            case LOC_RIGHT_CENTER:
+                return R.string.buttons_rect_right_center;
+            case LOC_RIGHT_BOTTOM:
+                return R.string.buttons_rect_right_bottom;
+            case LOC_TOP_LEFT:
+                return R.string.buttons_rect_top_left;
+            case LOC_TOP_CENTER:
+                return R.string.buttons_rect_top_center;
+            case LOC_TOP_RIGHT:
+                return R.string.buttons_rect_top_right;
+            case LOC_BOTTOM_LEFT:
+                return R.string.buttons_rect_bottom_left;
+            case LOC_BOTTOM_CENTER:
+                return R.string.buttons_rect_bottom_center;
+            case LOC_BOTTOM_RIGHT:
+                return R.string.buttons_rect_bottom_right;
+            default:
+                throw new IllegalArgumentException("Unexpected location zone");
+        }
+    }
+
+    /**
+     * For round devices, the location zone is defined using 16 points in a compass arrangement. If
+     * a button falls between anchor points, this method will return the closest anchor.
+     *
+     * <p>For rectangular devices, the location zone is defined by splitting each side into thirds.
+     * If a button falls anywhere within a zone, the method will return that zone. The constants for
+     * these zones are named LOC_[side in question]_[which third is affected]. E.g. LOC_TOP_RIGHT
+     * would refer to the right third of the top side of the device.
+     */
+    @VisibleForTesting
+    /* package */ static int getLocationZone(
+            boolean isRound, Point screenSize, float screenLocationX, float screenLocationY) {
+        if (screenLocationX == Float.MAX_VALUE || screenLocationY == Float.MAX_VALUE) {
+            return LOC_UNKNOWN;
+        }
+
+        return isRound
+                ? getLocationZoneRound(screenSize, screenLocationX, screenLocationY)
+                : getLocationZoneRectangular(screenSize, screenLocationX, screenLocationY);
+    }
+
+    private static int getLocationZoneRound(
+            Point screenSize, float screenLocationX, float screenLocationY) {
+        // Convert screen coordinate to Cartesian coordinate
+        float cartesianX = screenLocationX - screenSize.x / 2;
+        float cartesianY = screenSize.y / 2 - screenLocationY;
+
+        // Use polar coordinates to figure out which zone the point is in
+        double angle = Math.atan2(cartesianY, cartesianX);
+
+        // Convert angle to all positive values
+        if (angle < 0) {
+            angle += 2 * Math.PI;
+        }
+
+        // Return the associated section rounded to the nearest anchor.
+        // Using some clever math tricks and enum declaration, we can reduce this calculation
+        // down to a single formula that converts angle to enum value.
+        return Math.round((float) (angle / (Math.PI / 8))) % LOC_ROUND_COUNT;
+    }
+
+    private static int getLocationZoneRectangular(
+            Point screenSize, float screenLocationX, float screenLocationY) {
+        // Calculate distance to each edge.
+        float deltaFromLeft = screenLocationX;
+        float deltaFromRight = screenSize.x - screenLocationX;
+        float deltaFromTop = screenLocationY;
+        float deltaFromBottom = screenSize.y - screenLocationY;
+        float minDelta =
+                Math.min(
+                        deltaFromLeft,
+                        Math.min(deltaFromRight, Math.min(deltaFromTop, deltaFromBottom)));
+
+        // Prioritize ties to left and right sides of watch since they're more likely to be placed
+        // on the side. Buttons directly on the corner are not accounted for with this API.
+        if (minDelta == deltaFromLeft) {
+            // Left is the primary side
+            switch (whichThird(screenSize.y, screenLocationY)) {
+                case 0:
+                    return LOC_LEFT_TOP;
+                case 1:
+                    return LOC_LEFT_CENTER;
+                default:
+                    return LOC_LEFT_BOTTOM;
+            }
+        } else if (minDelta == deltaFromRight) {
+            // Right is primary side
+            switch (whichThird(screenSize.y, screenLocationY)) {
+                case 0:
+                    return LOC_RIGHT_TOP;
+                case 1:
+                    return LOC_RIGHT_CENTER;
+                default:
+                    return LOC_RIGHT_BOTTOM;
+            }
+        } else if (minDelta == deltaFromTop) {
+            // Top is primary side
+            switch (whichThird(screenSize.x, screenLocationX)) {
+                case 0:
+                    return LOC_TOP_LEFT;
+                case 1:
+                    return LOC_TOP_CENTER;
+                default:
+                    return LOC_TOP_RIGHT;
+            }
+        } else /* if (minDelta == deltaFromBottom) */ {
+            // Bottom is primary side
+            switch (whichThird(screenSize.x, screenLocationX)) {
+                case 0:
+                    return LOC_BOTTOM_LEFT;
+                case 1:
+                    return LOC_BOTTOM_CENTER;
+                default:
+                    return LOC_BOTTOM_RIGHT;
+            }
+        }
+    }
+
+    // Returns 0, 1, or 2 which correspond to the index of the third the screen point lies in
+    // from 'left to right' or 'top to bottom'.
+    private static int whichThird(float screenLength, float screenLocation) {
+        if (screenLocation <= screenLength / 3) {
+            return 0;
+        } else if (screenLocation <= screenLength * 2 / 3) {
+            return 1;
+        } else {
+            return 2;
+        }
+    }
+
+    private static boolean isLeftyModeEnabled(Context context) {
+        return Settings.System.getInt(
+                        context.getContentResolver(),
+                        Settings.System.USER_ROTATION,
+                        Surface.ROTATION_0)
+                == Surface.ROTATION_180;
+    }
+
+    /** Metadata for a specific button. */
+    public static final class ButtonInfo {
+        private final int mKeycode;
+        private final float mX;
+        private final float mY;
+
+        /**
+         * The location zone of the button as defined in the {@link #getButtonInfo(Context, int)}
+         * method. The intended use is to help developers attach a friendly String to the button
+         * location. This value is LOC_UNKNOWN if the information is not available.
+         */
+        private final int mLocationZone;
+
+        /**
+         * Gets the keycode this {@code ButtonInfo} provides information for.
+         *
+         * @return The keycode this {@code ButtonInfo} provides information for
+         */
+        public int getKeycode() {
+            return mKeycode;
+        }
+
+        /** The x coordinate of the button in screen coordinates. */
+        public float getX() {
+            return mX;
+        }
+
+        /** The y coordinate of the button in screen coordinates. */
+        public float getY() {
+            return mY;
+        }
+
+        /** The location zone of the button (e.g. LOC_EAST) */
+        public int getLocationZone() {
+            return mLocationZone;
+        }
+
+        /** @hide */
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        @VisibleForTesting
+        public ButtonInfo(int keycode, float x, float y, int locationZone) {
+            this.mKeycode = keycode;
+            this.mX = x;
+            this.mY = y;
+            this.mLocationZone = locationZone;
+        }
+    }
+}
diff --git a/wear/wear-input/src/main/java/androidx/wear/input/WearableButtonsProvider.java b/wear/wear-input/src/main/java/androidx/wear/input/WearableButtonsProvider.java
new file mode 100644
index 0000000..5a68920
--- /dev/null
+++ b/wear/wear-input/src/main/java/androidx/wear/input/WearableButtonsProvider.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.wear.input;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * A provider interface to allow {@link WearableButtons} to query for information on the device's
+ * buttons from the platform. This exists to allow for the button provider to be switched out for
+ * testing, for example, by using {@link androidx.wear.input.testing.TestWearableButtonsProvider}.
+ */
+public interface WearableButtonsProvider {
+    /**
+     * Returns a bundle containing the metadata of a specific button. Currently, only location is
+     * supported. Use with {@link com.google.android.wearable.input.WearableInputDevice#X_KEY} and
+     * {@link com.google.android.wearable.input.WearableInputDevice#Y_KEY}. The key will not be
+     * present if the information is not available for the requested keycode.
+     *
+     * <p>The location returned is a Cartesian coordinate where the bottom left corner of the screen
+     * is the origin. The unit of measurement is in pixels. The coordinates do not take rotation
+     * into account and assume that the device is in the standard upright position.
+     *
+     * @param context The context of the current activity
+     * @param keycode The keycode associated with the hardware button of interest
+     * @return A {@link Bundle} containing the metadata for the given keycode
+     */
+    @NonNull
+    Bundle getButtonInfo(@NonNull Context context, int keycode);
+
+    /**
+     * Get the keycodes of available hardware buttons on device. This function based on key's
+     * locations from system property. This count includes the primary stem key as well as any
+     * secondary stem keys available.
+     *
+     * @param context The context of the current activity
+     * @return An int array of available button keycodes, or null if no keycodes could be read.
+     */
+    @Nullable
+    int[] getAvailableButtonKeyCodes(@NonNull Context context);
+}
diff --git a/wear/wear-input/src/main/res/color/button_icon_color.xml b/wear/wear-input/src/main/res/color/button_icon_color.xml
new file mode 100644
index 0000000..cc6e02b
--- /dev/null
+++ b/wear/wear-input/src/main/res/color/button_icon_color.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="?android:attr/textColorPrimary" android:state_enabled="true" />
+    <item android:color="?android:colorButtonNormal" android:state_enabled="false" />
+</selector>
diff --git a/wear/wear-input/src/main/res/drawable/action_item_background.xml b/wear/wear-input/src/main/res/drawable/action_item_background.xml
new file mode 100644
index 0000000..22d254d
--- /dev/null
+++ b/wear/wear-input/src/main/res/drawable/action_item_background.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ripple
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <color android:color="?android:attr/colorControlHighlight" />
+    </item>
+</ripple>
diff --git a/wear/wear-input/src/main/res/drawable/action_item_icon_background.xml b/wear/wear-input/src/main/res/drawable/action_item_icon_background.xml
new file mode 100644
index 0000000..b10a515
--- /dev/null
+++ b/wear/wear-input/src/main/res/drawable/action_item_icon_background.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+  <solid android:color="?android:attr/colorForeground"/>
+</shape>
diff --git a/wear/wear-input/src/main/res/drawable/ic_cc_settings_button_bottom.xml b/wear/wear-input/src/main/res/drawable/ic_cc_settings_button_bottom.xml
new file mode 100644
index 0000000..cdbb37a
--- /dev/null
+++ b/wear/wear-input/src/main/res/drawable/ic_cc_settings_button_bottom.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+  android:width="24dp"
+  android:height="24dp"
+  android:tint="@color/button_icon_color"
+  android:viewportHeight="24.0"
+  android:viewportWidth="24.0">
+    <path
+      android:fillColor="@android:color/white"
+      android:pathData="M20.8,19.1V4.9C20.8,3.8 20,3 18.9,3H4.7C3.6,3 2.8,3.8 2.8,4.9v14.1c0,1.1 0.8,1.9 1.9,1.9h14.1C20,21 20.8,20.2 20.8,19.1zM18.9,19.1H4.7V4.9h14.1V19.1z" />
+    <path
+      android:fillColor="@android:color/white"
+      android:pathData="M24,15.9v2.6c0,0.4 -0.3,0.6 -0.6,0.6h-1.3v-3.9h1.3C23.7,15.2 24,15.5 24,15.9z" />
+</vector>
diff --git a/wear/wear-input/src/main/res/drawable/ic_cc_settings_button_center.xml b/wear/wear-input/src/main/res/drawable/ic_cc_settings_button_center.xml
new file mode 100644
index 0000000..364c77d
--- /dev/null
+++ b/wear/wear-input/src/main/res/drawable/ic_cc_settings_button_center.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+  android:width="24dp"
+  android:height="24dp"
+  android:tint="@color/button_icon_color"
+  android:viewportHeight="24.0"
+  android:viewportWidth="24.0">
+    <path
+      android:fillColor="@android:color/white"
+      android:pathData="M18.9,3H4.7C3.6,3 2.8,3.8 2.8,4.9v14.1c0,1.1 0.8,1.9 1.9,1.9h14.1c1.1,0 1.9,-0.8 1.9,-1.9V4.9C20.8,3.8 20,3 18.9,3zM18.9,19.1H4.7V4.9h14.1V19.1z" />
+    <path
+      android:fillColor="@android:color/white"
+      android:pathData="M23.4,14h-1.3v-3.9h1.3c0.4,0 0.6,0.3 0.6,0.6v2.6C24,13.7 23.7,14 23.4,14z" />
+</vector>
diff --git a/wear/wear-input/src/main/res/drawable/ic_cc_settings_button_e.xml b/wear/wear-input/src/main/res/drawable/ic_cc_settings_button_e.xml
new file mode 100644
index 0000000..df85799
--- /dev/null
+++ b/wear/wear-input/src/main/res/drawable/ic_cc_settings_button_e.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+  android:width="24dp"
+  android:height="24dp"
+  android:tint="@color/button_icon_color"
+  android:viewportHeight="24.0"
+  android:viewportWidth="24.0">
+    <path
+      android:fillColor="@android:color/white"
+      android:pathData="M12,21c5,0 9,-4 9,-9s-4,-9 -9,-9c-5,0 -9,4 -9,9S7,21 12,21zM12,5c3.9,0 7,3.1 7,7s-3.1,7 -7,7s-7,-3.1 -7,-7S8.1,5 12,5z" />
+    <path
+      android:fillColor="@android:color/white"
+      android:pathData="M22.2,9H24v6h-1.8V9z" />
+</vector>
diff --git a/wear/wear-input/src/main/res/drawable/ic_cc_settings_button_top.xml b/wear/wear-input/src/main/res/drawable/ic_cc_settings_button_top.xml
new file mode 100644
index 0000000..cc5c342
--- /dev/null
+++ b/wear/wear-input/src/main/res/drawable/ic_cc_settings_button_top.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+  android:width="24dp"
+  android:height="24dp"
+  android:tint="@color/button_icon_color"
+  android:viewportHeight="24.0"
+  android:viewportWidth="24.0">
+    <path
+      android:fillColor="@android:color/white"
+      android:pathData="M18.9,3H4.7C3.6,3 2.8,3.8 2.8,4.9v14.1c0,1.1 0.8,1.9 1.9,1.9h14.1c1.1,0 1.9,-0.8 1.9,-1.9V4.9C20.8,3.8 20,3 18.9,3zM18.9,19.1H4.7V4.9h14.1V19.1z" />
+    <path
+      android:fillColor="@android:color/white"
+      android:pathData="M23.4,8.8h-1.3V4.9h1.3c0.4,0 0.6,0.3 0.6,0.6v2.6C24,8.5 23.7,8.8 23.4,8.8z" />
+</vector>
diff --git a/wear/wear-input/src/main/res/drawable/ic_expand_less_white_22.xml b/wear/wear-input/src/main/res/drawable/ic_expand_less_white_22.xml
new file mode 100644
index 0000000..2dbcf36
--- /dev/null
+++ b/wear/wear-input/src/main/res/drawable/ic_expand_less_white_22.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="22dp"
+        android:height="22dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M12,8l-6,6 1.41,1.41L12,10.83l4.59,4.58L18,14z"/>
+</vector>
diff --git a/wear/wear-input/src/main/res/drawable/ic_more_horiz_24dp_wht.xml b/wear/wear-input/src/main/res/drawable/ic_more_horiz_24dp_wht.xml
new file mode 100644
index 0000000..723b606
--- /dev/null
+++ b/wear/wear-input/src/main/res/drawable/ic_more_horiz_24dp_wht.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/wear/wear-input/src/main/res/drawable/ic_more_vert_24dp_wht.xml b/wear/wear-input/src/main/res/drawable/ic_more_vert_24dp_wht.xml
new file mode 100644
index 0000000..fbb5018
--- /dev/null
+++ b/wear/wear-input/src/main/res/drawable/ic_more_vert_24dp_wht.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#ffffffff"
+        android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/wear/wear-input/src/main/res/drawable/preference_wrapped_icon.xml b/wear/wear-input/src/main/res/drawable/preference_wrapped_icon.xml
new file mode 100644
index 0000000..789d5ee
--- /dev/null
+++ b/wear/wear-input/src/main/res/drawable/preference_wrapped_icon.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list
+  xmlns:android="http://schemas.android.com/apk/res/android"
+  xmlns:tools="http://schemas.android.com/tools">
+
+  <item>
+    <shape android:shape="oval" android:tint="?android:colorPrimary">
+      <solid android:color="@android:color/white" />
+      <size android:width="40dp" android:height="40dp" />
+    </shape>
+  </item>
+  <item
+      android:id="@+id/nested_icon"
+      android:drawable="@android:color/transparent"
+      android:height="24dp"
+      android:width="24dp"
+      android:gravity="center" />
+</layer-list>
diff --git a/wear/wear-input/src/main/res/values/color.xml b/wear/wear-input/src/main/res/values/color.xml
new file mode 100644
index 0000000..cdac867
--- /dev/null
+++ b/wear/wear-input/src/main/res/values/color.xml
@@ -0,0 +1,65 @@
+<resources>
+    <color name="white">@android:color/white</color>
+    <color name="black">@android:color/black</color>
+
+    <!-- Blue A400 -->
+    <color name="blue_a400">#2979FF</color>
+    <!-- Blue 800 -->
+    <color name="dark_blue">#1565C0</color>
+    <!-- Green A700 -->
+    <color name="green">#00C853</color>
+    <!-- Grey 400 -->
+    <color name="light_grey">#bdbdbd</color>
+    <!-- Grey 600 -->
+    <color name="grey">#757575</color>
+    <!-- Grey 800 -->
+    <color name="dark_grey">#424242</color>
+    <!-- Red A200 -->
+    <color name="red_a200">#FF5252</color>
+    <!-- Red 700 -->
+    <color name="dark_red">#D32F2F</color>
+    <!-- Amber 900 -->
+    <color name="orange">#FF6D3F</color>
+
+    <color name="semitransparent_grey">#66777777</color>
+
+    <color name="dismiss_overlay_bg">#80000000</color>
+    <color name="dismiss_close">#FF5151</color>
+    <color name="dismiss_close_pressed">#B83120</color>
+
+    <!-- Light Blue 700 -->
+    <color name="circular_button_normal">#0288D1</color>
+    <!-- Light Blue 900 -->
+    <color name="circular_button_pressed">#01579B</color>
+    <color name="circular_button_disabled">@color/grey</color>
+    <color name="primary_text_light">@color/dark_grey</color>
+    <!-- Grey 500 -->
+    <color name="secondary_text_light">#9E9E9E</color>
+    <!-- Grey 300 -->
+    <color name="disabled_text_light">#E0E0E0</color>
+
+    <color name="primary_text_dark">@color/white</color>
+    <color name="ambient_mode_text">@color/primary_text_dark</color>
+    <!-- Grey 300 -->
+    <color name="card_default_background">#E0E0E0</color>
+
+    <!-- 86% Black -->
+    <color name="black_86p">#DE000000</color>
+    <!-- 54% Black -->
+    <color name="black_54p">#8A000000</color>
+
+    <!-- 30% Black -->
+    <color name="action_button_background">#4D000000</color>
+
+    <color name="dialog_background">#414141</color>
+    <color name="dialog_shade_background">#606060</color>
+
+    <!-- Default colors to be used by the spinner if none are provided -->
+    <array name="progress_spinner_sequence">
+        <item>#FFF4B400</item>
+        <item>#FFDB4437</item>
+        <item>#FF4285F4</item>
+        <item>#FF0F9D58</item>
+    </array>
+
+</resources>
diff --git a/wear/wear-input/src/main/res/values/strings.xml b/wear/wear-input/src/main/res/values/strings.xml
new file mode 100644
index 0000000..4536847
--- /dev/null
+++ b/wear/wear-input/src/main/res/values/strings.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- String describing a button location on a round device when the button is in the top center position [CHAR LIMIT=35] -->
+    <string name="buttons_round_top_center">Top</string>
+    <!-- String describing a button location on a round device when the button is in the top right position [CHAR LIMIT=35] -->
+    <string name="buttons_round_top_right">Top right</string>
+    <!-- String describing a button location on a round device when the button is in the center right position [CHAR LIMIT=35] -->
+    <string name="buttons_round_center_right">Center right</string>
+    <!-- String describing a button location on a round device when the button is in the bottom right position [CHAR LIMIT=35] -->
+    <string name="buttons_round_bottom_right">Bottom right</string>
+    <!-- String describing a button location on a round device when the button is in the bottom center position [CHAR LIMIT=35] -->
+    <string name="buttons_round_bottom_center">Bottom</string>
+    <!-- String describing a button location on a round device when the button is in the bottom left position [CHAR LIMIT=35] -->
+    <string name="buttons_round_bottom_left">Bottom left</string>
+    <!-- String describing a button location on a round device when the button is in the center left position [CHAR LIMIT=35] -->
+    <string name="buttons_round_center_left">Center left</string>
+    <!-- String describing a button location on a round device when the button is in the top left position [CHAR LIMIT=35] -->
+    <string name="buttons_round_top_left">Top left</string>
+    <!-- String describing a button location on a round device when the button between 12 o'clock and halfway between 1 and 2 o'clock [CHAR LIMIT=35] -->
+    <string name="buttons_round_top_right_upper">Top right, upper</string>
+    <!-- String describing a button location on a round device when the button between halfway between 1 and 2 o'clock and 3 o'clock [CHAR LIMIT=35] -->
+    <string name="buttons_round_top_right_lower">Top right, lower</string>
+    <!-- String describing a button location on a round device when the button between 3 o'clock and halfway between 4 and 5 o'clock [CHAR LIMIT=35] -->
+    <string name="buttons_round_bottom_right_upper">Bottom right, upper</string>
+    <!-- String describing a button location on a round device when the button between halfway between 4 and 5 o'clock and 6 o'clock [CHAR LIMIT=35] -->
+    <string name="buttons_round_bottom_right_lower">Bottom right, lower</string>
+    <!-- String describing a button location on a round device when the button between 6 o'clock and halfway between 7 and 8 o'clock [CHAR LIMIT=35] -->
+    <string name="buttons_round_bottom_left_lower">Bottom left, lower</string>
+    <!-- String describing a button location on a round device when the button between halfway between 7 and 8 o'clock and 9 o'clock [CHAR LIMIT=35] -->
+    <string name="buttons_round_bottom_left_upper">Bottom left, upper</string>
+    <!-- String describing a button location on a round device when the button between 9 o'clock and halfway between 10 and 11 o'clock [CHAR LIMIT=35] -->
+    <string name="buttons_round_top_left_lower">Top left, lower</string>
+    <!-- String describing a button location on a round device when the button between halfway between 10 and 11 o'clock and 12 o'clock [CHAR LIMIT=35] -->
+    <string name="buttons_round_top_left_upper">Top left, upper</string>
+    <!-- String describing a button location on a square device when the button is on the left side near the top corner [CHAR LIMIT=35] -->
+    <string name="buttons_rect_left_top">Top, left side</string>
+    <!-- String describing a button location on a square device when the button is on the left side near the center [CHAR LIMIT=35] -->
+    <string name="buttons_rect_left_center">Center left</string>
+    <!-- String describing a button location on a square device when the button is on the left side near the bottom corner [CHAR LIMIT=35] -->
+    <string name="buttons_rect_left_bottom">Bottom, left side</string>
+    <!-- String describing a button location on a square device when the button is on the right side near the top corner [CHAR LIMIT=35] -->
+    <string name="buttons_rect_right_top">Top, right side</string>
+    <!-- String describing a button location on a square device when the button is on the right side near the center [CHAR LIMIT=35] -->
+    <string name="buttons_rect_right_center">Center right</string>
+    <!-- String describing a button location on a square device when the button is on the right side near the bottom corner [CHAR LIMIT=35] -->
+    <string name="buttons_rect_right_bottom">Bottom, right side</string>
+    <!-- String describing a button location on a square device when the button is on the top side near the left corner [CHAR LIMIT=35] -->
+    <string name="buttons_rect_top_left">Top left</string>
+    <!-- String describing a button location on a square device when the button is on the top side near the center [CHAR LIMIT=35] -->
+    <string name="buttons_rect_top_center">Top</string>
+    <!-- String describing a button location on a square device when the button is on the top side near the right corner [CHAR LIMIT=35] -->
+    <string name="buttons_rect_top_right">Top right</string>
+    <!-- String describing a button location on a square device when the button is on the bottom side near the left corner [CHAR LIMIT=35] -->
+    <string name="buttons_rect_bottom_left">Bottom left</string>
+    <!-- String describing a button location on a square device when the button is on the bottom side near the center [CHAR LIMIT=35] -->
+    <string name="buttons_rect_bottom_center">Bottom</string>
+    <!-- String describing a button location on a square device when the button is on the bottom side near the right corner [CHAR LIMIT=35] -->
+    <string name="buttons_rect_bottom_right">Bottom right</string>
+</resources>
diff --git a/wear/wear-input/src/test/java/androidx/wear/input/WearableButtonsTest.java b/wear/wear-input/src/test/java/androidx/wear/input/WearableButtonsTest.java
new file mode 100644
index 0000000..99b4c4a
--- /dev/null
+++ b/wear/wear-input/src/test/java/androidx/wear/input/WearableButtonsTest.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.wear.input;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.drawable.RotateDrawable;
+import android.os.Build;
+import android.provider.Settings;
+import android.view.Display;
+import android.view.KeyEvent;
+import android.view.Surface;
+import android.view.WindowManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.wear.R;
+import androidx.wear.input.testing.TestWearableButtonsProvider;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.internal.DoNotInstrument;
+import org.robolectric.shadows.ShadowDrawable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** Unit tests for {@link WearableButtons}. */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+@Config(sdk = Build.VERSION_CODES.P)
+public class WearableButtonsTest {
+    private final Point mScreenSize = new Point(480, 480);
+
+    @Test
+    public void testExactPoints_round() {
+        // Positions were calculated using the following formula:
+        // ScreenCoordinate.x = r*(1+cos(a))
+        // ScreenCoordinate.y = r*(1-sin(a))
+        // Angle = pi/8 * N, where N was the cardinal position
+        // r = 240 because the screen is 480x480
+        // Wolfram Alpha was helpful:  x=240*(1+cos(a)), y=240*(1-sin(a)), a=pi/8.0
+
+        assertEquals(
+                WearableButtons.LOC_EAST,
+                WearableButtons.getLocationZone(true, mScreenSize, 480, 240));
+        assertEquals(
+                WearableButtons.LOC_ENE,
+                WearableButtons.getLocationZone(true, mScreenSize, 461.731f, 148.156f));
+        assertEquals(
+                WearableButtons.LOC_NE,
+                WearableButtons.getLocationZone(true, mScreenSize, 409.706f, 70.294f));
+        assertEquals(
+                WearableButtons.LOC_NNE,
+                WearableButtons.getLocationZone(true, mScreenSize, 331.844f, 18.269f));
+        assertEquals(
+                WearableButtons.LOC_NORTH,
+                WearableButtons.getLocationZone(true, mScreenSize, 240, 0));
+        assertEquals(
+                WearableButtons.LOC_NNW,
+                WearableButtons.getLocationZone(true, mScreenSize, 148.156f, 18.269f));
+        assertEquals(
+                WearableButtons.LOC_NW,
+                WearableButtons.getLocationZone(true, mScreenSize, 70.294f, 70.294f));
+        assertEquals(
+                WearableButtons.LOC_WNW,
+                WearableButtons.getLocationZone(true, mScreenSize, 18.269f, 148.156f));
+        assertEquals(
+                WearableButtons.LOC_WEST,
+                WearableButtons.getLocationZone(true, mScreenSize, 0, 240));
+        assertEquals(
+                WearableButtons.LOC_WSW,
+                WearableButtons.getLocationZone(true, mScreenSize, 18.269f, 331.844f));
+        assertEquals(
+                WearableButtons.LOC_SW,
+                WearableButtons.getLocationZone(true, mScreenSize, 70.294f, 409.706f));
+        assertEquals(
+                WearableButtons.LOC_SSW,
+                WearableButtons.getLocationZone(true, mScreenSize, 148.156f, 461.731f));
+        assertEquals(
+                WearableButtons.LOC_SOUTH,
+                WearableButtons.getLocationZone(true, mScreenSize, 240, 480));
+        assertEquals(
+                WearableButtons.LOC_SSE,
+                WearableButtons.getLocationZone(true, mScreenSize, 331.844f, 461.731f));
+        assertEquals(
+                WearableButtons.LOC_SE,
+                WearableButtons.getLocationZone(true, mScreenSize, 409.706f, 409.706f));
+        assertEquals(
+                WearableButtons.LOC_ESE,
+                WearableButtons.getLocationZone(true, mScreenSize, 461.731f, 331.844f));
+    }
+
+    @Test
+    public void testNonExactPoints_round() {
+        // Positions were randomly determined based on #testExactPoints calculations
+        // 1 degree in radians = Math.PI / 180;
+        // Using formula from #testExactPoints, add and subtract X degrees from each exact point and
+        // test edge cases
+        // Wolfram Alpha helps again:  x = 240*(1+cos(a)), y=240*(1-sin(a)), a=(15.0*pi/8.0)+pi/180
+
+        // Exact +1 degree
+        assertEquals(
+                WearableButtons.LOC_ESE,
+                WearableButtons.getLocationZone(true, mScreenSize, 463.3f, 327.96f));
+        // Exact -1 degree
+        assertEquals(
+                WearableButtons.LOC_ESE,
+                WearableButtons.getLocationZone(true, mScreenSize, 460.094f, 335.7f));
+
+        // Exact +5 degrees
+        assertEquals(
+                WearableButtons.LOC_WNW,
+                WearableButtons.getLocationZone(true, mScreenSize, 16.7f, 152.04f));
+        // Exact -5 degrees
+        assertEquals(
+                WearableButtons.LOC_WNW,
+                WearableButtons.getLocationZone(true, mScreenSize, 19.906f, 144.3f));
+
+        // Exactly between SE and ESE
+        assertEquals(
+                WearableButtons.LOC_ESE,
+                WearableButtons.getLocationZone(true, mScreenSize, 439.553f, 373.337f));
+
+        // Exactly between SSE and SE
+        assertEquals(
+                WearableButtons.LOC_SE,
+                WearableButtons.getLocationZone(true, mScreenSize, 373.337f, 439.553f));
+    }
+
+    @Test
+    public void testEdgePoints_rect() {
+        assertEquals(
+                WearableButtons.LOC_LEFT_TOP,
+                WearableButtons.getLocationZone(false, mScreenSize, 0, 160));
+        assertEquals(
+                WearableButtons.LOC_LEFT_CENTER,
+                WearableButtons.getLocationZone(false, mScreenSize, 0, 320));
+        assertEquals(
+                WearableButtons.LOC_LEFT_BOTTOM,
+                WearableButtons.getLocationZone(false, mScreenSize, 0, 480));
+        assertEquals(
+                WearableButtons.LOC_RIGHT_TOP,
+                WearableButtons.getLocationZone(false, mScreenSize, 480, 160));
+        assertEquals(
+                WearableButtons.LOC_RIGHT_CENTER,
+                WearableButtons.getLocationZone(false, mScreenSize, 480, 320));
+        assertEquals(
+                WearableButtons.LOC_RIGHT_BOTTOM,
+                WearableButtons.getLocationZone(false, mScreenSize, 480, 480));
+        assertEquals(
+                WearableButtons.LOC_TOP_LEFT,
+                WearableButtons.getLocationZone(false, mScreenSize, 160, 0));
+        assertEquals(
+                WearableButtons.LOC_TOP_CENTER,
+                WearableButtons.getLocationZone(false, mScreenSize, 320, 0));
+        assertEquals(
+                WearableButtons.LOC_TOP_RIGHT,
+                WearableButtons.getLocationZone(false, mScreenSize, 479, 0));
+        assertEquals(
+                WearableButtons.LOC_BOTTOM_LEFT,
+                WearableButtons.getLocationZone(false, mScreenSize, 160, 480));
+        assertEquals(
+                WearableButtons.LOC_BOTTOM_CENTER,
+                WearableButtons.getLocationZone(false, mScreenSize, 320, 480));
+        assertEquals(
+                WearableButtons.LOC_BOTTOM_RIGHT,
+                WearableButtons.getLocationZone(false, mScreenSize, 479, 480));
+    }
+
+    @Test
+    public void testNonEdgePoints_rect() {
+        assertEquals(
+                WearableButtons.LOC_LEFT_TOP,
+                WearableButtons.getLocationZone(false, mScreenSize, 50, 100));
+        assertEquals(
+                WearableButtons.LOC_LEFT_CENTER,
+                WearableButtons.getLocationZone(false, mScreenSize, 80, 300));
+        assertEquals(
+                WearableButtons.LOC_LEFT_BOTTOM,
+                WearableButtons.getLocationZone(false, mScreenSize, 100, 350));
+        assertEquals(
+                WearableButtons.LOC_RIGHT_TOP,
+                WearableButtons.getLocationZone(false, mScreenSize, 460, 120));
+        assertEquals(
+                WearableButtons.LOC_RIGHT_CENTER,
+                WearableButtons.getLocationZone(false, mScreenSize, 450, 270));
+        assertEquals(
+                WearableButtons.LOC_RIGHT_BOTTOM,
+                WearableButtons.getLocationZone(false, mScreenSize, 430, 400));
+        assertEquals(
+                WearableButtons.LOC_TOP_LEFT,
+                WearableButtons.getLocationZone(false, mScreenSize, 130, 20));
+        assertEquals(
+                WearableButtons.LOC_TOP_CENTER,
+                WearableButtons.getLocationZone(false, mScreenSize, 280, 30));
+        assertEquals(
+                WearableButtons.LOC_TOP_RIGHT,
+                WearableButtons.getLocationZone(false, mScreenSize, 429, 40));
+        assertEquals(
+                WearableButtons.LOC_BOTTOM_LEFT,
+                WearableButtons.getLocationZone(false, mScreenSize, 140, 470));
+        assertEquals(
+                WearableButtons.LOC_BOTTOM_CENTER,
+                WearableButtons.getLocationZone(false, mScreenSize, 290, 460));
+        assertEquals(
+                WearableButtons.LOC_BOTTOM_RIGHT,
+                WearableButtons.getLocationZone(false, mScreenSize, 439, 450));
+    }
+
+    @Test
+    public void testSimpleStrings() {
+        assertEquals(
+                R.string.buttons_round_bottom_right,
+                WearableButtons.getFriendlyLocationZoneStringId(WearableButtons.LOC_ESE, 1));
+        assertEquals(
+                R.string.buttons_round_bottom_left_upper,
+                WearableButtons.getFriendlyLocationZoneStringId(WearableButtons.LOC_ESE, 2));
+        assertEquals(
+                R.string.buttons_rect_bottom_left,
+                WearableButtons.getFriendlyLocationZoneStringId(
+                        WearableButtons.LOC_BOTTOM_LEFT, 1));
+        assertEquals(
+                R.string.buttons_rect_bottom_left,
+                WearableButtons.getFriendlyLocationZoneStringId(
+                        WearableButtons.LOC_BOTTOM_LEFT, 2));
+    }
+
+    @Test
+    public void testSimpleIcons() {
+        testRotateDrawable(WearableButtons.LOC_ESE, R.drawable.ic_cc_settings_button_e, 45);
+        testRotateDrawable(WearableButtons.LOC_WNW, R.drawable.ic_cc_settings_button_e, -135);
+        testRotateDrawable(
+                WearableButtons.LOC_BOTTOM_LEFT, R.drawable.ic_cc_settings_button_bottom, 90);
+        testRotateDrawable(
+                WearableButtons.LOC_TOP_CENTER, R.drawable.ic_cc_settings_button_center, -90);
+        testRotateDrawable(WearableButtons.LOC_TOP_LEFT, R.drawable.ic_cc_settings_button_top, -90);
+    }
+
+    @Test
+    public void testGetButtonsRighty() {
+        Map<Integer, TestWearableButtonsProvider.TestWearableButtonLocation> buttons =
+                new HashMap<>();
+        buttons.put(
+                KeyEvent.KEYCODE_STEM_1,
+                new TestWearableButtonsProvider.TestWearableButtonLocation(1, 2, 3, 4));
+
+        TestWearableButtonsProvider provider = new TestWearableButtonsProvider(buttons);
+        WearableButtons.setWearableButtonsProvider(provider);
+
+        setLeftyModeEnabled(false);
+        WearableButtons.ButtonInfo info =
+                WearableButtons.getButtonInfo(
+                        ApplicationProvider.getApplicationContext(), KeyEvent.KEYCODE_STEM_1);
+        assertNotNull(info);
+        assertEquals(1, info.getX(), 1.0e-7);
+        assertEquals(2, info.getY(), 1.0e-7);
+    }
+
+    @Test
+    public void testGetButtonsLefty() {
+        setLeftyModeEnabled(true);
+        Map<Integer, TestWearableButtonsProvider.TestWearableButtonLocation> buttons =
+                new HashMap<>();
+        buttons.put(
+                KeyEvent.KEYCODE_STEM_1,
+                new TestWearableButtonsProvider.TestWearableButtonLocation(1, 2, 3, 4));
+
+        TestWearableButtonsProvider provider = new TestWearableButtonsProvider(buttons);
+        WearableButtons.setWearableButtonsProvider(provider);
+        WearableButtons.ButtonInfo info =
+                WearableButtons.getButtonInfo(
+                        ApplicationProvider.getApplicationContext(), KeyEvent.KEYCODE_STEM_1);
+        assertNotNull(info);
+        assertEquals(3, info.getX(), 1.0e-7);
+        assertEquals(4, info.getY(), 1.0e-7);
+    }
+
+    @Test
+    public void testGetButtonsLeftyNoData() {
+        Context context = ApplicationProvider.getApplicationContext();
+        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        Display display = wm.getDefaultDisplay();
+        Shadows.shadowOf(display).setWidth(480);
+        Shadows.shadowOf(display).setHeight(480);
+
+        setLeftyModeEnabled(true);
+        Map<Integer, TestWearableButtonsProvider.TestWearableButtonLocation> buttons =
+                new HashMap<>();
+        buttons.put(
+                KeyEvent.KEYCODE_STEM_1,
+                new TestWearableButtonsProvider.TestWearableButtonLocation(1, 2));
+
+        TestWearableButtonsProvider provider = new TestWearableButtonsProvider(buttons);
+        WearableButtons.setWearableButtonsProvider(provider);
+        WearableButtons.ButtonInfo info =
+                WearableButtons.getButtonInfo(context, KeyEvent.KEYCODE_STEM_1);
+        assertNotNull(info);
+        assertEquals(479, info.getX(), 1.0e-7); // == 480 - 1
+        assertEquals(478, info.getY(), 1.0e-7); // == 480 - 2
+    }
+
+    private void testRotateDrawable(
+            int locationZone, int expectedDrawableId, int expectedDegreeRotation) {
+        RotateDrawable rotateDrawable =
+                WearableButtons.getButtonIconFromLocationZone(
+                        ApplicationProvider.getApplicationContext(), locationZone);
+
+        // We need Robolectric to pull out the underlying resource ID.
+        ShadowDrawable rotateDrawableShadow = Shadows.shadowOf(rotateDrawable.getDrawable());
+
+        assertEquals(rotateDrawableShadow.getCreatedFromResId(), expectedDrawableId);
+        assertEquals(expectedDegreeRotation, rotateDrawable.getFromDegrees(), .001);
+    }
+
+    private void setLeftyModeEnabled(boolean enabled) {
+        Settings.System.putInt(
+                ApplicationProvider.getApplicationContext().getContentResolver(),
+                Settings.System.USER_ROTATION,
+                enabled ? Surface.ROTATION_180 : Surface.ROTATION_0);
+    }
+}
diff --git a/wear/wear/build.gradle b/wear/wear/build.gradle
index 2cf341a..0ecccdf 100644
--- a/wear/wear/build.gradle
+++ b/wear/wear/build.gradle
@@ -41,6 +41,7 @@
     name = "Android Wear Support UI"
     publish = Publish.SNAPSHOT_AND_RELEASE
     mavenGroup = LibraryGroups.WEAR
+    mavenVersion = LibraryVersions.WEAR
     inceptionYear = "2016"
     description = "Android Wear Support UI"
 }